Near-Field Communication
unter Android

2012-01-25

Andreas Schildbach
andreas@schildbach.de


Android 2.3 (API Level 9) — Basics

Android 2.3.3 (API Level 10) — NDEF - NFC Data Exchange Format

Android 4.0 (API Level 14) — "Android Beam"


Eigenschaften


Geräte


Use-Cases


NDEF — NFC Data Exchange Format


AndroidManifest.xml


<uses-sdk android:targetSdkVersion="9|10|14" />

...

<uses-permission android:name="android.permission.NFC" />

...

<uses-feature android:name="android.hardware.nfc"
              android:required="true|false" />

Initialisierung, für Android 2.3+:
NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
NfcAdapter adapter = manager.getDefaultAdapter();
if (adapter != null && adapter.isEnabled()) {
    ...
}

Rückwärtskompatibel:
Object manager = context.getSystemService(Context.NFC_SERVICE);
if (manager != null) {
    NfcTools.publish(nfcManager, ...);
}

class NfcTools {
    public static void publish(Object nfcManager, ...) {
        NfcAdapter adapter = ((NfcManager) nfcManager).getDefaultAdapter();
        if (adapter != null && adapter.isEnabled()) {
            ...
        }
    }
}

Tag mit NDEF Message beschreiben


protected void onResume() {
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
            new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
    IntentFilter intentFilter = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);

    adapter.enableForegroundDispatch(this, pendingIntent, new IntentFilter[] { intentFilter }, null);
    ...
}

protected void onPause() {
    adapter.disableForegroundDispatch(this);
    ...
}

public void onNewIntent(Intent intent) {
    Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    Ndef ndef = Ndef.get(tag);

    NdefRecord record = uriRecord("http://schildbach.de/talks/nfc/");
    NdefMessage message = new NdefMessage(new NdefRecord[] { record });

    ndef.connect();
    // ndef.getMaxSize();
    ndef.writeNdefMessage(message);
    // ndef.makeReadOnly();
    ndef.close();
}

private NdefRecord uriRecord(String uri) {
    final byte[] uriBytes = uri.getBytes(UTF_8);
    final byte[] recordBytes = new byte[uriBytes.length + 1];
    recordBytes[0] = (byte) 0x0;
    System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length);
    return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, new byte[0], recordBytes);
}

private NdefRecord mimeRecord(String mimeType, byte[] payload) {
    final byte[] mimeBytes = mimeType.getBytes(US_ASCII);
    return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, mimeBytes, new byte[0], payload);
}

Android 4+:

NdefRecord.createUri(String uri)


NDEF Message von Tag lesen

protected void onResume() {
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
            new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
    IntentFilter intentFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
    intentFilter.addDataScheme("http");

    adapter.enableForegroundDispatch(this, pendingIntent, new IntentFilter[] { intentFilter }, null);
    ...
}

protected void onPause() {
    adapter.disableForegroundDispatch(this);
    ...
}

public void onNewIntent(Intent intent) {
    Uri uri = intent.getData();
    ...
}

Intent Dispatch

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <data android:scheme="bitcoin" /> oder
    <data android:mimeType="text/x-­vcard" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
protected void onCreate(Bundle savedInstanceState) {
    handleIntent(getIntent());
    ...
}

protected void onNewIntent(Intent intent) {
    handleIntent(intent);
}

private void handleIntent(Intent intent) {
    Uri data = intent.getData();
    if (data != null && "bitcoin".equals(data.getScheme())) {
        ...
    }
}
Funktioniert ohne Permission!

NDEF Push / Tag emulation

protected void onResume() {
    NdefRecord record = uriRecord("http://schildbach.de/talks/nfc/");
    NdefMessage message = new NdefMessage(new NdefRecord[] { record });

    adapter.enableForegroundNdefPush(Activity.this, message);
    ...
}

protected void onPause() {
    adapter.disableForegroundNdefPush(Activity.this);
    ...
}
Variante mit Lifecycle-Management (Android 4+):
protected void onCreate() {
    NdefRecord record = NdefRecord.createUri("http://schildbach.de/talks/nfc/");
    NdefMessage message = new NdefMessage(new NdefRecord[] { record });

    adapter.setNdefPushMessage(message, Activity.this);
    ...
}
Message dynamisch erstellen (Android 4+):
protected void onCreate() {
	CreateNdefMessageCallback callback = new CreateNdefMessageCallback() {
        public NdefMessage createNdefMessage(NfcEvent event) {
            NdefRecord record = NdefRecord.createUri("http://schildbach.de/talks/nfc/");
            return new NdefMessage(new NdefRecord[] { record });
        }
    };

    adapter.setNdefPushMessageCallback(callback, Activity.this);
}
Außerdem Android 4+:
adapter.setOnNdefPushCompleteCallback(callback, Activity.this);

Android Application Record

Android 4+:
NdefRecord record = NdefRecord.createUri("bitcoin:1CX6NdhkSiyYvsauTr2HUgeyuaEqJAN6st");

NdefRecord appRecord = NdefRecord.createApplicationRecord("de.schildbach.wallet");

NdefMessage message = new NdefMessage(new NdefRecord[] { record, appRecord });

Rückwärtskompatibel:
NdefRecord record = uriRecord("bitcoin:1CX6NdhkSiyYvsauTr2HUgeyuaEqJAN6st");

final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes(US_ASCII);
NdefRecord appRecord = new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE,
                       RTD_ANDROID_APP, new byte[0], "de.schildbach.wallet".getBytes(US_ASCII));

NdefMessage message = new NdefMessage(new NdefRecord[] { record, appRecord });

Achtung: Application Records stehen offenem App-Ökosystem entgegen!

Low Level

<intent-filter>
    <action android:name="android.nfc.action.TECH_DISCOVERED"/>
</intent-filter>

<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
    android:resource="@xml/nfc_tech_filter" />


<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.MifareClassic</tech>
    </tech-list>
</resources>


MifareClassic mifare = MifareClassic.get(intent.getParcelableExtra(NfcAdapter.EXTRA_TAG));

mifare.connect();
mifare.authenticateSectorWithKeyA(...);
mifare.readBlock(...);
mifare.writeBlock(...);
mifare.close();


<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
    </tech-list>
</resources>


IsoDep isodep = IsoDep.get(intent.getParcelableExtra(NfcAdapter.EXTRA_TAG));

isodep.connect();
byte[] request = new byte[] { ... };
byte[] response = isodep.transceive(request);
isodep.close();


Vortragsfolien — http://schildbach.de/talks/nfc/


Weitere Infos:

NFC Spezifikationen — http://www.nfc-forum.org/specs/spec_list/

NFC im Android Dev Guide — http://developer.android.com/guide/topics/nfc/index.html

Android Beam Demo — http://developer.android.com/resources/samples/AndroidBeamDemo/index.html


Q & A


Andreas Schildbach andreas@schildbach.de