Citerus

NFC i Android - Okomplicerat och roligt! - av Patrik Åkerfeld

Blogginlägg   •   Maj 10, 2012 21:44 CEST

Near Field Communication, NFC, är en spännande teknologi med många möjligheter. Kanske inte minst betallösningar som vi förhoppningsvis får se ute i butikerna inom en överskådlig framtid. Trots att jag har haft en telefon med NFC-teknologi i över ett år nu, först Nexus S och nu Galaxy Nexus, så har jag aldrig nyttjat funktionen. Dels för att det inte funnits några intressanta tillämpningar och dels för att jag själv inte hittat någon spännande tillämpning att själv implementera. Tills häromdagen.

För att få tillgång till NFC-funktioner krävs följande tillstånd:

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

I mitt fall behöver jag även API-nivå 10 för att kunna läsa och skriva till taggarna. Det betyder dock inte att man måste kräva denna API-nivå. Om NFC-funktionerna inte är en nödvändighet för appen så kan man helt enkelt utesluta dessa för telefoner som saknar denna möjlighet.

if(Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD_MR1) { // do whatever necessary to hide NFC features }

Man behöver även kontrollera att telefonen har stöd för NFC, dvs om den har ett NFC-chip. Det gör man enklast genom att kolla om NfcAdapter.getDefaultAdapter(context) returnerar null. I RemoteStick finns möjligheten att båda skriva och läsa från taggar. Taggarna måste nämligen först programmeras med information om vilka enheter som ska styras och på vilket sätt. För detta använder jag en separat aktivitet i appen. Här väljer man vilka enheter som ska styras och på vilket sätt och sedan håller man helt enkelt upp taggen för telefonen för att programmera den. För att undvika att andra appar också försöker läsa av taggen i detta skede kan man fånga upp intents och hindra andra aktiviteter att få dessa. Detta kallas Foreground Dispatch.

public void onPause() { super.onPause(); mAdapter.disableForegroundDispatch(this); } public void onResume() { super.onResume(); mAdapter.enableForegroundDispatch(this, pendingIntent,         intentFiltersArray, techListsArray); } public void onNewIntent(Intent intent) { Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); //do something with tagFromIntent }

PendingIntent och IntentFilter skapas på följande sätt:

PendingIntent pendingIntent = PendingIntent.getActivity( this, 0, new Intent(this,     getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED); try { ndef.addDataType("*/*");             /* Handles all MIME based dispatches. You should specify only the ones that you need. */ } catch (MalformedMimeTypeException e) { throw new RuntimeException("fail", e); } intentFiltersArray = new IntentFilter[] {ndef, };

Skrivning till taggen går till så här (om än typiskt i flera steg):

Ndef tag = Ndef.get(tagFromIntent); ... tag.connect(); tag.writeNdefMessage(new NdefMessage(new NdefRecord[] {     new NdefRecord(NdefRecord.TNF_MIME_MEDIA,     “application/org.remotestick”.toBytes(), new byte[0], payload) })); tag.close();

Jag vill att taggen ska tolkas som MIME-data med typen “application/org.remotestick”. Detta gör det betydligt lättare att identifiera RemoteStick-programmerade taggar när de senare läses av. Payload i det här fallet är den byte-array som skrivs till taggen och vars format, som jag definierat själv, är oväsentligt för denna artikel. När en tagg sedan läses in vill jag inte öppna RemoteStick utan bara utföra de kommandon som finns lagrade. Det naturliga vore här att sätta ett intent-filter på en BroadcastReceiver eftersom jag egentligen inte vill öppna RemoteStick men intents för NFC-taggar skickas bara till aktiviteter. I fallet RemoteStick ser därför manifestet ut såhär:

<activity android:name=".activities.NfcCommandDispatcher"></activity> <action android:name="android.nfc.action.NDEF_DISCOVERED"></action> <category android:name="android.intent.category.DEFAULT"></category> <data android:mimetype="application/org.remotestick"></data>

Och i aktivteten (null checks och try-catch exkluderade):

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); Tag tagExtra = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); Ndef tag = Ndef.get(tagExtra); tag.connect(); NdefMessage msg = tag.getNdefMessage(); for (NdefRecord rec : msg.getRecords()) { byte[] payload = rec.getPayload(); // parse payload // send broadcast intent } tag.close(); finish(); }

RemoteStick behöver alltså inte vara körandes för att taggarna ska läsas in av programmet. Aktiviteten läser in och tolkar datat från taggen och skickar det vidare till en BroadcastReceiver. Faktum är att det är samma BroadcastReceiver som används i andra sammanhang (i integrationen med apparna Locale och Tasker) så även om en dispatcher på detta vis kan kännas aningen ful så integrerade det väldigt bra med redan befintlig kod. Just den här tillämpningen kan till exempel användas för att släcka ner hela huset med en tagg vid sängkanten eller slå på belysning när man kommer hem med en tagg vid entrédörren. Hur skulle du vilja se NFC-teknologin användas?