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 Forum Specifications & Application Documents のサイトにアクセスして、一般的なタイプの 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. タグ ディスパッチ システム

可能な限り、NDEF メッセージと ACTION_NDEF_DISCOVERED インテントを使用してください。これは、3 つの中で最も具体的なものであるためです。このインテントを使用すると、他の 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 セットは個別に考慮され、1 つの tech-list セットが getTechList() から返されるテクノロジーのサブセットである場合、アクティビティは一致と見なされます。これにより、テクノロジーをマッチングするための ANDOR のセマンティクスが提供されます。次の例は、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 レコードを作成する

このセクションでは、NFC タグへの書き込みや Android ビームを使用したデータの送信に役立つ、一般的なタイプの NDEF レコードを作成する方法について説明します。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 に変換します。これは、この例のインテント フィルタが宣言している 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 タグをデプロイする場合は、ほとんどのデバイス(Android デバイスやその他のデバイス)をサポートできるように NFC タグを作成する方法も検討してください。これを行うには、比較的一意の MIME タイプまたは URI を定義して、アプリケーションが区別しやすくします。

Android には、AAR(createApplicationRecord())を作成するためのシンプルな API が用意されています。必要な作業は、NdefMessage の任意の場所に AAR を埋め込むことだけです。AAR が NdefMessage 内の唯一のレコードである場合を除き、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 レコードの作成をご覧ください。