summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Hamilton <jham@android.com>2010-12-02 09:39:12 -0600
committerJeff Hamilton <jham@android.com>2010-12-11 22:50:49 -0600
commit3fb30ae5bf51d9ffe6271a345d55905dade8040d (patch)
tree51a126ef946daa06ac502f88a3541e3cb937253b
parentaae427142dc22e7e419c146bc7748d9daff518e8 (diff)
downloadpackages_apps_nfc-3fb30ae5bf51d9ffe6271a345d55905dade8040d.zip
packages_apps_nfc-3fb30ae5bf51d9ffe6271a345d55905dade8040d.tar.gz
packages_apps_nfc-3fb30ae5bf51d9ffe6271a345d55905dade8040d.tar.bz2
First pass at advanced NFC tag dispatching APIs and other cleanup.
Change-Id: I8469af074325fc8731aace1c9681bbddfa55dc89
-rw-r--r--jni/com_android_nfc.cpp5
-rw-r--r--jni/com_android_nfc.h13
-rw-r--r--jni/com_android_nfc_NativeNfcTag.cpp3
-rwxr-xr-xsrc/com/android/nfc/NfcService.java267
-rwxr-xr-xsrc/com/android/nfc/mytag/MyTagServer.java48
5 files changed, 236 insertions, 100 deletions
diff --git a/jni/com_android_nfc.cpp b/jni/com_android_nfc.cpp
index 11c6fa6..8395350 100644
--- a/jni/com_android_nfc.cpp
+++ b/jni/com_android_nfc.cpp
@@ -460,8 +460,9 @@ void nfc_jni_get_technology_tree(JNIEnv* e, phLibNfc_RemoteDevList_t* devList,
}break;
case phNfc_eJewel_PICC:
{
- index = addTechIfNeeded(technologies, handles, index, MAX_NUM_TECHNOLOGIES,
- TARGET_TYPE_JEWEL, handle);
+// TODO expose Jewel in the Java APIs
+// index = addTechIfNeeded(technologies, handles, index, MAX_NUM_TECHNOLOGIES,
+// TARGET_TYPE_JEWEL, handle);
index = addTechIfNeeded(technologies, handles, index, MAX_NUM_TECHNOLOGIES,
TARGET_TYPE_ISO14443_3A, handle);
}break;
diff --git a/jni/com_android_nfc.h b/jni/com_android_nfc.h
index fd22696..04a4249 100644
--- a/jni/com_android_nfc.h
+++ b/jni/com_android_nfc.h
@@ -80,13 +80,12 @@ extern "C" {
#define TARGET_TYPE_ISO14443_3A 1
#define TARGET_TYPE_ISO14443_3B 2
#define TARGET_TYPE_ISO14443_4 3
-#define TARGET_TYPE_ISO15693 21
-#define TARGET_TYPE_MIFARE_CLASSIC 200
-#define TARGET_TYPE_MIFARE_UL 202
-#define TARGET_TYPE_MIFARE_DESFIRE 203
-#define TARGET_TYPE_FELICA 11
-#define TARGET_TYPE_JEWEL 101
-#define TARGET_TYPE_NDEF_FORMATABLE 110
+#define TARGET_TYPE_FELICA 4
+#define TARGET_TYPE_ISO15693 5
+#define TARGET_TYPE_NDEF 6
+#define TARGET_TYPE_NDEF_FORMATABLE 7
+#define TARGET_TYPE_MIFARE_CLASSIC 8
+#define TARGET_TYPE_MIFARE_UL 9
/* Utility macros for logging */
#define GET_LEVEL(status) ((status)==NFCSTATUS_SUCCESS)?ANDROID_LOG_DEBUG:ANDROID_LOG_WARN
diff --git a/jni/com_android_nfc_NativeNfcTag.cpp b/jni/com_android_nfc_NativeNfcTag.cpp
index 801e8f1..2d9ecf8 100644
--- a/jni/com_android_nfc_NativeNfcTag.cpp
+++ b/jni/com_android_nfc_NativeNfcTag.cpp
@@ -710,17 +710,18 @@ static jbyteArray com_android_nfc_NativeNfcTag_doTransceive(JNIEnv *e,
buflen = outlen = (uint32_t)e->GetArrayLength(data);
switch (selectedTech) {
+/* TODO figure out how to pipe Jewel commands through from Java
case TARGET_TYPE_JEWEL:
transceive_info.cmd.JewelCmd = phNfc_eJewel_Raw;
transceive_info.addr = 0;
break;
+*/
case TARGET_TYPE_FELICA:
transceive_info.cmd.FelCmd = phNfc_eFelica_Raw;
transceive_info.addr = 0;
break;
case TARGET_TYPE_MIFARE_CLASSIC:
case TARGET_TYPE_MIFARE_UL:
- case TARGET_TYPE_MIFARE_DESFIRE:
if (raw) {
transceive_info.cmd.MfCmd = phHal_eMifareRaw;
transceive_info.addr = 0;
diff --git a/src/com/android/nfc/NfcService.java b/src/com/android/nfc/NfcService.java
index 593d48a..a5e6b2d 100755
--- a/src/com/android/nfc/NfcService.java
+++ b/src/com/android/nfc/NfcService.java
@@ -29,20 +29,22 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.net.Uri;
import android.nfc.ErrorCodes;
import android.nfc.FormatException;
import android.nfc.ILlcpConnectionlessSocket;
import android.nfc.ILlcpServiceSocket;
import android.nfc.ILlcpSocket;
import android.nfc.INfcAdapter;
+import android.nfc.INfcSecureElement;
import android.nfc.INfcTag;
import android.nfc.IP2pInitiator;
import android.nfc.IP2pTarget;
import android.nfc.LlcpPacket;
import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
-import android.nfc.INfcSecureElement;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
@@ -57,6 +59,8 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.charset.Charsets;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.ListIterator;
@@ -72,6 +76,50 @@ public class NfcService extends Application {
System.loadLibrary("nfc_jni");
}
+ /**
+ * NFC Forum "URI Record Type Definition"
+ *
+ * This is a mapping of "URI Identifier Codes" to URI string prefixes,
+ * per section 3.2.2 of the NFC Forum URI Record Type Definition document.
+ */
+ private static final String[] URI_PREFIX_MAP = new String[] {
+ "", // 0x00
+ "http://www.", // 0x01
+ "https://www.", // 0x02
+ "http://", // 0x03
+ "https://", // 0x04
+ "tel:", // 0x05
+ "mailto:", // 0x06
+ "ftp://anonymous:anonymous@", // 0x07
+ "ftp://ftp.", // 0x08
+ "ftps://", // 0x09
+ "sftp://", // 0x0A
+ "smb://", // 0x0B
+ "nfs://", // 0x0C
+ "ftp://", // 0x0D
+ "dav://", // 0x0E
+ "news:", // 0x0F
+ "telnet://", // 0x10
+ "imap:", // 0x11
+ "rtsp://", // 0x12
+ "urn:", // 0x13
+ "pop:", // 0x14
+ "sip:", // 0x15
+ "sips:", // 0x16
+ "tftp:", // 0x17
+ "btspp://", // 0x18
+ "btl2cap://", // 0x19
+ "btgoep://", // 0x1A
+ "tcpobex://", // 0x1B
+ "irdaobex://", // 0x1C
+ "file://", // 0x1D
+ "urn:epc:id:", // 0x1E
+ "urn:epc:tag:", // 0x1F
+ "urn:epc:pat:", // 0x20
+ "urn:epc:raw:", // 0x21
+ "urn:epc:", // 0x22
+ };
+
public static final String SERVICE_NAME = "nfc";
private static final String TAG = "NfcService";
@@ -1507,7 +1555,7 @@ public class NfcService extends Application {
}
@Override
- public NdefMessage read(int nativeHandle) throws RemoteException {
+ public NdefMessage ndefRead(int nativeHandle) throws RemoteException {
mContext.enforceCallingOrSelfPermission(NFC_PERM, NFC_PERM_ERROR);
NativeNfcTag tag;
@@ -1535,7 +1583,7 @@ public class NfcService extends Application {
}
@Override
- public int write(int nativeHandle, NdefMessage msg) throws RemoteException {
+ public int ndefWrite(int nativeHandle, NdefMessage msg) throws RemoteException {
mContext.enforceCallingOrSelfPermission(NFC_PERM, NFC_PERM_ERROR);
NativeNfcTag tag;
@@ -1562,20 +1610,17 @@ public class NfcService extends Application {
@Override
public int getLastError(int nativeHandle) throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
+ throw new UnsupportedOperationException();
}
@Override
- public int getModeHint(int nativeHandle) throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
+ public boolean ndefIsWritable(int nativeHandle) throws RemoteException {
+ throw new UnsupportedOperationException();
}
@Override
- public int makeReadOnly(int nativeHandle) throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
+ public int ndefMakeReadOnly(int nativeHandle) throws RemoteException {
+ throw new UnsupportedOperationException();
}
@Override
@@ -2429,14 +2474,9 @@ public class NfcService extends Application {
Tag tag = Tag.createMockTag(new byte[] { 0x00 },
new int[] { },
new Bundle[] { });
- Intent intent = buildTagIntent(tag, new NdefMessage[] { ndefMsg });
Log.d(TAG, "mock NDEF tag, starting corresponding activity");
Log.d(TAG, tag.toString());
- try {
- mContext.startActivity(intent);
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "No activity found for mock tag");
- }
+ dispatchTag(tag, new NdefMessage[] { ndefMsg });
break;
}
@@ -2456,20 +2496,7 @@ public class NfcService extends Application {
msgNdef[0] = new NdefMessage(buff);
nativeTag.addNdefTechnology(msgNdef[0],
supportedNdefLength, cardState);
- Tag tag = new Tag(nativeTag.getUid(),
- nativeTag.getTechList(),
- nativeTag.getTechExtras(),
- nativeTag.getHandle());
- Intent intent = buildTagIntent(tag, msgNdef);
- if (DBG) Log.d(TAG, "NDEF tag found, starting corresponding activity");
- if (DBG) Log.d(TAG, tag.toString());
- try {
- mContext.startActivity(intent);
- registerTagObject(nativeTag);
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "No activity found, disconnecting");
- nativeTag.disconnect();
- }
+ dispatchNativeTag(nativeTag, msgNdef);
} catch (FormatException e) {
// Create an intent anyway, without NDEF messages
generateEmptyIntent = true;
@@ -2478,44 +2505,23 @@ public class NfcService extends Application {
// Create an intent anyway, without NDEF messages
generateEmptyIntent = true;
}
+
if (generateEmptyIntent) {
// Create an intent with an empty ndef message array
nativeTag.addNdefTechnology(null, supportedNdefLength, cardState);
- Tag tag = new Tag(nativeTag.getUid(),
- nativeTag.getTechList(),
- nativeTag.getTechExtras(),
- nativeTag.getHandle());
- Intent intent = buildTagIntent(tag, new NdefMessage[] { });
- if (DBG) Log.d(TAG, "NDEF tag found, but length 0 or invalid format, starting corresponding activity");
- try {
- mContext.startActivity(intent);
- registerTagObject(nativeTag);
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "No activity found, disconnecting");
- nativeTag.disconnect();
- }
+ if (DBG) Log.d(TAG, "NDEF tag found, but length 0 or invalid format, " +
+ "starting corresponding activity");
+ dispatchNativeTag(nativeTag, new NdefMessage[] { });
}
} else {
- Tag tag = new Tag(nativeTag.getUid(),
- nativeTag.getTechList(),
- nativeTag.getTechExtras(),
- nativeTag.getHandle());
- Intent intent = buildTagIntent(tag, null);
- if (DBG) Log.d(TAG, "Non-NDEF tag found, starting corresponding activity");
- if (DBG) Log.d(TAG, tag.toString());
- try {
- mContext.startActivity(intent);
- registerTagObject(nativeTag);
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "No activity found, disconnecting");
- nativeTag.disconnect();
- }
+ dispatchNativeTag(nativeTag, null);
}
} else {
Log.w(TAG, "Failed to connect to tag");
nativeTag.disconnect();
}
break;
+
case MSG_CARD_EMULATION:
if (DBG) Log.d(TAG, "Card Emulation message");
byte[] aid = (byte[]) msg.obj;
@@ -2624,14 +2630,155 @@ public class NfcService extends Application {
}
}
- private Intent buildTagIntent(Tag tag, NdefMessage[] msgs) {
- Intent intent = new Intent(NfcAdapter.ACTION_TAG_DISCOVERED);
+ private Intent buildTagIntent(Tag tag, NdefMessage[] msgs, String action) {
+ Intent intent = new Intent(action);
intent.putExtra(NfcAdapter.EXTRA_TAG, tag);
intent.putExtra(NfcAdapter.EXTRA_ID, tag.getId());
intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, msgs);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}
+
+ private void dispatchNativeTag(NativeNfcTag nativeTag, NdefMessage[] msgs) {
+ Tag tag = new Tag(nativeTag.getUid(), nativeTag.getTechList(),
+ nativeTag.getTechExtras(), nativeTag.getHandle());
+ if (dispatchTag(tag, msgs)) {
+ registerTagObject(nativeTag);
+ } else {
+ nativeTag.disconnect();
+ }
+ }
+
+ public byte[] concat(byte[]... arrays) {
+ int length = 0;
+ for (byte[] array : arrays) {
+ length += array.length;
+ }
+ byte[] result = new byte[length];
+ int pos = 0;
+ for (byte[] array : arrays) {
+ System.arraycopy(array, 0, result, pos, array.length);
+ pos += array.length;
+ }
+ return result;
+ }
+
+ private Uri parseWellKnownUriRecord(NdefRecord record) {
+ byte[] payload = record.getPayload();
+
+ /*
+ * payload[0] contains the URI Identifier Code, per the
+ * NFC Forum "URI Record Type Definition" section 3.2.2.
+ *
+ * payload[1]...payload[payload.length - 1] contains the rest of
+ * the URI.
+ */
+ String prefix = URI_PREFIX_MAP[(payload[0] & 0xff)];
+ byte[] fullUri = concat(prefix.getBytes(Charsets.UTF_8),
+ Arrays.copyOfRange(payload, 1, payload.length));
+ return Uri.parse(new String(fullUri, Charsets.UTF_8));
+ }
+
+ private boolean setTypeOrDataFromNdef(Intent intent, NdefRecord record) {
+ short tnf = record.getTnf();
+ byte[] type = record.getType();
+ switch (tnf) {
+ case NdefRecord.TNF_MIME_MEDIA: {
+ intent.setType(new String(type, Charsets.US_ASCII));
+ return true;
+ }
+ case NdefRecord.TNF_ABSOLUTE_URI: {
+ intent.setData(Uri.parse(new String(type, Charsets.UTF_8)));
+ return true;
+ }
+ case NdefRecord.TNF_WELL_KNOWN: {
+ if (Arrays.equals(type, NdefRecord.RTD_TEXT)) {
+ intent.setType("text/plain");
+ return true;
+ } else if (Arrays.equals(type, NdefRecord.RTD_SMART_POSTER)) {
+ // Parse the smart poster looking for the URI
+ try {
+ NdefMessage msg = new NdefMessage(record.getPayload());
+ for (NdefRecord subRecord : msg.getRecords()) {
+ if (subRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN
+ && Arrays.equals(subRecord.getType(), NdefRecord.RTD_URI)) {
+ intent.setData(parseWellKnownUriRecord(subRecord));
+ return true;
+ }
+ }
+ } catch (FormatException e) {
+ return false;
+ }
+ } else if (Arrays.equals(type, NdefRecord.RTD_URI)) {
+ intent.setData(parseWellKnownUriRecord(record));
+ return true;
+ }
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private Uri buildTechListUri(Tag tag) {
+ int[] techList = tag.getTechnologyList();
+ Arrays.sort(techList);
+ Uri.Builder builder = new Uri.Builder();
+ builder.scheme("vnd.android.nfc").authority("tag");
+ for (int tech : techList) {
+ builder.appendPath(Integer.toString(tech));
+ }
+ builder.appendPath("");
+ return builder.build();
+ }
+
+ /** Returns false if no activities were found to dispatch to */
+ private boolean dispatchTag(Tag tag, NdefMessage[] msgs) {
+ if (DBG) {
+ Log.d(TAG, "Dispatching tag");
+ Log.d(TAG, tag.toString());
+ }
+
+ Intent intent;
+ if (msgs != null && msgs.length > 0) {
+ NdefMessage msg = msgs[0];
+ NdefRecord[] records = msg.getRecords();
+ if (records.length > 0) {
+ // Found valid NDEF data, try to dispatch that first
+ NdefRecord record = records[0];
+
+ intent = buildTagIntent(tag, msgs, NfcAdapter.ACTION_NDEF_DISCOVERED);
+ setTypeOrDataFromNdef(intent, record);
+
+ try {
+ mContext.startActivity(intent);
+ // If an activity is found then skip further dispatching
+ return true;
+ } catch (ActivityNotFoundException e) {
+ if (DBG) Log.d(TAG, "No activities for NDEF handling of " + intent);
+ }
+ }
+ }
+
+ // Try the technology specific dispatch
+ intent = buildTagIntent(tag, msgs, NfcAdapter.ACTION_TECHNOLOGY_DISCOVERED);
+ intent.setData(buildTechListUri(tag));
+ try {
+ mContext.startActivity(intent);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ if (DBG) Log.w(TAG, "No activities for technology handling of " + intent);
+ }
+
+ // Try the generic intent
+ intent = buildTagIntent(tag, msgs, NfcAdapter.ACTION_TAG_DISCOVERED);
+ try {
+ mContext.startActivity(intent);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "No tag fallback activity found for " + intent);
+ return false;
+ }
+ }
}
private NfcServiceHandler mHandler = new NfcServiceHandler();
diff --git a/src/com/android/nfc/mytag/MyTagServer.java b/src/com/android/nfc/mytag/MyTagServer.java
index cbed3f9..a00e283 100755
--- a/src/com/android/nfc/mytag/MyTagServer.java
+++ b/src/com/android/nfc/mytag/MyTagServer.java
@@ -36,11 +36,13 @@ import java.io.IOException;
public class MyTagServer {
private static final String TAG = "MyTagServer";
private static final boolean DBG = true;
+
private static final int SERVICE_SAP = 0x20;
static final String SERVICE_NAME = "com.android.mytag";
NfcService mService = NfcService.getInstance();
+
/** Protected by 'this', null when stopped, non-null when running */
ServerThread mServerThread = null;
@@ -48,13 +50,6 @@ public class MyTagServer {
private class ConnectionThread extends Thread {
private LlcpSocket mSock;
- private void trace(String msg) {
- if (DBG) Log.d(TAG, "Server (" + Thread.currentThread().getId() + "): " + msg);
- }
- private void error(String msg, Throwable e) {
- if (DBG) Log.e(TAG, "Server (" + Thread.currentThread().getId() + "): " + msg, e);
- }
-
ConnectionThread(LlcpSocket sock) {
super("MyTagServer");
mSock = sock;
@@ -62,7 +57,7 @@ public class MyTagServer {
@Override
public void run() {
- trace("starting connection thread");
+ if (DBG) Log.d(TAG, "starting connection thread");
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
byte[] partial = new byte[1024];
@@ -73,7 +68,7 @@ public class MyTagServer {
while(!connectionBroken) {
try {
size = mSock.receive(partial);
- trace("read " + size + " bytes");
+ if (DBG) Log.d(TAG, "read " + size + " bytes");
if (size < 0) {
connectionBroken = true;
break;
@@ -83,27 +78,27 @@ public class MyTagServer {
} catch (IOException e) {
// Connection broken
connectionBroken = true;
- error("connection broken by IOException", e);
+ if (DBG) Log.d(TAG, "connection broken by IOException", e);
}
}
// Build NDEF message from the stream
NdefMessage msg = new NdefMessage(buffer.toByteArray());
- trace("got message " + msg.toString());
+ if (DBG) Log.d(TAG, "got message " + msg.toString());
// Send the intent for the fake tag
mService.sendMockNdefTag(msg);
} catch (FormatException e) {
- error("badly formatted NDEF message, ignoring", e);
+ Log.e(TAG, "badly formatted NDEF message, ignoring", e);
} finally {
try {
- trace("about to close");
+ if (DBG) Log.d(TAG, "about to close");
mSock.close();
} catch (IOException e) {
// ignore
}
}
- trace("finished connection thread");
+ if (DBG) Log.d(TAG, "finished connection thread");
}
};
@@ -112,41 +107,34 @@ public class MyTagServer {
boolean mRunning = true;
LlcpServiceSocket mServerSocket;
- private void trace(String msg) {
- if (DBG) Log.d(TAG, "Comm (" + Thread.currentThread().getId() + "): " + msg);
- }
- private void error(String msg, Throwable e) {
- if (DBG) Log.e(TAG, "Comm (" + Thread.currentThread().getId() + "): " + msg, e);
- }
-
@Override
public void run() {
while (mRunning) {
- trace("about create LLCP service socket");
+ if (DBG) Log.d(TAG, "about create LLCP service socket");
mServerSocket = mService.createLlcpServiceSocket(SERVICE_SAP, null,
128, 1, 1024);
if (mServerSocket == null) {
- trace("failed to create LLCP service socket");
+ if (DBG) Log.d(TAG, "failed to create LLCP service socket");
return;
}
- trace("created LLCP service socket");
+ if (DBG) Log.d(TAG, "created LLCP service socket");
try {
while (mRunning) {
- trace("about to accept");
+ if (DBG) Log.d(TAG, "about to accept");
LlcpSocket communicationSocket = mServerSocket.accept();
- trace("accept returned " + communicationSocket);
+ if (DBG) Log.d(TAG, "accept returned " + communicationSocket);
if (communicationSocket != null) {
new ConnectionThread(communicationSocket).start();
}
}
- trace("stop running");
+ if (DBG) Log.d(TAG, "stop running");
} catch (LlcpException e) {
- error("llcp error", e);
+ Log.e(TAG, "llcp error", e);
} catch (IOException e) {
- error("IO error", e);
+ Log.e(TAG, "IO error", e);
} finally {
if (mServerSocket != null) {
- trace("about to close");
+ if (DBG) Log.d(TAG, "about to close");
mServerSocket.close();
mServerSocket = null;
}