Чтение нескольких NFC-меток в Android. IsoDep-тег не читается, пока экран разблокирован

Я пытался создать приложение, которое может считывать два разных типа NFC-тегов. Один должен быть HCE-IsoDep, эмулированным на Nexus 5, а другой — Ndef-tag. Однако я столкнулся с небольшой проблемой:

Мне удается читать оба типа тегов, но не так, как я хочу. Тег Ndef вообще не проблема. Когда я пытаюсь прочитать тег HCE, я сталкиваюсь со своей проблемой. Я могу прочитать тег только при включенном телефоне, на котором я эмулирую тег, заблокированный (экран включен, но блокировка включена). Всякий раз, когда я разблокирую экран, он больше не будет взаимодействовать, и, насколько я понимаю, вместо этого он пытается сиять.

Если я попытаюсь сделать это без onNewIntent и сразу перейду к onTagDiscovered, он будет работать как при заблокированном, так и при разблокированном устройстве HCE, но тогда я не смогу прочитать тег Ndef. В logcat я получаю сообщение: NfcService LLCP Activation Message когда я читаю тег HCE при разблокировке.

При блокировке получаю сообщение: NativeNfcTag Connect to a tag with a different handle (а до этого получаю: audio_hw_primary select_devices: out_snd_device(2: speaker) in_snd_device(0: ))

Мой код выглядит следующим образом:

Основной:

public class NfcReader extends Activity implements OnMessageReceived {

private static String TAG = NfcReader.class.getSimpleName();

private Button sendButton;
private ProgressBar callProgress;


private NfcAdapter nfcAdapter;
private PendingIntent pIntent;
private IntentFilter[] writeTagFilters;
private String[][] mTechLists;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TextView dateView = (TextView) findViewById(R.id.dateTextView);

    nfcAdapter = NfcAdapter.getDefaultAdapter(this);
    pIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);

    IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
    writeTagFilters = new IntentFilter[] { tagDetected };

    mTechLists = new String[][] {new String[] {
            Ndef.class.getName(),
            IsoDep.class.getName()
    }};
}

@Override
protected void onPause() {
    super.onPause();
    disableForegroundMode();
}

@Override
protected void onResume() {
    super.onResume();
    enableForegroundMode();
}

public void enableForegroundMode() {
    Log.d(TAG, "onResume");
    nfcAdapter.enableForegroundDispatch(this, pIntent, writeTagFilters, mTechLists);
}

public void disableForegroundMode() {
    Log.d(TAG, "onPause");
    nfcAdapter.disableForegroundDispatch(this);
}

@Override
public void onNewIntent(Intent intent) {
    Log.d(TAG, "onNewIntent");

    if(NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())){

        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        Ndef nDef = Ndef.get(tag);

        if (nDef != null) {
            onNdefDiscovered(tag);
        }
        else {
            onTagDiscovered(tag);
        }
    }
}

public void onNdefDiscovered(Tag tag) {
    Log.d(TAG, "Ndef found");
    new ReadTag().execute(tag);
}

public void onTagDiscovered(Tag tag) {
    Log.d(TAG, "HCEfound"); 
    IsoDep isoDep = IsoDep.get(tag);
    IsoDepTransceiver transceiver = new IsoDepTransceiver(isoDep, this);
    transceiver.run();

}

@Override
public void onMessage(final byte[] message) {
    runOnUiThread(new Runnable() {

        @Override
        public void run() {
            String readFromHce = new String(message);
            TextView result = (TextView) findViewById(R.id.refTextView);
            result.setText(readFromHce);

        }
    });
}

@Override
public void onError(Exception exception) {
    onMessage(exception.getMessage().getBytes());
}
}

Манифест:

<uses-sdk
    android:minSdkVersion="19"
    android:targetSdkVersion="19" />

<uses-permission 
    android:name="android.permission.INTERNET" />

<uses-permission 
    android:name="android.permission.NFC" />

<uses-feature
    android:name="android.hardware.nfc"
    android:required="true" />

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <activity
        android:name=".HceReader"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
            <action android:name="android.nfc.action.TECH_DISCOVERED"/>
            <action android:name="android.nfc.action.TAG_DISCOVERED" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="text/plain" />
        </intent-filter>

        <meta-data
            android:name="android.nfc.action.TECH_DISCOVERED"
            android:resource="@xml/filter_nfc"/>

filter_nfc.xml

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">

    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
    </tech-list>

    <tech-list>
        <tech>android.nfc.tech.Ndef</tech>
    </tech-list>
</resources>

Кто-нибудь знает, что я делаю неправильно? Я искал довольно много, но не нашел решения этой проблемы. Итак, еще раз. Я могу прочитать Ndef-тег без проблем. Я могу прочитать эмулированный тег IsoDep только тогда, когда экран на устройстве HCE заблокирован.

Благодарен за любую помощь
С уважением

Изменить: код ниже работает

public class NfcReader extends Activity implements OnMessageReceived, ReaderCallback {

private static String TAG = NfcReader.class.getSimpleName();

private NfcAdapter nfcAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TextView result = (TextView) findViewById(R.id.refTextView);

    nfcAdapter = NfcAdapter.getDefaultAdapter(this);
}

@Override
protected void onPause() {
    super.onPause();
    nfcAdapter.disableReaderMode(this);
}

@Override
protected void onResume() {
    super.onResume();
    nfcAdapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_A, null);
}

public void onTagDiscovered(Tag tag) {
    Log.d(TAG, "Tag Found"); 

    Ndef nDef = Ndef.get(tag);
    IsoDep isoDep = IsoDep.get(tag);

    if (nDef != null) {
        new ReadTag().execute(tag);
    }
    else if (isoDep != null){
        IsoDepTransceiver transceiver = new IsoDepTransceiver(isoDep, this);
        transceiver.run();      
    }
}

@Override
public void onMessage(final byte[] message) {
    runOnUiThread(new Runnable() {

        @Override
        public void run() {
            String readFromHce = new String(message);
            TextView result = (TextView) findViewById(R.id.refTextView);
            result.setText(readFromHce);
        }
    });
}

@Override
public void onError(Exception exception) {
    onMessage(exception.getMessage().getBytes());
}
}

Большое спасибо парню NFC за подсказку.


person Markus    schedule 20.12.2013    source источник


Ответы (2)


На Android 4.4 и выше следует использовать enableReaderMode() для этого.

В этом режиме контроллер NFC будет действовать только как устройство чтения/записи тегов NFC, тем самым отключая любые режимы одноранговой связи (Android Beam) и режимы эмуляции карты адаптера NFC на этом устройстве.

Для взаимодействия с тегами, которые эмулируются на другом устройстве Android с помощью эмуляции карты Android на основе хоста, рекомендуемые флаги: FLAG_READER_NFC_A и FLAG_READER_SKIP_NDEF_CHECK.

person NFC guy    schedule 20.12.2013
comment
Спасибо за Ваш ответ. Я знаю, что это то, что я использовал, когда заставил его работать на ранних стадиях. Однако тогда мне не удалось успешно реализовать чтение Ndef. Я посмотрю еще раз. - person Markus; 21.12.2013
comment
Тогда вы можете опустить флаг FLAG_READER_SKIP_NDEF_CHECK. - person NFC guy; 22.12.2013
comment
Это помогло! Все работает именно так, как я хотел, и в качестве бонуса я получил меньше кода :) - person Markus; 23.12.2013

Вы не сделали ничего плохого. То, что вы пытались сделать, к сожалению, не сработает.

Если на вашем телефоне работает и эмуляция карты, и одноранговая связь (Android Beam), а устройство чтения (другой ваш телефон) также поддерживает одноранговую связь, технология одноранговой связи будет иметь приоритет над эмуляция карты.

Это имеет смысл, если подумать: если вы поместите SIM-карту с поддержкой NFC в свой телефон, у вас будет несколько эмуляций сторонних карт на основе SIM-карты. Если бы одноранговая связь не имела приоритета над эмуляцией карты, луч Android перестал бы работать, и вместо этого вы увидели бы соединения с тегами IsoDep.

В случае, если ваш телефон заблокирован, одноранговая связь будет отключена, а эмуляция карты будет иметь приоритет. Поэтому вы можете получить доступ к эмуляции карты в этом состоянии.

Если вы хотите получить доступ к эмуляции карты даже в разблокированном состоянии экрана, ваш единственный вариант — использовать считывающее устройство, которое не активирует одноранговый протокол (например, автономный платежный терминал).

На Android нет возможности отключить одноранговую связь. Отключение Android Beam в настройках вам тоже не поможет, потому что будет отключен только высокоуровневый протокол Beam. Протокол одноранговой сети по-прежнему будет работать, чтобы активно препятствовать тому, чтобы вы видели эмуляцию карт других людей. Такое поведение обусловлено тем, что Google не хочет, чтобы люди случайно получали доступ к эмуляции платежной карты из соображений безопасности.

person Nils Pipenbrinck    schedule 20.12.2013
comment
Ваш ответ имеет смысл. Но мне интересно, почему это работает, когда я не использую функцию onNewIntent, а вместо этого напрямую использую onTagDiscovery. Я могу сканировать эмулируемый тег с Nexus 5 на другом устройстве, не активируя функцию луча, когда он разблокирован на эмулируемом устройстве. Я думал, что вся часть pendingIntent позаботится о том, чтобы моя активность переопределила ее. Хотя я явно ошибался. - person Markus; 20.12.2013
comment
Вместе с API19 вводятся два новых метода: enableReaderMode() и disableReaderMode() Возможно, это поможет заставить эмулирующий телефон оставаться в режиме эмуляции. - person corvairjo; 20.12.2013