First pass at advanced NFC tag dispatching APIs and other cleanup.

Change-Id: I8469af074325fc8731aace1c9681bbddfa55dc89
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 @@
             }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 @@
 #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 @@
     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.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.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 @@
         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 @@
         }
 
         @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 @@
         }
 
         @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 @@
 
         @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 @@
                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 @@
                                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 @@
                            // 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 @@
            }
         }
 
-        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 @@
 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 @@
     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 @@
 
         @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 @@
                 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 @@
                     } 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 @@
         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;
                     }