高度な NFC の概要

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

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

NFC タグと Android 搭載デバイスを使用する場合、タグのデータの読み取りと書き込みに使用する主な形式は NDEF です。Android では、デバイスが NDEF データを含むタグをスキャンすると、メッセージの解析と NdefMessage での配信がサポートされます(可能な場合)。ただし、NDEF データを含まないタグをスキャンする場合や、NDEF データを MIME タイプまたは URI にマッピングできない場合もあります。そのような場合は、タグと直接通信を行い、独自のプロトコル(未加工のバイト単位)でタグに対する読み取りと書き込みを行う必要があります。Android では、android.nfc.tech パッケージを使用してこれらのユースケースの一般的なサポートを提供しています(表 1 をご覧ください)。getTechList() メソッドを使用して、タグでサポートされているテクノロジーを判別し、android.nfc.tech によって提供されるいずれかのクラスを使用して、対応する TagTechnology オブジェクトを作成できます。

表 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 データおよび NDEF としてフォーマットされた NFC タグのオペレーションへのアクセスを提供します。
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,
              PendingIntent.FLAG_MUTABLE)
      

      Java

      PendingIntent pendingIntent = PendingIntent.getActivity(
          this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
          PendingIntent.FLAG_MUTABLE);
      
    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() の呼び出しにより必ず呼び出せます)。スキャンした NFC タグからのデータを処理するための onNewIntent コールバックも実装する必要があります。
  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
    }