Commits

Anonymous committed 359d4dd Merge

am 0215c987: Update the UI to match the latest spec.

Merge commit '0215c98773bad1b532c97b1f875aa2f0ef201bf8'

* commit '0215c98773bad1b532c97b1f875aa2f0ef201bf8':
Update the UI to match the latest spec.

Comments (0)

Files changed (11)

     <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;