NFC の基本

このドキュメントでは、Android で行う基本的な NFC タスクについて説明します。NFC データを NDEF メッセージの形式で送受信する方法と、これらの機能をサポートする Android フレームワーク API について説明します。NDEF 以外のデータの処理方法など、高度なトピックについては、高度な NFC をご覧ください。

NDEF データと Android を使用する場合、主に次の 2 つの使用例があります。

  • NDEF データを NFC タグから読み取る
  • Android BeamTM を使用して、デバイス間で NDEF メッセージをビームする

NFC タグからの NDEF データの読み取りは、タグ ディスパッチ システムによって処理されます。このシステムは、検出された NFC タグを分析し、データを適切に分類して、分類されたデータに関連するアプリを起動します。スキャンされた NFC タグを処理するアプリは、インテント フィルタを宣言し、そのデータの処理をリクエストできます。

Android BeamTM 機能を使用すると、デバイス同士を物理的にタップして、別のデバイスに NDEF メッセージをプッシュできます。NFC を使用すると、手動でのデバイスの検出やペア設定が不要になるため、Bluetooth などの他のワイヤレス テクノロジーよりも、このやり取りによりデータを簡単に送信できます。2 台のデバイスが通信範囲内に入ると、接続が自動的に開始されます。Android ビームは一連の NFC API を通じて利用できるため、どのアプリもデバイス間で情報を送信できます。たとえば、連絡先、ブラウザ、YouTube のアプリケーションは、Android ビームを使用して連絡先、ウェブページ、動画を他のデバイスと共有します。

タグ ディスパッチ システム

Android 搭載デバイスは通常、デバイスの [設定] メニューで NFC が無効になっている場合を除き、画面がロック解除されているときに NFC タグを探します。Android 搭載デバイスが NFC タグを検出したときの動作は、どのアプリを使用するかをユーザーに尋ねることなく、最適なアクティビティによってインテントを処理することです。デバイスは非常に短い距離で NFC タグをスキャンするため、ユーザーがアクティビティを手動で選択すると、デバイスをタグから離して接続が切断される可能性があります。アクティビティ選択ツールが表示されないようにするには、アクティビティに必要な NFC タグのみを処理するようにアクティビティを開発する必要があります。

この目標の達成を支援するために、Android には、スキャンされた NFC タグを分析して解析し、スキャンされたデータに関連するアプリを見つけるための特別なタグディスパッチ システムが用意されています。これは次の方法で行います。

  1. NFC タグを解析し、MIME タイプまたはタグ内のデータ ペイロードを識別する URI を特定する。
  2. MIME タイプまたは URI、およびペイロードをインテントにカプセル化します。この最初の 2 つのステップについては、NFC タグを MIME タイプと URI にマッピングする方法をご覧ください。
  3. インテントに基づいてアクティビティを開始します。詳細については、NFC タグがアプリにディスパッチされる方法を参照してください。

NFC タグが MIME タイプと URI にマッピングされる仕組み

NFC アプリケーションの作成を始める前に、さまざまなタイプの NFC タグ、タグ ディスパッチ システムが NFC タグを解析する方法、タグ ディスパッチ システムが NDEF メッセージを検出したときに実行する特別な処理を理解することが重要です。NFC タグは多種多様な技術で用意されており、さまざまな方法でデータを書き込むことができます。Android は、NFC フォーラムで定義されている NDEF 標準を最も多くサポートしています。

NDEF データは、1 つ以上のレコード(NdefRecord)を含むメッセージ(NdefMessage)内にカプセル化されます。各 NDEF レコードは、作成するレコードタイプの仕様に合わせて正しい形式にする必要があります。Android は、NDEF データを含まない他のタイプのタグもサポートしています。これらのタグは、android.nfc.tech パッケージ内のクラスを使用して操作できます。これらの技術の詳細については、高度な NFC のトピックをご覧ください。上記以外の種類のタグを使用する場合は、タグと通信する独自のプロトコル スタックを作成する必要があります。開発が容易になり、Android 搭載デバイスを最大限に活用できるようにするために、可能であれば NDEF を使用することをおすすめします。

注: 完全な NDEF 仕様をダウンロードするには、NFC フォーラムの仕様とアプリケーション ドキュメントのサイトにアクセスして、NDEF レコードの作成方法の例については、一般的なタイプの NDEF レコードの作成をご覧ください。

NFC タグの概要について理解したところで、次のセクションでは、Android が NDEF 形式のタグを処理する方法について詳しく説明します。Android 搭載デバイスは、NDEF 形式のデータを含む NFC タグをスキャンすると、メッセージを解析して、データの MIME タイプまたは識別 URI の特定を試みます。そのために、システムは NdefMessage 内の最初の NdefRecord を読み取って、NDEF メッセージ全体の解釈方法を決定します(NDEF メッセージには複数の NDEF レコードを含めることができます)。正しい形式の NDEF メッセージでは、最初の NdefRecord に次のフィールドが含まれます。

3 ビットの TNF(Type Name Format)
可変長タイプのフィールドの解釈方法を示します。有効な値については、表 1 をご覧ください。
可変長タイプ
レコードのタイプを記述します。TNF_WELL_KNOWN を使用する場合は、このフィールドを使用してレコードタイプ定義(RTD)を指定します。有効な RTD 値は表 2 に記載されています。
可変長 ID
当該レコードの一意の識別子。このフィールドはあまり使用されませんが、タグを一意に識別する必要がある場合は、ID を作成できます。
可変長ペイロード
読み書きする実際のデータ ペイロード。NDEF メッセージには複数の NDEF レコードを含めることができるため、完全なペイロードが NDEF メッセージの最初の NDEF レコードにあるとは考えないでください。

タグ ディスパッチ システムは、TNF とタイプ フィールドを使用して、MIME タイプまたは URI を NDEF メッセージにマッピングしようとします。成功した場合は、その情報を実際のペイロードとともに ACTION_NDEF_DISCOVERED インテント内にカプセル化します。ただし、タグ ディスパッチ システムが、最初の NDEF レコードに基づいてデータのタイプを判別できない場合もあります。これは、NDEF データを MIME タイプまたは URI にマッピングできない場合、または NFC タグに最初の NDEF データが含まれていない場合に発生します。このような場合、タグのテクノロジーとペイロードに関する情報を持つ Tag オブジェクトは、代わりに ACTION_TECH_DISCOVERED インテント内にカプセル化されます。

表 1 に、タグ ディスパッチ システムが TNF のフィールドとタイプ フィールドを MIME タイプまたは URI にマッピングする仕組みを示します。また、MIME タイプまたは URI にマッピングできない TNF についても説明します。このような場合、タグディスパッチ システムは ACTION_TECH_DISCOVERED にフォールバックします。

たとえば、タグ ディスパッチ システムは、TNF_ABSOLUTE_URI 型のレコードを検出すると、そのレコードの可変長型のフィールドを URI にマッピングします。タグ ディスパッチ システムは、この URI をペイロードなどのタグに関するその他の情報とともに ACTION_NDEF_DISCOVERED インテントのデータ フィールドにカプセル化します。一方、TNF_UNKNOWN タイプのレコードを検出した場合は、代わりにタグのテクノロジーをカプセル化するインテントを作成します。

表 1. サポートされている TNF とそのマッピング

Type Name Format(TNF) マッピング
TNF_ABSOLUTE_URI タイプ フィールドに基づく URI。
TNF_EMPTY ACTION_TECH_DISCOVERED にフォールバックします。
TNF_EXTERNAL_TYPE タイプ フィールドの URN に基づく URI。URN は、短縮された形式(<domain_name>:<service_name>)で NDEF タイプ フィールドにエンコードされます。 Android では、これを vnd.android.nfc://ext/<domain_name>:<service_name> という形式の URI にマッピングします。
TNF_MIME_MEDIA タイプ フィールドに基づく MIME タイプ。
TNF_UNCHANGED 最初のレコードでは無効なため、ACTION_TECH_DISCOVERED にフォールバックします。
TNF_UNKNOWN ACTION_TECH_DISCOVERED にフォールバックします。
TNF_WELL_KNOWN タイプ フィールドで設定したレコードタイプ定義(RTD)に応じた MIME タイプまたは URI。使用可能な RTD とそのマッピングについて詳しくは、表 2 をご覧ください。

表 2. TNF_WELL_KNOWN でサポートされている RTD とそのマッピング

レコードタイプ定義(RTD) マッピング
RTD_ALTERNATIVE_CARRIER ACTION_TECH_DISCOVERED にフォールバックします。
RTD_HANDOVER_CARRIER ACTION_TECH_DISCOVERED にフォールバックします。
RTD_HANDOVER_REQUEST ACTION_TECH_DISCOVERED にフォールバックします。
RTD_HANDOVER_SELECT ACTION_TECH_DISCOVERED にフォールバックします。
RTD_SMART_POSTER ペイロードのパースに基づく URI。
RTD_TEXT text/plain の MIME タイプ。
RTD_URI ペイロードに基づく URI。

NFC タグがアプリケーションにディスパッチされる仕組み

タグ ディスパッチ システムは、NFC タグとその識別情報をカプセル化するインテントの作成を完了すると、インテントをフィルタする関連アプリにインテントを送信します。複数のアプリがインテントを処理できる場合は、ユーザーがアクティビティを選択できるように、アクティビティ選択ツールが表示されます。タグ ディスパッチ システムは、優先度が高い順に 3 つのインテントを定義します。

  1. ACTION_NDEF_DISCOVERED: このインテントは、NDEF ペイロードを含むタグがスキャンされ、認識されたタイプである場合にアクティビティを開始するために使用されます。これは優先度が最も高いインテントです。タグ ディスパッチ システムは、可能な限り他のインテントよりも先に、このインテントでアクティビティを開始しようとします。
  2. ACTION_TECH_DISCOVERED: ACTION_NDEF_DISCOVERED インテントを処理するためのアクティビティが登録されていない場合、タグ ディスパッチ システムは、このインテントでアプリの起動を試みます。また、スキャン対象のタグに MIME タイプまたは URI にマッピングできない NDEF データが含まれている場合、またはタグに NDEF データが含まれていないが既知のタグ テクノロジーである場合、このインテントは(最初に ACTION_NDEF_DISCOVERED を開始せずに)直接開始されます。
  3. ACTION_TAG_DISCOVERED: ACTION_NDEF_DISCOVERED または ACTION_TECH_DISCOVERED インテントを処理するアクティビティがない場合、このインテントが開始されます。

タグ ディスパッチ システムの基本的な仕組みは次のとおりです。

  1. NFC タグの解析時にタグ ディスパッチ システムによって作成されたインテント(ACTION_NDEF_DISCOVERED または ACTION_TECH_DISCOVERED)でアクティビティを開始してみます。
  2. そのインテントのアクティビティ フィルタがない場合は、アプリがそのインテントをフィルタするか、タグ ディスパッチ システムが考え得るすべてのインテントを試行するまで、次に優先度の低いインテント(ACTION_TECH_DISCOVERED または ACTION_TAG_DISCOVERED)でアクティビティを開始しようとします。
  3. アプリケーションがどのインテントもフィルタしない場合は、何もしません。
図 1. タグ ディスパッチ システム

可能な限り、3 つの中で最も具体性の高い NDEF メッセージと ACTION_NDEF_DISCOVERED インテントを使用してください。このインテントを使用すると、他の 2 つのインテントよりも適切なタイミングでアプリを起動できるため、ユーザー エクスペリエンスが向上します。

Android マニフェストで NFC アクセスをリクエストする

デバイスの NFC ハードウェアにアクセスして NFC インテントを適切に処理する前に、AndroidManifest.xml ファイルで以下の項目を宣言してください。

  • NFC ハードウェアにアクセスするための NFC <uses-permission> 要素:
    <uses-permission android:name="android.permission.NFC" />
    
  • アプリケーションでサポート可能な SDK の最小バージョン。API レベル 9 では、ACTION_TAG_DISCOVERED を介した制限付きのタグのディスパッチのみがサポートされ、EXTRA_NDEF_MESSAGES エクストラを介した NDEF メッセージへのアクセスのみが許可されます。他のタグ プロパティや I/O オペレーションにはアクセスできません。API レベル 10 では、包括的なリーダー/ライターのサポートとフォアグラウンドの NDEF プッシュが可能です。API レベル 14 では、Android ビームと NDEF レコードを作成する便利なメソッドを使って、他のデバイスに NDEF メッセージをプッシュする簡単な方法が提供されます。
    <uses-sdk android:minSdkVersion="10"/>
    
  • uses-feature 要素: NFC ハードウェアを搭載したデバイスでのみアプリが Google Play に表示されるようにします。
    <uses-feature android:name="android.hardware.nfc" android:required="true" />
    

    アプリで NFC 機能を使用するものの、その機能がアプリにとって重要でない場合は、uses-feature 要素を省略し、getDefaultAdapter()null かどうかをチェックして実行時に NFC が利用可能かどうかを確認できます。

NFC インテントのフィルタ

処理する NFC タグがスキャンされたときにアプリを起動するには、Android マニフェストの 1 つ、2 つ、または 3 つすべての NFC インテントをアプリでフィルタできます。ただし、通常は、アプリの起動タイミングを細かく制御するために、ACTION_NDEF_DISCOVERED インテントでフィルタすることをおすすめします。ACTION_TECH_DISCOVERED インテントは、アプリが ACTION_NDEF_DISCOVERED をフィルタしていない場合、またはペイロードが NDEF でない場合の、ACTION_NDEF_DISCOVERED のフォールバックです。通常、ACTION_TAG_DISCOVERED のフィルタリングは、カテゴリとしては一般的すぎるためフィルタできません。多くのアプリケーションは ACTION_TAG_DISCOVERED の前に ACTION_NDEF_DISCOVERED または ACTION_TECH_DISCOVERED でフィルタするため、アプリが起動する可能性は低くなります。ACTION_TAG_DISCOVERED は、ACTION_NDEF_DISCOVERED または ACTION_TECH_DISCOVERED インテントを処理するアプリが他にインストールされていない場合に、アプリがフィルタする最後の手段としてのみ利用できます。

NFC タグのデプロイは多種多様であり、多くの場合、ユーザーの制御下にはないため、これが不可能な場合もあります。そのため、必要に応じて他の 2 つのインテントにフォールバックできます。タグの種類と書き込まれるデータを制御できる場合は、NDEF を使用してタグをフォーマットすることをおすすめします。次のセクションでは、インテントのタイプごとにフィルタを適用する方法について説明します。

ACTION_NDEF_DISCOVERED

ACTION_NDEF_DISCOVERED インテントをフィルタするには、フィルタするデータの種類とともにインテント フィルタを宣言します。次の例では、MIME タイプが text/plainACTION_NDEF_DISCOVERED インテントをフィルタしています。

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
</intent-filter>

次の例では、https://developer.android.com/index.html 形式の URI でフィルタしています。

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
   <data android:scheme="https"
              android:host="developer.android.com"
              android:pathPrefix="/index.html" />
</intent-filter>

ACTION_TECH_DISCOVERED

アクティビティで ACTION_TECH_DISCOVERED インテントをフィルタする場合は、アクティビティが tech-list セット内でサポートするテクノロジーを指定する XML リソース ファイルを作成する必要があります。tech-list セットがタグでサポートされているテクノロジーのサブセットである場合、アクティビティは一致とみなされます。これらのテクノロジーは getTechList() を呼び出すことで取得できます。

たとえば、スキャンされるタグが MifareClassic、NdefFormatable、NfcA をサポートしている場合、アクティビティを照合するには、tech-list セットで 3 つ、2 つ、または 1 つのテクノロジーすべてを指定する必要があります。

次の例では、テクノロジーをすべて定義しています。お使いの NFC タグでサポートされていないタグを削除する必要があります。このファイルを <project-root>/res/xml フォルダに保存します(任意の名前を付けることができます)。

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
        <tech>android.nfc.tech.NfcA</tech>
        <tech>android.nfc.tech.NfcB</tech>
        <tech>android.nfc.tech.NfcF</tech>
        <tech>android.nfc.tech.NfcV</tech>
        <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.NdefFormatable</tech>
        <tech>android.nfc.tech.MifareClassic</tech>
        <tech>android.nfc.tech.MifareUltralight</tech>
    </tech-list>
</resources>

また、複数の tech-list セットを指定できます。各 tech-list セットは個別に考慮され、getTechList() から返されるテクノロジーのサブセットが 1 つの tech-list セットである場合、アクティビティは一致とみなされます。これにより、テクノロジーを照合するための AND および OR セマンティクスが提供されます。次の例は、NfcA および Ndef テクノロジーをサポートできるタグ、または NfcB および Ndef テクノロジーをサポートできるタグと一致します。

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

次の例のように、AndroidManifest.xml ファイルで、作成したリソース ファイルを <activity> 要素内の <meta-data> 要素に指定します。

<activity>
...
<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" />
...
</activity>

タグ テクノロジーと ACTION_TECH_DISCOVERED インテントの操作について詳しくは、高度な NFC のドキュメントのサポートされているタグ テクノロジーの操作をご覧ください。

ACTION_TAG_DISCOVERED

ACTION_TAG_DISCOVERED をフィルタするには、次のインテント フィルタを使用します。

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

インテントから情報を取得する

NFC インテントによってアクティビティが開始した場合、スキャンされた NFC タグに関する情報をインテントから取得できます。インテントには、スキャンされたタグに応じて次のエクストラを含めることができます。

  • EXTRA_TAG(必須): スキャンされたタグを表す Tag オブジェクト。
  • EXTRA_NDEF_MESSAGES(省略可): タグから解析された NDEF メッセージの配列。ACTION_NDEF_DISCOVERED インテントでは、このエクストラは必須です。
  • EXTRA_ID(任意): タグの低レベル ID。

これらのエクストラを取得するには、NFC インテントのいずれかでアクティビティが起動されたかどうかを確認し、タグがスキャンされたことを確認してから、インテントからエクストラを取得します。次の例では、ACTION_NDEF_DISCOVERED インテントを確認し、インテント エクストラから NDEF メッセージを取得しています。

Kotlin

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    ...
    if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
        intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)?.also { rawMessages ->
            val messages: List<NdefMessage> = rawMessages.map { it as NdefMessage }
            // Process the messages array.
            ...
        }
    }
}

Java

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    ...
    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
        Parcelable[] rawMessages =
            intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
        if (rawMessages != null) {
            NdefMessage[] messages = new NdefMessage[rawMessages.length];
            for (int i = 0; i < rawMessages.length; i++) {
                messages[i] = (NdefMessage) rawMessages[i];
            }
            // Process the messages array.
            ...
        }
    }
}

または、インテントから Tag オブジェクトを取得することもできます。このオブジェクトにはペイロードが含まれ、タグのテクノロジーを列挙できます。

Kotlin

val tag: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)

Java

Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

一般的なタイプの NDEF レコードを作成する

このセクションでは、一般的なタイプの NDEF レコードを作成して、NFC タグへの書き込みや Android ビームによるデータの送信を行う方法について説明します。Android 4.0(API レベル 14)以降では、createUri() メソッドを使って URI レコードを自動的に作成できます。Android 4.1(API レベル 16)以降では、createExternal()createMime() を使用して、MIME および外部タイプの NDEF レコードを作成できます。NDEF レコードを手動で作成する際のミスを回避するため、可能な限りこれらのヘルパー メソッドを使用してください。

このセクションでは、レコードに対応するインテント フィルタを作成する方法についても説明します。これらの NDEF レコードの例はすべて、タグまたはビームに書き込む NDEF メッセージの最初の NDEF レコードに含まれている必要があります。

TNF_ABSOLUTE_URI

注: TNF_ABSOLUTE_URI ではなく RTD_URI タイプを使用することをおすすめします。その方が効率的です。

TNF_ABSOLUTE_URI NDEF レコードは次の方法で作成できます。

Kotlin

val uriRecord = ByteArray(0).let { emptyByteArray ->
    NdefRecord(
            TNF_ABSOLUTE_URI,
            "https://developer.android.com/index.html".toByteArray(Charset.forName("US-ASCII")),
            emptyByteArray,
            emptyByteArray
    )
}

Java

NdefRecord uriRecord = new NdefRecord(
    NdefRecord.TNF_ABSOLUTE_URI ,
    "https://developer.android.com/index.html".getBytes(Charset.forName("US-ASCII")),
    new byte[0], new byte[0]);

前の NDEF レコードのインテント フィルタは次のようになります。

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="https"
        android:host="developer.android.com"
        android:pathPrefix="/index.html" />
</intent-filter>

TNF_MIME_MEDIA

TNF_MIME_MEDIA NDEF レコードは次の方法で作成できます。

createMime() メソッドを使用:

Kotlin

val mimeRecord = NdefRecord.createMime(
        "application/vnd.com.example.android.beam",
        "Beam me up, Android".toByteArray(Charset.forName("US-ASCII"))
)

Java

NdefRecord mimeRecord = NdefRecord.createMime("application/vnd.com.example.android.beam",
    "Beam me up, Android".getBytes(Charset.forName("US-ASCII")));

手動で NdefRecord を作成:

Kotlin

val mimeRecord = Charset.forName("US-ASCII").let { usAscii ->
    NdefRecord(
            NdefRecord.TNF_MIME_MEDIA,
            "application/vnd.com.example.android.beam".toByteArray(usAscii),
            ByteArray(0),
            "Beam me up, Android!".toByteArray(usAscii)
    )
}

Java

NdefRecord mimeRecord = new NdefRecord(
    NdefRecord.TNF_MIME_MEDIA ,
    "application/vnd.com.example.android.beam".getBytes(Charset.forName("US-ASCII")),
    new byte[0], "Beam me up, Android!".getBytes(Charset.forName("US-ASCII")));

前の NDEF レコードのインテント フィルタは次のようになります。

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="application/vnd.com.example.android.beam" />
</intent-filter>

TNF_WELL_KNOWN(RTD_TEXT)

TNF_WELL_KNOWN NDEF レコードは次の方法で作成できます。

Kotlin

fun createTextRecord(payload: String, locale: Locale, encodeInUtf8: Boolean): NdefRecord {
    val langBytes = locale.language.toByteArray(Charset.forName("US-ASCII"))
    val utfEncoding = if (encodeInUtf8) Charset.forName("UTF-8") else Charset.forName("UTF-16")
    val textBytes = payload.toByteArray(utfEncoding)
    val utfBit: Int = if (encodeInUtf8) 0 else 1 shl 7
    val status = (utfBit + langBytes.size).toChar()
    val data = ByteArray(1 + langBytes.size + textBytes.size)
    data[0] = status.toByte()
    System.arraycopy(langBytes, 0, data, 1, langBytes.size)
    System.arraycopy(textBytes, 0, data, 1 + langBytes.size, textBytes.size)
    return NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, ByteArray(0), data)
}

Java

public NdefRecord createTextRecord(String payload, Locale locale, boolean encodeInUtf8) {
    byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
    Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8") : Charset.forName("UTF-16");
    byte[] textBytes = payload.getBytes(utfEncoding);
    int utfBit = encodeInUtf8 ? 0 : (1 << 7);
    char status = (char) (utfBit + langBytes.length);
    byte[] data = new byte[1 + langBytes.length + textBytes.length];
    data[0] = (byte) status;
    System.arraycopy(langBytes, 0, data, 1, langBytes.length);
    System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);
    NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
    NdefRecord.RTD_TEXT, new byte[0], data);
    return record;
}

前の NDEF レコードのインテント フィルタは次のようになります。

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/plain" />
</intent-filter>

TNF_WELL_KNOWN(RTD_URI)

TNF_WELL_KNOWN NDEF レコードは次の方法で作成できます。

createUri(String) メソッドを使用:

Kotlin

val rtdUriRecord1 = NdefRecord.createUri("https://example.com")

Java

NdefRecord rtdUriRecord1 = NdefRecord.createUri("https://example.com");

createUri(Uri) メソッドを使用:

Kotlin

val rtdUriRecord2 = Uri.parse("https://example.com").let { uri ->
    NdefRecord.createUri(uri)
}

Java

Uri uri = Uri.parse("https://example.com");
NdefRecord rtdUriRecord2 = NdefRecord.createUri(uri);

手動で NdefRecord を作成:

Kotlin

val uriField = "example.com".toByteArray(Charset.forName("US-ASCII"))
val payload = ByteArray(uriField.size + 1)                   //add 1 for the URI Prefix
payload [0] = 0x01                                           //prefixes https://www. to the URI
System.arraycopy(uriField, 0, payload, 1, uriField.size)     //appends URI to payload
val rtdUriRecord = NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, ByteArray(0), payload)

Java

byte[] uriField = "example.com".getBytes(Charset.forName("US-ASCII"));
byte[] payload = new byte[uriField.length + 1];              //add 1 for the URI Prefix
payload[0] = 0x01;                                           //prefixes https://www. to the URI
System.arraycopy(uriField, 0, payload, 1, uriField.length);  //appends URI to payload
NdefRecord rtdUriRecord = new NdefRecord(
    NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, new byte[0], payload);

前の NDEF レコードのインテント フィルタは次のようになります。

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="https"
        android:host="example.com"
        android:pathPrefix="" />
</intent-filter>

TNF_EXTERNAL_TYPE

TNF_EXTERNAL_TYPE NDEF レコードは次の方法で作成できます。

createExternal() メソッドを使用する場合:

Kotlin

var payload: ByteArray //assign to your data
val domain = "com.example" //usually your app's package name
val type = "externalType"
val extRecord = NdefRecord.createExternal(domain, type, payload)

Java

byte[] payload; //assign to your data
String domain = "com.example"; //usually your app's package name
String type = "externalType";
NdefRecord extRecord = NdefRecord.createExternal(domain, type, payload);

手動で NdefRecord を作成:

Kotlin

var payload: ByteArray
...
val extRecord = NdefRecord(
        NdefRecord.TNF_EXTERNAL_TYPE,
        "com.example:externalType".toByteArray(Charset.forName("US-ASCII")),
        ByteArray(0),
        payload
)

Java

byte[] payload;
...
NdefRecord extRecord = new NdefRecord(
    NdefRecord.TNF_EXTERNAL_TYPE, "com.example:externalType".getBytes(Charset.forName("US-ASCII")),
    new byte[0], payload);

前の NDEF レコードのインテント フィルタは次のようになります。

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="vnd.android.nfc"
        android:host="ext"
        android:pathPrefix="/com.example:externalType"/>
</intent-filter>

一般的な NFC タグのデプロイに TNF_EXTERNAL_TYPE を使用すると、Android デバイスと Android 以外のデバイスの両方をより適切にサポートできます。

: TNF_EXTERNAL_TYPE の URN の正規形式は urn:nfc:ext:example.com:externalType ですが、NFC フォーラムの RTD 仕様では、URN の urn:nfc:ext: 部分を NDEF レコードから除外する必要があると宣言しています。したがって、指定する必要があるのは、ドメイン(この例では example.com)と型(この例では externalType)をコロンで区切ったことだけです。TNF_EXTERNAL_TYPE をディスパッチすると、Android は urn:nfc:ext:example.com:externalType URN を vnd.android.nfc://ext/example.com:externalType URI に変換します。これは、この例のインテント フィルタで宣言されています。

Android アプリのレコード

Android 4.0(API レベル 14)で導入された Android Application Record(AAR)により、NFC タグのスキャン時にアプリが起動されることをより確実に確信できます。AAR には、NDEF レコード内に埋め込まれたアプリケーションのパッケージ名が含まれます。Android は NDEF メッセージ全体で AAR を検索するため、NDEF メッセージの任意の NDEF レコードに AAR を追加できます。AAR を検出すると、AAR 内のパッケージ名に基づいてアプリを起動します。アプリがデバイスに存在しない場合は、Google Play が起動し、アプリがダウンロードされます。

AAR は、他のアプリケーションが同じインテントでフィルタリングし、デプロイした特定のタグが処理されるのを防ぎたい場合に便利です。AAR はパッケージ名の制約によりアプリレベルでのみサポートされます。インテント フィルタリングのようにアクティビティ レベルではサポートされません。アクティビティ レベルでインテントを処理する場合は、インテント フィルタを使用します。

タグに AAR が含まれている場合、タグ ディスパッチ システムによるディスパッチ方法は以下のとおりです。

  1. 通常どおりに、インテント フィルタを使用したアクティビティの開始を試みます。インテントに一致するアクティビティが AAR とも一致する場合は、そのアクティビティを開始します。
  2. インテントをフィルタするアクティビティが AAR と一致しない場合、複数のアクティビティがインテントを処理できる場合、またはインテントを処理するアクティビティがない場合は、AAR で指定されたアプリを起動します。
  3. AAR で開始できるアプリがない場合は、Google Play にアクセスして、AAR をベースにしたアプリをダウンロードします。

注: AAR とインテント ディスパッチ システムをフォアグラウンド ディスパッチ システムでオーバーライドすると、NFC タグが検出されたときにフォアグラウンド アクティビティを優先させることができます。この方法では、AAR とインテント ディスパッチ システムをオーバーライドするために、アクティビティがフォアグラウンドに存在する必要があります。

それでも AAR を含まないスキャンされたタグをフィルタする場合は、インテント フィルタを通常どおり宣言できます。これは、アプリケーションが AAR を含まない他のタグに関心がある場合に役立ちます。たとえば、サードパーティがデプロイした一般的なタグだけでなく、自社でデプロイした独自のタグをアプリケーションで処理できるようにしたい場合があります。AAR は Android 4.0 以降のデバイスに固有のものであるため、タグをデプロイする際には、最も幅広いデバイスをサポートするために AAR と MIME タイプ/URI を組み合わせて使用することをおすすめします。また、NFC タグをデプロイする際には、NFC タグをどのように記述するかを検討し、ほとんどのデバイス(Android 搭載デバイスやその他のデバイス)でサポートできるようにします。これを行うには、比較的一意の MIME タイプまたは URI を定義して、アプリが区別しやすくします。

Android には、AAR(createApplicationRecord())を作成するためのシンプルな API が用意されています。NdefMessage の任意の場所に AAR を埋め込むだけです。NdefMessage の最初のレコードは、AAR が NdefMessage の唯一のレコードである場合を除き、使用しません。これは、Android システムが NdefMessage の最初のレコードを調べてタグの MIME タイプまたは URI を判別するためです。これにより、アプリをフィルタするインテントが作成されます。次のコードは AAR の作成方法を示しています。

Kotlin

val msg = NdefMessage(
        arrayOf(
                ...,
                NdefRecord.createApplicationRecord("com.example.android.beam")
        )
)

Java

NdefMessage msg = new NdefMessage(
        new NdefRecord[] {
            ...,
            NdefRecord.createApplicationRecord("com.example.android.beam")}
        );
)

NDEF メッセージを他のデバイスにビームする

Android ビームを使用すると、2 台の Android デバイス間でシンプルなピアツーピアのデータ交換が可能になります。別のデバイスにデータをビームするアプリは、フォアグラウンドで動作し、データを受信するデバイスがロックされないようにする必要があります。ビーム発信デバイスが受信デバイスと十分に近づくと、「タップしてビーム」の UI が表示されます。ユーザーは、受信デバイスにメッセージをビームするかどうかを選択できます。

注: フォアグラウンドの NDEF プッシュは、Android ビームと同様の機能を提供する API レベル 10 で利用可能でした。これらの API は非推奨になりましたが、古いデバイスのサポートには利用可能です。詳しくは、enableForegroundNdefPush()をご覧ください。

アプリケーションの Android ビームを有効にするには、次の 2 つのメソッドのいずれかを呼び出します。

  • setNdefPushMessage(): NdefMessage を受け入れ、ビームするメッセージとして設定します。2 つのデバイスが十分に近くにあるときに、メッセージを自動的にビームします。
  • setNdefPushMessageCallback(): デバイスがデータのビーム範囲内にある場合に呼び出される createNdefMessage() を含むコールバックを受け入れます。コールバックを使用すると、必要な場合にのみ NDEF メッセージを作成できます。

アクティビティは一度に 1 つの NDEF メッセージしかプッシュできないため、両方が設定されている場合、setNdefPushMessageCallback()setNdefPushMessage() よりも優先されます。Android ビームを使用するには、次の一般的なガイドラインを満たす必要があります。

  • データをビームするアクティビティは、フォアグラウンドでなければなりません。両方のデバイスで画面ロックを解除する必要があります。
  • ビームするデータは、NdefMessage オブジェクトにカプセル化する必要があります。
  • ビームデータを受信する NFC デバイスは、com.android.npp NDEF プッシュ プロトコルまたは NFC フォーラムの SNEP(Simple NDEF Exchange Protocol)をサポートしている必要があります。com.android.npp プロトコルは、API レベル 9(Android 2.3)から API レベル 13(Android 3.2)までのデバイスに必要です。com.android.npp と SNEP は、どちらも API レベル 14(Android 4.0)以降で必要です。

注: アクティビティが Android ビームを有効にしてフォアグラウンドにある場合、標準のインテント ディスパッチ システムは無効になります。ただし、アクティビティで フォアグラウンド ディスパッチも有効にしている場合、フォアグラウンドでのディスパッチで設定したインテント フィルタに一致するタグは引き続きスキャンできます。

Android ビームを有効にするには:

  1. 他のデバイスにプッシュする NdefRecord を含む NdefMessage を作成します。
  2. NdefMessage を指定して setNdefPushMessage() を呼び出すか、setNdefPushMessageCallback を呼び出してアクティビティの onCreate() メソッドで NfcAdapter.CreateNdefMessageCallback オブジェクトを渡します。これらのメソッドには、Android Beam で有効にするアクティビティを少なくとも 1 つと、有効にする他のアクティビティのオプション リストが必要です。

    一般に、2 つのデバイスが通信のために圏内にあるとき、アクティビティが常に同じ NDEF メッセージを push するだけでよい場合は、通常 setNdefPushMessage() を使用します。setNdefPushMessageCallback は、アプリがアプリの現在のコンテキストを考慮し、ユーザーがアプリ内で何をしているかに応じて NDEF メッセージをプッシュする場合に使用されます。

次のサンプルは、シンプルなアクティビティがアクティビティの onCreate() メソッド内で NfcAdapter.CreateNdefMessageCallback を呼び出す方法を示しています(サンプル全体については AndroidBeamDemo をご覧ください)。この例には、MIME レコードの作成に便利なメソッドもあります。

Kotlin

package com.example.android.beam

import android.app.Activity
import android.content.Intent
import android.nfc.NdefMessage
import android.nfc.NdefRecord
import android.nfc.NfcAdapter
import android.nfc.NfcAdapter.CreateNdefMessageCallback
import android.nfc.NfcEvent
import android.os.Bundle
import android.os.Parcelable
import android.widget.TextView
import android.widget.Toast
import java.nio.charset.Charset

class Beam : Activity(), NfcAdapter.CreateNdefMessageCallback {
    
    private var nfcAdapter: NfcAdapter? = null
    private lateinit var textView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        textView = findViewById(R.id.textView)
        // Check for available NFC Adapter
        nfcAdapter = NfcAdapter.getDefaultAdapter(this)
        if (nfcAdapter == null) {
            Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show()
            finish()
            return
        }
        // Register callback
        nfcAdapter?.setNdefPushMessageCallback(this, this)
    }

    override fun createNdefMessage(event: NfcEvent): NdefMessage {
        val text = "Beam me up, Android!\n\n" +
                "Beam Time: " + System.currentTimeMillis()
        return NdefMessage(
                arrayOf(
                        createMime("application/vnd.com.example.android.beam", text.toByteArray())
                )
                /**
                 * The Android Application Record (AAR) is commented out. When a device
                 * receives a push with an AAR in it, the application specified in the AAR
                 * is guaranteed to run. The AAR overrides the tag dispatch system.
                 * You can add it back in to guarantee that this
                 * activity starts when receiving a beamed message. For now, this code
                 * uses the tag dispatch system.
                 *///,NdefRecord.createApplicationRecord("com.example.android.beam")
        )
    }

    override fun onResume() {
        super.onResume()
        // Check to see that the Activity started due to an Android Beam
        if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
            processIntent(intent)
        }
    }

    override fun onNewIntent(intent: Intent) {
        // onResume gets called after this to handle the intent
        setIntent(intent)
    }

    /**
     * Parses the NDEF Message from the intent and prints to the TextView
     */
    private fun processIntent(intent: Intent) {
        textView = findViewById(R.id.textView)
        // only one message sent during the beam
        intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)?.also { rawMsgs ->
            (rawMsgs[0] as NdefMessage).apply {
                // record 0 contains the MIME type, record 1 is the AAR, if present
                textView.text = String(records[0].payload)
            }
        }
    }
}

Java

package com.example.android.beam;

import android.app.Activity;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
import android.nfc.NfcEvent;
import android.os.Bundle;
import android.os.Parcelable;
import android.widget.TextView;
import android.widget.Toast;
import java.nio.charset.Charset;


public class Beam extends Activity implements CreateNdefMessageCallback {
    NfcAdapter nfcAdapter;
    TextView textView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView textView = (TextView) findViewById(R.id.textView);
        // Check for available NFC Adapter
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (nfcAdapter == null) {
            Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show();
            finish();
            return;
        }
        // Register callback
        nfcAdapter.setNdefPushMessageCallback(this, this);
    }

    @Override
    public NdefMessage createNdefMessage(NfcEvent event) {
        String text = ("Beam me up, Android!\n\n" +
                "Beam Time: " + System.currentTimeMillis());
        NdefMessage msg = new NdefMessage(
                new NdefRecord[] { createMime(
                        "application/vnd.com.example.android.beam", text.getBytes())
         /**
          * The Android Application Record (AAR) is commented out. When a device
          * receives a push with an AAR in it, the application specified in the AAR
          * is guaranteed to run. The AAR overrides the tag dispatch system.
          * You can add it back in to guarantee that this
          * activity starts when receiving a beamed message. For now, this code
          * uses the tag dispatch system.
          */
          //,NdefRecord.createApplicationRecord("com.example.android.beam")
        });
        return msg;
    }

    @Override
    public void onResume() {
        super.onResume();
        // Check to see that the Activity started due to an Android Beam
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
            processIntent(getIntent());
        }
    }

    @Override
    public void onNewIntent(Intent intent) {
        // onResume gets called after this to handle the intent
        setIntent(intent);
    }

    /**
     * Parses the NDEF Message from the intent and prints to the TextView
     */
    void processIntent(Intent intent) {
        textView = (TextView) findViewById(R.id.textView);
        Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
                NfcAdapter.EXTRA_NDEF_MESSAGES);
        // only one message sent during the beam
        NdefMessage msg = (NdefMessage) rawMsgs[0];
        // record 0 contains the MIME type, record 1 is the AAR, if present
        textView.setText(new String(msg.getRecords()[0].getPayload()));
    }
}

AAR に関するコードはコメントアウトされており、削除できます。AAR を有効にすると、AAR で指定されたアプリは常に Android ビーム メッセージを受信します。アプリが存在しない場合、Google Play はアプリのダウンロードを開始します。したがって、AAR が使用されている場合、Android 4.0 以降のデバイスでは、次のインテント フィルタは技術的に必要ありません。

<intent-filter>
  <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
  <category android:name="android.intent.category.DEFAULT"/>
  <data android:mimeType="application/vnd.com.example.android.beam"/>
</intent-filter>

このインテント フィルタを使用すると、NFC タグをスキャンするとき、または com.example.android.beam タイプの AAR の Android ビームを受信したとき、または NDEF 形式のメッセージに application/vnd.com.example.android.beam タイプの MIME レコードが含まれている場合に、com.example.android.beam アプリを起動できます。

AAR によってアプリの起動またはダウンロードが保証される場合でも、インテント フィルタを使用することをおすすめします。AAR で指定されたパッケージ内でメイン アクティビティを常に開始する代わりに、アプリ内で任意のアクティビティを開始できるためです。AAR にはアクティビティ レベルの粒度はありません。また、一部の Android 搭載デバイスは AAR に対応していないため、念のため、NDEF メッセージの最初の NDEF レコードに識別情報を埋め込み、それもフィルタする必要があります。レコードの作成方法について詳しくは、一般的なタイプの NDEF レコードの作成をご覧ください。