I'm working on my NFC-HCE project for Android. I stuck into a problem that my app doesn't recognize APDU SELECT Query.
I'm using ACL122U reader powered by chrome-nfc with Forum Type 4 support and APDU_SELECT query goes like 00A4040007F039414848483500. I checked it many time, there can't be error in AID. I tried to change several times, I found the working app on the Net which was working when I changed AID in my chrome-nfc. I guess there is something about manifest problem or service launching problem. I will appreciate any help.
AndroidManifest.xml
<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>
ApduService.xml
<?xml version="1.0" encoding="utf-8"?>
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/servicedesc"
android:requireDeviceUnlock="false">
<aid-group android:description="@string/aiddescription"
android:category="other">
<aid-filter android:name="F0394148484835"/>
</aid-group>
</host-apdu-service>
myHostApduService.java
public class myHostApduService extends HostApduService {
private static final String TAG = "myHostApduService";
//
private static final byte[] APDU_SELECT = {
(byte)0x00, // CLA - Class - Class of instruction
(byte)0xA4, // INS - Instruction - Instruction code
(byte)0x04, // P1 - Parameter 1 - Instruction parameter 1
(byte)0x00, // P2 - Parameter 2 - Instruction parameter 2
(byte)0x07, // Lc field - Number of bytes present in the data field of the command
(byte)0xF0, (byte)0x39, (byte)0x41, (byte)0x48, (byte)0x48, (byte)0x48, (byte)0x35, // NDEF Tag Application name
(byte)0x00 // Le field - Maximum number of bytes expected in the data field of the response to the command
};
private static final byte[] CAPABILITY_CONTAINER = {
(byte)0x00, // CLA - Class - Class of instruction
(byte)0xa4, // INS - Instruction - Instruction code
(byte)0x00, // P1 - Parameter 1 - Instruction parameter 1
(byte)0x0c, // P2 - Parameter 2 - Instruction parameter 2
(byte)0x02, // Lc field - Number of bytes present in the data field of the command
(byte)0xe1, (byte)0x03 // file identifier of the CC file
};
private static final byte[] READ_CAPABILITY_CONTAINER = {
(byte)0x00, // CLA - Class - Class of instruction
(byte)0xb0, // INS - Instruction - Instruction code
(byte)0x00, // P1 - Parameter 1 - Instruction parameter 1
(byte)0x00, // P2 - Parameter 2 - Instruction parameter 2
(byte)0x0f // Lc field - Number of bytes present in the data field of the command
};
// In the scenario that we have done a CC read, the same byte[] match
// for ReadBinary would trigger and we don't want that in succession
private boolean READ_CAPABILITY_CONTAINER_CHECK = false;
private static final byte[] READ_CAPABILITY_CONTAINER_RESPONSE = {
(byte)0x00, (byte)0x0F, // CCLEN length of the CC file
(byte)0x20, // Mapping Version 2.0
(byte)0x00, (byte)0x3B, // MLe maximum 59 bytes R-APDU data size
(byte)0x00, (byte)0x34, // MLc maximum 52 bytes C-APDU data size
(byte)0x04, // T field of the NDEF File Control TLV
(byte)0x06, // L field of the NDEF File Control TLV
(byte)0xE1, (byte)0x04, // File Identifier of NDEF file
(byte)0x00, (byte)0x32, // Maximum NDEF file size of 50 bytes
(byte)0x00, // Read access without any security
(byte)0x00, // Write access without any security
(byte)0x90, (byte)0x00 // A_OKAY
};
private static final byte[] NDEF_SELECT = {
(byte)0x00, // CLA - Class - Class of instruction
(byte)0xa4, // Instruction byte (INS) for Select command
(byte)0x00, // Parameter byte (P1), select by identifier
(byte)0x0c, // Parameter byte (P1), select by identifier
(byte)0x02, // Lc field - Number of bytes present in the data field of the command
(byte)0xE1, (byte)0x04 // file identifier of the NDEF file retrieved from the CC file
};
private static final byte[] NDEF_READ_BINARY_NLEN = {
(byte)0x00, // Class byte (CLA)
(byte)0xb0, // Instruction byte (INS) for ReadBinary command
(byte)0x00, (byte)0x00, // Parameter byte (P1, P2), offset inside the CC file
(byte)0x02 // Le field
};
private static final byte[] NDEF_READ_BINARY_GET_NDEF = {
(byte)0x00, // Class byte (CLA)
(byte)0xb0, // Instruction byte (INS) for ReadBinary command
(byte)0x00, (byte)0x00, // Parameter byte (P1, P2), offset inside the CC file
(byte)0x0f // Le field
};
private static final byte[] A_OKAY = {
(byte)0x90, // SW1 Status byte 1 - Command processing status
(byte)0x00 // SW2 Status byte 2 - Command processing qualifier
};
private static final byte[] NDEF_ID = {
(byte)0xE1,
(byte)0x04
};
private NdefRecord NDEF_URI = new NdefRecord(
NdefRecord.TNF_WELL_KNOWN,
NdefRecord.RTD_TEXT,
NDEF_ID,
"Hello world, bitch!".getBytes(Charset.forName("UTF-8"))
);
private byte[] NDEF_URI_BYTES = NDEF_URI.toByteArray();
private byte[] NDEF_URI_LEN = BigInteger.valueOf(NDEF_URI_BYTES.length).toByteArray();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.hasExtra("ndefMessage")) {
NDEF_URI = new NdefRecord(
NdefRecord.TNF_WELL_KNOWN,
NdefRecord.RTD_TEXT,
NDEF_ID,
intent.getStringExtra("ndefMessage").getBytes(Charset.forName("UTF-8"))
);
NDEF_URI_BYTES = NDEF_URI.toByteArray();
NDEF_URI_LEN = BigInteger.valueOf(NDEF_URI_BYTES.length).toByteArray();
Context context = getApplicationContext();
CharSequence text = "Your NDEF text has been set!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
}
Log.i(TAG, "onStartCommand() | NDEF" + NDEF_URI.toString());
return 0;
}
@Override
public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
//
// The following flow is based on Appendix E "Example of Mapping Version 2.0 Command Flow"
// in the NFC Forum specification
//
Log.i(TAG, "processCommandApdu() | incoming commandApdu: " + utils.bytesToHex(commandApdu));
//
// First command: NDEF Tag Application select (Section 5.5.2 in NFC Forum spec)
//
if (utils.isEqual(APDU_SELECT, commandApdu)) {
Log.i(TAG, "APDU_SELECT triggered. Our Response: " + utils.bytesToHex(A_OKAY));
return A_OKAY;
}
//
// Second command: Capability Container select (Section 5.5.3 in NFC Forum spec)
//
if (utils.isEqual(CAPABILITY_CONTAINER, commandApdu)) {
Log.i(TAG, "CAPABILITY_CONTAINER triggered. Our Response: " + utils.bytesToHex(A_OKAY));
return A_OKAY;
}
//
// Third command: ReadBinary data from CC file (Section 5.5.4 in NFC Forum spec)
//
if (utils.isEqual(READ_CAPABILITY_CONTAINER, commandApdu) && !READ_CAPABILITY_CONTAINER_CHECK) {
Log.i(TAG, "READ_CAPABILITY_CONTAINER triggered. Our Response: " + utils.bytesToHex(READ_CAPABILITY_CONTAINER_RESPONSE));
READ_CAPABILITY_CONTAINER_CHECK = true;
return READ_CAPABILITY_CONTAINER_RESPONSE;
}
//
// Fourth command: NDEF Select command (Section 5.5.5 in NFC Forum spec)
//
if (utils.isEqual(NDEF_SELECT, commandApdu)) {
Log.i(TAG, "NDEF_SELECT triggered. Our Response: " + utils.bytesToHex(A_OKAY));
return A_OKAY;
}
//
// Fifth command: ReadBinary, read NLEN field
//
if (utils.isEqual(NDEF_READ_BINARY_NLEN, commandApdu)) {
byte[] start = {
(byte)0x00
};
// Build our response
byte[] response = new byte[start.length + NDEF_URI_LEN.length + A_OKAY.length];
System.arraycopy(start, 0, response, 0, start.length);
System.arraycopy(NDEF_URI_LEN, 0, response, start.length, NDEF_URI_LEN.length);
System.arraycopy(A_OKAY, 0, response, start.length + NDEF_URI_LEN.length, A_OKAY.length);
Log.i(TAG, response.toString());
Log.i(TAG, "NDEF_READ_BINARY_NLEN triggered. Our Response: " + utils.bytesToHex(response));
return response;
}
//
// Sixth command: ReadBinary, get NDEF data
//
if (utils.isEqual(NDEF_READ_BINARY_GET_NDEF, commandApdu)) {
Log.i(TAG, "processCommandApdu() | NDEF_READ_BINARY_GET_NDEF triggered");
byte[] start = {
(byte)0x00
};
// Build our response
byte[] response = new byte[start.length + NDEF_URI_LEN.length + NDEF_URI_BYTES.length + A_OKAY.length];
System.arraycopy(start, 0, response, 0, start.length);
System.arraycopy(NDEF_URI_LEN, 0, response, start.length, NDEF_URI_LEN.length);
System.arraycopy(NDEF_URI_BYTES, 0, response, start.length + NDEF_URI_LEN.length, NDEF_URI_BYTES.length);
System.arraycopy(A_OKAY, 0, response, start.length + NDEF_URI_LEN.length + NDEF_URI_BYTES.length, A_OKAY.length);
Log.i(TAG, NDEF_URI.toString());
Log.i(TAG, "NDEF_READ_BINARY_GET_NDEF triggered. Our Response: " + utils.bytesToHex(response));
Context context = getApplicationContext();
CharSequence text = "NDEF text has been sent to the reader!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
READ_CAPABILITY_CONTAINER_CHECK = false;
return response;
}
//
// We're doing something outside our scope
//
Log.wtf(TAG, "processCommandApdu() | I don't know what's going on!!!.");
return "Can I help you?".getBytes();
}
@Override
public void onDeactivated(int reason) {
Log.i(TAG, "onDeactivated() Fired! Reason: " + reason);
}
}
sendOverNfcActivity.java
public class sendOverNFCActivity extends Activity{
private static final String TAG = "sendOverNFCActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sendnfc);
Button setNdef = (Button) findViewById(R.id.set_ndef_button);
setNdef.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//
// Technically, if this is past our byte limit,
// it will cause issues.
//
// TODO: add validation
//
TextView getNdefString = (TextView) findViewById(R.id.ndef_text);
String test = getNdefString.getText().toString();
Intent intent = new Intent(getApplicationContext(), myHostApduService.class);
intent.putExtra("ndefMessage", test);
startService(intent);
}
}
);
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onPause() {
super.onPause();
}
@Override
public void onStop() {
super.onStop();
}
@Override
public void onDestroy() {
super.onDestroy();
}
The problem is that public byte[] processCommandApdu(byte[] commandApdu, Bundle extras)
never runs and app sending 6a82 every time in response that mean file/app was not found. I totally can't understand why this happens even if AID is correct.