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.