I am poking around with Android Host-Based Card Emulation (HCE) on my Huawei phone (Android 10). My first goal is to make my phone aware that a point-of-sale (POS) is talking to it. I don't need to pay with it so far. As I don't know how to test my app reliably at my place I bother the local bakery (visiting the bakery once a day is not unusual in France).
Unfortunately this morning when I tested my app on the actual bakery POS, the POS reset to 0.00 although my app processCommandApdu
function did not trigger. I tried approaching my phone a second time (after the baker re-entered the amount) and the POS reset again without any message displayed on my app side (Google Pay did not show either as it was not set as default app for NFC). I did not try a third time (because of other customers present) and paid with my NFC enabled Credit Card.
Please note that my phone NFC settings read as follows : NFC is enabled, default app is my App, and Use the currently running app.
I followed this guide as well as Android documentation on HCE. So my apduservice.xml
registers all Visa Card AIDs shown on wikipedia and looks like :
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/servicedesc"
android:requireDeviceUnlock="false">
<aid-group
android:category="payment"
android:description="@string/aiddescription">
<aid-filter android:name="A0000000031010"/>
<aid-filter android:name="A0000000032010"/>
<aid-filter android:name="A0000000032020"/>
<aid-filter android:name="A0000000038010"/>
</aid-group>
</host-apdu-service>
The HCE service is like
class HostCardEmulatorService : HostApduService() {
companion object {
val TAG = "Host Card Emulator"
val STATUS_SUCCESS = "9000"
val STATUS_FAILED = "6F00"
}
/**
* The function always returns success as this is just a test
*/
override fun processCommandApdu(commandApdu: ByteArray?, extras: Bundle?): ByteArray {
Toast.makeText(this, "ProcessCommandApdu called with \n $commandApdu", Toast.LENGTH_SHORT).show()
println("ProcessCommandApdu called!")
return Utils.hexStringToByteArray(STATUS_SUCCESS)
}
/**
* The `onDeactiveted` method will be called when the a different AID has been selected or the NFC connection has been lost.
*/
override fun onDeactivated(reason: Int) {
Toast.makeText(this, "Connection lost because of $reason!", Toast.LENGTH_SHORT).show()
}
}
The manifest file requires the NFC permissions and asks for the NFC service :
<uses-permission android:name="android.permission.NFC" />
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
<uses-permission android:name="android.permission.NFC_TRANSACTION_EVENT" />
... (standard ANdroid project)
<service
android:name=".HostCardEmulatorService"
android:exported="true"
android:permission="android.permission.BIND_NFC_SERVICE">
<intent-filter>
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
</intent-filter>
<meta-data
android:name="android.nfc.cardemulation.host_apdu_service"
android:resource="@xml/apduservice" />
</service>
And the MainActivity
starts/stops the HCE service :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
//...
val hceService: HostCardEmulatorService = HostCardEmulatorService()
val hceIntent = Intent(applicationContext, HostCardEmulatorService::class.java)
binding.mainButton.setOnClickListener {
when (serviceEnabled) {
false -> {
applicationContext.startService(hceIntent)
if (isServiceRunning(HostCardEmulatorService::class.java)) {
binding.output.setText("Service enabled!")
// Changing state
serviceEnabled = true;
}
}
true -> {
applicationContext.stopService(hceIntent)
binding.output.setText("Service disabled!")
// Changing state
serviceEnabled = false;
}
}
}
}
Now I wonder why my app processCommandApdu
function did not trigger while obviously the POS reached something on my phone ?
Furthermore can I reliably test my first goal (detect a POS is talking to my app) with another NFC enabled phone through an NFC reader app so that I don't bother the baker every day with my geeky tests? I mean is an NFC reader app running on another phone sufficient to trigger my app processCommandApdu
the same way as an actual POS would do ?
Finally (if this can be answered) did the actual POS reset to 0.00 and did not keep the amount and asked for another trial because I returned the "Success command" in processCommandApdu
function (in that case it would mean the function ran but the toast message did not appear or I missed it) ?
Any help appreciated :-)