Search code examples
androidnfchce

Host card emulation in android when screen is off


This is how I set up HCE: Manifest.xml

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc.hce" android:required="true"/>

<application
    android:allowBackup="true"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.NFCHCE"
    tools:targetApi="33">
    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:theme="@style/Theme.NFCHCE">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <service
        android:name=".MyHostApduService"
        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>
</application>
</manifest>

apduservice.xml

<?xml version="1.0" encoding="utf-8"?>
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/app_name"
    android:requireDeviceUnlock="false">
    <aid-group android:description="@string/app_name"
        android:category="payment">
        <aid-filter android:name="A0000000041010"/>
    </aid-group>
</host-apdu-service>

MyHostApduService.kt

class MyHostApduService : HostApduService() {
    override fun processCommandApdu(commandApdu: ByteArray?, extras: Bundle?): ByteArray {
        Log.d("commandApdu Test", "commandApdu - $commandApdu")
        commandApdu?.forEach {
            Log.d("commandApdu Test", "connected - $it")
        }
        return byteArrayOf(0x90.toByte(), 0x00.toByte())
    }


    override fun onDeactivated(reason: Int) {
        Log.d("commandApdu Test", "onDeactivated reason: $reason")
    }
}

According to official docs. When setting up HCE in android device, if I set android:requireDeviceUnlock="false" processCommandApdu should get triggered whenever it is tapped to NFC reader. When I tested it I cannot use this feature. I can make it work only when devices screen is on. and also it works even app is cleared from recent apps.

Tested on Samsung A32 android ver. 13 and Xiaomi 12 Lite android ver. 14


Solution

  • There is a difference between unlocked and screen is on.

    If you look at the doc

    HostApduService_requireDeviceScreenOn

    public static final int HostApduService_requireDeviceScreenOn

    Whether the device must be screen on before routing data to this service. > The default is true.

    May be a boolean value, such as "true" or "false".

    As you have only set requireDeviceUnlock="false" and the default requireDeviceScreenOn="True" then this matches the behaviour you are seeing which the screen must be on but it does not need to be unlocked.

    Set requireDeviceScreenOn="false" in your xml and try again.