1# HCE卡模拟开发指南 2 3<!--Kit: Connectivity Kit--> 4<!--Subsystem: Communication--> 5<!--Owner: @amunra03--> 6<!--Designer: @wenxiaolin--> 7<!--Tester: @zs_111--> 8<!--Adviser: @zhang_yixin13--> 9 10## 简介 11近场通信(Near Field Communication,NFC)是一种短距高频的无线电技术,在13.56MHz频率运行,通信距离一般在10厘米距离内。HCE(Host Card Emulation),称为基于主机的卡模拟,表示不依赖安全单元芯片,电子设备上的应用程序模拟NFC卡片和NFC读卡器通信,实现NFC刷卡业务。 12 13## 场景介绍 14应用程序模拟NFC卡片,和NFC读卡器通信完成NFC刷卡业务。从使用场景上,可以分成HCE应用前台刷卡和HCE应用后台刷卡。 15- HCE应用前台刷卡<br> 16前台刷卡是指在触碰NFC读卡器之前,用户明确想使用在电子设备上打开特定的应用程序和NFC读卡器进行刷卡操作。当用户打开应用程序在前台,并且进入应用的刷卡页面时,电子设备触碰NFC读卡器后,会把刷卡交易数据分发给前台应用。若应用切换至后台或退出运行时,前台优先分发规则也随即被暂停。 17- HCE应用后台刷卡<br> 18后台刷卡是指不打开特定的HCE应用程序,当电子设备触碰NFC读卡器时,根据NFC读卡器选择的应用ID(Applet ID,AID,参考ISO/IEC 7816-4规范)匹配到HCE应用程序,并自动和匹配的HCE应用程序通信完成刷卡交易。如果NFC读卡器选择的应用ID,匹配到多个HCE应用程序时,说明存在冲突,需要用户打开指定的HCE应用,重新靠近NFC读卡器触发刷卡。 19 20## HCE应用刷卡的约束条件 211. 基于刷卡安全性考虑,不论HCE应用是前台方式还是后台方式刷卡,均不支持电子设备在灭屏或熄屏状态下的HCE刷卡操作。<br> 222. 电子设备必须具备NFC控制器芯片,才支持HCE刷卡能力。对于是否具有NFC安全单元芯片,没有约束要求。<br> 233. HCE应用程序需要声明NFC卡模拟权限,具体见示例。<br> 24 25## 接口说明 26 27NFC卡模拟完整的API说明以及实例代码请参考:[NFC卡模拟接口](../../reference/apis-connectivity-kit/js-apis-cardEmulation.md)。 28 29完成HCE卡模拟功能,可能使用到下面的接口。 30 31| 接口名 | 功能描述 | 32| ---------------------------------- | ------------------------------------------------------------------------------ | 33| start(elementName: ElementName, aidList: string[]): void | 启动HCE业务功能。包括设置当前应用为前台优先,动态注册AID列表。 | 34| stop(elementName: ElementName): void | 停止HCE业务功能。包括取消APDU数据接收的订阅、退出当前应用前台优先、释放动态注册的AID列表。 | 35| on(type: 'hceCmd', callback: AsyncCallback\<number[]>): void | 订阅回调,用于接收对端读卡设备发送的APDU数据。 | 36| transmit(response: number[]): Promise\<void> | 发送APDU数据到对端读卡设备。| 37| off(type: 'hceCmd', callback?: AsyncCallback\<number[]>): void| 取消APDU数据接收的订阅。| 38 39## 开发准备 40 41### HCE应用支持前台或后台刷卡的选择 42HCE应用开发者根据业务需要,可以选择实现前台刷卡或者后台刷卡。两种不同的刷卡方式,代码实现上会存在一些差异。 43- HCE应用前台刷卡<br> 441. 在配置文件module.json5中,不需要静态声明NFC读卡器选择的应用ID(AID,参考ISO/IEC 7816-4规范),而是通过[start](../../reference/apis-connectivity-kit/js-apis-cardEmulation.md#start9)来动态注册。 452. HCE应用的刷卡页面退出时,需要显性调用[stop](../../reference/apis-connectivity-kit/js-apis-cardEmulation.md#stop9)来释放动态注册的AID刷卡配置项。 46- HCE应用后台刷卡<br> 471. 在配置文件module.json5中,需要静态声明NFC读卡器选择的应用ID(AID)。根据业务选择, 选择声明的AID是属于Payment类型,还是Other类型。 482. 如果选择Payment类型,该HCE应用会在系统设置页面的NFC"默认付款应用"里出现。用户必须选择该HCE应用作为默认支付应用后,才能实现后台刷卡功能。由于提供了默认支付应用的选项, 因此Payment类型的HCE应用,不会出现多个冲突的情况。 493. 如果选择Other类型,该HCE应用不会出现在系统设置页面的NFC"默认付款应用"里,但是多个HCE应用如果都声明了相同的Other类型的AID时,会出现冲突的可能。 504. HCE应用后台刷卡的实现,不需要调用接口start和stop。 51 52> **注意:** 53> 54> - 从API version 9之后的应用开发新增支持Stage模型,作为目前主推并长期演进的模型。 55> - HCE示例代码的提供,全部按照Stage模型来说明。 56 57## 开发步骤 58 59### HCE应用前台刷卡 601. 在module.json5文件中声明NFC卡模拟权限,以及声明HCE特定的action。 612. import需要的NFC卡模拟模块和其他相关的模块。 623. 判断设备是否支持NFC能力和HCE能力。 634. 使能前台HCE应用程序优先处理NFC刷卡功能。 645. 订阅HCE APDU数据的接收。 656. 完成HCE刷卡APDU数据的接收和发送。 667. 退出应用程序NFC刷卡页面时,退出前台优先功能。 67 68```ts 69 "abilities": [ 70 { 71 "name": "EntryAbility", 72 "srcEntry": "./ets/entryability/EntryAbility.ts", 73 "description": "$string:EntryAbility_desc", 74 "icon": "$media:icon", 75 "label": "$string:EntryAbility_label", 76 "startWindowIcon": "$media:icon", 77 "startWindowBackground": "$color:start_window_background", 78 "exported": true, 79 "skills": [ 80 { 81 "entities": [ 82 "entity.system.home" 83 ], 84 "actions": [ 85 "ohos.want.action.home", 86 87 // actions必须包含"ohos.nfc.cardemulation.action.HOST_APDU_SERVICE" 88 "ohos.nfc.cardemulation.action.HOST_APDU_SERVICE" 89 ] 90 } 91 ] 92 } 93 ], 94 "requestPermissions": [ 95 { 96 // 添加使用NFC卡模拟需要的权限 97 "name": "ohos.permission.NFC_CARD_EMULATION", 98 "reason": "$string:app_name", 99 } 100 ] 101``` 102 103```ts 104import { cardEmulation } from '@kit.ConnectivityKit'; 105import { BusinessError } from '@kit.BasicServicesKit'; 106import { hilog } from '@kit.PerformanceAnalysisKit'; 107import { AsyncCallback } from '@kit.BasicServicesKit'; 108import { AbilityConstant, UIAbility, Want, bundleManager } from '@kit.AbilityKit'; 109 110let hceElementName: bundleManager.ElementName; 111let hceService: cardEmulation.HceService; 112 113const hceCommandCb : AsyncCallback<number[]> = (error : BusinessError, hceCommand : number[]) => { 114 if (!error) { 115 if (hceCommand == null) { 116 hilog.error(0x0000, 'testTag', 'hceCommandCb has invalid hceCommand.'); 117 return; 118 } 119 // 应用程序根据自己业务实现,检查接收到的指令内容,发送匹配的响应数据 120 hilog.info(0x0000, 'testTag', 'hceCommand = %{public}s', JSON.stringify(hceCommand)); 121 let responseData = [0x90, 0x00]; // 根据接收到的不同指令更改响应数据 122 hceService.transmit(responseData).then(() => { 123 hilog.info(0x0000, 'testTag', 'hceService transmit Promise success.'); 124 }).catch((err: BusinessError) => { 125 hilog.error(0x0000, 'testTag', 'hceService transmit Promise error = %{public}s', JSON.stringify(err)); 126 }); 127 } else { 128 hilog.error(0x0000, 'testTag', 'hceCommandCb error %{public}s', JSON.stringify(error)); 129 } 130} 131 132export default class EntryAbility extends UIAbility { 133 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 134 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 135 136 // 判断设备是否支持NFC能力和HCE能力 137 if (!canIUse("SystemCapability.Communication.NFC.Core")) { 138 hilog.error(0x0000, 'testTag', 'nfc unavailable.'); 139 return; 140 } 141 if (!cardEmulation.hasHceCapability()) { 142 hilog.error(0x0000, 'testTag', 'hce unavailable.'); 143 return; 144 } 145 146 // hceElementName中元素不能为空,通过want获取应用的elementname或按应用实际信息填写 147 hceElementName = { 148 bundleName: want.bundleName ?? '', 149 abilityName: want.abilityName ?? '', 150 moduleName: want.moduleName, 151 } 152 hceService = new cardEmulation.HceService(); 153 } 154 155 onForeground() { 156 // 应用进入前台 157 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); 158 if (hceElementName != undefined) { 159 try { 160 // 调用接口使能前台HCE应用程序优先处理NFC刷卡功能 161 let aidList = ["A0000000031010", "A0000000031011"]; // 修改为正确的aid 162 hceService.start(hceElementName, aidList); 163 164 // 订阅HCE APDU数据的接收 165 hceService.on('hceCmd', hceCommandCb); 166 } catch (error) { 167 hilog.error(0x0000, 'testTag', 'hceService.start error = %{public}s', JSON.stringify(error)); 168 } 169 } 170 } 171 172 onBackground() { 173 // 应用退到后台 174 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); 175 // 退出应用程序NFC标签页面时,调用tag模块退出前台优先功能 176 if (hceElementName != undefined) { 177 try { 178 hceService.stop(hceElementName); 179 } catch (error) { 180 hilog.error(0x0000, 'testTag', 'hceService.stop error = %{public}s', JSON.stringify(error)); 181 } 182 } 183 } 184} 185``` 186 187### HCE应用后台刷卡 1881. 在module.json5文件中声明NFC卡模拟权限,声明HCE特定的action,声明应用能够处理的AID。 1892. import需要的NFC卡模拟模块和其他相关的模块。 1903. 判断设备是否支持NFC能力和HCE能力。 1914. 订阅HCE APDU数据的接收。 1925. 完成HCE刷卡APDU数据的接收和发送。 1936. 退出应用程序时,退出订阅功能。 194 195```ts 196 "abilities": [ 197 { 198 "name": "EntryAbility", 199 "srcEntry": "./ets/entryability/EntryAbility.ts", 200 "description": "$string:EntryAbility_desc", 201 "icon": "$media:icon", 202 "label": "$string:EntryAbility_label", 203 "startWindowIcon": "$media:icon", 204 "startWindowBackground": "$color:start_window_background", 205 "exported": true, 206 "skills": [ 207 { 208 "entities": [ 209 "entity.system.home" 210 ], 211 "actions": [ 212 "ohos.want.action.home", 213 214 // actions必须包含"ohos.nfc.cardemulation.action.HOST_APDU_SERVICE" 215 "ohos.nfc.cardemulation.action.HOST_APDU_SERVICE" 216 ] 217 } 218 ], 219 220 // 根据业务需要至少定义一个Payment类型或Other类型的AID,可以定义多个 221 "metadata": [ 222 { 223 "name": "payment-aid", 224 "value": "A0000000031010" // 定义Payment类型的AID 225 }, 226 { 227 "name": "other-aid", 228 "value": "A0000000031011" // 定义Other类型的AID 229 } 230 ] 231 } 232 ], 233 "requestPermissions": [ 234 { 235 // 添加使用NFC卡模拟需要的权限 236 "name": "ohos.permission.NFC_CARD_EMULATION", 237 "reason": "$string:app_name", 238 } 239 ] 240``` 241 242```ts 243import { cardEmulation } from '@kit.ConnectivityKit'; 244import { BusinessError } from '@kit.BasicServicesKit'; 245import { hilog } from '@kit.PerformanceAnalysisKit'; 246import { AsyncCallback } from '@kit.BasicServicesKit'; 247import { AbilityConstant, UIAbility, Want, bundleManager } from '@kit.AbilityKit'; 248 249let hceElementName : bundleManager.ElementName; 250let hceService: cardEmulation.HceService; 251 252const hceCommandCb : AsyncCallback<number[]> = (error : BusinessError, hceCommand : number[]) => { 253 if (!error) { 254 if (hceCommand == null) { 255 hilog.error(0x0000, 'testTag', 'hceCommandCb has invalid hceCommand.'); 256 return; 257 } 258 259 // 应用程序根据自己业务实现,检查接收到的指令内容,发送匹配的响应数据 260 hilog.info(0x0000, 'testTag', 'hceCommand = %{public}s', JSON.stringify(hceCommand)); 261 let responseData = [0x90, 0x00]; // 根据接收到的不同指令更改响应数据 262 hceService.transmit(responseData).then(() => { 263 hilog.info(0x0000, 'testTag', 'hceService transmit Promise success.'); 264 }).catch((err: BusinessError) => { 265 hilog.error(0x0000, 'testTag', 'hceService transmit Promise error = %{public}s', JSON.stringify(err)); 266 }); 267 } else { 268 hilog.error(0x0000, 'testTag', 'hceCommandCb error %{public}s', JSON.stringify(error)); 269 } 270} 271 272export default class EntryAbility extends UIAbility { 273 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 274 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 275 276 // 判断设备是否支持NFC能力和HCE能力 277 if (!canIUse("SystemCapability.Communication.NFC.Core")) { 278 hilog.error(0x0000, 'testTag', 'nfc unavailable.'); 279 return; 280 } 281 if (!cardEmulation.hasHceCapability()) { 282 hilog.error(0x0000, 'testTag', 'hce unavailable.'); 283 return; 284 } 285 286 // 应用程序被运行到前台时,订阅HCE刷卡数据的接收 287 hceService = new cardEmulation.HceService(); 288 hceService.on('hceCmd', hceCommandCb); 289 } 290 291 onForeground() { 292 // 应用进入前台 293 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); 294 } 295 296 onDestroy() { 297 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); 298 // 退出应用程序,取消订阅接受HCE刷卡数据 299 hceService.off('hceCmd', hceCommandCb); 300 } 301} 302```