以前から興味はあったのですが、ようやく仕事で使う機会に恵まれそうなので、 NFC を試してみました。
既にネットにいろいろと情報がありますが、自分なりに気づいた点をメモしておきます。
なお、検証は Nexus 6P, Android 8.1.0 で行いました。
NFC 全般について
最初はネットの記事などをいろいろと呼んでいたのですが、今一つ腑に落ちません。ということで、ちょっと昔の本になりますが、一冊 Amazon で買いました。
- 作者:株式会社ブリリアントサービス
- 発売日: 2013/11/30
- メディア: 単行本(ソフトカバー)
私の場合、やっぱり、何か新しいものを理解しようとするときは、本で概要をつかむほうが手っ取り早いです。 NFC Forum Tag の仕様なども載っていて、NFC 触るなら手元に一冊あってもいい感じだと思います。
タグの購入
NFCのテスト用に Amazon で買いました。
NXP Ntag 213 チップ, Type-2 タグ。シールタイプ。
にしても、手軽に買えるようになりましたね。
読み込みサンプル
大きく分けて、NFC の読み込みを行う際は、
- スマホで NFC タグにタッチした際に、アプリを起動させる
- アプリを起動している際に、 NFC にタッチして読み込む
の2パターンがあると思います。
前者を実現するには、 AndroidManifest にインテントフィルタ(暗黙的インテント)を定義してやります。後者に対応するには、アプリ内で NfcAdapter#enableForegroundDispatch を呼び出す必要があります(NfcAdapter#enableReaderMode でもできると思いますが、今回は試していません)。
個人的には後者でやるほうが、動きがわかりやすいように思います。
なお、今回作成した NFC の読み取りと(この記事では別段書き込みに触れてるところはないですが)書き込みサンプルをそれぞれ Gist に上げておきます。何かの参考にしてください。
インテントの指定方法
Android のドキュメントには、NFC を読み込んだ際、3種類のインテントが発行されるとあります。この際、ACTION_TAG_DISCOVERED 以外のインテントにはインテント名だけでなく、インテントフィルタの定義に追加情報が必要です。
インテント | 追加情報 |
---|---|
ACTION_NDEF_DISCOVERED | mimetype |
ACTION_TECH_DISCOVERED | tech list |
ACTION_TAG_DISCOVERED | - |
追加情報がない場合、インテントフィルタを定義していてもその、正しくインテントの呼び出しがおこなわれませんでした(追加情報なしだからといって、ワイルドカード的にはならないようです)。以下に、具体例を示しておきます。
(参考) ACTION_TECH_DISCOVERED に tech list が必要という話
Android NFC Intent-filter for all type - Stack Overflow
ACTION_NDEF_DISCOVERED の例
NDEF に text/plain の情報が書き込まれているNFCタグを読み取る場合を想定します。
AndroidManifest での定義
例えば、AndroidManifest にインテントフィルタを定義して、スマホでNFCタグにタッチした際に、アプリを起動させてみると、
指定した mimeType | アプリ起動 |
---|---|
text/plain | 〇 |
image/jpeg | × |
なし | × |
となります。
mimeType 指定が text/plain を指定
<activity android:name=".MainActivity" android:launchMode="singleTask"> <intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter>
mimeType 指定なし
<activity android:name=".MainActivity" android:launchMode="singleTask"> <intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter>
NfcAdapter#enableForegroundDispatch による指定
今度は、NfcAdapter#enableForegroundDispatch を呼び出し、アプリを起動中に、NFCタグにタッチして正しく読み込めるか試すと、
mimeType 指定 | 読み込み(画面表示) |
---|---|
text/plain | 〇 |
image/jpeg | × |
なし | × |
となりました。
mimeType 指定が text/plain の場合
@Override protected void onResume() { super.onResume(); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0); IntentFilter ndefFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED); try { ndefFilter.addDataType("text/plain"); } catch (IntentFilter.MalformedMimeTypeException e) { e.printStackTrace(); } IntentFilter[] intentFilters = new IntentFilter[] {ndefFilter}; mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null); }
mimeType 指定がなし
@Override protected void onResume() { super.onResume(); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0); IntentFilter ndefFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED); IntentFilter[] intentFilters = new IntentFilter[] {ndefFilter}; mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null); }
ACTION_TECH_DISCOVERED の例
次に、購入直後のNFCタグ(一度も書き込みを行っていないタグ, Type-2タグ)を読み取る場合を想定します。
AndroidManifest での定義
上記と同じく、AndroidManifest にインテントフィルタを定義して、スマホでNFCタグにタッチした際に、アプリを起動させてみると、
tech list 指定 | アプリ起動 |
---|---|
tech list あり | 〇 |
なし | × |
となりました(下記に示すように tech list として xml/nfc_tect_list.xml を作成して、その中で Ndef を指定しています)。
AndroidManifest.xml
<intent-filter> <action android:name="android.nfc.action.TECH_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />
xml/nfc_tect_list.xml の中身
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <tech-list> <tech>android.nfc.tech.Ndef</tech> </tech-list> </resources>
NfcAdapter#enableForegroundDispatch による指定
例えば、NfcAdapter#enableForegroundDispatch を呼び出し、アプリを起動中に、NFCタグにタッチして正しく読み込めるか試すと、
tech list 指定 | アプリ起動 |
---|---|
tech list あり | 〇 |
なし | × |
となります。
tech list がある場合の NfcAdapter#enableForegroundDispatch 呼び出し
@Override protected void onResume() { super.onResume(); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0); IntentFilter techFilter = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED); IntentFilter[] intentFilters = new IntentFilter[] {techFilter}; String tech = Ndef.class.getName(); String techlist[] = new String[]{tech}; // and 条件 String techLists[][] = new String[][] {techlist}; // or 条件 // TECH_DISCOVERED, tech list 必須 mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, techLists); }
tech list がない場合の NfcAdapter#enableForegroundDispatch 呼び出し
@Override protected void onResume() { super.onResume(); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0); IntentFilter techFilter = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED); IntentFilter[] intentFilters = new IntentFilter[] {techFilter}; // TECH_DISCOVERED, tech list 必須 mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null); }
ACTION_NDEF_DISCOVERED の例外
ドキュメントによると NfcAdapter#enableForegroundDispatch のインテントフィルタと tech list 引数の両方に null を渡した場合、 ACTION_TAG_DISCOVERED を取得できるとあります。
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0); // すべてを受け付ける mNfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
でも、実はこの時、 ACTION_TAG_DISCOVERED だけでなく、 ACTION_NDEF_DISCOVERED にも反応していました。この時は、上記と異なり、mimeType の指定がなくてもです。
AndroidManifest でのインテントフィルタと NfcAdapter#enableForegroundDispatch のインテントフィルタは同じように指定できると思っていたのですが、この部分については両者で異なるようです。
なお、 NfcAdapter#enableForegroundDispatch を呼び出す際に、 ACTION_TAG_DISCOVERED をインテントフィルタで明示的に指定しても、取得することができました。
ACTION_TAG_DISCOVERED に反応するアプリについて
なお、今回実機でいろいろと試していたら、下記の画面が表示されることがありました。
これは、購入してまだ書き込みを行っていない NFC タグを読み取ったときに出てきました。
どうもこれは、 ACTION_TECH_DISCOVERED および ACTION_TAG_DISCOVERED に対応するアプリがインストールされていないときに、おそらくシステムが用意しているアプリが反応したのだと思われます。似たような話が下記にありました。
また、自分で作成したNFC読み取りサンプルが ACTION_TECH_DISCOVERED または ACTION_TAG_DISCOVERED に対応させたときは、このアプリの表示もなくなったので、ほぼあたっているかと思います。特に問題があるわけではありませんが、こういうものがありました、というメモです。
NDEFメッセージについて
Gist の処理を見るとわかりますが、 ACTION_NDEF_DISCOVERED の場合に、 NdefMessage を取得しようとすると、配列の形式で取得します。 でも、 NFC (NDEF) の仕様を眺めていても、Ndefメッセージは一つだけのようです。これはなぜなんでしょうか?
調べてみると、やはり、基本的には1つのNFCタグには1つのNDEFメッセージのようです(参考までに、1つのNDEFメッセージには複数のNDEFレコードを入れることができるようです)。
1つのNFCタグに2つのNDEFメッセージ/レコード - Android
Android のドキュメントにも通常一つのNDEFメッセージとあります。でも、将来の拡張性のため配列にして扱ってる、と書いてます。このため、 NdefMessage を取得する時、配列で戻ってきているようです。
NfcAdapter | Android デベロッパー | Android Developers
これで、だいたい感触がつかめたので、引き続きいろいろと試そうと思います。