Switch to a simpler storage model.

Going back to the previous storage model of where we only store the messages.
We reference records by appending the offset of the record to the end of the
uri.

When a tag is discovered we start the service to save it. A PendingIntent
is created that will resolve to the currently running activity.

Change-Id: I32d1dd70960fdf67129d1707399d667de030ebe5
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 280ad6f..d12185b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -47,6 +47,7 @@
 
         <activity android:name="TagViewer"
             android:theme="@android:style/Theme.NoTitleBar"
+            android:launchMode="singleTop"
         >
             <intent-filter>
                 <action android:name="android.nfc.action.NDEF_TAG_DISCOVERED"/>
@@ -68,7 +69,6 @@
             android:authorities="com.android.apps.tag"
             android:syncable="false"
             android:multiprocess="false"
-            android:exported="false"
         />
 
     </application>
diff --git a/res/layout/tag_viewer.xml b/res/layout/tag_viewer.xml
index 5cb2bdd..6ec7739 100644
--- a/res/layout/tag_viewer.xml
+++ b/res/layout/tag_viewer.xml
@@ -108,14 +108,6 @@
         style="@android:style/ButtonBar"
     >
 
-        <Button android:id="@+id/button_delete"
-            android:layout_width="0dip"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-
-            android:text="@string/button_delete"
-        />
-
         <Button android:id="@+id/button_done"
             android:layout_width="0dip"
             android:layout_height="wrap_content"
@@ -124,6 +116,14 @@
             android:text="@string/button_done"
         />
 
+        <Button android:id="@+id/button_delete"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+
+            android:text="@string/button_delete"
+        />
+
     </LinearLayout>
 
 </LinearLayout>
\ No newline at end of file
diff --git a/src/com/android/apps/tag/TagService.java b/src/com/android/apps/tag/TagService.java
index 6cfb966..1a2c433 100644
--- a/src/com/android/apps/tag/TagService.java
+++ b/src/com/android/apps/tag/TagService.java
@@ -16,23 +16,19 @@
 
 package com.android.apps.tag;
 
-import com.android.apps.tag.provider.TagContract;
 import com.android.apps.tag.provider.TagContract.NdefMessages;
-import com.android.apps.tag.provider.TagContract.NdefTags;
 
 import android.app.IntentService;
-import android.content.ContentProviderOperation;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
-import android.content.OperationApplicationException;
 import android.net.Uri;
+import android.nfc.NdefMessage;
 import android.nfc.NdefTag;
-import android.os.RemoteException;
 import android.util.Log;
 
-import java.util.ArrayList;
-
 public class TagService extends IntentService {
     private static final String TAG = "TagService";
 
@@ -41,6 +37,9 @@
     private static final String EXTRA_STAR_URI = "set_star";
     private static final String EXTRA_UNSTAR_URI = "remove_star";
     private static final String EXTRA_STARRED = "starred";
+    private static final String EXTRA_PENDING_INTENT = "pending";
+
+    private static final boolean DEBUG = true;
 
     public TagService() {
         super("SaveTagService");
@@ -49,17 +48,26 @@
     @Override
     public void onHandleIntent(Intent intent) {
         if (intent.hasExtra(EXTRA_SAVE_TAG)) {
+
             NdefTag tag = (NdefTag) intent.getParcelableExtra(EXTRA_SAVE_TAG);
-            boolean starred = intent.getBooleanExtra(EXTRA_STARRED, false);
-            ArrayList<ContentProviderOperation> ops = NdefTags.toContentProviderOperations(this,
-                    tag, starred);
-            try {
-                getContentResolver().applyBatch(TagContract.AUTHORITY, ops);
-            } catch (OperationApplicationException e) {
-                Log.e(TAG, "Failed to save tag", e);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to save tag", e);
+            NdefMessage msg = tag.getNdefMessages()[0];
+
+            ContentValues values = NdefMessages.toValues(this, msg, false, System.currentTimeMillis());
+            Uri uri = getContentResolver().insert(NdefMessages.CONTENT_URI, values);
+
+            if (intent.hasExtra(EXTRA_PENDING_INTENT)) {
+                Intent result = new Intent();
+                result.setData(uri);
+
+                PendingIntent pending = (PendingIntent) intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+
+                try {
+                    pending.send(this, 0, result);
+                } catch (CanceledException e) {
+                    if (DEBUG) Log.d(TAG, "Pending intent was canceled.");
+                }
             }
+
             return;
         }
 
@@ -84,10 +92,11 @@
         }
     }
 
-    public static void saveTag(Context context, NdefTag tag, boolean starred) {
+    public static void saveTag(Context context, NdefTag tag, boolean starred, PendingIntent pending) {
         Intent intent = new Intent(context, TagService.class);
         intent.putExtra(TagService.EXTRA_SAVE_TAG, tag);
         intent.putExtra(TagService.EXTRA_STARRED, starred);
+        intent.putExtra(TagService.EXTRA_PENDING_INTENT, pending);
         context.startService(intent);
     }
 
diff --git a/src/com/android/apps/tag/TagViewer.java b/src/com/android/apps/tag/TagViewer.java
index 275edf4..72aef85 100644
--- a/src/com/android/apps/tag/TagViewer.java
+++ b/src/com/android/apps/tag/TagViewer.java
@@ -22,7 +22,10 @@
 import com.android.apps.tag.record.ParsedNdefRecord;
 
 import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.media.AudioManager;
@@ -34,6 +37,7 @@
 import android.nfc.NfcAdapter;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -47,6 +51,7 @@
 import android.widget.TextView;
 
 import java.io.IOException;
+import java.util.List;
 
 /**
  * An {@link Activity} which handles a broadcast of a new tag that the device just discovered.
@@ -75,10 +80,7 @@
 
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
-                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                 | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_DIM_BEHIND
         );
 
         setContentView(R.layout.tag_viewer);
@@ -99,19 +101,50 @@
         resolveIntent(getIntent());
     }
 
+    @Override
+    public void onRestart() {
+        super.onRestart();
+        if (mTagUri == null) {
+            // Someone how the user was fast enough to navigate away from the activity
+            // before the service was able to save the tag and call back onto this
+            // activity with the pending intent. Since we don't know what do display here
+            // just finish the activity.
+            finish();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+
+        PendingIntent pending = getPendingIntent();
+        pending.cancel();
+    }
+
+    private PendingIntent getPendingIntent() {
+        Intent callback = new Intent();
+        callback.setClass(this, TagViewer.class);
+        callback.setAction(Intent.ACTION_VIEW);
+        callback.setFlags(Intent. FLAG_ACTIVITY_CLEAR_TOP);
+
+        return PendingIntent.getActivity(this, 0, callback, PendingIntent.FLAG_CANCEL_CURRENT);
+    }
+
+
     void resolveIntent(Intent intent) {
         // Parse the intent
         String action = intent.getAction();
         if (NfcAdapter.ACTION_NDEF_TAG_DISCOVERED.equals(action)) {
-            // Get the messages from the tag
-            //TODO check if the tag is writable and offer to write it?
+            // When a tag is discovered we send it to the service to be save. We
+            // include a PendingIntent for the service to call back onto. This
+            // will cause this activity to be restarted with onNewIntent(). At
+            // that time we read it from the database and view it.
+
             NdefTag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
-            NdefMessage[] msgs = tag.getNdefMessages();
-            if (msgs == null || msgs.length == 0) {
-                Log.e(TAG, "No NDEF messages");
-                finish();
-                return;
-            }
+
+            PendingIntent pending = getPendingIntent();
+
+            TagService.saveTag(this, tag, false, pending);
 
             // Setup the views
             setTitle(R.string.title_scanned_tag);
@@ -136,11 +169,6 @@
                 Log.w(TAG, "Sound creation failed for tag discovery");
             }
 
-            // Mark tag that were just scanned for saving
-            mTag = tag;
-
-            // Build the views for the tag
-            buildTagViews(msgs);
         } else if (Intent.ACTION_VIEW.equals(action)) {
             // Setup the views
             setTitle(R.string.title_existing_tag);
@@ -174,21 +202,19 @@
         ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msgs[0]);
 
         // Build views for all of the sub records
-        for (ParsedNdefRecord record : parsedMsg.getRecords()) {
-            content.addView(record.getView(this, inflater, content));
+        List<ParsedNdefRecord> records = parsedMsg.getRecords();
+        final int size = records.size();
+
+        for (int i = 0 ; i < size ; i++) {
+            ParsedNdefRecord record = records.get(i);
+            content.addView(record.getView(this, inflater, content, i));
             inflater.inflate(R.layout.tag_divider, content, true);
         }
     }
 
     @Override
     public void onNewIntent(Intent intent) {
-        // If we get a new scan while looking at a tag just save off the old tag...
-        if (mTag != null) {
-            TagService.saveTag(this, mTag, mStar.isChecked());
-            mTag = null;
-        }
-
-        // ... and show the new one.
+        setIntent(intent);
         resolveIntent(intent);
     }
 
@@ -218,15 +244,6 @@
         }
     }
 
-    @Override
-    public void onStop() {
-        super.onStop();
-        if (mTag != null) {
-            TagService.saveTag(this, mTag, mStar.isChecked());
-            mTag = null;
-        }
-    }
-
     interface ViewTagQuery {
         final static String[] PROJECTION = new String[] {
                 NdefMessages.BYTES, // 0
diff --git a/src/com/android/apps/tag/message/NdefMessageParser.java b/src/com/android/apps/tag/message/NdefMessageParser.java
index a1012b1..e80d0e5 100644
--- a/src/com/android/apps/tag/message/NdefMessageParser.java
+++ b/src/com/android/apps/tag/message/NdefMessageParser.java
@@ -22,6 +22,7 @@
 import com.android.apps.tag.record.SmartPoster;
 import com.android.apps.tag.record.TextRecord;
 import com.android.apps.tag.record.UriRecord;
+import com.android.apps.tag.record.VCardRecord;
 
 import android.nfc.NdefMessage;
 import android.nfc.NdefRecord;
@@ -77,6 +78,8 @@
                 elements.add(SmartPoster.parse(record));
             } else if (ImageRecord.isImage(record)) {
                 elements.add(ImageRecord.parse(record));
+            } else if (VCardRecord.isVCard(record)) {
+                elements.add(VCardRecord.parse(record));
             } else if (MimeRecord.isMime(record)) {
                 elements.add(MimeRecord.parse(record));
             }
diff --git a/src/com/android/apps/tag/provider/TagContract.java b/src/com/android/apps/tag/provider/TagContract.java
index 81191b2..acc487e 100644
--- a/src/com/android/apps/tag/provider/TagContract.java
+++ b/src/com/android/apps/tag/provider/TagContract.java
@@ -18,20 +18,13 @@
 
 import com.android.apps.tag.message.NdefMessageParser;
 import com.android.apps.tag.message.ParsedNdefMessage;
-import com.google.common.collect.Lists;
 
-import android.content.ContentProviderOperation;
 import android.content.ContentValues;
 import android.content.Context;
 import android.net.Uri;
-import android.nfc.FormatException;
 import android.nfc.NdefMessage;
-import android.nfc.NdefRecord;
-import android.nfc.NdefTag;
 import android.provider.OpenableColumns;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Locale;
 
 public class TagContract {
@@ -64,182 +57,29 @@
 
         // columns
         public static final String _ID = "_id";
-        public static final String TAG_ID = "tag_id";
         public static final String TITLE = "title";
         public static final String BYTES = "bytes";
         public static final String DATE = "date";
         public static final String STARRED = "starred";
-        public static final String ORDINAL = "ordinal";
+
+
+        public static class MIME implements OpenableColumns {
+            public static final String CONTENT_DIRECTORY_MIME = "mime";
+            public static final String _ID = "_id";
+        }
+
 
         /**
          * Converts an NdefMessage to ContentValues that can be insrted into this table.
          */
-        static ContentValues toValues(Context context, NdefMessage msg,
-                boolean isStarred, long date, int ordinal) {
+        public static ContentValues toValues(Context context, NdefMessage msg, boolean isStarred, long date) {
             ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
             ContentValues values = new ContentValues();
             values.put(BYTES, msg.toByteArray());
             values.put(DATE, date);
             values.put(STARRED, isStarred ? 1 : 0);
             values.put(TITLE, parsedMsg.getSnippet(context, Locale.getDefault()));
-            values.put(ORDINAL, ordinal);
             return values;
         }
     }
-
-    public static final class NdefRecords {
-        /**
-         * Utility class, cannot be instantiated.
-         */
-        private NdefRecords() {}
-
-        public static final Uri CONTENT_URI =
-                AUTHORITY_URI.buildUpon().appendPath("ndef_records").build();
-
-        /**
-         * The MIME type of {@link #CONTENT_URI} providing a directory of
-         * NDEF messages.
-         */
-        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/ndef_record";
-
-        /**
-         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
-         * NDEF message.
-         */
-        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/ndef_record";
-
-        // columns
-        public static final String _ID = "_id";
-        public static final String MESSAGE_ID = "msg_id";
-        public static final String TNF = "tnf";
-        public static final String TYPE = "type";
-        public static final String BYTES = "bytes";
-        public static final String ORDINAL = "ordinal";
-        public static final String TAG_ID = "tag_id";
-        public static final String POSTER_ID = "poster_id";
-
-        static ContentValues toValues(Context context, NdefRecord record, int ordinal) {
-            ContentValues values = new ContentValues();
-            values.put(TNF, record.getTnf());
-            values.put(TYPE, record.getTnf());
-            values.put(BYTES, record.getPayload());
-            values.put(ORDINAL, ordinal);
-            values.put(ORDINAL, ordinal);
-            return values;
-        }
-
-        static final class MIME implements OpenableColumns {
-            /**
-             * A sub directory of a single entry in this table that treats the entry as raw
-             * MIME data.
-             */
-            public static final String CONTENT_DIRECTORY_MIME = "mime";
-
-            public static final String _ID = "_id";
-        }
-    }
-
-    public static final class NdefTags {
-        /**
-         * Utility class, cannot be instantiated.
-         */
-        private NdefTags() {}
-
-        public static final Uri CONTENT_URI =
-                AUTHORITY_URI.buildUpon().appendPath("ndef_tags").build();
-
-        /**
-         * The MIME type of {@link #CONTENT_URI} providing a directory of
-         * NDEF messages.
-         */
-        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/ndef_tag";
-
-        /**
-         * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
-         * NDEF message.
-         */
-        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/ndef_tag";
-
-        public static final String _ID = "_id";
-        public static final String DATE = "date";
-
-        public static ArrayList<ContentProviderOperation> toContentProviderOperations(
-                Context context, NdefTag tag, boolean starred) {
-            ArrayList<ContentProviderOperation> ops = Lists.newArrayList();
-            long now = System.currentTimeMillis();
-
-            // Create the tag entry
-            ContentProviderOperation op = ContentProviderOperation.newInsert(CONTENT_URI)
-                    .withValue(DATE, now)
-                    .build();
-
-            ops.add(op);
-
-            // Create the message entries
-            NdefMessage[] msgs = tag.getNdefMessages();
-
-            int msgOrdinal = 0;
-            int recordOrdinal = 0;
-
-            for (NdefMessage msg : msgs) {
-                int messageOffset = ops.size();
-                ContentValues values = NdefMessages.toValues(context, msg, false, now, msgOrdinal);
-                op = ContentProviderOperation.newInsert(NdefMessages.CONTENT_URI)
-                        .withValues(values)
-                        .withValue(NdefMessages.STARRED, starred ? 1 : 0)
-                        .withValueBackReference(NdefMessages.TAG_ID, 0)
-                        .build();
-                ops.add(op);
-
-                for (NdefRecord record : msg.getRecords()) {
-                    values = NdefRecords.toValues(context, record, recordOrdinal);
-
-                    op = ContentProviderOperation.newInsert(NdefRecords.CONTENT_URI)
-                            .withValues(values)
-                            .withValueBackReference(NdefRecords.MESSAGE_ID, messageOffset)
-                            .withValueBackReference(NdefRecords.TAG_ID, 0)
-                            .build();
-
-                    ops.add(op);
-
-                    recordOrdinal++;
-
-                    if (record.getTnf() == NdefRecord.TNF_WELL_KNOWN &&
-                            Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER)) {
-
-                        // This is a poster. Store all of its records as well.
-                        int posterOffset = ops.size() - 1;
-
-                        try {
-                            NdefMessage subRecords = new NdefMessage(record.getPayload());
-
-                            for (NdefRecord subRecord : subRecords.getRecords()) {
-                                values = NdefRecords.toValues(context, subRecord, recordOrdinal);
-
-                                op = ContentProviderOperation.newInsert(NdefRecords.CONTENT_URI)
-                                        .withValues(values)
-                                        .withValueBackReference(NdefRecords.POSTER_ID,
-                                                posterOffset)
-                                        .withValueBackReference(NdefRecords.MESSAGE_ID,
-                                                messageOffset)
-                                        .withValueBackReference(NdefRecords.TAG_ID, 0)
-                                        .build();
-
-                                ops.add(op);
-
-                                recordOrdinal++;
-                            }
-
-                        } catch (FormatException e) {
-                            // ignore
-                        }
-                    }
-                }
-
-                msgOrdinal++;
-            }
-
-           return ops;
-        }
-    }
 }
diff --git a/src/com/android/apps/tag/provider/TagDBHelper.java b/src/com/android/apps/tag/provider/TagDBHelper.java
index 72b5fd6..26cd2cb 100644
--- a/src/com/android/apps/tag/provider/TagDBHelper.java
+++ b/src/com/android/apps/tag/provider/TagDBHelper.java
@@ -17,8 +17,6 @@
 package com.android.apps.tag.provider;
 
 import com.android.apps.tag.provider.TagContract.NdefMessages;
-import com.android.apps.tag.provider.TagContract.NdefRecords;
-import com.android.apps.tag.provider.TagContract.NdefTags;
 import com.google.common.annotations.VisibleForTesting;
 
 import android.content.Context;
@@ -31,11 +29,9 @@
 public class TagDBHelper extends SQLiteOpenHelper {
 
     private static final String DATABASE_NAME = "tags.db";
-    private static final int DATABASE_VERSION = 11;
+    private static final int DATABASE_VERSION = 12;
 
     public static final String TABLE_NAME_NDEF_MESSAGES = "ndef_msgs";
-    public static final String TABLE_NAME_NDEF_RECORDS = "ndef_records";
-    public static final String TABLE_NAME_NDEF_TAGS = "ndef_tags";
 
     TagDBHelper(Context context) {
         this(context, DATABASE_NAME);
@@ -51,53 +47,17 @@
 
         db.execSQL("CREATE TABLE " + TABLE_NAME_NDEF_MESSAGES + " (" +
                 NdefMessages._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
-                NdefMessages.TAG_ID + " INTEGER NOT NULL, " +
                 NdefMessages.DATE + " INTEGER NOT NULL, " +
                 NdefMessages.TITLE + " TEXT NOT NULL DEFAULT ''," +
                 NdefMessages.BYTES + " BLOB NOT NULL, " +
-                NdefMessages.STARRED + " INTEGER NOT NULL DEFAULT 0," +  // boolean
-                NdefMessages.ORDINAL + " INTEGER NOT NULL " +
+                NdefMessages.STARRED + " INTEGER NOT NULL DEFAULT 0" +  // boolean
                 ");");
-
-        db.execSQL("CREATE INDEX mags_tag_id_index ON " + TABLE_NAME_NDEF_MESSAGES + " (" +
-                NdefMessages.TAG_ID +
-                ")");
-
-        db.execSQL("CREATE TABLE " + TABLE_NAME_NDEF_RECORDS + " (" +
-                NdefRecords._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
-                NdefRecords.TAG_ID + " INTEGER NOT NULL, " +
-                NdefRecords.MESSAGE_ID + " INTEGER NOT NULL, " +
-                NdefRecords.TNF + " INTEGER NOT NULL, " +
-                NdefRecords.TYPE + " BLOB NOT NULL, " +
-                NdefRecords.POSTER_ID + " INTEGER, " +
-                NdefRecords.BYTES + " BLOB NOT NULL, " +
-                NdefRecords.ORDINAL + " INTEGER NOT NULL " +
-                ");");
-
-        db.execSQL("CREATE INDEX records_msg_id_index ON " + TABLE_NAME_NDEF_RECORDS + " (" +
-                NdefRecords.MESSAGE_ID +
-                ")");
-
-        db.execSQL("CREATE INDEX records_poster_id_index ON " + TABLE_NAME_NDEF_RECORDS + " (" +
-                NdefRecords.POSTER_ID +
-                ")");
-
-        db.execSQL("CREATE INDEX records_tag_id_index ON " + TABLE_NAME_NDEF_RECORDS + " (" +
-                NdefRecords.TAG_ID +
-                ")");
-
-        db.execSQL("CREATE TABLE " + TABLE_NAME_NDEF_TAGS + " (" +
-                NdefTags._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
-                NdefTags.DATE + " INTEGER NOT NULL " +
-                ")");
     }
 
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
         // Drop everything and recreate it for now
         db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME_NDEF_MESSAGES);
-        db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME_NDEF_RECORDS);
-        db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME_NDEF_TAGS);
         onCreate(db);
     }
 }
diff --git a/src/com/android/apps/tag/provider/TagProvider.java b/src/com/android/apps/tag/provider/TagProvider.java
index 07c7139..b8bdb9e 100644
--- a/src/com/android/apps/tag/provider/TagProvider.java
+++ b/src/com/android/apps/tag/provider/TagProvider.java
@@ -18,12 +18,9 @@
 
 import com.android.apps.tag.R;
 import com.android.apps.tag.provider.TagContract.NdefMessages;
-import com.android.apps.tag.provider.TagContract.NdefRecords;
-import com.android.apps.tag.provider.TagContract.NdefTags;
 import com.google.common.base.Charsets;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
 
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -34,6 +31,9 @@
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
+import android.nfc.FormatException;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
 import android.os.AsyncTask;
 import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
@@ -55,40 +55,21 @@
 
     private static final int NDEF_RECORDS = 2000;
     private static final int NDEF_RECORDS_ID = 2001;
-    private static final int NDEF_RECORDS_ID_MIME = 2002;
+    private static final int NDEF_MESSAGES_ID_MIME = 2002;
 
     private static final int NDEF_TAGS = 3000;
     private static final int NDEF_TAGS_ID = 3001;
 
     private static final UriMatcher MATCHER;
 
-    private static final Map<String, String> NDEF_TAGS_PROJECTION_MAP =
-            ImmutableMap.<String, String>builder()
-                .put(NdefTags._ID, NdefTags._ID)
-                .put(NdefTags.DATE, NdefTags.DATE)
-                .build();
 
     private static final Map<String, String> NDEF_MESSAGES_PROJECTION_MAP =
             ImmutableMap.<String, String>builder()
                 .put(NdefMessages._ID, NdefMessages._ID)
-                .put(NdefMessages.TAG_ID, NdefMessages.TAG_ID)
                 .put(NdefMessages.TITLE, NdefMessages.TITLE)
                 .put(NdefMessages.BYTES, NdefMessages.BYTES)
                 .put(NdefMessages.DATE, NdefMessages.DATE)
                 .put(NdefMessages.STARRED, NdefMessages.STARRED)
-                .put(NdefMessages.ORDINAL, NdefMessages.ORDINAL)
-                .build();
-
-    private static final Map<String, String> NDEF_RECORDS_PROJECTION_MAP =
-            ImmutableMap.<String, String>builder()
-                .put(NdefRecords._ID, NdefRecords._ID)
-                .put(NdefRecords.MESSAGE_ID, NdefRecords.MESSAGE_ID)
-                .put(NdefRecords.TNF, NdefRecords.TNF)
-                .put(NdefRecords.TYPE, NdefRecords.TYPE)
-                .put(NdefRecords.BYTES, NdefRecords.BYTES)
-                .put(NdefRecords.ORDINAL, NdefRecords.ORDINAL)
-                .put(NdefRecords.TAG_ID, NdefRecords.TAG_ID)
-                .put(NdefRecords.POSTER_ID, NdefRecords.POSTER_ID)
                 .build();
 
     private Map<String, String> mNdefRecordsMimeProjectionMap;
@@ -99,13 +80,8 @@
 
         MATCHER.addURI(auth, "ndef_msgs", NDEF_MESSAGES);
         MATCHER.addURI(auth, "ndef_msgs/#", NDEF_MESSAGES_ID);
+        MATCHER.addURI(auth, "ndef_msgs/#/#/mime", NDEF_MESSAGES_ID_MIME);
 
-        MATCHER.addURI(auth, "ndef_records", NDEF_RECORDS);
-        MATCHER.addURI(auth, "ndef_records/#", NDEF_RECORDS_ID);
-        MATCHER.addURI(auth, "ndef_records/#/mime", NDEF_RECORDS_ID_MIME);
-
-        MATCHER.addURI(auth, "ndef_tags", NDEF_TAGS);
-        MATCHER.addURI(auth, "ndef_tags/#", NDEF_TAGS_ID);
     }
 
     @Override
@@ -114,12 +90,11 @@
 
         // Build the projection map for the MIME records using a localized display name
         mNdefRecordsMimeProjectionMap = ImmutableMap.<String, String>builder()
-                .put(NdefRecords.MIME._ID, NdefRecords.MIME._ID)
-                .put(NdefRecords.MIME.SIZE,
-                        "LEN(" + NdefRecords.BYTES + ") AS " + NdefRecords.MIME.SIZE)
-                .put(NdefRecords.MIME.DISPLAY_NAME,
+                .put(NdefMessages.MIME._ID, NdefMessages.MIME._ID)
+                .put(NdefMessages.MIME.SIZE, NdefMessages.MIME.SIZE)
+                .put(NdefMessages.MIME.DISPLAY_NAME,
                         "'" + getContext().getString(R.string.mime_display_name) + "' AS "
-                        + NdefRecords.MIME.DISPLAY_NAME)
+                        + NdefMessages.MIME.DISPLAY_NAME)
                 .build();
 
         return result;
@@ -165,19 +140,6 @@
         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
         int match = MATCHER.match(uri);
         switch (match) {
-            case NDEF_TAGS_ID: {
-                selection = concatenateWhere(selection,
-                        TagDBHelper.TABLE_NAME_NDEF_TAGS + "._id=?");
-                selectionArgs = appendSelectionArgs(selectionArgs,
-                        new String[] { Long.toString(ContentUris.parseId(uri)) });
-                // fall through
-            }
-            case NDEF_TAGS: {
-                qb.setTables(TagDBHelper.TABLE_NAME_NDEF_TAGS);
-                qb.setProjectionMap(NDEF_TAGS_PROJECTION_MAP);
-                break;
-            }
-
             case NDEF_MESSAGES_ID: {
                 selection = concatenateWhere(selection,
                         TagDBHelper.TABLE_NAME_NDEF_MESSAGES + "._id=?");
@@ -191,25 +153,12 @@
                 break;
             }
 
-            case NDEF_RECORDS_ID: {
+            case NDEF_MESSAGES_ID_MIME: {
                 selection = concatenateWhere(selection,
-                        TagDBHelper.TABLE_NAME_NDEF_RECORDS + "._id=?");
+                        TagDBHelper.TABLE_NAME_NDEF_MESSAGES + "._id=?");
                 selectionArgs = appendSelectionArgs(selectionArgs,
                         new String[] { Long.toString(ContentUris.parseId(uri)) });
-                // fall through
-            }
-            case NDEF_RECORDS: {
-                qb.setTables(TagDBHelper.TABLE_NAME_NDEF_RECORDS);
-                qb.setProjectionMap(NDEF_RECORDS_PROJECTION_MAP);
-                break;
-            }
-
-            case NDEF_RECORDS_ID_MIME: {
-                selection = concatenateWhere(selection,
-                        TagDBHelper.TABLE_NAME_NDEF_RECORDS + "._id=?");
-                selectionArgs = appendSelectionArgs(selectionArgs,
-                        new String[] { Long.toString(ContentUris.parseId(uri)) });
-                qb.setTables(TagDBHelper.TABLE_NAME_NDEF_RECORDS);
+                qb.setTables(TagDBHelper.TABLE_NAME_NDEF_MESSAGES);
                 qb.setProjectionMap(mNdefRecordsMimeProjectionMap);
                 break;
             }
@@ -237,16 +186,6 @@
                 break;
             }
 
-            case NDEF_RECORDS: {
-                id = db.insert(TagDBHelper.TABLE_NAME_NDEF_RECORDS, "", values);
-                break;
-            }
-
-            case NDEF_TAGS: {
-                id = db.insert(TagDBHelper.TABLE_NAME_NDEF_TAGS, "", values);
-                break;
-            }
-
             default: {
                 throw new IllegalArgumentException("unkown uri " + uri);
             }
@@ -312,16 +251,39 @@
         return count;
     }
 
+    private NdefRecord getRecord(Uri uri) {
+        NdefRecord record = null;
+
+        Cursor cursor = null;
+        try {
+            SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
+            cursor = db.query(TagDBHelper.TABLE_NAME_NDEF_MESSAGES,
+                    new String[] { NdefMessages.BYTES }, "_id=?",
+                    new String[] { uri.getPathSegments().get(1) }, null, null, null, null);
+            if (cursor.moveToFirst()) {
+                NdefMessage msg = new NdefMessage(cursor.getBlob(0));
+                NdefRecord[] records = msg.getRecords();
+
+                int offset = Integer.parseInt(uri.getPathSegments().get(2));
+
+                if (records != null && offset < records.length) {
+                    record = records[offset];
+                }
+            }
+        } catch (FormatException e) {
+            Log.e(TAG, "Invalid NdefMessage format", e);
+        } finally {
+            if (cursor != null) cursor.close();
+        }
+
+        return record;
+    }
+
+
     @Override
     public String getType(Uri uri) {
         int match = MATCHER.match(uri);
         switch (match) {
-            case NDEF_TAGS_ID: {
-                return NdefTags.CONTENT_ITEM_TYPE;
-            }
-            case NDEF_TAGS: {
-                return NdefTags.CONTENT_TYPE;
-            }
 
             case NDEF_MESSAGES_ID: {
                 return NdefMessages.CONTENT_ITEM_TYPE;
@@ -330,25 +292,10 @@
                 return NdefMessages.CONTENT_TYPE;
             }
 
-            case NDEF_RECORDS_ID: {
-                return NdefRecords.CONTENT_ITEM_TYPE;
-            }
-            case NDEF_RECORDS: {
-                return NdefRecords.CONTENT_TYPE;
-            }
-
-            case NDEF_RECORDS_ID_MIME: {
-                Cursor cursor = null;
-                try {
-                    SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
-                    cursor = db.query(TagDBHelper.TABLE_NAME_NDEF_RECORDS,
-                            new String[] { NdefRecords.TYPE }, "_id=?",
-                            new String[] { uri.getPathSegments().get(1) }, null, null, null, null);
-                    if (cursor.moveToFirst()) {
-                        return new String(cursor.getBlob(0), Charsets.US_ASCII);
-                    }
-                } finally {
-                    if (cursor != null) cursor.close();
+            case NDEF_MESSAGES_ID_MIME: {
+                NdefRecord record = getRecord(uri);
+                if (record != null) {
+                    return new String(record.getType(), Charsets.US_ASCII).toLowerCase();
                 }
                 return null;
             }
@@ -366,23 +313,16 @@
 
     @Override
     public void writeMimeDataToPipe(ParcelFileDescriptor output, Uri uri) {
-        Cursor cursor = null;
+        NdefRecord record = getRecord(uri);
+        if (record == null) return;
+
         try {
-            SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
-            cursor = db.query(TagDBHelper.TABLE_NAME_NDEF_RECORDS,
-                    new String[] { NdefRecords.BYTES }, "_id=?",
-                    new String[] { uri.getPathSegments().get(1) }, null, null, null, null);
-            if (cursor.moveToFirst()) {
-                byte[] data = cursor.getBlob(0);
-                FileOutputStream os = new FileOutputStream(output.getFileDescriptor());
-                os.write(data);
-                os.flush();
-                // openMimePipe() will close output for us, don't close it here.
-            }
+            byte[] data = record.getPayload();
+            FileOutputStream os = new FileOutputStream(output.getFileDescriptor());
+            os.write(data);
+            os.flush();
         } catch (IOException e) {
             Log.e(TAG, "failed to write MIME data to " + uri, e);
-        } finally {
-            if (cursor != null) cursor.close();
         }
     }
 
@@ -428,7 +368,7 @@
     @Override
     public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
         Preconditions.checkArgument("r".equals(mode));
-        Preconditions.checkArgument(MATCHER.match(uri) == NDEF_RECORDS_ID_MIME);
+        Preconditions.checkArgument(MATCHER.match(uri) == NDEF_MESSAGES_ID_MIME);
         return openMimePipe(uri, this);
     }
 }
diff --git a/src/com/android/apps/tag/record/ImageRecord.java b/src/com/android/apps/tag/record/ImageRecord.java
index a012ea1..f15f9b5 100644
--- a/src/com/android/apps/tag/record/ImageRecord.java
+++ b/src/com/android/apps/tag/record/ImageRecord.java
@@ -47,7 +47,7 @@
 /**
  * A NdefRecord corresponding to an image type.
  */
-public class ImageRecord implements ParsedNdefRecord {
+public class ImageRecord extends ParsedNdefRecord {
 
     public static final String RECORD_TYPE = "ImageRecord";
 
@@ -58,7 +58,7 @@
     }
 
     @Override
-    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
+    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
         ImageView image = (ImageView) inflater.inflate(R.layout.tag_image, parent, false);
         image.setImageBitmap(mBitmap);
         return image;
@@ -216,10 +216,12 @@
         @SuppressWarnings("unused")
         public static final Parcelable.Creator<ImageRecordEditInfo> CREATOR =
                 new Parcelable.Creator<ImageRecordEditInfo>() {
+            @Override
             public ImageRecordEditInfo createFromParcel(Parcel in) {
                 return new ImageRecordEditInfo(in);
             }
 
+            @Override
             public ImageRecordEditInfo[] newArray(int size) {
                 return new ImageRecordEditInfo[size];
             }
diff --git a/src/com/android/apps/tag/record/MimeRecord.java b/src/com/android/apps/tag/record/MimeRecord.java
index ed7250e..5f53b76 100644
--- a/src/com/android/apps/tag/record/MimeRecord.java
+++ b/src/com/android/apps/tag/record/MimeRecord.java
@@ -33,7 +33,7 @@
 /**
  * A {@link ParsedNdefRecord} corresponding to a MIME object.
  */
-public class MimeRecord implements ParsedNdefRecord {
+public class MimeRecord extends ParsedNdefRecord {
     private final String mType;
     private final byte[] mContent;
 
@@ -54,7 +54,7 @@
     }
 
     @Override
-    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
+    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
         TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false);
         text.setText(mType);
         return text;
diff --git a/src/com/android/apps/tag/record/ParsedNdefRecord.java b/src/com/android/apps/tag/record/ParsedNdefRecord.java
index 60a839e..14c9bcc 100644
--- a/src/com/android/apps/tag/record/ParsedNdefRecord.java
+++ b/src/com/android/apps/tag/record/ParsedNdefRecord.java
@@ -24,10 +24,11 @@
 /**
  * TODO: come up with a better name.
  */
-public interface ParsedNdefRecord {
+public abstract class ParsedNdefRecord {
 
     /**
      * Returns a view to display this record.
      */
-    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent);
+    public abstract View getView(Activity activity, LayoutInflater inflater, ViewGroup parent,
+            int offset);
 }
diff --git a/src/com/android/apps/tag/record/RecordUtils.java b/src/com/android/apps/tag/record/RecordUtils.java
index 5cc23d5..496e839 100644
--- a/src/com/android/apps/tag/record/RecordUtils.java
+++ b/src/com/android/apps/tag/record/RecordUtils.java
@@ -17,6 +17,7 @@
 package com.android.apps.tag.record;
 
 import com.android.apps.tag.R;
+import com.google.common.collect.Lists;
 
 import android.app.Activity;
 import android.content.ComponentName;
@@ -24,6 +25,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -33,6 +35,8 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -60,9 +64,10 @@
             ViewGroup parent, OnClickListener listener, Intent intent, String description) {
         // Lookup which packages can handle this intent.
         PackageManager pm = activity.getPackageManager();
-        List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
+        int flags = PackageManager.GET_RESOLVED_FILTER | PackageManager.MATCH_DEFAULT_ONLY;
+        List<ResolveInfo> activities = pm.queryIntentActivities(intent, flags);
         int numActivities = activities.size();
-        if (numActivities == 0) {
+        if (numActivities == 0 || (numActivities == 1 && !activities.get(0).activityInfo.enabled)) {
             TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false);
             text.setText(description);
             return text;
@@ -76,13 +81,26 @@
             container.setLayoutParams(new LayoutParams(
                     LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
 
+            Collections.sort(activities, new Comparator<ResolveInfo>() {
+                @Override
+                public int compare(ResolveInfo l, ResolveInfo r) {
+                    return l.preferredOrder - r.preferredOrder;
+                }
+            });
+
             // Create an entry for each activity that can handle the URI
             for (ResolveInfo resolveInfo : activities) {
+                if (!resolveInfo.activityInfo.enabled) {
+                    continue;
+                }
+
                 if (container.getChildCount() > 0) {
                     inflater.inflate(R.layout.tag_divider, container);
                 }
+                // Clone the intent for each view so they can each have their own components setup
+                Intent clone = new Intent(intent);
                 container.addView(buildActivityView(activity, resolveInfo, pm, inflater, container,
-                        listener, intent, description));
+                        listener, clone, description));
             }
             return container;
         }
@@ -95,6 +113,8 @@
             LayoutInflater inflater, ViewGroup parent, OnClickListener listener, Intent intent,
             String defaultText) {
         ActivityInfo activityInfo = resolveInfo.activityInfo;
+
+        intent.setAction(resolveInfo.filter.getAction(0));
         intent.setComponent(new ComponentName(activityInfo.packageName, activityInfo.name));
 
         View item = inflater.inflate(R.layout.tag_uri, parent, false);
diff --git a/src/com/android/apps/tag/record/SmartPoster.java b/src/com/android/apps/tag/record/SmartPoster.java
index dda86af..d3bfb30 100644
--- a/src/com/android/apps/tag/record/SmartPoster.java
+++ b/src/com/android/apps/tag/record/SmartPoster.java
@@ -41,7 +41,7 @@
 /**
  * A representation of an NFC Forum "Smart Poster".
  */
-public class SmartPoster implements ParsedNdefRecord {
+public class SmartPoster extends ParsedNdefRecord {
 
     /**
      * NFC Forum Smart Poster Record Type Definition section 3.2.1.
@@ -155,7 +155,7 @@
     }
 
     @Override
-    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
+    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
         if (mTitleRecord != null) {
             // Build a container to hold the title and the URI
             LinearLayout container = new LinearLayout(activity);
@@ -163,13 +163,13 @@
             container.setLayoutParams(new LayoutParams(
                     LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
 
-            container.addView(mTitleRecord.getView(activity, inflater, container));
+            container.addView(mTitleRecord.getView(activity, inflater, container, offset));
             inflater.inflate(R.layout.tag_divider, container);
-            container.addView(mUriRecord.getView(activity, inflater, container));
+            container.addView(mUriRecord.getView(activity, inflater, container, offset));
             return container;
         } else {
             // Just a URI, return a view for it directly
-            return mUriRecord.getView(activity, inflater, parent);
+            return mUriRecord.getView(activity, inflater, parent, offset);
         }
     }
 
diff --git a/src/com/android/apps/tag/record/TextRecord.java b/src/com/android/apps/tag/record/TextRecord.java
index d504095..dceb334 100644
--- a/src/com/android/apps/tag/record/TextRecord.java
+++ b/src/com/android/apps/tag/record/TextRecord.java
@@ -36,7 +36,7 @@
 /**
  * An NFC Text Record
  */
-public class TextRecord implements ParsedNdefRecord {
+public class TextRecord extends ParsedNdefRecord {
 
     /** ISO/IANA language code */
     private final String mLanguageCode;
@@ -48,7 +48,7 @@
     }
 
     @Override
-    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
+    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
         TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false);
         text.setText(mText);
         return text;
diff --git a/src/com/android/apps/tag/record/UriRecord.java b/src/com/android/apps/tag/record/UriRecord.java
index 3777771..590c275 100644
--- a/src/com/android/apps/tag/record/UriRecord.java
+++ b/src/com/android/apps/tag/record/UriRecord.java
@@ -52,7 +52,7 @@
 /**
  * A parsed record containing a Uri.
  */
-public class UriRecord implements ParsedNdefRecord, OnClickListener {
+public class UriRecord extends ParsedNdefRecord implements OnClickListener {
     private static final String TAG = "UriRecord";
 
     public static final String RECORD_TYPE = "UriRecord";
@@ -140,7 +140,7 @@
     }
 
     @Override
-    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
+    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
         return RecordUtils.getViewsForIntent(activity, inflater, parent, this, getIntentForUri(),
                 getPrettyUriString(activity));
     }
@@ -205,7 +205,8 @@
     /** Parse and absolute URI record */
     private static UriRecord parseAbsolute(NdefRecord record) {
         byte[] payload = record.getPayload();
-        return new UriRecord(Uri.parse(new String(payload, Charset.forName("UTF-8"))));
+        Uri uri = Uri.parse(new String(payload, Charset.forName("UTF-8")));
+        return new UriRecord(uri);
     }
 
     /** Parse an well known URI record */
@@ -226,7 +227,9 @@
         byte[] fullUri = Bytes.concat(
                 prefix.getBytes(Charset.forName("UTF-8")),
                 Arrays.copyOfRange(payload, 1, payload.length));
-        return new UriRecord(Uri.parse(new String(fullUri, Charset.forName("UTF-8"))));
+
+        Uri uri = Uri.parse(new String(fullUri, Charset.forName("UTF-8")));
+        return new UriRecord(uri);
     }
 
     public static boolean isUri(NdefRecord record) {
diff --git a/src/com/android/apps/tag/record/VCardRecord.java b/src/com/android/apps/tag/record/VCardRecord.java
index 71d94a9..9cbc133 100644
--- a/src/com/android/apps/tag/record/VCardRecord.java
+++ b/src/com/android/apps/tag/record/VCardRecord.java
@@ -17,10 +17,10 @@
 package com.android.apps.tag.record;
 
 import com.android.apps.tag.R;
-import com.android.apps.tag.record.RecordUtils.ClickInfo;
 import com.google.common.base.Preconditions;
 
 import android.app.Activity;
+import android.content.ActivityNotFoundException;
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
@@ -35,6 +35,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.ContactsContract;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -52,21 +53,25 @@
 /**
  * VCard Ndef Record object
  */
-public class VCardRecord implements ParsedNdefRecord, OnClickListener {
+public class VCardRecord extends ParsedNdefRecord implements OnClickListener {
+    private static final String TAG = VCardRecord.class.getSimpleName();
 
     public static final String RECORD_TYPE = "vcard";
 
     private final byte[] mVCard;
 
-    private VCardRecord(byte[] content) {
+    private VCardRecord( byte[] content) {
         mVCard = content;
     }
 
     @Override
-    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
-        // TODO hookup a way to read the VCARD data from the content provider.
-        Intent intent = new Intent();
-//        intent.setType("text/x-vcard");
+    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
+
+        Uri uri = activity.getIntent().getData();
+        uri = Uri.withAppendedPath(uri, Integer.toString(offset));
+        uri = Uri.withAppendedPath(uri, "mime");
+
+        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
         return RecordUtils.getViewsForIntent(activity, inflater, parent, this, intent,
                 activity.getString(R.string.import_vcard));
     }
@@ -109,9 +114,14 @@
 
     @Override
     public void onClick(View view) {
-        ClickInfo info = (ClickInfo) view.getTag();
-        info.activity.startActivity(info.intent);
-        info.activity.finish();
+        RecordUtils.ClickInfo info = (RecordUtils.ClickInfo) view.getTag();
+        try {
+            info.activity.startActivity(info.intent);
+            info.activity.finish();
+        } catch (ActivityNotFoundException e) {
+            // The activity wansn't found for some reason. Don't crash, but don't do anything.
+            Log.e(TAG, "Failed to launch activity for intent " + info.intent, e);
+        }
     }
 
     public static boolean isVCard(NdefRecord record) {
@@ -295,10 +305,12 @@
         @SuppressWarnings("unused")
         public static final Parcelable.Creator<VCardRecordEditInfo> CREATOR =
                 new Parcelable.Creator<VCardRecordEditInfo>() {
+            @Override
             public VCardRecordEditInfo createFromParcel(Parcel in) {
                 return new VCardRecordEditInfo(in);
             }
 
+            @Override
             public VCardRecordEditInfo[] newArray(int size) {
                 return new VCardRecordEditInfo[size];
             }
diff --git a/tests/src/com/android/apps/tag/provider/ProviderTests.java b/tests/src/com/android/apps/tag/provider/ProviderTests.java
deleted file mode 100644
index 15063e2..0000000
--- a/tests/src/com/android/apps/tag/provider/ProviderTests.java
+++ /dev/null
@@ -1,362 +0,0 @@
-package com.android.apps.tag.provider;
-
-import com.android.apps.tag.provider.TagContract.NdefMessages;
-import com.android.apps.tag.provider.TagContract.NdefRecords;
-import com.android.apps.tag.provider.TagContract.NdefTags;
-import com.android.apps.tag.record.TextRecord;
-import com.google.common.collect.Lists;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.OperationApplicationException;
-import android.content.UriMatcher;
-import android.database.AbstractCursor;
-import android.database.Cursor;
-import android.net.Uri;
-import android.nfc.NdefMessage;
-import android.nfc.NdefRecord;
-import android.nfc.NdefTag;
-import android.nfc.Tag;
-import android.test.AndroidTestCase;
-import android.test.mock.MockContentProvider;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.Map.Entry;
-import java.util.Set;
-
-public class ProviderTests extends AndroidTestCase {
-
-    public static class ContentValuesCursor extends AbstractCursor {
-
-        private String[] mColumnNames;
-        private ArrayList<ContentValues> mValues;
-
-        public ContentValuesCursor(String[] columnNames, ArrayList<ContentValues> values) {
-            mColumnNames = columnNames;
-            mValues = values;
-        }
-
-        @Override
-        public int getCount() {
-            return mValues.size();
-        }
-
-        @Override
-        public String[] getColumnNames() {
-            return mColumnNames;
-        }
-
-        @Override
-        public String getString(int column) {
-            return mValues.get(mPos).getAsString(mColumnNames[column]);
-        }
-
-        @Override
-        public short getShort(int column) {
-            return mValues.get(mPos).getAsShort(mColumnNames[column]);
-        }
-
-        @Override
-        public int getInt(int column) {
-            return mValues.get(mPos).getAsInteger(mColumnNames[column]);
-        }
-
-        @Override
-        public long getLong(int column) {
-            return mValues.get(mPos).getAsLong(mColumnNames[column]);
-        }
-
-        @Override
-        public float getFloat(int column) {
-            return mValues.get(mPos).getAsFloat(mColumnNames[column]);
-        }
-
-        @Override
-        public double getDouble(int column) {
-            return mValues.get(mPos).getAsDouble(mColumnNames[column]);
-        }
-
-        @Override
-        public boolean isNull(int column) {
-            return mValues.get(mPos).containsKey(mColumnNames[column]) == false;
-        }
-
-        @Override
-        public byte[] getBlob(int column) {
-            return mValues.get(mPos).getAsByteArray(mColumnNames[column]);
-        }
-    }
-
-    public static class TestProvider extends MockContentProvider {
-
-        private static final int NDEF_MESSAGES = 1000;
-        private static final int NDEF_RECORDS = 1002;
-        private static final int NDEF_TAGS = 1004;
-
-        private static final UriMatcher MATCHER;
-
-        static {
-            MATCHER = new UriMatcher(0);
-            String auth = TagContract.AUTHORITY;
-
-            MATCHER.addURI(auth, "ndef_msgs", NDEF_MESSAGES);
-            MATCHER.addURI(auth, "ndef_records", NDEF_RECORDS);
-            MATCHER.addURI(auth, "ndef_tags", NDEF_TAGS);
-        }
-
-        private int nextId = 0;
-        private ArrayList<ContentValues> messages = Lists.newArrayList();
-        private ArrayList<ContentValues> records = Lists.newArrayList();
-        private ArrayList<ContentValues> tags = Lists.newArrayList();
-
-        @Override
-        public boolean onCreate() {
-            return false;
-        }
-
-        @Override
-        public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) {
-            final int numOperations = operations.size();
-            final ContentProviderResult[] results = new ContentProviderResult[numOperations];
-            try {
-                for (int i = 0; i < numOperations; i++) {
-                    final ContentProviderOperation operation = operations.get(i);
-                    results[i] = operation.apply(this, results, i);
-                }
-            } catch (OperationApplicationException e) {
-                fail(e.toString());
-            }
-
-            return results;
-        }
-
-        @Override
-        public Uri insert(Uri uri, ContentValues values) {
-            int match = MATCHER.match(uri);
-            switch (match) {
-            case NDEF_MESSAGES: {
-                messages.add(values);
-                break;
-            }
-
-            case NDEF_RECORDS: {
-                records.add(values);
-                break;
-            }
-
-            case NDEF_TAGS: {
-                tags.add(values);
-                break;
-            }
-            }
-
-            return ContentUris.withAppendedId(uri, nextId++);
-        }
-
-        @Override
-        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
-            int match = MATCHER.match(uri);
-            switch (match) {
-            case NDEF_MESSAGES: {
-                return toCursor(messages);
-            }
-
-            case NDEF_RECORDS: {
-                return toCursor(records);
-            }
-
-            case NDEF_TAGS: {
-                return toCursor(tags);
-            }
-            }
-
-            return null;
-        }
-
-        private String[] getColumnNames(ArrayList<ContentValues> values) {
-            if (values.size() < 0) {
-                return null;
-            }
-
-            ContentValues cv = values.get(0);
-
-            final int size = cv.size();
-            String[] columns = new String[size];
-
-            Set<Entry<String, Object>> valueSet = cv.valueSet();
-
-            int index = 0;
-            for (Entry<String, Object> entry : valueSet) {
-                columns[index++] = entry.getKey();
-            }
-
-            return columns;
-        }
-
-        private Cursor toCursor(ArrayList<ContentValues> values) {
-            String[] columnNames = getColumnNames(values);
-            assertNotNull(columnNames);
-            return new ContentValuesCursor(columnNames, values);
-        }
-    }
-
-    public void testOrdinals() throws Exception {
-        // This test creates a NdefTag with three NdefMessage each containing a single
-        // record and ensures the ordinal for the records and messages
-
-        NdefMessage[] msgs = new NdefMessage[3];
-        msgs[0] = new NdefMessage(new NdefRecord[] { TextRecord.newTextRecord("0", Locale.US) });
-        msgs[1] = new NdefMessage(new NdefRecord[] { TextRecord.newTextRecord("1", Locale.US) });
-        msgs[2] = new NdefMessage(new NdefRecord[] { TextRecord.newTextRecord("2", Locale.US) });
-
-        NdefTag tag = NdefTag.createMockNdefTag(new byte[] { },
-                new String[] { Tag.TARGET_ISO_14443_4 },
-                null, null, new String[] { NdefTag.TARGET_TYPE_4 },
-                new NdefMessage[][] { msgs });
-
-        Context context = getContext();
-
-        ArrayList<ContentProviderOperation> ops = NdefTags.toContentProviderOperations(context, tag,
-                false);
-
-        // There should be seven operations. tag insert, three msg inserts, and three record inserts
-        assertEquals(7, ops.size());
-
-        // Write the operation out to the database
-
-        TestProvider provider = new TestProvider();
-        provider.applyBatch(ops);
-
-        // Now verify the ordinals of the messages and records
-
-        Cursor cursor = provider.query(NdefMessages.CONTENT_URI, null, null, null, null);
-        assertNotNull(cursor);
-        assertEquals(3, cursor.getCount());
-
-        int ordinalIndex = cursor.getColumnIndex(NdefMessages.ORDINAL);
-        assertTrue(ordinalIndex != -1);
-
-        int expectedOrdinal = 0;
-        while (cursor.moveToNext()) {
-            int ordinal = cursor.getInt(ordinalIndex);
-            assertEquals(expectedOrdinal++, ordinal);
-        }
-
-        // Test the records
-        cursor = provider.query(NdefRecords.CONTENT_URI, null, null, null, null);
-        assertNotNull(cursor);
-        assertEquals(3, cursor.getCount());
-
-        ordinalIndex = cursor.getColumnIndex(NdefRecords.ORDINAL);
-        assertTrue(ordinalIndex != -1);
-
-        int bytesIndex = cursor.getColumnIndex(NdefRecords.BYTES);
-        assertTrue(bytesIndex != -1);
-
-        int index = 0;
-        while (cursor.moveToNext()) {
-            assertEquals(index, cursor.getInt(ordinalIndex));
-            assertTrue(Arrays.equals(msgs[index].getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
-            index++;
-        }
-    }
-
-    public void testSmartPoster() throws Exception {
-
-        NdefMessage posterMessage = new NdefMessage(new NdefRecord[] {
-                TextRecord.newTextRecord("4", Locale.US),
-                TextRecord.newTextRecord("5", Locale.US) });
-
-        NdefRecord poster = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_SMART_POSTER,
-                new byte[] { }, posterMessage.toByteArray());
-
-
-        NdefMessage[] msgs = new NdefMessage[4];
-        msgs[0] = new NdefMessage(new NdefRecord[] {
-                TextRecord.newTextRecord("0", Locale.US),
-                TextRecord.newTextRecord("1", Locale.US)
-                });
-        msgs[1] = new NdefMessage(new NdefRecord[] { TextRecord.newTextRecord("2", Locale.US) });
-        msgs[2] = new NdefMessage(new NdefRecord[] { poster });
-        msgs[3] = new NdefMessage(new NdefRecord[] { TextRecord.newTextRecord("6", Locale.US) });
-
-        NdefTag tag = NdefTag.createMockNdefTag(new byte[] { },
-                new String[] { Tag.TARGET_ISO_14443_4 },
-                null, null, new String[] { NdefTag.TARGET_TYPE_4 },
-                new NdefMessage[][] { msgs });
-
-        Context context = getContext();
-
-        ArrayList<ContentProviderOperation> ops = NdefTags.toContentProviderOperations(context, tag,
-                false);
-
-        // There should be seven operations. tag insert, three msg inserts, and three record inserts
-        assertEquals(12, ops.size());
-
-        // Write the operation out to the database
-
-        TestProvider provider = new TestProvider();
-        provider.applyBatch(ops);
-
-        Cursor cursor = provider.query(NdefMessages.CONTENT_URI, null, null, null, null);
-        assertNotNull(cursor);
-        assertEquals(4, cursor.getCount());
-
-        int ordinalIndex = cursor.getColumnIndex(NdefMessages.ORDINAL);
-        assertTrue(ordinalIndex != -1);
-
-        int expectedOrdinal = 0;
-        while (cursor.moveToNext()) {
-            int ordinal = cursor.getInt(ordinalIndex);
-            assertEquals(expectedOrdinal++, ordinal);
-        }
-
-        // Test the records
-        cursor = provider.query(NdefRecords.CONTENT_URI, null, null, null, null);
-        assertNotNull(cursor);
-        assertEquals(7, cursor.getCount());
-
-        ordinalIndex = cursor.getColumnIndex(NdefRecords.ORDINAL);
-        assertTrue(ordinalIndex != -1);
-
-        int bytesIndex = cursor.getColumnIndex(NdefRecords.BYTES);
-        assertTrue(bytesIndex != -1);
-
-        int index = 0;
-
-        cursor.moveToNext();
-        assertEquals(0, cursor.getInt(ordinalIndex));
-        assertTrue(Arrays.equals(msgs[index].getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
-
-        cursor.moveToNext();
-        assertEquals(1, cursor.getInt(ordinalIndex));
-        assertTrue(Arrays.equals(msgs[index].getRecords()[1].getPayload(), cursor.getBlob(bytesIndex)));
-
-        index++;
-
-        cursor.moveToNext();
-        assertEquals(2, cursor.getInt(ordinalIndex));
-        assertTrue(Arrays.equals(msgs[index++].getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
-
-        cursor.moveToNext();
-        assertEquals(3, cursor.getInt(ordinalIndex));
-        assertTrue(Arrays.equals(msgs[index++].getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
-
-        cursor.moveToNext();
-        assertEquals(4, cursor.getInt(ordinalIndex));
-        assertTrue(Arrays.equals(posterMessage.getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
-
-        cursor.moveToNext();
-        assertEquals(5, cursor.getInt(ordinalIndex));
-        assertTrue(Arrays.equals(posterMessage.getRecords()[1].getPayload(), cursor.getBlob(bytesIndex)));
-
-        cursor.moveToNext();
-        assertEquals(6, cursor.getInt(ordinalIndex));
-        assertTrue(Arrays.equals(msgs[index++].getRecords()[0].getPayload(), cursor.getBlob(bytesIndex)));
-    }
-}