Visão geral avançada da NFC

Este documento descreve tópicos avançados de NFC, como trabalhar com várias tecnologias de etiqueta, gravar em tags NFC e fazer envios em primeiro plano, o que permite que um aplicativo em primeiro plano processe intents mesmo quando outros aplicativos aplicam filtros para as mesmas.

Trabalhar com tecnologias de etiqueta compatíveis

Ao trabalhar com etiquetas NFC e dispositivos Android, o formato principal usado para ler e gravar dados nas etiquetas é NDEF. Quando um dispositivo faz a leitura de uma tag com dados NDEF, o Android oferece suporte para analisar a mensagem e entregar a mensagem em um NdefMessage quando possível. No entanto, há casos em que você verifica uma tag que não contém dados NDEF ou quando esses dados não podem ser mapeados para um tipo MIME ou URI. Nesses casos, você precisa abrir a comunicação diretamente com a tag e ler e gravar nela com seu próprio protocolo (em bytes brutos). O Android oferece suporte genérico para esses casos de uso com o pacote android.nfc.tech, descrito na Tabela 1. É possível usar o método getTechList() para determinar as tecnologias compatíveis com a tag e criar o objeto TagTechnology correspondente com uma das classes fornecidas por android.nfc.tech.

Tabela 1. Tecnologias de etiqueta compatíveis

Classe. Descrição
TagTechnology A interface que todas as classes de tecnologia de etiquetas precisam implementar.
NfcA Fornece acesso às propriedades NFC-A (ISO 14443-3A) e operações de E/S.
NfcB Fornece acesso às propriedades NFC-B (ISO 14443-3B) e operações de E/S.
NfcF Fornece acesso às propriedades da NFC-F (JIS 6319-4) e operações de E/S.
NfcV Fornece acesso às propriedades NFC-V (ISO 15693) e operações de E/S.
IsoDep Fornece acesso às propriedades ISO-DEP (ISO 14443-4) e operações de E/S.
Ndef Fornece acesso a dados e operações NDEF em etiquetas NFC formatadas como NDEF.
NdefFormatable Fornece operações de formato para etiquetas que podem ser formatadas como NDEF.

As tecnologias de tag a seguir não precisam ter suporte de dispositivos Android.

Tabela 2. Tecnologias de etiqueta compatíveis opcionais

Classe. Descrição
MifareClassic Fornece acesso a propriedades MIFARE Classic e operações de E/S, se este dispositivo Android oferecer suporte a MIFARE.
MifareUltralight Fornece acesso a propriedades MIFARE Ultralight e operações de E/S, se este dispositivo Android for compatível com MIFARE.

Trabalhando com tecnologias de etiqueta e com o intent ACTION_TECH_DISCOVERED

Quando um dispositivo faz a leitura de uma etiqueta que tem dados NDEF, mas não pôde ser mapeada para um MIME ou URI, o sistema de expedição de etiquetas tenta iniciar uma atividade com a intent ACTION_TECH_DISCOVERED. O ACTION_TECH_DISCOVERED também é usado quando uma etiqueta com dados não NDEF é lida. Com esse substituto, você poderá trabalhar diretamente com os dados na tag se o sistema de expedição não puder analisá-los. As etapas básicas ao trabalhar com tecnologias de tag são as seguintes:

  1. Filtre por uma intent ACTION_TECH_DISCOVERED especificando as tecnologias de tag que você quer processar. Consulte Como filtrar intents NFC para ver mais informações. Em geral, o sistema de expedição de etiquetas tenta iniciar um intent ACTION_TECH_DISCOVERED quando uma mensagem NDEF não pode ser mapeada para um tipo MIME ou URI ou se a tag lida não continha dados NDEF. Para mais informações sobre como isso é determinado, consulte o sistema de expedição de etiquetas.
  2. Quando o aplicativo receber a intent, extraia o objeto Tag dela:

    Kotlin

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

    Java

    Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    
  3. Consiga uma instância de um TagTechnology chamando um dos métodos de fábrica get das classes no pacote android.nfc.tech. É possível enumerar as tecnologias compatíveis da tag chamando getTechList() antes de chamar um método de fábrica get. Por exemplo, para conseguir uma instância de MifareUltralight de um Tag, faça o seguinte:

    Kotlin

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

    Java

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

Ler e gravar em etiquetas

Ler e gravar em uma tag NFC envolve obter a etiqueta da intent e abrir a comunicação com a etiqueta. Você precisa definir sua própria pilha de protocolos para ler e gravar dados na tag. No entanto, lembre-se de que você ainda pode ler e gravar dados NDEF ao trabalhar diretamente com uma tag. Cabe a você decidir como quer estruturar as coisas. O exemplo a seguir mostra como trabalhar com uma tag 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;
    }
}

Usar o sistema de expedição em primeiro plano

O sistema de expedição em primeiro plano permite que uma atividade intercepte uma intent e reivindique prioridade sobre outras atividades que processem a mesma intent. O uso desse sistema envolve a criação de algumas estruturas de dados para que o sistema Android possa enviar os intents apropriados para o aplicativo. Para ativar o sistema de expedição em primeiro plano:

  1. Adicione o seguinte código no método onCreate() da sua atividade:
    1. Crie um objeto PendingIntent mutável para que o sistema Android possa preenchê-lo com os detalhes da tag quando ela for lida.

      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. Declare os filtros de intent para processar os intents que você quer interceptar. O sistema de expedição em primeiro plano verifica os filtros de intent especificados com o intent recebido quando o dispositivo faz a leitura de uma tag. Se corresponderem, o aplicativo processará o intent. Se não houver correspondência, o sistema de expedição em primeiro plano volta para o sistema de expedição de intents. Especificar uma matriz null de filtros de intent e filtros de tecnologia define que você quer filtrar todas as tags que substituem a intent TAG_DISCOVERED. O snippet de código abaixo processa todos os tipos MIME para NDEF_DISCOVERED. Processe apenas os necessários.

      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. Configurar uma matriz de tecnologias de etiqueta que o aplicativo quer processar. Chame o método Object.class.getName() para receber a classe da tecnologia que você quer oferecer.

      Kotlin

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

      Java

      techListsArray = new String[][] { new String[] { NfcF.class.getName() } };
      
  2. Modifique os seguintes callbacks do ciclo de vida da atividade e adicione lógica para ativar e desativar a remessa em primeiro plano quando a atividade perder (onPause()) e recuperar (onResume()) o foco. enableForegroundDispatch() precisa ser chamado na linha de execução principal e somente quando a atividade está em primeiro plano. Chamar onResume() garante isso. Também é necessário implementar o callback onNewIntent para processar os dados da tag NFC lida.
  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
    }