A bunch of work on the UI.

- Change the tab order and remember the
  current tab
- Proper icon for the starred tab, renamed
  from saved
- Set the proper sort order for the tag lists
- Very rough first pass at full screen tag viewer
- Hookup the delete button in the tag viewer
- Store the snippets for tags in the database
- Added view creation logic to the parsed
  messages and records so they can render
  themselves
- Make the URI records look much better
- For URI records if there are multiple activities
  that can handle the URI build one item per
  activity and bypass the activity chooser
- Pretty print sms[to] and tel URIs
- Hookup URI entries in the viewer to launch the
  activities
- Implement the spec for saving tags and timing
  out the viewer for scanned tags
- Made a few more strings localizable

Change-Id: I6bdb8adf52445499c62a1b046f99d5b119aff068
diff --git a/apps/Tag/AndroidManifest.xml b/apps/Tag/AndroidManifest.xml
index 1b1e6b9..4349180 100644
--- a/apps/Tag/AndroidManifest.xml
+++ b/apps/Tag/AndroidManifest.xml
@@ -23,6 +23,7 @@
     package="com.android.apps.tag"
 >
 
+    <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.NFC" />
 
     <application android:label="Tags">
@@ -39,7 +40,7 @@
         <activity android:name="TagList" />
 
         <activity android:name="TagViewer"
-            android:theme="@android:style/Theme.Dialog"
+            android:theme="@android:style/Theme.NoTitleBar"
         >
             <intent-filter>
                 <action android:name="android.nfc.action.NDEF_TAG_DISCOVERED"/>
@@ -47,5 +48,7 @@
             </intent-filter>
         </activity>
 
+        <service android:name="TagService" />
+
     </application>
 </manifest>
diff --git a/apps/Tag/res/drawable-hdpi/ic_menu_desk_clock.png b/apps/Tag/res/drawable-hdpi/ic_menu_desk_clock.png
deleted file mode 100644
index 0a82ad6..0000000
--- a/apps/Tag/res/drawable-hdpi/ic_menu_desk_clock.png
+++ /dev/null
Binary files differ
diff --git a/apps/Tag/res/drawable-hdpi/ic_tab_selected_starred.png b/apps/Tag/res/drawable-hdpi/ic_tab_selected_starred.png
new file mode 100644
index 0000000..d9182b5
--- /dev/null
+++ b/apps/Tag/res/drawable-hdpi/ic_tab_selected_starred.png
Binary files differ
diff --git a/apps/Tag/res/drawable-hdpi/ic_tab_unselected_starred.png b/apps/Tag/res/drawable-hdpi/ic_tab_unselected_starred.png
new file mode 100644
index 0000000..259d2d3
--- /dev/null
+++ b/apps/Tag/res/drawable-hdpi/ic_tab_unselected_starred.png
Binary files differ
diff --git a/apps/Tag/res/drawable-mdpi/ic_menu_desk_clock.png b/apps/Tag/res/drawable-mdpi/ic_menu_desk_clock.png
deleted file mode 100644
index e4fa671..0000000
--- a/apps/Tag/res/drawable-mdpi/ic_menu_desk_clock.png
+++ /dev/null
Binary files differ
diff --git a/apps/Tag/res/drawable-mdpi/ic_tab_selected_starred.png b/apps/Tag/res/drawable-mdpi/ic_tab_selected_starred.png
new file mode 100644
index 0000000..089dfe9
--- /dev/null
+++ b/apps/Tag/res/drawable-mdpi/ic_tab_selected_starred.png
Binary files differ
diff --git a/apps/Tag/res/drawable-mdpi/ic_tab_unselected_starred.png b/apps/Tag/res/drawable-mdpi/ic_tab_unselected_starred.png
new file mode 100644
index 0000000..32fe67d
--- /dev/null
+++ b/apps/Tag/res/drawable-mdpi/ic_tab_unselected_starred.png
Binary files differ
diff --git a/apps/Tag/res/layout/tag_viewer_list.xml b/apps/Tag/res/drawable/ic_tab_starred.xml
similarity index 64%
copy from apps/Tag/res/layout/tag_viewer_list.xml
copy to apps/Tag/res/drawable/ic_tab_starred.xml
index 3d322d5..e84db4a 100644
--- a/apps/Tag/res/layout/tag_viewer_list.xml
+++ b/apps/Tag/res/drawable/ic_tab_starred.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- Copyright (C) 2008 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at
-  
+
           http://www.apache.org/licenses/LICENSE-2.0
-  
+
      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,8 +14,8 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/ic_tab_selected_starred" />
+    <item android:drawable="@drawable/ic_tab_unselected_starred" />
+</selector>
+
diff --git a/apps/Tag/res/layout/main.xml b/apps/Tag/res/layout/main.xml
index 50b2324..3880a3a 100644
--- a/apps/Tag/res/layout/main.xml
+++ b/apps/Tag/res/layout/main.xml
@@ -33,7 +33,6 @@
         <FrameLayout
             android:id="@android:id/tabcontent"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:padding="5dp" />
+            android:layout_height="match_parent" />
     </LinearLayout>
 </TabHost>
diff --git a/apps/Tag/res/layout/tag_uri.xml b/apps/Tag/res/layout/tag_uri.xml
new file mode 100644
index 0000000..3ce4b2f
--- /dev/null
+++ b/apps/Tag/res/layout/tag_uri.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+
+    android:paddingTop="4dip"
+    android:paddingBottom="4dip"
+>
+
+    <ImageView android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_centerVertical="true"
+        
+        android:paddingLeft="8dip"
+        android:paddingRight="8dip"
+    />
+
+    <TextView android:id="@+id/primary"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_toRightOf="@id/icon"
+        android:layout_marginTop="4dip"
+
+        android:textAppearance="?android:attr/textAppearanceMedium"
+    />
+
+    <TextView android:id="@+id/secondary"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/primary"
+        android:layout_alignLeft="@id/primary"
+
+        android:textAppearance="?android:attr/textAppearanceSmall"
+    />
+</RelativeLayout>
+
diff --git a/apps/Tag/res/layout/tag_viewer.xml b/apps/Tag/res/layout/tag_viewer.xml
new file mode 100644
index 0000000..55e2f7d
--- /dev/null
+++ b/apps/Tag/res/layout/tag_viewer.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+
+    android:orientation="vertical"
+>
+    <!-- Title -->
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="56dip"
+
+        android:orientation="horizontal"
+        android:background="@android:color/black"
+    >
+
+        <ImageView android:id="@+id/icon"
+            android:layout_width="32dip"
+            android:layout_height="32dip"
+            android:layout_gravity="center_vertical"
+        />
+
+        <TextView android:id="@+id/title"
+            android:layout_width="0dip"
+            android:layout_weight="1"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+        />
+
+        <CheckBox android:id="@+id/star"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+
+            style="?android:attr/starStyle"
+        />
+
+    </LinearLayout>
+
+    <!-- Content -->
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+
+        android:background="@android:color/white"
+    >
+
+        <LinearLayout android:id="@+id/list"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+
+            android:orientation="vertical"
+        />
+
+    </ScrollView>
+
+    <!-- Bottom button area -->
+
+    <TextView android:id="@+id/cancel_help_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+
+        android:paddingLeft="4dip"
+        android:paddingRight="4dip"
+        android:text="@string/cancel_help_text"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:background="@android:color/black"
+        android:gravity="center"
+    />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+
+        android:orientation="horizontal"
+        style="@style/ButtonBar"
+    >
+
+        <Button android:id="@+id/btn_delete"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+
+            android:text="@string/button_delete"
+        />
+
+        <Button android:id="@+id/btn_cancel"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+
+            android:text="@android:string/cancel"
+        />
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/Tag/res/menu/mailbox_menu.xml b/apps/Tag/res/menu/mailbox_menu.xml
deleted file mode 100644
index 8bb8d7b..0000000
--- a/apps/Tag/res/menu/mailbox_menu.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:id="@+id/help_info_menu_item"
-        android:icon="@android:drawable/ic_menu_help"
-        android:title="@string/help_and_info" />
-</menu>
diff --git a/apps/Tag/res/values/strings.xml b/apps/Tag/res/values/strings.xml
index 9b73678..ed0525a 100644
--- a/apps/Tag/res/values/strings.xml
+++ b/apps/Tag/res/values/strings.xml
@@ -14,16 +14,29 @@
      limitations under the License.
 -->
 
-<resources>
-
-    <string name="hello_activity_text_text">Hello, World!</string>
-    <string name="help_and_info">help and info</string>
-    <string name="saved">Saved</string>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
     <!-- The title of the tab that displays all recently scanned NFC tags -->
-    <string name="tab_recent">Recent</string>
+    <string name="tab_tags">Tags</string>
 
-    <!-- The title of the tab that displays all saved NFC tags -->
-    <string name="tab_saved">Saved</string>
+    <!-- The title of the tab that displays all starred NFC tags -->
+    <string name="tab_starred">Starred</string>
 
+    <!-- The title displayed for unknown tag types -->
+    <string name="tag_unknown">Unknown tag type</string>
+
+    <!-- The title displayed for an empty tag -->
+    <string name="tag_empty">Empty tag</string>
+
+    <!-- Button label indicating that the user wants to delete a tag -->
+    <string name="button_delete">Delete</string>
+
+    <!-- String describing that if the user doesn't want to save a tag they should touch the button labeled Cancel. The text for the cancel button comes from the system string android.R.string.cancel. -->
+    <string name="cancel_help_text">To skip adding this tag to your collection, press Cancel</string>
+
+    <!-- String displayed for an action to send a text message to a phone number -->
+    <string name="action_text">Text <xliff:g id="phone_number">%s</xliff:g></string>
+
+    <!-- String displayed for an action to call a phone number -->
+    <string name="action_call">Call <xliff:g id="phone_number">%s</xliff:g></string>
 </resources>
diff --git a/apps/Tag/res/layout/tag_viewer_list.xml b/apps/Tag/res/values/styles.xml
similarity index 65%
rename from apps/Tag/res/layout/tag_viewer_list.xml
rename to apps/Tag/res/values/styles.xml
index 3d322d5..bed5900 100644
--- a/apps/Tag/res/layout/tag_viewer_list.xml
+++ b/apps/Tag/res/values/styles.xml
@@ -4,9 +4,9 @@
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at
-  
+
           http://www.apache.org/licenses/LICENSE-2.0
-  
+
      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,8 +14,12 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-/>
+<resources>
+    <style name="ButtonBar">
+        <item name="android:paddingTop">5dip</item>
+        <item name="android:paddingLeft">4dip</item>
+        <item name="android:paddingRight">4dip</item>
+        <item name="android:paddingBottom">1dip</item>
+        <item name="android:background">@android:color/black</item>
+    </style>
+</resources>
diff --git a/apps/Tag/src/com/android/apps/tag/TagAdapter.java b/apps/Tag/src/com/android/apps/tag/TagAdapter.java
index 44757c4..8fbf064 100644
--- a/apps/Tag/src/com/android/apps/tag/TagAdapter.java
+++ b/apps/Tag/src/com/android/apps/tag/TagAdapter.java
@@ -16,12 +16,11 @@
 
 package com.android.apps.tag;
 
+import com.android.apps.tag.TagDBHelper.NdefMessagesTable;
+
 import android.content.Context;
 import android.database.Cursor;
-import android.nfc.FormatException;
-import android.nfc.NdefMessage;
 import android.text.format.DateUtils;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -29,12 +28,6 @@
 import android.widget.CursorAdapter;
 import android.widget.TextView;
 
-import com.android.apps.tag.TagDBHelper.NdefMessagesTable;
-import com.android.apps.tag.message.NdefMessageParser;
-import com.android.apps.tag.message.ParsedNdefMessage;
-
-import java.util.Locale;
-
 /**
  * A custom {@link Adapter} that renders tag entries for a list.
  */
@@ -52,19 +45,7 @@
         TextView mainLine = (TextView) view.findViewById(R.id.title);
         TextView dateLine = (TextView) view.findViewById(R.id.date);
 
-        NdefMessage msg = null;
-        try {
-            msg = new NdefMessage(cursor.getBlob(cursor.getColumnIndex(NdefMessagesTable.BYTES)));
-        } catch (FormatException e) {
-            Log.e("foo", "poorly formatted message", e);
-        }
-
-        if (msg == null) {
-            mainLine.setText("Invalid tag");
-        } else {
-            ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
-            mainLine.setText(parsedMsg.getSnippet(Locale.getDefault()));
-        }
+        mainLine.setText(cursor.getString(cursor.getColumnIndex(NdefMessagesTable.TITLE)));
         dateLine.setText(DateUtils.getRelativeTimeSpanString(
                 context, cursor.getLong(cursor.getColumnIndex(NdefMessagesTable.DATE))));
     }
diff --git a/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java b/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java
index 38d3bfa..eb21113 100644
--- a/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java
+++ b/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java
@@ -18,7 +18,9 @@
 
 import android.app.Activity;
 import android.app.TabActivity;
+import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.widget.TabHost;
@@ -36,16 +38,34 @@
         Resources res = getResources();
         TabHost tabHost = getTabHost();
 
-        TabHost.TabSpec spec1 = tabHost.newTabSpec("saved")
-                .setIndicator(getText(R.string.tab_saved), res.getDrawable(R.drawable.ic_menu_tag))
-                .setContent(new Intent().setClass(this, TagList.class)
-                        .putExtra(TagList.EXTRA_SHOW_SAVED_ONLY, true));
-        tabHost.addTab(spec1);
+        tabHost.addTab(tabHost.newTabSpec("tags")
+                .setIndicator(getText(R.string.tab_tags),
+                        res.getDrawable(R.drawable.ic_menu_tag))
+                .setContent(new Intent().setClass(this, TagList.class)));
 
-        TabHost.TabSpec spec2 = tabHost.newTabSpec("recent")
-                .setIndicator(getText(R.string.tab_recent), res.getDrawable(R.drawable.ic_menu_desk_clock))
-                .setContent(new Intent().setClass(this, TagList.class));
-        tabHost.addTab(spec2);
+        tabHost.addTab(tabHost.newTabSpec("starred")
+                .setIndicator(getText(R.string.tab_starred),
+                        res.getDrawable(R.drawable.ic_tab_starred))
+                .setContent(new Intent().setClass(this, TagList.class)
+                        .putExtra(TagList.EXTRA_SHOW_STARRED_ONLY, true)));
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        // Restore the last active tab
+        SharedPreferences prefs = getSharedPreferences("prefs", Context.MODE_PRIVATE);
+        getTabHost().setCurrentTabByTag(prefs.getString("tab", "tags"));
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+
+        // Save the active tab
+        SharedPreferences.Editor edit = getSharedPreferences("prefs", Context.MODE_PRIVATE).edit();
+        edit.putString("tab", getTabHost().getCurrentTabTag());
+        edit.apply();
     }
 }
-
diff --git a/apps/Tag/src/com/android/apps/tag/TagDBHelper.java b/apps/Tag/src/com/android/apps/tag/TagDBHelper.java
index dd11d94..9c91afb 100644
--- a/apps/Tag/src/com/android/apps/tag/TagDBHelper.java
+++ b/apps/Tag/src/com/android/apps/tag/TagDBHelper.java
@@ -25,6 +25,10 @@
 import android.nfc.NdefMessage;
 import android.nfc.NdefRecord;
 
+import java.util.Locale;
+
+import com.android.apps.tag.message.NdefMessageParser;
+import com.android.apps.tag.message.ParsedNdefMessage;
 import com.google.common.annotations.VisibleForTesting;
 
 /**
@@ -33,7 +37,7 @@
 public class TagDBHelper extends SQLiteOpenHelper {
 
     private static final String DATABASE_NAME = "tags.db";
-    private static final int DATABASE_VERSION = 3;
+    private static final int DATABASE_VERSION = 5;
 
     public interface NdefMessagesTable {
         public static final String TABLE_NAME = "nedf_msg";
@@ -42,11 +46,13 @@
         public static final String TITLE = "title";
         public static final String BYTES = "bytes";
         public static final String DATE = "date";
-        public static final String SAVED = "saved";
+        public static final String STARRED = "starred";
     }
 
     private static TagDBHelper sInstance;
 
+    private Context mContext;
+
     public static synchronized TagDBHelper getInstance(Context context) {
         if (sInstance == null) {
             sInstance = new TagDBHelper(context.getApplicationContext());
@@ -56,11 +62,13 @@
 
     private TagDBHelper(Context context) {
         this(context, DATABASE_NAME);
+        mContext = context;
     }
 
     @VisibleForTesting
     TagDBHelper(Context context, String dbFile) {
         super(context, dbFile, null, DATABASE_VERSION);
+        mContext = context;
     }
 
     @Override
@@ -70,12 +78,12 @@
                 NdefMessagesTable.TITLE + " TEXT NOT NULL DEFAULT ''," +
                 NdefMessagesTable.BYTES + " BLOB NOT NULL, " +
                 NdefMessagesTable.DATE + " INTEGER NOT NULL, " +
-                NdefMessagesTable.SAVED + " INTEGER NOT NULL DEFAULT 0" +  // boolean
+                NdefMessagesTable.STARRED + " INTEGER NOT NULL DEFAULT 0" +  // boolean
                 ");");
 
         db.execSQL("CREATE INDEX msgIndex ON " + NdefMessagesTable.TABLE_NAME + " (" +
                 NdefMessagesTable.DATE + " DESC, " +
-                NdefMessagesTable.SAVED + " ASC" +
+                NdefMessagesTable.STARRED + " ASC" +
                 ")");
 
         addTestData(db);
@@ -114,15 +122,18 @@
         }
     }
 
-    public void insertNdefMessage(SQLiteDatabase db, NdefMessage msg, boolean isSaved) {
+    public void insertNdefMessage(SQLiteDatabase db, NdefMessage msg, boolean isStarred) {
+        ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
         SQLiteStatement stmt = null;
         try {
             stmt = db.compileStatement("INSERT INTO " + NdefMessagesTable.TABLE_NAME +
                     "(" + NdefMessagesTable.BYTES + ", " + NdefMessagesTable.DATE + ", " +
-                    NdefMessagesTable.SAVED + ") values (?, ?, ?)");
+                    NdefMessagesTable.STARRED + "," + NdefMessagesTable.TITLE + ") " +
+                    "values (?, ?, ?, ?)");
             stmt.bindBlob(1, msg.toByteArray());
             stmt.bindLong(2, System.currentTimeMillis());
-            stmt.bindLong(3, isSaved ? 1 : 0);
+            stmt.bindLong(3, isStarred ? 1 : 0);
+            stmt.bindString(4, parsedMsg.getSnippet(mContext, Locale.getDefault()));
             stmt.executeInsert();
         } finally {
             if (stmt != null) stmt.close();
diff --git a/apps/Tag/src/com/android/apps/tag/TagList.java b/apps/Tag/src/com/android/apps/tag/TagList.java
index 7d09bae..71e3097 100644
--- a/apps/Tag/src/com/android/apps/tag/TagList.java
+++ b/apps/Tag/src/com/android/apps/tag/TagList.java
@@ -41,7 +41,7 @@
 public class TagList extends ListActivity implements DialogInterface.OnClickListener {
     static final String TAG = "TagList";
 
-    static final String EXTRA_SHOW_SAVED_ONLY = "show_saved_only";
+    static final String EXTRA_SHOW_STARRED_ONLY = "show_starred_only";
 
     SQLiteDatabase mDatabase;
     TagAdapter mAdapter;
@@ -50,9 +50,9 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        boolean showSavedOnly = getIntent().getBooleanExtra(EXTRA_SHOW_SAVED_ONLY, false);
+        boolean showStarredOnly = getIntent().getBooleanExtra(EXTRA_SHOW_STARRED_ONLY, false);
         mDatabase = TagDBHelper.getInstance(this).getReadableDatabase();
-        String selection = showSavedOnly ? NdefMessagesTable.SAVED + "=1" : null;
+        String selection = showStarredOnly ? NdefMessagesTable.STARRED + "=1" : null;
 
         new TagLoaderTask().execute(selection);
         mAdapter = new TagAdapter(this);
@@ -119,7 +119,7 @@
                             NdefMessagesTable.DATE,
                             NdefMessagesTable.TITLE },
                     selection,
-                    null, null, null, null);
+                    null, null, null, NdefMessagesTable.DATE + " DESC");
             cursor.getCount();
             return cursor;
         }
diff --git a/apps/Tag/src/com/android/apps/tag/TagService.java b/apps/Tag/src/com/android/apps/tag/TagService.java
new file mode 100644
index 0000000..8748a55
--- /dev/null
+++ b/apps/Tag/src/com/android/apps/tag/TagService.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.apps.tag;
+
+import com.android.apps.tag.TagDBHelper.NdefMessagesTable;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.database.sqlite.SQLiteDatabase;
+import android.nfc.NdefMessage;
+import android.os.Parcelable;
+
+public class TagService extends IntentService {
+    public static final String EXTRA_SAVE_MSGS = "msgs";
+    public static final String EXTRA_DELETE_ID = "delete";
+
+    public TagService() {
+        super("SaveTagService");
+    }
+
+    @Override
+    public void onHandleIntent(Intent intent) {
+        TagDBHelper helper = TagDBHelper.getInstance(this);
+        SQLiteDatabase db = helper.getWritableDatabase();
+        if (intent.hasExtra(EXTRA_SAVE_MSGS)) {
+            Parcelable[] parcels = intent.getParcelableArrayExtra(EXTRA_SAVE_MSGS);
+            db.beginTransaction();
+            try {
+                for (Parcelable parcel : parcels) {
+                    helper.insertNdefMessage(db, (NdefMessage) parcel, false);
+                }
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+            }
+            return;
+        } else if (intent.hasExtra(EXTRA_DELETE_ID)) {
+            long id = intent.getLongExtra(EXTRA_DELETE_ID, 0);
+            db.delete(NdefMessagesTable.TABLE_NAME, NdefMessagesTable._ID + "=?",
+                    new String[] { Long.toString(id) });
+            return;
+        }
+    }
+}
diff --git a/apps/Tag/src/com/android/apps/tag/TagViewer.java b/apps/Tag/src/com/android/apps/tag/TagViewer.java
index a1d8900..f31666e 100644
--- a/apps/Tag/src/com/android/apps/tag/TagViewer.java
+++ b/apps/Tag/src/com/android/apps/tag/TagViewer.java
@@ -16,39 +16,57 @@
 
 package com.android.apps.tag;
 
+import com.android.apps.tag.message.NdefMessageParser;
+import com.android.apps.tag.message.ParsedNdefMessage;
+import com.android.apps.tag.record.ParsedNdefRecord;
+
 import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
-import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Color;
 import android.nfc.NdefMessage;
 import android.nfc.NdefTag;
 import android.nfc.NfcAdapter;
-import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.android.apps.tag.message.NdefMessageParser;
-import com.android.apps.tag.message.ParsedNdefMessage;
-
 import java.util.Locale;
 
 /**
  * An {@link Activity} which handles a broadcast of a new tag that the device just discovered.
  */
-public class TagViewer extends Activity {
+public class TagViewer extends Activity implements OnClickListener, Handler.Callback {
     static final String TAG = "SaveTag";    
     static final String EXTRA_TAG_DB_ID = "db_id";
     static final String EXTRA_MESSAGE = "msg";
 
+    /** This activity will finish itself in this amount of time if the user doesn't do anything. */
+    static final int ACTIVITY_TIMEOUT_MS = 10 * 1000;
+
     long mTagDatabaseId;
+    ImageView mIcon;
+    TextView mTitle;
+    CheckBox mStar;
+    Button mDeleteButton;
+    Button mCancelButton;
+    NdefMessage[] mMessagesToSave = null;
 
     @Override
-    protected void onStart() {
-        super.onStart();
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
@@ -58,6 +76,18 @@
                 | WindowManager.LayoutParams.FLAG_DIM_BEHIND
         );
 
+        setContentView(R.layout.tag_viewer);
+
+        mTitle = (TextView) findViewById(R.id.title);
+        mIcon = (ImageView) findViewById(R.id.icon);
+        mStar = (CheckBox) findViewById(R.id.star);
+        mDeleteButton = (Button) findViewById(R.id.btn_delete);
+        mCancelButton = (Button) findViewById(R.id.btn_cancel);
+
+        mDeleteButton.setOnClickListener(this);
+        mCancelButton.setOnClickListener(this);
+        mIcon.setImageResource(R.drawable.ic_launcher_nfc);
+
         Intent intent = getIntent();
         NdefMessage[] msgs = null;
         NdefTag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
@@ -68,10 +98,18 @@
             if (msg != null) {
                 msgs = new NdefMessage[] { msg };
             }
+
+            // Hide the text about saving the tag, it's already in the database
+            findViewById(R.id.cancel_help_text).setVisibility(View.GONE);
         } else {
             msgs = tag.getNdefMessages();
-            // TODO use a service to avoid the process getting reaped during saving 
-            new SaveTagTask().execute(msgs);
+            mDeleteButton.setVisibility(View.GONE);
+
+            // Set a timer on this activity since it wasn't created by the user
+            new Handler(this).sendEmptyMessageDelayed(0, ACTIVITY_TIMEOUT_MS);
+            
+            // Save the messages that were just scanned
+            mMessagesToSave = msgs;
         }
 
         if (msgs == null || msgs.length == 0) {
@@ -80,43 +118,72 @@
             return;
         }
 
-        
-        LayoutInflater inflater = LayoutInflater.from(
-                new ContextThemeWrapper(this, android.R.style.Theme_Light));
-        LinearLayout list = (LinearLayout) inflater.inflate(R.layout.tag_viewer_list, null, false);
-        // TODO figure out why the background isn't white, the CTW should force that...
-        list.setBackgroundColor(Color.WHITE);
-        setContentView(list);
+        Context contentContext = new ContextThemeWrapper(this, android.R.style.Theme_Light); 
+        LayoutInflater inflater = LayoutInflater.from(contentContext);
+        LinearLayout list = (LinearLayout) findViewById(R.id.list);
+
         buildTagViews(list, inflater, msgs);
+
+        if (TextUtils.isEmpty(getTitle())) {
+            // There isn't a snippet for this tag, use a default title
+            setTitle(R.string.tag_unknown);
+        }
     }
 
     private void buildTagViews(LinearLayout list, LayoutInflater inflater, NdefMessage[] msgs) {
-        // The body of the dialog should use the light theme
+        if (msgs == null || msgs.length == 0) {
+            return;
+        }
 
         // Build the views from the logical records in the messages
-        for (NdefMessage msg : msgs) {
-            ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
-            TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false);
-            text.setText(parsedMsg.getSnippet(Locale.getDefault()));
-            list.addView(text);
+        NdefMessage msg = msgs[0];
+
+        // Set the title to be the snippet of the message
+        ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
+        setTitle(parsedMsg.getSnippet(this, Locale.getDefault()));
+
+        // Build views for all of the sub records
+        for (ParsedNdefRecord record : parsedMsg.getRecords()) {
+            list.addView(record.getView(this, inflater, list));
+            inflater.inflate(R.layout.tag_divider, list, true);
         }
     }
-    
-    final class SaveTagTask extends AsyncTask<NdefMessage, Void, Void> {
-        @Override
-        public Void doInBackground(NdefMessage... msgs) {
-            TagDBHelper helper = TagDBHelper.getInstance(TagViewer.this);
-            SQLiteDatabase db = helper.getWritableDatabase();
-            db.beginTransaction();
-            try {
-                for (NdefMessage msg : msgs) {
-                    helper.insertNdefMessage(db, msg, false);
-                }
-                db.setTransactionSuccessful();
-            } finally {
-                db.endTransaction();
-            }
-            return null;
+
+    @Override
+    public void setTitle(CharSequence title) {
+        mTitle.setText(title);
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view == mDeleteButton) {
+            Intent save = new Intent(this, TagService.class);
+            save.putExtra(TagService.EXTRA_DELETE_ID, mTagDatabaseId);
+            startService(save);
+            finish();
+        } else if (view == mCancelButton) {
+            mMessagesToSave = null;
+            finish();
         }
     }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (mMessagesToSave != null) {
+            saveMessages(mMessagesToSave);
+        }
+    }
+
+    void saveMessages(NdefMessage[] msgs) {
+        Intent save = new Intent(this, TagService.class);
+        save.putExtra(TagService.EXTRA_SAVE_MSGS, msgs);
+        startService(save);
+    }
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        finish();
+        return true;
+    }
 }
diff --git a/apps/Tag/src/com/android/apps/tag/message/EmptyMessage.java b/apps/Tag/src/com/android/apps/tag/message/EmptyMessage.java
index 48f9e9f..0bf3fe6 100644
--- a/apps/Tag/src/com/android/apps/tag/message/EmptyMessage.java
+++ b/apps/Tag/src/com/android/apps/tag/message/EmptyMessage.java
@@ -16,18 +16,26 @@
 
 package com.android.apps.tag.message;
 
+import com.android.apps.tag.R;
+import com.android.apps.tag.record.ParsedNdefRecord;
+
+import android.content.Context;
+
+import java.util.ArrayList;
 import java.util.Locale;
 
 /**
  * A parsed message containing no elements.
  */
-class EmptyMessage implements ParsedNdefMessage {
+class EmptyMessage extends ParsedNdefMessage {
 
-    /* package private */ EmptyMessage() { }
+    /* package private */ EmptyMessage() {
+        super(new ArrayList<ParsedNdefRecord>());
+    }
 
     @Override
-    public String getSnippet(Locale locale) {
-        return "Empty Tag";  // TODO: localize
+    public String getSnippet(Context context, Locale locale) {
+        return context.getString(R.string.tag_empty);
     }
 
     @Override
diff --git a/apps/Tag/src/com/android/apps/tag/message/NdefMessageParser.java b/apps/Tag/src/com/android/apps/tag/message/NdefMessageParser.java
index 1823f98..101ebcf 100644
--- a/apps/Tag/src/com/android/apps/tag/message/NdefMessageParser.java
+++ b/apps/Tag/src/com/android/apps/tag/message/NdefMessageParser.java
@@ -47,13 +47,13 @@
 
         if (elements.size() == 1) {
             if (first instanceof SmartPoster) {
-                return new SmartPosterMessage((SmartPoster) first);
+                return new SmartPosterMessage((SmartPoster) first, elements);
             }
             if (first instanceof TextRecord) {
-                return new TextMessage((TextRecord) first);
+                return new TextMessage((TextRecord) first, elements);
             }
             if (first instanceof UriRecord) {
-                return new UriMessage((UriRecord) first);
+                return new UriMessage((UriRecord) first, elements);
             }
         }
 
diff --git a/apps/Tag/src/com/android/apps/tag/message/ParsedNdefMessage.java b/apps/Tag/src/com/android/apps/tag/message/ParsedNdefMessage.java
index 427aae4..b9f5210 100644
--- a/apps/Tag/src/com/android/apps/tag/message/ParsedNdefMessage.java
+++ b/apps/Tag/src/com/android/apps/tag/message/ParsedNdefMessage.java
@@ -16,20 +16,39 @@
 
 package com.android.apps.tag.message;
 
+import com.android.apps.tag.record.ParsedNdefRecord;
+import com.google.common.collect.ImmutableList;
+
+import android.content.Context;
+
+import java.util.List;
 import java.util.Locale;
 
 /**
  * A parsed version of an {@link android.nfc.NdefMessage}
  */
-public interface ParsedNdefMessage {
+public abstract class ParsedNdefMessage {
+
+    private List<ParsedNdefRecord> mRecords;
+
+    public ParsedNdefMessage(List<ParsedNdefRecord> records) {
+        mRecords = ImmutableList.copyOf(records);
+    }
+
+    /**
+     * Returns the list of parsed records on this message.
+     */
+    public List<ParsedNdefRecord> getRecords() {
+        return mRecords;
+    }
 
     /**
      * Returns the snippet information associated with the NdefMessage
      * most appropriate for the given {@code locale}.
      */
-    public String getSnippet(Locale locale);
+    public abstract String getSnippet(Context context, Locale locale);
 
     // TODO: Determine if this is the best place for holding whether
     // the user has starred this parsed message.
-    public boolean isStarred();
+    public abstract boolean isStarred();
 }
diff --git a/apps/Tag/src/com/android/apps/tag/message/SmartPosterMessage.java b/apps/Tag/src/com/android/apps/tag/message/SmartPosterMessage.java
index 28cd7b3..b5cc743 100644
--- a/apps/Tag/src/com/android/apps/tag/message/SmartPosterMessage.java
+++ b/apps/Tag/src/com/android/apps/tag/message/SmartPosterMessage.java
@@ -16,27 +16,32 @@
 
 package com.android.apps.tag.message;
 
+import com.android.apps.tag.record.ParsedNdefRecord;
 import com.android.apps.tag.record.SmartPoster;
 import com.android.apps.tag.record.TextRecord;
 import com.google.common.base.Preconditions;
 
+import android.content.Context;
+
+import java.util.List;
 import java.util.Locale;
 
 /**
  * A message consisting of one {@link SmartPoster} object.
  */
-class SmartPosterMessage implements ParsedNdefMessage {
+class SmartPosterMessage extends ParsedNdefMessage {
     private final SmartPoster mPoster;
 
-    SmartPosterMessage(SmartPoster poster) {
+    SmartPosterMessage(SmartPoster poster, List<ParsedNdefRecord> records) {
+        super(Preconditions.checkNotNull(records));
         mPoster = Preconditions.checkNotNull(poster);
     }
 
     @Override
-    public String getSnippet(Locale locale) {
+    public String getSnippet(Context context, Locale locale) {
         TextRecord title = mPoster.getTitle();
         if (title == null) {
-            return mPoster.getUriRecord().getUri().toString();
+            return mPoster.getUriRecord().getPrettyUriString(context);
         }
         return title.getText();
     }
diff --git a/apps/Tag/src/com/android/apps/tag/message/TextMessage.java b/apps/Tag/src/com/android/apps/tag/message/TextMessage.java
index 3494331..025b6d3 100644
--- a/apps/Tag/src/com/android/apps/tag/message/TextMessage.java
+++ b/apps/Tag/src/com/android/apps/tag/message/TextMessage.java
@@ -16,23 +16,28 @@
 
 package com.android.apps.tag.message;
 
+import com.android.apps.tag.record.ParsedNdefRecord;
 import com.android.apps.tag.record.TextRecord;
 import com.google.common.base.Preconditions;
 
+import android.content.Context;
+
+import java.util.List;
 import java.util.Locale;
 
 /**
  * A message containing one text element
  */
-class TextMessage implements ParsedNdefMessage {
+class TextMessage extends ParsedNdefMessage {
     private final TextRecord mRecord;
 
-    TextMessage(TextRecord record) {
+    TextMessage(TextRecord record, List<ParsedNdefRecord> records) {
+        super(Preconditions.checkNotNull(records));
         mRecord = Preconditions.checkNotNull(record);
     }
 
     @Override
-    public String getSnippet(Locale locale) {
+    public String getSnippet(Context context, Locale locale) {
         return mRecord.getText();
     }
 
diff --git a/apps/Tag/src/com/android/apps/tag/message/UnknownMessage.java b/apps/Tag/src/com/android/apps/tag/message/UnknownMessage.java
index a30445f..fff586f 100644
--- a/apps/Tag/src/com/android/apps/tag/message/UnknownMessage.java
+++ b/apps/Tag/src/com/android/apps/tag/message/UnknownMessage.java
@@ -16,26 +16,27 @@
 
 package com.android.apps.tag.message;
 
+import com.android.apps.tag.R;
 import com.android.apps.tag.record.ParsedNdefRecord;
-import com.google.common.collect.ImmutableList;
+import com.google.common.base.Preconditions;
 
+import android.content.Context;
+
+import java.util.List;
 import java.util.Locale;
 
 /**
  * The catchall parsed message format for when nothing else better applies.
  */
-class UnknownMessage implements ParsedNdefMessage {
+class UnknownMessage extends ParsedNdefMessage {
 
-    private final ImmutableList<ParsedNdefRecord> mRecords;
-
-    UnknownMessage(Iterable<ParsedNdefRecord> records) {
-        mRecords = ImmutableList.copyOf(records);
+    UnknownMessage(List<ParsedNdefRecord> records) {
+        super(Preconditions.checkNotNull(records));
     }
 
     @Override
-    public String getSnippet(Locale locale) {
-        // TODO: localize
-        return "Unknown record type with " + mRecords.size() + " elements.";
+    public String getSnippet(Context context, Locale locale) {
+        return context.getString(R.string.tag_unknown);
     }
 
     @Override
diff --git a/apps/Tag/src/com/android/apps/tag/message/UriMessage.java b/apps/Tag/src/com/android/apps/tag/message/UriMessage.java
index 85bfa41..012306b 100644
--- a/apps/Tag/src/com/android/apps/tag/message/UriMessage.java
+++ b/apps/Tag/src/com/android/apps/tag/message/UriMessage.java
@@ -16,26 +16,31 @@
 
 package com.android.apps.tag.message;
 
+import com.android.apps.tag.record.ParsedNdefRecord;
 import com.android.apps.tag.record.UriRecord;
 import com.google.common.base.Preconditions;
 
+import android.content.Context;
+
+import java.util.List;
 import java.util.Locale;
 
 /**
  * A {@link ParsedNdefMessage} consisting of one {@link UriRecord}.
  */
-class UriMessage implements ParsedNdefMessage {
+class UriMessage extends ParsedNdefMessage {
 
     private final UriRecord mRecord;
 
-    UriMessage(UriRecord record) {
+    UriMessage(UriRecord record, List<ParsedNdefRecord> records) {
+        super(Preconditions.checkNotNull(records));
         mRecord = Preconditions.checkNotNull(record);
     }
 
     @Override
-    public String getSnippet(Locale locale) {
+    public String getSnippet(Context context, Locale locale) {
         // URIs cannot be localized
-        return mRecord.getUri().toString();
+        return mRecord.getPrettyUriString(context);
     }
 
     @Override
diff --git a/apps/Tag/src/com/android/apps/tag/record/ParsedNdefRecord.java b/apps/Tag/src/com/android/apps/tag/record/ParsedNdefRecord.java
index 8de4a3f..89a6a09 100644
--- a/apps/Tag/src/com/android/apps/tag/record/ParsedNdefRecord.java
+++ b/apps/Tag/src/com/android/apps/tag/record/ParsedNdefRecord.java
@@ -16,6 +16,11 @@
 
 package com.android.apps.tag.record;
 
+import android.app.Activity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
 /**
  * TODO: come up with a better name.
  */
@@ -23,4 +28,9 @@
 
     // Just a placeholder for now.  Probably not needed nor desired.
     public String getRecordType();
+
+    /**
+     * Returns a view to display this record.
+     */
+    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent);
 }
diff --git a/apps/Tag/src/com/android/apps/tag/record/SmartPoster.java b/apps/Tag/src/com/android/apps/tag/record/SmartPoster.java
index 935b234..781d51f 100644
--- a/apps/Tag/src/com/android/apps/tag/record/SmartPoster.java
+++ b/apps/Tag/src/com/android/apps/tag/record/SmartPoster.java
@@ -16,16 +16,25 @@
 
 package com.android.apps.tag.record;
 
-import android.nfc.FormatException;
-import android.nfc.NdefMessage;
-import android.nfc.NdefRecord;
-
+import com.android.apps.tag.R;
 import com.android.apps.tag.message.NdefMessageParser;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Iterables;
 
+import android.app.Activity;
+import android.nfc.FormatException;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
 import java.util.Arrays;
 import java.util.NoSuchElementException;
+
 import javax.annotation.Nullable;
 
 /**
@@ -105,4 +114,23 @@
     public String getRecordType() {
         return "SmartPoster";
     }
+
+    @Override
+    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
+        if (mTitleRecord != null) {
+            // Build a container to hold the title and the URI
+            LinearLayout container = new LinearLayout(activity);
+            container.setOrientation(LinearLayout.VERTICAL);
+            container.setLayoutParams(new LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+
+            container.addView(mTitleRecord.getView(activity, inflater, container));
+            inflater.inflate(R.layout.tag_divider, container);
+            container.addView(mUriRecord.getView(activity, inflater, container));
+            return container;
+        } else {
+            // Just a URI, return a view for it directly
+            return mUriRecord.getView(activity, inflater, parent);
+        }
+    }
 }
diff --git a/apps/Tag/src/com/android/apps/tag/record/TextRecord.java b/apps/Tag/src/com/android/apps/tag/record/TextRecord.java
index 716e5af..145a6e8 100644
--- a/apps/Tag/src/com/android/apps/tag/record/TextRecord.java
+++ b/apps/Tag/src/com/android/apps/tag/record/TextRecord.java
@@ -16,10 +16,16 @@
 
 package com.android.apps.tag.record;
 
-import android.nfc.NdefRecord;
-
+import com.android.apps.tag.R;
 import com.google.common.base.Preconditions;
 
+import android.app.Activity;
+import android.nfc.NdefRecord;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
 import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 
@@ -42,6 +48,13 @@
         return "Text";
     }
 
+    @Override
+    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
+        TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false);
+        text.setText(mText);
+        return text;
+    }
+
     public String getText() {
         return mText;
     }
diff --git a/apps/Tag/src/com/android/apps/tag/record/UriRecord.java b/apps/Tag/src/com/android/apps/tag/record/UriRecord.java
index 7d5e9bc..cebbec9 100644
--- a/apps/Tag/src/com/android/apps/tag/record/UriRecord.java
+++ b/apps/Tag/src/com/android/apps/tag/record/UriRecord.java
@@ -16,23 +16,49 @@
 
 package com.android.apps.tag.record;
 
-import android.net.Uri;
-import android.nfc.NdefRecord;
-
+import com.android.apps.tag.R;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.primitives.Bytes;
 
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.nfc.NdefRecord;
+import android.telephony.PhoneNumberUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
 import java.nio.charset.Charsets;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * A parsed record containing a Uri.
  */
-public class UriRecord implements ParsedNdefRecord {
-    private static final byte[] EMPTY = new byte[0];
-
+public class UriRecord implements ParsedNdefRecord, OnClickListener {
+    private static final class ClickInfo {
+        public Activity activity;
+        public Intent intent;
+        
+        public ClickInfo(Activity activity, Intent intent) {
+            this.activity = activity;
+            this.intent = intent;
+        }
+    }
+    
     /**
      * NFC Forum "URI Record Type Definition"
      *
@@ -89,6 +115,96 @@
         return "Uri";
     }
 
+    public Intent getIntentForUri() {
+        String scheme = mUri.getScheme();
+        if ("tel".equals(scheme)) {
+            return new Intent(Intent.ACTION_CALL, mUri);
+        } else if ("sms".equals(scheme) || "smsto".equals(scheme)) {
+            return new Intent(Intent.ACTION_SENDTO, mUri);
+        } else {
+            return new Intent(Intent.ACTION_VIEW, mUri);
+        }
+    }
+
+    public String getPrettyUriString(Context context) {
+        String scheme = mUri.getScheme();
+        boolean tel = "tel".equals(scheme);
+        boolean sms = "sms".equals(scheme) || "smsto".equals(scheme); 
+        if (tel || sms) {
+            String ssp = mUri.getSchemeSpecificPart();
+            int offset = ssp.indexOf('?');
+            if (offset >= 0) {
+                ssp = ssp.substring(0, offset);
+            }
+            if (tel) {
+                return context.getString(R.string.action_call, PhoneNumberUtils.formatNumber(ssp));
+            } else {
+                return context.getString(R.string.action_text, PhoneNumberUtils.formatNumber(ssp));
+            }
+        } else {
+            return mUri.toString();
+        }
+    }
+
+    @Override
+    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
+        Intent intent = getIntentForUri();
+        PackageManager pm = activity.getPackageManager();
+        List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
+        int numActivities = activities.size();
+        if (numActivities == 0) {
+            TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false);
+            text.setText(mUri.toString());
+            return text;
+        } else if (numActivities == 1) {
+            return buildActivityView(activity, activities.get(0), pm, inflater, parent);
+        } else {
+            // Build a container to hold the multiple entries
+            LinearLayout container = new LinearLayout(activity);
+            container.setOrientation(LinearLayout.VERTICAL);
+            container.setLayoutParams(new LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+
+            // Create an entry for each activity that can handle the URI
+            for (ResolveInfo resolveInfo : activities) {
+                if (container.getChildCount() > 0) {
+                    inflater.inflate(R.layout.tag_divider, container);
+                }
+                container.addView(buildActivityView(activity, resolveInfo, pm, inflater, container));
+            }
+            return container;
+        }
+    }
+
+    private View buildActivityView(Activity activity, ResolveInfo resolveInfo, PackageManager pm,
+            LayoutInflater inflater, ViewGroup parent) {
+        Intent intent = getIntentForUri();
+        ActivityInfo activityInfo = resolveInfo.activityInfo;
+        intent.setComponent(new ComponentName(activityInfo.packageName, activityInfo.name));
+
+        View item = inflater.inflate(R.layout.tag_uri, parent, false);
+        item.setOnClickListener(this);
+        item.setTag(new ClickInfo(activity, intent));
+
+        ImageView icon = (ImageView) item.findViewById(R.id.icon);
+        icon.setImageDrawable(resolveInfo.loadIcon(pm));
+
+        TextView text = (TextView) item.findViewById(R.id.secondary);
+        text.setText(resolveInfo.loadLabel(pm));
+
+        text = (TextView) item.findViewById(R.id.primary);
+        text.setText(getPrettyUriString(activity));
+
+        return item;
+    }
+
+    @Override
+    public void onClick(View view) {
+        ClickInfo info = (ClickInfo) view.getTag();
+        info.activity.startActivity(info.intent);
+        info.activity.finish();
+    }
+
     public Uri getUri() {
         return mUri;
     }