Commits

Anonymous committed 0215c98

Update the UI to match the latest spec.

The TagViewer now loads tags from the database
instead of taking them as arguments in intents.

It should also now properly handle scanning a
new tag while viewing one, but I can't test that.

Change-Id: Ib0325d9838ce14cb50e04cade6f467be2dbe1694

  • Participants
  • Parent commits 15ee1b9

Comments (0)

Files changed (11)

AndroidManifest.xml

     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.NFC" />
 
-    <application android:label="Tags">
+    <application
+        android:icon="@drawable/ic_launcher_nfc"
+        android:label="@string/app_name"
+    >
         <activity android:name="TagBrowserActivity"
-            android:icon="@drawable/ic_launcher_nfc"
             android:theme="@android:style/Theme.NoTitleBar"
         >
             <intent-filter>
                 <action android:name="android.nfc.action.NDEF_TAG_DISCOVERED"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW"/>
+                <data android:mimeType="vnd.android.cursor.item/ndef_msg"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
         </activity>
 
         <service android:name="TagService" />

res/drawable-hdpi/title_bar_tall.9.png

Added
New image

res/drawable-mdpi/title_bar_tall.9.png

Added
New image

res/layout/tag_viewer.xml

         android:layout_height="56dip"
 
         android:orientation="horizontal"
-        android:background="@android:color/black"
+        android:background="@drawable/title_bar_tall"
     >
 
         <ImageView android:id="@+id/icon"
 
             android:singleLine="true"
             android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textStyle="bold"
         />
 
         <CheckBox android:id="@+id/star"
         android:layout_width="match_parent"
         android:layout_height="0dip"
         android:layout_weight="1"
-
-        android:background="@android:color/white"
     >
 
         <LinearLayout android:id="@+id/list"
 
     <!-- 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"
+        style="@android:style/ButtonBar"
     >
 
-        <Button android:id="@+id/btn_delete"
+        <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/btn_cancel"
+        <Button android:id="@+id/button_done"
             android:layout_width="0dip"
             android:layout_height="wrap_content"
             android:layout_weight="1"
 
-            android:text="@android:string/cancel"
+            android:text="@string/button_done"
         />
 
     </LinearLayout>

res/values/strings.xml

 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
+    <!-- The title of the NFC tag application -->
+    <string name="app_name">Tags</string>
+
+    <!-- The title for the activity that shows a tag that was just scanned -->
+    <string name="title_scanned_tag">New tag collected</string>
+
+    <!-- The title for the activity that shows a tag that was stored from a past scan -->
+    <string name="title_existing_tag">Tag</string>
+
     <!-- The title of the tab that displays all recently scanned NFC tags -->
     <string name="tab_tags">Tags</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>
+    <!-- Button label indicating that the user wants to delete a tag -->
+    <string name="button_done">Done</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 button_done. -->
+    <string name="cancel_help_text">To skip adding this tag to your collection, press Done</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>

res/values/styles.xml

-<?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.
--->
-
-<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>

src/com/android/apps/tag/TagList.java

 
 package com.android.apps.tag;
 
-import com.android.apps.tag.provider.TagContract;
 import com.android.apps.tag.provider.TagContract.NdefMessages;
 
 import android.app.Activity;
 import android.app.ListActivity;
+import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
 import android.database.CharArrayBuffer;
 import android.database.Cursor;
-import android.nfc.FormatException;
-import android.nfc.NdefMessage;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.text.format.DateUtils;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
     @Override
     protected void onListItemClick(ListView l, View v, int position, long id) {
-        Cursor cursor = mAdapter.getCursor();
-        cursor.moveToPosition(position);
-        byte[] tagBytes = cursor.getBlob(cursor.getColumnIndexOrThrow(TagContract.NdefMessages.BYTES));
-        try {
-            NdefMessage msg = new NdefMessage(tagBytes);
-            Intent intent = new Intent(this, TagViewer.class);
-            intent.putExtra(TagViewer.EXTRA_MESSAGE, msg);
-            intent.putExtra(TagViewer.EXTRA_TAG_DB_ID, id);
-            startActivity(intent);
-        } catch (FormatException e) {
-            Log.e(TAG, "bad format for tag " + id + ": " + tagBytes, e);
-            return;
-        }
+        Intent intent = new Intent(Intent.ACTION_VIEW,
+                ContentUris.withAppendedId(NdefMessages.CONTENT_URI, id));
+        startActivity(intent);
     }
 
     interface TagQuery {
                     TagQuery.PROJECTION,
                     selection,
                     null, NdefMessages.DATE + " DESC");
-            if (cursor != null)
-            cursor.getCount();
+
+            // Ensure the cursor executes and fills its window
+            if (cursor != null) cursor.getCount();
             return cursor;
         }
 

src/com/android/apps/tag/TagService.java

 
 import android.app.IntentService;
 import android.content.ContentProviderOperation;
-import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Intent;
 import android.content.OperationApplicationException;
+import android.net.Uri;
 import android.nfc.NdefMessage;
 import android.os.Parcelable;
 import android.os.RemoteException;
     private static final String TAG = "TagService";
 
     public static final String EXTRA_SAVE_MSGS = "msgs";
-    public static final String EXTRA_DELETE_ID = "delete";
+    public static final String EXTRA_DELETE_URI = "delete";
 
     public TagService() {
         super("SaveTagService");
                 Log.e(TAG, "Failed to save messages", e);
             }
             return;
-        } else if (intent.hasExtra(EXTRA_DELETE_ID)) {
-            long id = intent.getLongExtra(EXTRA_DELETE_ID, 0);
-            getContentResolver().delete(ContentUris.withAppendedId(NdefMessages.CONTENT_URI, id),
-                    null, null);
+        } else if (intent.hasExtra(EXTRA_DELETE_URI)) {
+            Uri uri = (Uri) intent.getParcelableExtra(EXTRA_DELETE_URI);
+            getContentResolver().delete(uri, null, null);
             return;
         }
     }

src/com/android/apps/tag/TagViewer.java

 
 import com.android.apps.tag.message.NdefMessageParser;
 import com.android.apps.tag.message.ParsedNdefMessage;
+import com.android.apps.tag.provider.TagContract.NdefMessages;
 import com.android.apps.tag.record.ParsedNdefRecord;
 
 import android.app.Activity;
-import android.content.Context;
 import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.nfc.FormatException;
 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.widget.LinearLayout;
 import android.widget.TextView;
 
-import java.util.Locale;
-
 /**
  * An {@link Activity} which handles a broadcast of a new tag that the device just discovered.
  */
     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;
+    static final int ACTIVITY_TIMEOUT_MS = 5 * 1000;
 
-    long mTagDatabaseId;
+    Uri mTagUri;
     ImageView mIcon;
     TextView mTitle;
     CheckBox mStar;
     Button mDeleteButton;
-    Button mCancelButton;
+    Button mDoneButton;
     NdefMessage[] mMessagesToSave = null;
+    LinearLayout mTagContent;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
 
         setContentView(R.layout.tag_viewer);
 
+        mTagContent = (LinearLayout) findViewById(R.id.list);
         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 = (Button) findViewById(R.id.button_delete);
+        mDoneButton = (Button) findViewById(R.id.button_done);
 
         mDeleteButton.setOnClickListener(this);
-        mCancelButton.setOnClickListener(this);
+        mDoneButton.setOnClickListener(this);
         mIcon.setImageResource(R.drawable.ic_launcher_nfc);
 
-        Intent intent = getIntent();
-        NdefMessage[] msgs = null;
-        NdefTag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
-        if (tag == null) {
-            // Maybe it came from the database? 
-            mTagDatabaseId = intent.getLongExtra(EXTRA_TAG_DB_ID, -1);
-            NdefMessage msg = intent.getParcelableExtra(EXTRA_MESSAGE);
-            if (msg != null) {
-                msgs = new NdefMessage[] { msg };
+        resolveIntent(getIntent());
+    }
+
+    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?
+            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;
             }
 
-            // 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();
-            mDeleteButton.setVisibility(View.GONE);
+            // Setup the views
+            setTitle(R.string.title_scanned_tag);
+            mStar.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
+//            new Handler(this).sendEmptyMessageDelayed(0, ACTIVITY_TIMEOUT_MS);
+
+            // Mark messages that were just scanned for saving
             mMessagesToSave = msgs;
-        }
 
-        if (msgs == null || msgs.length == 0) {
-            Log.e(TAG, "No NDEF messages");
+            // Build the views for the tag
+            buildTagViews(msgs);
+        } else if (Intent.ACTION_VIEW.equals(action)) {
+            // Setup the views
+            setTitle(R.string.title_existing_tag);
+            mStar.setVisibility(View.VISIBLE);
+
+            // Read the tag from the database asynchronously
+            mTagUri = intent.getData();
+            new LoadTagTask().execute(mTagUri);
+        } else {
+            Log.e(TAG, "Unknown intent " + intent);
             finish();
             return;
         }
-
-        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) {
+    void buildTagViews(NdefMessage[] msgs) {
         if (msgs == null || msgs.length == 0) {
             return;
         }
 
-        // Build the views from the logical records in the messages
-        NdefMessage msg = msgs[0];
+        LayoutInflater inflater = LayoutInflater.from(this);
+        LinearLayout content = mTagContent;
+
+        // Clear out any old views in the content area, for example if you scan two tags in a row.
+        content.removeAllViews();
 
-        // Set the title to be the snippet of the message
-        ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
-        setTitle(parsedMsg.getSnippet(this, Locale.getDefault()));
+        // Parse the first message in the list
+        //TODO figure out what to do when/if we support multiple messages per tag
+        ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msgs[0]);
 
         // 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);
+            content.addView(record.getView(this, inflater, content));
+            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 (mMessagesToSave != null) {
+            saveMessages(mMessagesToSave);
+            mMessagesToSave = null;
+        }
+
+        // ... and show the new one.
+        resolveIntent(intent);
+    }
+
+    @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;
+            if (mTagUri == null) {
+                // The tag hasn't been saved yet, so indicate it shouldn't be saved
+                mMessagesToSave = null;
+                finish();
+            } else {
+                // The tag came from the database, start a service to delete it
+                Intent delete = new Intent(this, TagService.class);
+                delete.putExtra(TagService.EXTRA_DELETE_URI, mTagUri);
+                startService(delete);
+                finish();
+            }
+        } else if (view == mDoneButton) {
             finish();
         }
     }
         }
     }
 
+    /**
+     * Starts a service to asynchronously save the messages to the content provider.
+     */
     void saveMessages(NdefMessage[] msgs) {
         Intent save = new Intent(this, TagService.class);
         save.putExtra(TagService.EXTRA_SAVE_MSGS, msgs);
         finish();
         return true;
     }
+
+    /**
+     * Loads a tag from the database, parses it, and builds the views
+     */
+    final class LoadTagTask extends AsyncTask<Uri, Void, NdefMessage> {
+        @Override
+        public NdefMessage doInBackground(Uri... args) {
+            Cursor cursor = getContentResolver().query(args[0], new String[] { NdefMessages.BYTES },
+                    null, null, null);
+            try {
+                if (cursor.moveToFirst()) {
+                    return new NdefMessage(cursor.getBlob(0));
+                }
+            } catch (FormatException e) {
+                Log.e(TAG, "invalid tag format", e);
+            } finally {
+                if (cursor != null) cursor.close();
+            }
+            return null;
+        }
+
+        @Override
+        public void onPostExecute(NdefMessage msg) {
+            buildTagViews(new NdefMessage[] { msg });
+        }
+    }
 }

src/com/android/apps/tag/provider/TagProvider.java

 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
-import android.util.Log;
 
 import java.util.HashMap;
 
+/**
+ * Stores NFC tags in a database. The contract is defined in {@link TagContract}.
+ */
 public class TagProvider extends SQLiteContentProvider {
 
     private static final int NDEF_MESSAGES = 1000;

src/com/android/apps/tag/record/SmartPoster.java

 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;