Question

I've got a lot of experience working with MIFARE (since 1996, when working with cards manufactured by GEMPLUS). I've even written low-level code to emulate MIFARE cards... but now that things are much simpler and higher level I can't make it to work with Java, Android and Android Studio! I think I'm getting dumber with time...

All I'm trying to do is to launch an application when a MIFARE card is detected. I know it can be done because I've used NFC Card Info app in my device and it is launched correctly in the presence of the MIFARE card. I've uninstalled it to make sure the only NFC app was my own. I've tried to follow the less than satisfying documentation found on http://developer.android.com/guide/topics/connectivity/nfc/nfc.html and http://developer.android.com/guide/topics/connectivity/nfc/advanced-nfc.html.

The problem is that my app is never launched. The code is very simple, and, AFAIK should be working... Here is the app manifest:

    <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.nfctest.app" >

    <uses-sdk android:minSdkVersion="10"/>
    <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"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.nfctest.app.MainActivity"
            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"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
            <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>
    </application>
</manifest>

It filters by NDEF_DISCOVERED because TECH_DISCOVERED didn't work alone.

Here is the nfc_tech_filter resource file:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <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>

And here is the activity code:

package com.example.nfctest.app;

import android.content.Intent;
import android.nfc.NfcAdapter;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity {

    TextView label;

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

    @Override
    public void onResume() {
        super.onResume();
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
            label.setText("NDEF_DISCOVERED");
        } else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(getIntent().getAction())) {
            label.setText("TECH_DISCOVERED");
        }
    }

    @Override
    public void onNewIntent(Intent intent) {
        label.setText("onNewIntent!!!");
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

The activity layout is as simple as it gets:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.example.nfctest.app.MainActivity">
    <TextView
        android:id="@+id/label"
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

All I needed to get started programming the actual NFC code was to have the app launched whenever the TAG as detected. The documentation makes it sound so easy that I'm certain that it is me, again, being stupid again...

UPDATE:

I am able to use the foregroundDispatcher to read the tag UID correctly by changing the activity code to:

package com.example.nfctest.app;

import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.widget.TextView;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class MainActivity extends ActionBarActivity {

    TextView label;
    IntentFilter[] filters;
    String[][] techs;
    PendingIntent pendingIntent;
    NfcAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        label = (TextView)findViewById(R.id.label);
        pendingIntent = PendingIntent.getActivity(
                this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        IntentFilter mifare = new IntentFilter((NfcAdapter.ACTION_TECH_DISCOVERED));
        filters = new IntentFilter[] { mifare };
        techs = new String[][] { new String[] {  NfcA.class.getName() } };
        adapter = NfcAdapter.getDefaultAdapter(this);
    }

    public void onPause() {
        super.onPause();
        adapter.disableForegroundDispatch(this);
    }

    public void onResume() {
        super.onResume();
        adapter.enableForegroundDispatch(this, pendingIntent, filters, techs);
    }

    public void onNewIntent(Intent intent) {
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        byte[] id = tag.getId();
        ByteBuffer wrapped = ByteBuffer.wrap(id);
        wrapped.order(ByteOrder.LITTLE_ENDIAN);
        int signedInt = wrapped.getInt();
        long number = signedInt & 0xffffffffl;
        label.setText("Tag detected: " + number);
    }
}

I don't even need any intent filters set up in the manifest for this to work, just a simple manifest like this will do:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.nfctest.app" >

    <uses-sdk android:minSdkVersion="10"/>
    <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"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.nfctest.app.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

With this code I can read the MIFARE card serial number when my app is in the foreground, but I'm still unable to set things up so that ANDROID launches my activity when it is in the background...

I'm still at a loss on how to get ANDROID to launch my app/activity when a tag (MIFARE or NOT) is detected.

Was it helpful?

Solution

Your problem is the tech filter XML file (as you finally correctly found out yourself). The tech filter you were originally using,

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <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>

does not make any sense. <tech> entries within one <tech-filter> entry are combined using a logical AND.

So you would need to have a tag that is Ndef and NdefFormatable and MifareClassic and MifareUltralight. This is impossible for two reasons:

  1. Ndef and NdefFormatable are mutually exclusive. A tag can either already contain NDEF data/an empty NDEF message (-> Ndef) or it may be ready to be formatted with an NDEF message (-> NdefFormatable).
  2. MifareClassic and MifareUltralight are mutually exclusive. A tag can either be a MIFARE Classic tag or a MIFARE Ultralight tag, but not both at the same time.

So, for instance, a proper tech filter that triggers upon tags that contain an NDEF message and are either MIFARE Classic or MIFARE Ultralight would look like this:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.MifareClassic</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.MifareUltralight</tech>
    </tech-list>
</resources>

All <tech-list> entries are combined with logical OR then: (Ndef and MifareClassic) or Ndef and MifareUltralight).

In your case, you seem to be less interested in NDEF but more in getting anything from a MIFARE card. Assuming that by MIFARE you mean a MIFARE Classic card (since you were talking about 1996 ;-) ), your tech filter should look like this:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.MifareClassic</tech>
    </tech-list>
</resources>

But note that this only works on devices with NXP chipset (MIFARE Classic is proprietary NXP technology. While NXP licenses the card-side to other manufacturers, they do not license the reader-side. Consequently, only1 NXP's reader products read MIFARE Classic.). On devices with Broadcom chipset, MIFARE Classic cards2 are not detected as MIFARE Classic. But since MIFARE Classic uses the standard ISO 14443-3 Type A anti-collision and activation sequence, these devices usually3 detect such cards as NfcA:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.NfcA</tech>
    </tech-list>
</resources>

Note that using a second entry with MifareClassic tag technology would be redundant as every device that detects MIFARE Classic will detect it as both, MifareClassic and NfcA.


1) There are exceptions to that rule as some manufacturers do not agree with NXP's position (or simply ignore it?) that implementing MIFARE Classic support on the reader-side infringes their rights.

2) And only MIFARE Classic. This does not apply to standards conformant MIFARE products like Ultralight, DESFire, NTAG, etc.

3) Some Samsung devices with Broadcom NFC chipset, like the S4, are exceptions to that rule. On these devices Samsung decided to ban MIFARE Classic completely and instead display a "tag not supported" error. As they claim, to improve user experience as user's would otherwise not understand why they can't write data to those tags. Or as I interpret it, to make your users hate you as an app developer as you can't make your app work on their phones with their tags. See the negative reviews on the ReTag app or on my own app.

OTHER TIPS

After a LOT of searching and frustration I started experimenting and realized that you can't filter for all Nfc tag types. Some of them doesn't play well with others (NfcA and MifareClassic for example). I didn't tried to figure out all the possible invalid combinations, but filtering only by NfcA did the trick (MifareClassic and Ultralight will be detected too as they behave like NfcA).

The tech list was changed to this:

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

The Manifest was changed to:

<uses-sdk android:minSdkVersion="10"/>
<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"
    android:theme="@style/AppTheme" >
    <activity
        android:name="com.example.nfctest.app.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
            <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>
</application>

And then it worked! The activity was launched as expected.

There was no mention to this in the ANDROID documentation. I find it rather lacking most of the time I need to refer to it! But that's life... Another sleepless night (4AM here in Brazil), one less problem for today. :-)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top