diff options
author | Jeff Hamilton <jham@android.com> | 2010-12-02 09:39:12 -0600 |
---|---|---|
committer | Jeff Hamilton <jham@android.com> | 2010-12-11 22:50:49 -0600 |
commit | 3fb30ae5bf51d9ffe6271a345d55905dade8040d (patch) | |
tree | 51a126ef946daa06ac502f88a3541e3cb937253b | |
parent | aae427142dc22e7e419c146bc7748d9daff518e8 (diff) | |
download | packages_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.cpp | 5 | ||||
-rw-r--r-- | jni/com_android_nfc.h | 13 | ||||
-rw-r--r-- | jni/com_android_nfc_NativeNfcTag.cpp | 3 | ||||
-rwxr-xr-x | src/com/android/nfc/NfcService.java | 267 | ||||
-rwxr-xr-x | src/com/android/nfc/mytag/MyTagServer.java | 48 |
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; } |