一、简介
- NFC(Near Field Communication,近距离无线通信技术) 是一种非接触式识别和互联技术,让移动设备、消费类电子产品、PC 和智能设备之间可以进行近距离无线通信。
- HarmonyOS 的 NFC 提供的功能有:
-
- NFC 基础查询:在进行 NFC 功能开发之前,开发者应该先确认设备是否支持 NFC 功能、NFC 是否打开等基本信息。
-
- 访问安全单元(Secure Element,简称为 SE):SE 可用于保存重要信息,应用可以访问指定 SE,并发送数据到 SE 上。
-
- 卡模拟:设备可以模拟卡片,替代卡片完成对应操作,如模拟门禁卡、公交卡等。
-
- NFC 消息通知:通过这个模块,开发者可以获取 NFC 开关状态改变的消息以及 NFC 的场强消息。
二、NFC 基础查询
- 要进行 NFC 功能开发,需要设备支持 NFC 功能。
- 开发者可以通过 NfcController 类的方法 isNfcAvailable() 来确认设备是否支持 NFC 功能。如果设备支持 NFC 功能,可通过 isNfcOpen() 来查询 NFC 的开关状态。
- 示例代码如下:
// 查询本机是否支持NFCif (context != null) { NfcController nfcController = NfcController.getInstance(context);} else { return;}boolean isAvailable = nfcController.isNfcAvailable();if (isAvailable) { // 调用查询NFC是否打开接口,返回值为NFC是否是打开的状态 boolean isOpen = nfcController.isNfcOpen();}
三、访问安全单元
① 应用场景
- 安全单元(Secure Element,简称为 SE)可用于保存重要信息,应用或者其他模块可以通过接口完成以下功能:
-
-
-
-
-
- 发送 APDU(Application Protocol Data Unit)数据到安全单元上。
② API 说明
类名 |
接口名 |
功能描述 |
SEService |
SEService() |
创建一个安全单元服务的实例 |
isConnected() |
查询安全单元服务是否已连接 |
shutdown() |
关闭安全单元服务 |
getReaders() |
获取全部安全单元 |
getVersion() |
获得安全单元服务的版本 |
OnCallback |
用于回调的内部类,用于定义回调接口。 在服务连接成功后,回调该接口通知应用 |
Reader |
getName() |
获取安全单元的名称 |
isSecureElementPresent() |
检查安全单元是否在位 |
openSession() |
打开当前安全单元上的session |
closeSession() |
关闭当前安全单元上的所有session |
Session |
openBasicChannel(Aid aid) |
打开基础通道 |
openLogicalChannel(Aid aid) |
创建逻辑通道 |
getATR() |
获得重设安全单元指令的响应 |
closeSessionChannels() |
关闭当前session的所有通道 |
Channel |
isClosed() |
判断通道是否关闭 |
isBasicChannel() |
判断是否是基础通道 |
transmit(byte[] command) |
发送指令到安全单元 |
getSelectResponse() |
获得应用程序选择指令的响应 |
closeChannel() |
关闭通道 |
Aid |
Aid(byte[] aid, int offset, int length) |
构造一个AID类的实例 |
isAidValid() |
查询AID是否有效 |
getAidBytes() |
获取AID的字节数组形式的值 |
③ 使用流程
- 调用 SEService 类的构造函数,创建一个安全单元服务的实例,用于访问安全单元。
- 调用 isConnected() 接口,查询安全单元服务的连接状态。
- 调用 getReaders() 接口,获取本机的全部安全单元。
- 调用 Reader 类的 openSession() 接口打开 Session,返回一个打开的 Session 实例。
- 调用 Session 类的 openBasicChannel(Aid aid) 接口打开基础通道,或者调用 openLogicalChannel(Aid aid) 接口打开逻辑通道,返回一个打开通道 Channel 实例。
- 调用 Channel 类的 transmit(byte[] command),发送 APDU 到安全单元。
- 调用 Channel 类的 closeChannel() 接口关闭通道。
- 调用 Session 类的 closeSessionChannels() 接口关闭 Session 的所有通道。
- 调用 Reader 类的 closeSessions() 接口关闭安全单元的所有 Session。
- 调用 SEService 类的 shutdown() 接口关闭安全单元服务。
private static final String ESE = "eSE";private class AppServiceConnectedCallback implements SEService.OnCallback { @Override public void serviceConnected() { // 应用自实现 }}// 创建安全单元服务实例SEService sEService = new SEService(context, new AppServiceConnectedCallback());// 查询安全单元服务的连接状态boolean isConnected = sEService.isConnected();// 获取本机的全部安全单元,并获取指定的安全单元eSEReader[] elements = sEService.getReaders();Reader eSe = null;for (int i = 0; i < elements.length; i++) { if (ESE.equals(elements[i].getName())) { eSe = elements[i]; break; }}if (eSe == null) { return;}// 查询安全单元是否在位boolean isPresent = eSe.isSecureElementPresent();// 打开SessionOptional<Session> optionalSession = eSe.openSession();Session session = optionalSession.orElse(null);if (session == null) { return;}// 打开通道if (eSe != null) { byte[] aidValue = new byte[]{(byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04, (byte)0x05}; // 创建Aid实例 Aid aid = new Aid(aidValue, 0, aidValue.length); // 打开基础通道 Optional<Channel> optionalChannel = session.openBasicChannel(aid); Channel basicChannel = optionalChannel.orElse(null); // 打开逻辑通道 optionalChannel = session.openLogicalChannel(aid); Channel logicalChannel = optionalChannel.orElse(null); // 发送指令给安全单元,返回值为安全单元对指令的响应 byte[] resp = logicalChannel.transmit(new byte[]{(byte)0x00, (byte)0xa4, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x00, (byte)0x00}); // 关闭通道资源 if (basicChannel.isPresent()) { basicChannel.closeChannel(); } if (logicalChannel.isPresent()) { logicalChannel.closeChannel(); }// 关闭Session资源session.close();// 关闭安全单元资源eSe.closeSessions();// 关闭安全单元服务资源sEService.shutdown();
四、卡模拟功能
① 应用场景
- 设备可以模拟卡片,替代卡片完成对应操作,如模拟门禁卡、公交卡等。
- 应用或者其他模块可以通过接口完成以下功能:
-
- 查询是否支持指定安全单元的卡模拟功能,安全单元包括 HCE(Host Card Emulation)、ESE(Embedded Secure Element)和 SIM(Subscriber Identity Module)卡。
-
- 打开或关闭指定技术类型的卡模拟,并查询卡模拟状态。
-
- 获取 NFC 信息,包括当前激活的安全单元、Hisee 上电状态、是否支持RSSI(Received Signal Strength Indication)查询等。
-
- 根据 NFC 服务的类型获取刷卡时选择服务的方式,包括支付(Payment)类型和非支付(Other)类型。
-
-
- NFC 应用的 AID(Application Identifier,应用标识)相关操作,包括注册和删除应用的 AID、查询应用是否是指定 AID 的默认应用、获取应用的 AID 等。
-
- 定义 Host 和 OffHost 服务的抽象类,应用可以通过继承抽象类来实现 NFC 卡模拟功能。
② API 说明
- NFC 卡模拟功能的主要接口说明如下,在使用对应的接口前,需要申请 ohos.permission.NFC_CARD_EMULATION 权限。
- NFC 卡模拟功能的主要接口如下表所示:
类名 |
接口名 |
功能描述 |
CardEmulation |
getInstance(NfcController controller) |
创建一个卡模拟类的实例 |
isSupported(int feature) |
查询是否支持卡模拟功能 |
setListenMode(int mode) |
设置卡模拟模式 |
isListenModeEnabled() |
查询卡模拟功能是否打开 |
getNfcInfo(String key) |
获取NFC的信息 |
getSelectionType(String category) |
根据NFC服务的类型获取刷卡时选择服务的方式 |
registerForegroundPreferred(Ability appAbility, ElementName appName) |
动态设置前台优先应用 |
unregisterForegroundPreferred(Ability appAbility) |
取消设置前台优先应用 |
isDefaultForAid(ElementName appName, String aid) |
判断应用是否是指定AID的默认处理应用 |
registerAids(ElementName appName, String type, List aids) |
给应用注册指定类型的AID |
removeAids(ElementName appName, String type) |
删除应用的指定类型的AID |
getAids(ElementName appName, String type) |
获取应用中指定类型的AID列表 |
HostService |
sendResponse(byte[] response) |
发送响应的数据到对端设备 |
handleRemoteCommand(byte[] cmd, IntentParams params) |
处理对端设备发送的命令 |
disabledCallback(int errCode) |
连接异常的回调 |
③ 查询是否支持卡模拟功能
- 调用 NfcController 类的 getInstance(Context context) 接口,获取 NfcController 实例。
- 调用 CardEmulation 类的 getInstance(NfcController controller) 接口,获取 CardEmulation 实例,去管理本机卡模拟模块操作。
- 调用 isSupported(int feature) 接口去查询是否支持 HCE、UICC、ESE 卡模拟。
// 获取NFC控制对象NfcController nfcController = NfcController.getInstance(context);// 获取卡模拟控制对象CardEmulation cardEmulation = CardEmulation.getInstance(nfcController);// 查询是否支持HCE、UICC、ESE卡模拟,返回值表示是否支持对应安全单元的卡模拟boolean isSupportedHce = cardEmulation.isSupported(CardEmulation.FEATURE_HCE);boolean isSupportedUicc = cardEmulation.isSupported(CardEmulation.FEATURE_UICC);boolean isSupportedEse = cardEmulation.isSupported(CardEmulation.FEATURE_ESE);
④ 开关卡模拟及查询卡模拟状态
- 调用 NfcController 类的 getInstance(Context context) 接口,获取 NfcController 实例。
- 调用 CardEmulation 类的 getInstance(NfcController controller) 接口,获取 CardEmulation 实例,去管理本机卡模拟模块操作。
- 调用 setListenMode(int mode) 接口去打开或者关闭卡模拟。
- 调用 isListenModeEnabled() 接口去查询卡模拟是否打开。
// 获取NFC控制对象NfcController nfcController = NfcController.getInstance(context);// 获取卡模拟控制对象CardEmulation cardEmulation = CardEmulation.getInstance(nfcController);// 打开卡模拟cardEmulation.setListenMode(CardEmulation.ENABLE_MODE_ALL);// 调用查询卡模拟开关状态的接口,返回值为卡模拟是否是打开的状态boolean isEnabled = cardEmulation.isListenModeEnabled(); // 关闭卡模拟cardEmulation.setListenMode(CardEmulation.DISABLE_MODE_A_B);// 调用查询卡模拟开关状态的接口,返回值为卡模拟是否是打开的状态isEnabled = cardEmulation.isListenModeEnabled();
⑤ 获取 NFC 信息
- 调用 NfcController 类的 getInstance(Context context) 接口,获取 NfcController 实例。
- 调用 CardEmulation 类的 getInstance(NfcController controller) 接口,获取 CardEmulation 实例,去管理本机卡模拟模块操作。
- 调用 getNfcInfo(String key) 接口去获取 NFC 信息。
// 获取NFC控制对象NfcController nfcController = NfcController.getInstance(context);// 获取卡模拟控制对象CardEmulation cardEmulation = CardEmulation.getInstance(nfcController);// 查询本机当前使能的安全单元类型String seType = cardEmulation.getNfcInfo(CardEmulation.KEY_ENABLED_SE_TYPE); // ENABLED_SE_TYPE_ESE// 查询Hisee上电状态String hiseeState = cardEmulation.getNfcInfo(CardEmulation.KEY_HISEE_READY);// 查询是否支持RSSI的查询String rssiAbility = cardEmulation.getNfcInfo(CardEmulation.KEY_RSSI_SUPPORTED);
⑥ 根据 NFC 服务的类型获取刷卡时选择服务的方式
- 调用 NfcController 类的 getInstance(Context context) 接口,获取 NfcController 实例。
- 调用 CardEmulation 类的 getInstance(NfcController controller) 接口,获取 CardEmulation 实例,去管理本机卡模拟模块操作。
- 调用 getSelectionType(Sring category) 接口去获取选择服务的方式。
// 获取NFC控制对象NfcController nfcController = NfcController.getInstance(context);// 获取卡模拟控制对象CardEmulation cardEmulation = CardEmulation.getInstance(nfcController);// 获取选择服务的方式int result = cardEmulation.getSelectionType(CardEmulation.CATEGORY_PAYMENT); // SELECTION_TYPE_PREFER_DEFAULTresult = cardEmulation.getSelectionType(CardEmulation.CATEGORY_OTHER); // SELECTION_TYPE_ASK_IF_CONFLICT
⑦ 动态设置和注销前台优先应用
- 调用 NfcController 类的 getInstance(Context context) 接口,获取 NfcController 实例。
- 调用 CardEmulation 类的 getInstance(NfcController controller) 接口,获取 CardEmulation 实例,去管理本机卡模拟模块操作。
- 调用 registerForegroundPreferred(Ability appAbility, ElementName appName) 接口去动态设置前台优先应用。
- 调用 unregisterForegroundPreferred(Ability appAbility) 接口去取消设置前台优先应用。
// 获取NFC控制对象NfcController nfcController = NfcController.getInstance(context);// 获取卡模拟控制对象CardEmulation cardEmulation = CardEmulation.getInstance(nfcController);// 动态设置前台优先应用Ability ability = new Ability();cardEmulation.registerForegroundPreferred(ability, new ElementName());// 注销前台优先应用cardEmulation.unregisterForegroundPreferred(ability);
⑧
五、NFC 消息通知
① 应用场景
- NFC 消息通知是 HarmonyOS 内部或者与应用之间跨进程通讯的机制,注册者在注册消息通知后,一旦符合条件的消息被发出,注册者即可接收到该消息。
② API 说明
描述 |
通知名 |
附加参数 |
NFC状态 |
usual.event.nfc.action.ADAPTER_STATE_CHANGED |
extra_nfc_state |
进场消息 |
usual.event.nfc.action.RF_FIELD_ON_DETECTED |
extra_nfc_transaction |
离场消息 |
usual.event.nfc.action.RF_FIELD_OFF_DETECTED |
- |
③ 注册并获取 NFC 状态改变消息
- 构建消息通知接收者 NfcStateEventSubscriber。
- 注册 NFC 状态改变消息。
- NfcStateEventSubscriber 接收并处理 NFC 状态改变消息。
// 构建消息接收者/注册者class NfcStateEventSubscriber extends CommonEventSubscriber { NfcStateEventSubscriber (CommonEventSubscribeInfo info) { super(info); } @Override public void onReceiveEvent(CommonEventData commonEventData) { if (commonEventData == null || commonEventData.getIntent() == null) { return; } if (NfcController.STATE_CHANGED.equals(commonEventData.getIntent().getAction())) { IntentParams params = commonEventData.getIntent().getParams(); int currState = commonEventData.getIntent().getIntParam(NfcController.EXTRA_NFC_STATE, NfcController.STATE_OFF); } }}// 注册消息MatchingSkills matchingSkills = new MatchingSkills();// 增加获取NFC状态改变消息matchingSkills.addEvent(NfcController.STATE_CHANGED);matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_NFC_ACTION_ADAPTER_STATE_CHANGED);CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);NfcStateEventSubscriber subscriber = new NfcStateEventSubscriber(subscribeInfo);try { CommonEventManager.subscribeCommonEvent(subscriber);} catch (RemoteException e) { HiLog.error(TAG, "doSubscribe occur exception: %{public}s" ,e.toString());}
④ 注册并获取 NFC 场强消息
- 构建消息通知接收者 NfcFieldOnAndOffEventSubscriber。
- 注册 NFC 场强消息。
- NfcFieldOnAndOffEventSubscriber 接收并处理 NFC 场强消息。
// 构建消息接收者/注册者class NfcFieldOnAndOffEventSubscriber extends CommonEventSubscriber { NfcFieldOnAndOffEventSubscriber (CommonEventSubscribeInfo info) { super(info); } @Override public void onReceiveEvent(CommonEventData commonEventData) { if (commonEventData == null || commonEventData.getIntent() == null) { return; } if (NfcController.FIELD_ON_DETECTED.equals(commonEventData.getIntent().getAction())) { IntentParams params = commonEventData.getIntent().getParams(); if (params == null) { HiLog.info(TAG, "Pure FIELD_ON_DETECTED"); } else { HiLog.info(TAG, "Transaction FIELD_ON_DETECTED"); Intent transactionIntent = (Intent) params.getParam("transactionIntent"); } } else if (NfcController.FIELD_OFF_DETECTED.equals(commonEventData.getIntent().getAction())) { HiLog.info(TAG, "FIELD_OFF_DETECTED"); } HiLog.info(TAG, "NfcFieldOnAndOffEventSubscriber onReceiveEvent: %{public}s", commonEventData.getIntent().getAction()); }}// 注册消息MatchingSkills matchingSkills = new MatchingSkills();// 增加获取NFC状态改变消息matchingSkills.addEvent(NfcController.FIELD_ON_DETECTED);matchingSkills.addEvent(NfcController.FIELD_OFF_DETECTED);CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);HiLog.info(TAG, "subscribeInfo permission: %{public}s", subscribeInfo.getPermission());NfcFieldOnAndOffEventSubscriber subscriber = new NfcFieldOnAndOffEventSubscriber(subscribeInfo);try { CommonEventManager.subscribeCommonEvent(subscriber);} catch (RemoteException e) { HiLog.error(TAG, "doSubscribe occur exception: %{public}s", e.toString());}