高度な NFC の概要

このドキュメントでは、さまざまなタグ テクノロジー、NFC タグへの書き込み、フォアグラウンド ディスパッチ(これにより、フォアグラウンドのアプリケーションは、他のアプリケーションが同じインテントをフィルタリングする場合でも、そのインテントを処理できます)など、高度な NFC に関するトピックについて説明します。

サポートされているタグ テクノロジーを利用する

NFC タグや Android デバイスを利用する場合、タグの読み書きに使用する主な形式は NDEF です。デバイスが NDEF データが含まれるタグをスキャンすると、Android は、可能であればメッセージの解析と NdefMessage への配信をサポートします。ただし、NDEF データを含まないタグをスキャンした場合、または NDEF データを MIME タイプまたは URI にマップできない場合があります。その場合、タグと直接通信し、独自のプロトコルで(生のバイト列で)タグを読み書きする必要があります。Android は、android.nfc.tech パッケージ(表 1 を参照)を使用してこうした使用例の一般的なサポートを提供します。たとえば、getTechList() メソッドを使用して、タグでサポートされているテクノロジーを特定し、対応する TagTechnology オブジェクトを android.nfc.tech が提供するクラスのいずれかで作成できます。

表 1. サポートされているタグ テクノロジー

クラス 説明
TagTechnology すべてのタグ テクノロジー クラスで実装する必要があるインターフェースです。
NfcA NFC-A(ISO 14443-3A)プロパティと I/O オペレーションへのアクセスを提供します。
NfcB NFC-B(ISO 14443-3B)プロパティと I/O オペレーションへのアクセスを提供します。
NfcF NFC-F(JIS 6319-4)プロパティと I/O オペレーションへのアクセスを提供します。
NfcV NFC-V(ISO 15693)プロパティと I/O オペレーションへのアクセスを提供します。
IsoDep ISO-DEP(ISO 14443-4)プロパティと I/O オペレーションへのアクセスを提供します。
Ndef NDEF としてフォーマットされた NFC タグの NDEF データとオペレーションへのアクセスを提供します。
NdefFormatable NDEF にフォーマット可能なタグに対するフォーマット オペレーションを提供します。

次のタグ テクノロジーは、Android デバイスでサポートされていなくてもかまいません。

表 2. サポートされているタグ テクノロジー(オプション)

クラス 説明
MifareClassic 当該 Android デバイスが MIFARE をサポートしている場合、MIFARE Classic プロパティと I/O オペレーションへのアクセスを提供します。
MifareUltralight 当該 Android デバイスが MIFARE をサポートしている場合、MIFARE Ultralight プロパティと I/O オペレーションへのアクセスを提供します。

タグ テクノロジーと ACTION_TECH_DISCOVERED インテントを利用する

デバイスで NDEF データを含むタグをスキャンしたが MIME または URI にマッピングできなかった場合、タグ ディスパッチ システムは ACTION_TECH_DISCOVERED インテントを利用してアクティビティを開始しようとします。ACTION_TECH_DISCOVERED は、NDEF 以外のデータを含むタグをスキャンする場合にも使用されます。このフォールバックがあれば、タグ ディスパッチ システムがデータを解析できなかった場合に、タグのデータを直接処理できます。タグ テクノロジーを利用する際の基本的な手順は次のとおりです。

  1. 処理するタグ テクノロジーを指定する ACTION_TECH_DISCOVERED インテントをフィルタリングします。詳細については、NFC インテントのフィルタリングをご覧ください。一般的に、タグ ディスパッチ システムは、NDEF メッセージを MIME タイプまたは URI にマッピングできない場合、またはスキャンされたタグに NDEF データが含まれていない場合、ACTION_TECH_DISCOVERED インテントを開始しようとします。この決定方法に関する詳細は、タグ ディスパッチ システムをご覧ください。
  2. アプリがインテントを受け取ったら、そのインテントから Tag オブジェクトを取得します。

    Kotlin

        var tagFromIntent: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
        

    Java

        Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        
  3. android.nfc.tech パッケージに含まれるクラスの get ファクトリ メソッドのいずれかを呼び出して、TagTechnology のインスタンスを取得します。サポートされているタグ テクノロジーを列挙するには、get ファクトリ メソッドを呼び出す前に getTechList() を呼び出します。たとえば、Tag から MifareUltralight のインスタンスを取得するには、以下のようにします。

    Kotlin

        MifareUltralight.get(intent.getParcelableExtra(NfcAdapter.EXTRA_TAG))
        

    Java

        MifareUltralight.get(intent.getParcelableExtra(NfcAdapter.EXTRA_TAG));
        

タグを読み書きする

NFC タグの読み取りと書き込みには、インテントからタグを取得し、タグとの通信を開始します。タグに対してデータを読み書きするには、独自のプロトコル スタックを定義する必要があります。ただし、タグを直接処理する場合は、NDEF データの読み取りと書き込みが可能です。どのように構築するかはユーザー次第です。次の例は、MIFARE Ultralight タグを利用する方法を示しています。

Kotlin

    package com.example.android.nfc
    import android.nfc.Tag
    import android.nfc.tech.MifareUltralight
    import java.io.IOException
    import java.nio.charset.Charset

    class MifareUltralightTagTester {

        fun writeTag(tag: Tag, tagText: String) {
            MifareUltralight.get(tag)?.use { ultralight ->
                ultralight.connect()
                Charset.forName("US-ASCII").also { usAscii ->
                    ultralight.writePage(4, "abcd".toByteArray(usAscii))
                    ultralight.writePage(5, "efgh".toByteArray(usAscii))
                    ultralight.writePage(6, "ijkl".toByteArray(usAscii))
                    ultralight.writePage(7, "mnop".toByteArray(usAscii))
                }
            }

            fun readTag(tag: Tag): String? {
                return MifareUltralight.get(tag)?.use { mifare ->
                    mifare.connect()
                    val payload = mifare.readPages(4)
                    String(payload, Charset.forName("US-ASCII"))
                }
            }
        }
    }
    

Java

    package com.example.android.nfc;

    import android.nfc.Tag;
    import android.nfc.tech.MifareUltralight;
    import android.util.Log;
    import java.io.IOException;
    import java.nio.charset.Charset;

    public class MifareUltralightTagTester {

        private static final String TAG = MifareUltralightTagTester.class.getSimpleName();

        public void writeTag(Tag tag, String tagText) {
            MifareUltralight ultralight = MifareUltralight.get(tag);
            try {
                ultralight.connect();
                ultralight.writePage(4, "abcd".getBytes(Charset.forName("US-ASCII")));
                ultralight.writePage(5, "efgh".getBytes(Charset.forName("US-ASCII")));
                ultralight.writePage(6, "ijkl".getBytes(Charset.forName("US-ASCII")));
                ultralight.writePage(7, "mnop".getBytes(Charset.forName("US-ASCII")));
            } catch (IOException e) {
                Log.e(TAG, "IOException while writing MifareUltralight...", e);
            } finally {
                try {
                    ultralight.close();
                } catch (IOException e) {
                    Log.e(TAG, "IOException while closing MifareUltralight...", e);
                }
            }
        }

        public String readTag(Tag tag) {
            MifareUltralight mifare = MifareUltralight.get(tag);
            try {
                mifare.connect();
                byte[] payload = mifare.readPages(4);
                return new String(payload, Charset.forName("US-ASCII"));
            } catch (IOException e) {
                Log.e(TAG, "IOException while reading MifareUltralight message...", e);
            } finally {
                if (mifare != null) {
                   try {
                       mifare.close();
                   }
                   catch (IOException e) {
                       Log.e(TAG, "Error closing tag...", e);
                   }
                }
            }
            return null;
        }
    }
    

フォアグラウンド ディスパッチ システムを使用する

フォアグラウンド ディスパッチ システムにより、アクティビティはインテントをインターセプトし、同じインテントを処理する他のアクティビティよりも優先することができます。このシステムを使用するには、Android システムがアプリケーションに適切なインテントを送信できるように、データ構造をいくつか構築する必要があります。フォアグラウンド ディスパッチ システムを有効にするには:

  1. アクティビティの onCreate() メソッドに次のコードを追加します。
    1. PendingIntent オブジェクトを作成し、Android システムがタグのスキャン時に詳細を入力できるようにします。

      Kotlin

          val intent = Intent(this, javaClass).apply {
              addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
          }
          var pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
          

      Java

          PendingIntent pendingIntent = PendingIntent.getActivity(
              this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
          
    2. インターセプトするインテントを処理するインテント フィルタを宣言します。フォアグラウンド ディスパッチ システムは、デバイスがタグのスキャン時に受信したインテントを使用して、指定されたインテント フィルタをチェックします。一致した場合、アプリはそのインテントを処理します。一致しない場合、フォアグラウンド ディスパッチ システムはインテント ディスパッチ システムにフォールバックします。 インテント フィルタとテクノロジー フィルタの null 配列を指定すると、TAG_DISCOVERED インテントにフォールバックするタグをすべてフィルタすることになります。以下のコード スニペットは、NDEF_DISCOVERED の MIME タイプをすべて処理します。必要なものだけを処理する必要があります。

      Kotlin

          val ndef = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED).apply {
              try {
                  addDataType("*/*")    /* Handles all MIME based dispatches.
                                           You should specify only the ones that you need. */
              } catch (e: IntentFilter.MalformedMimeTypeException) {
                  throw RuntimeException("fail", e)
              }
          }
      
          intentFiltersArray = arrayOf(ndef)
          

      Java

          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, };
          
    3. アプリケーションで処理するタグ テクノロジーの配列を設定します。Object.class.getName() メソッドを呼び出して、サポートするテクノロジーのクラスを取得します。

      Kotlin

          techListsArray = arrayOf(arrayOf<String>(NfcF::class.java.name))
          

      Java

          techListsArray = new String[][] { new String[] { NfcF.class.getName() } };
          
  2. 次のアクティビティ ライフサイクル コールバックをオーバーライドし、アクティビティがフォーカスを失って(onPause())回復した(onResume())ときにフォアグラウンド ディスパッチを有効化および無効化するロジックを追加します。enableForegroundDispatch() はメインスレッドから、アクティビティがフォアグラウンドにある場合にのみ呼び出す必要があります(これは、onResume() を呼び出すと保証されます)。また、onNewIntent コールバックを実装してスキャンした NFC タグのデータを処理する必要があります。
  3. Kotlin

        public override fun onPause() {
            super.onPause()
            adapter.disableForegroundDispatch(this)
        }
    
        public override fun onResume() {
            super.onResume()
            adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray)
        }
    
        public override fun onNewIntent(intent: Intent) {
            val tagFromIntent: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
            //do something with tagFromIntent
        }
        

    Java

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

完全なサンプルについては、API デモの ForegroundDispatch サンプルをご覧ください。