HarmonyOS

文章40 |   阅读 14609 |   点赞0

来源:https://blog.csdn.net/forever_wj/category_11128883.html

HarmonyOS之深入解析NFC的功能和使用

x33g5p2x  于2022-03-07 转载在 其他  
字(11.2k)|赞(0)|评价(0)|浏览(503)
一、简介
  • 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 的开关状态。
  • 示例代码如下:
// 查询本机是否支持NFC
	if (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 说明
  • NFC 访问安全单元功能的的主要接口:

| 类名 | 接口名 | 功能描述 |
| SEService | SEService() | 创建一个安全单元服务的实例 |
| isConnected() | 查询安全单元服务是否已连接 |
| shutdown() | 关闭安全单元服务 |
| getReaders() | 获取全部安全单元 |
| getVersion() | 获得安全单元服务的版本 |
| OnCallback | 用于回调的内部类,用于定义回调接口。<br>在服务连接成功后,回调该接口通知应用 |
| 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();
	
	// 获取本机的全部安全单元,并获取指定的安全单元eSE
	Reader[] 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();
	
	// 打开Session
	Optional<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_DEFAULT
	result = 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 消息通知的相关广播介绍:
描述通知名附加参数
NFC状态usual.event.nfc.action.ADAPTER_STATE_CHANGEDextra_nfc_state
进场消息usual.event.nfc.action.RF_FIELD_ON_DETECTEDextra_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());
	}

相关文章