Re-implements MyTagList UI.
Ben Komalo [Thu, 9 Dec 2010 21:37:32 +0000 (13:37 -0800)]
Mostly a revert of:
  commit 8e2c2b329fe523d782df6cd313b78c9d11f90cc3.
  "DO NOT MERGE revert to simple My Tag UI."

Change-Id: I00581a4f2246a84a84481a6ec4528f6253f82e86

13 files changed:
AndroidManifest.xml
res/layout/edit_tag_activity.xml [new file with mode: 0644]
res/layout/my_tag_activity.xml
res/layout/tag_divider.xml
res/values/strings.xml
src/com/android/apps/tag/EditTagActivity.java
src/com/android/apps/tag/MyTagActivity.java [deleted file]
src/com/android/apps/tag/MyTagList.java [new file with mode: 0644]
src/com/android/apps/tag/TagBrowserActivity.java
src/com/android/apps/tag/TagList.java
src/com/android/apps/tag/TagService.java
src/com/android/apps/tag/provider/TagContract.java
src/com/android/apps/tag/provider/TagDBHelper.java

index 0af8309..3a58005 100644 (file)
@@ -43,6 +43,8 @@
 
         <activity android:name="TagList" />
 
+        <activity android:name="MyTagList" />
+
         <activity android:name="TagViewer"
             android:theme="@android:style/Theme.NoTitleBar"
             android:launchMode="singleTop"
@@ -67,7 +69,7 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="MyTagActivity" android:label="@string/tab_my_tag">
+        <activity android:name="EditTagActivity" android:label="@string/tab_my_tag">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/res/layout/edit_tag_activity.xml b/res/layout/edit_tag_activity.xml
new file mode 100644 (file)
index 0000000..151cd48
--- /dev/null
@@ -0,0 +1,102 @@
+<?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:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+>
+
+    <ScrollView
+        android:layout_weight="1"
+        android:layout_width="match_parent"
+        android:layout_height="0dip">
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <!-- Tag text -->
+            <LinearLayout
+                android:padding="8dip"
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" >
+
+                <TextView
+                    android:text="@string/tag_text"
+                    style="@style/record_title"
+                    />
+
+                <EditText
+                    android:id="@+id/input_tag_text"
+                    android:inputType="textMultiLine"
+                    android:lines="3"
+                    android:gravity="top"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/content_parent"
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <include layout="@layout/tag_divider" />
+
+            <!-- Control to add content. -->
+            <TextView
+                android:id="@+id/add_content_target"
+                android:text="@string/add_content"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:paddingLeft="8dip"
+                android:paddingRight="8dip"
+                android:paddingTop="16dip"
+                android:paddingBottom="16dip"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <include layout="@layout/tag_divider" />
+
+        </LinearLayout>
+    </ScrollView>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom"
+    >
+        <Button
+            android:id="@+id/save"
+            android:text="@string/button_save"
+            android:layout_weight="0.5"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <Button
+            android:id="@+id/cancel"
+            android:text="@string/button_cancel"
+            android:layout_weight="0.5"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+</LinearLayout>
index cd8f7b3..8c91f45 100644 (file)
@@ -15,99 +15,127 @@ See the License for the specific language governing permissions and
 limitations under the License.
 -->
 
-<ScrollView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <LinearLayout
-        android:orientation="vertical"
+    <!-- The touch target for enabling/disabling "My Tag". -->
+    <RelativeLayout
+        android:id="@+id/toggle_enabled_target"
+        android:padding="8dip"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <!-- The checkbox simply presents state to the user - its parent
+        is the real click target -->
+        <CheckBox android:id="@+id/toggle_enabled_checkbox"
+            android:layout_marginRight="4dip"
+            android:focusable="false"
+            android:focusableInTouchMode="false"
+            android:clickable="false"
+            android:layout_gravity="center_vertical"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <TextView
+            android:id="@+id/toggle_enabled_hint_title"
+            android:text="@string/turn_on_my_tag"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:layout_toRightOf="@+id/toggle_enabled_checkbox"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <TextView
+            android:text="@string/turn_on_my_tag_subtitle"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:layout_toRightOf="@+id/toggle_enabled_checkbox"
+            android:layout_below="@+id/toggle_enabled_hint_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+    </RelativeLayout>
+
+    <TextView
+        android:text="@string/active_tag"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="?android:attr/listSeparatorTextViewStyle"
+    />
+
+    <!-- Currently selected tag -->
+    <FrameLayout
+        android:id="@+id/active_tag"
+        android:padding="8dip"
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
 
-        <!-- The touch target for enabling/disabling "My Tag". -->
         <RelativeLayout
-            android:id="@+id/toggle_enabled_target"
-            android:padding="8dip"
-            android:focusable="true"
-            android:focusableInTouchMode="true"
-            android:orientation="horizontal"
+            android:id="@+id/active_tag_details"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content">
-
-            <!-- The checkbox simply presents state to the user - its parent
-            is the real click target -->
-            <CheckBox android:id="@+id/toggle_enabled_checkbox"
-                android:layout_marginRight="4dip"
-                android:focusable="false"
-                android:focusableInTouchMode="false"
-                android:clickable="false"
-                android:layout_gravity="center_vertical"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+        >
 
             <TextView
-                android:id="@+id/toggle_enabled_hint_title"
-                android:text="@string/turn_on_my_tag"
+                android:id="@+id/active_tag_title"
                 android:textAppearance="?android:attr/textAppearanceMedium"
-                android:layout_toRightOf="@+id/toggle_enabled_checkbox"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content" />
 
             <TextView
-                android:text="@string/turn_on_my_tag_subtitle"
+                android:text="@string/choose_my_tag"
                 android:textAppearance="?android:attr/textAppearanceSmall"
-                android:layout_toRightOf="@+id/toggle_enabled_checkbox"
-                android:layout_below="@+id/toggle_enabled_hint_title"
+                android:layout_below="@+id/active_tag_title"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content" />
-        </RelativeLayout>
 
-        <include layout="@layout/tag_divider" />
+            <!-- TODO: green active image -->
 
-        <!-- Tag text -->
-        <LinearLayout
-            android:padding="8dip"
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" >
-
-            <TextView
-                android:text="@string/tag_text"
-                style="@style/record_title"
-            />
-
-            <EditText
-                android:id="@+id/input_tag_text"
-                android:inputType="textMultiLine"
-                android:lines="3"
-                android:gravity="top"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
-
-        </LinearLayout>
-
-        <LinearLayout
-            android:id="@+id/content_parent"
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-
-        <include layout="@layout/tag_divider" />
+        </RelativeLayout>
 
-        <!-- Control to add content. -->
         <TextView
-            android:id="@+id/add_content_target"
-            android:text="@string/add_content"
+            android:id="@+id/choose_my_tag"
+            android:text="@string/choose_my_tag"
             android:textAppearance="?android:attr/textAppearanceMedium"
-            android:paddingLeft="8dip"
-            android:paddingRight="8dip"
-            android:paddingTop="16dip"
-            android:paddingBottom="16dip"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            android:layout_gravity="center_vertical"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+        />
+    </FrameLayout>
+
+    <TextView
+        android:text="@string/manage_my_tags"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="?android:attr/listSeparatorTextViewStyle"
+    />
+
+    <!-- Tag list. -->
+    <ListView
+        android:id="@android:id/list"
+        android:layout_weight="1"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+    />
+
+    <include layout="@layout/tag_divider" />
+
+    <!-- TODO: add this to end of list, instead. -->
+    <!-- Control to add new tag. -->
+    <TextView
+        android:id="@+id/add_tag"
+        android:text="@string/add_tag"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:paddingLeft="8dip"
+        android:paddingRight="8dip"
+        android:paddingTop="16dip"
+        android:paddingBottom="16dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
 
-        <include layout="@layout/tag_divider" />
+    <include layout="@layout/tag_divider" />
+</LinearLayout>
 
-    </LinearLayout>
-</ScrollView>
index b6b1b7c..049c588 100644 (file)
@@ -17,6 +17,6 @@
 
 <View xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="2dip"
     android:background="?android:attr/listDivider"
-/>
\ No newline at end of file
+/>
index 756b600..f3cc98d 100644 (file)
     <!-- Button label indicating that the user wants to delete a tag -->
     <string name="button_done">Done</string>
 
+    <!-- Button label indicating that the user wants to save a tag -->
+    <string name="button_save">Save</string>
+
+    <!-- Button label indicating that the user wants to cancel editing a tag -->
+    <string name="button_cancel">Cancel</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, touch Done</string>
 
@@ -164,4 +170,16 @@ Networks).</string>
     <!-- Popup text for confirming the deletion of a tag -->
     <string name="tag_deleted">Tag deleted</string>
 
+    <!-- Anchor text for touch target for creating a new My tag. [CHAR LIMIT=50] -->
+    <string name="add_tag">Add new tag</string>
+
+    <!-- Anchor text for touch target for selecting a tag to share as My tag. [CHAR LIMIT=50] -->
+    <string name="choose_my_tag">Choose a tag to share</string>
+
+    <!-- Active tag header for My tag [CHAR LIMIT=50] -->
+    <string name="active_tag">Active tag</string>
+
+    <!-- Header for list of tags that can be set as My tag. [CHAR LIMIT=50] -->
+    <string name="manage_my_tags">Manage my tags</string>
+
 </resources>
index 50c4df9..fc7690a 100644 (file)
 
 package com.android.apps.tag;
 
+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.ImageRecord;
 import com.android.apps.tag.record.ParsedNdefRecord;
 import com.android.apps.tag.record.RecordEditInfo;
 import com.android.apps.tag.record.RecordEditInfo.EditCallbacks;
+import com.android.apps.tag.record.TextRecord;
 import com.android.apps.tag.record.UriRecord;
 import com.android.apps.tag.record.VCardRecord;
 import com.google.common.base.Preconditions;
@@ -29,14 +33,29 @@ import com.google.common.collect.Lists;
 import android.app.Activity;
 import android.app.Dialog;
 import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.nfc.FormatException;
 import android.nfc.NdefMessage;
 import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.os.AsyncTask;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 
 /**
@@ -46,10 +65,13 @@ import java.util.Set;
  * {@link ParsedNdefRecord} types. Each type of {@link ParsedNdefRecord} can build views to
  * pick/select a new piece of content, or edit an existing content for the {@link NdefMessage}.
  */
-public abstract class EditTagActivity extends Activity implements OnClickListener, EditCallbacks {
+public class EditTagActivity extends Activity implements OnClickListener, EditCallbacks {
+
+    private static final String LOG_TAG = "Tags";
 
     private static final String BUNDLE_KEY_OUTSTANDING_PICK = "outstanding-pick";
     protected static final int DIALOG_ID_ADD_CONTENT = 0;
+    public static final String EXTRA_RESULT_MSG = "msg";
 
     private static final Set<String> SUPPORTED_RECORD_TYPES = ImmutableSet.of(
         ImageRecord.RECORD_TYPE,
@@ -72,23 +94,34 @@ public abstract class EditTagActivity extends Activity implements OnClickListene
      */
     private RecordEditInfo mRecordWithOutstandingPick;
 
+    /**
+     * Whether or not data was already parsed from an {@link Intent}. This happens when the user
+     * shares data via the My tag feature.
+     */
+    private boolean mParsedIntent = false;
+
+    private EditText mTextView;
+
     private LayoutInflater mInflater;
 
     @Override
     protected void onCreate(Bundle savedState) {
         super.onCreate(savedState);
+        setContentView(R.layout.edit_tag_activity);
 
         if (savedState != null) {
             mRecordWithOutstandingPick = savedState.getParcelable(BUNDLE_KEY_OUTSTANDING_PICK);
         }
         mInflater = LayoutInflater.from(this);
-    }
 
-    protected ViewGroup getContentRoot() {
-        if (mContentRoot == null) {
-            mContentRoot = (ViewGroup) findViewById(R.id.content_parent);
-        }
-        return mContentRoot;
+        findViewById(R.id.add_content_target).setOnClickListener(this);
+        findViewById(R.id.save).setOnClickListener(this);
+        findViewById(R.id.cancel).setOnClickListener(this);
+
+        mTextView = (EditText) findViewById(R.id.input_tag_text);
+        mContentRoot = (ViewGroup) findViewById(R.id.content_parent);
+
+        resolveIntent();
     }
 
     /**
@@ -137,14 +170,14 @@ public abstract class EditTagActivity extends Activity implements OnClickListene
      * Adds a child editor view for a record.
      */
     public void addViewForRecord(RecordEditInfo editInfo) {
-        ViewGroup root = getContentRoot();
+        ViewGroup root = mContentRoot;
         View editView = editInfo.getEditView(this, mInflater, root, this);
         root.addView(mInflater.inflate(R.layout.tag_divider, root, false));
         root.addView(editView);
     }
 
     protected void rebuildChildViews() {
-        ViewGroup root = getContentRoot();
+        ViewGroup root = mContentRoot;
         root.removeAllViews();
         for (RecordEditInfo editInfo : mRecords) {
             addViewForRecord(editInfo);
@@ -247,4 +280,191 @@ public abstract class EditTagActivity extends Activity implements OnClickListene
             outState.putParcelable(BUNDLE_KEY_OUTSTANDING_PICK, mRecordWithOutstandingPick);
         }
     }
+
+    interface GetTagQuery {
+        final static String[] PROJECTION = new String[] {
+                NdefMessages.BYTES
+        };
+
+        static final int COLUMN_BYTES = 0;
+    }
+
+    /**
+     * Loads a tag from the database, parses it, and builds the views.
+     */
+    final class LoadTagTask extends AsyncTask<Uri, Void, Cursor> {
+        @Override
+        public Cursor doInBackground(Uri... args) {
+            Cursor cursor = getContentResolver().query(args[0], GetTagQuery.PROJECTION,
+                    null, null, null);
+
+            // Ensure the cursor loads its window.
+            if (cursor != null) {
+                cursor.getCount();
+            }
+            return cursor;
+        }
+
+        @Override
+        public void onPostExecute(Cursor cursor) {
+            NdefMessage msg = null;
+            try {
+                if (cursor != null && cursor.moveToFirst()) {
+                    msg = new NdefMessage(cursor.getBlob(GetTagQuery.COLUMN_BYTES));
+                    if (msg != null) {
+                        populateFromMessage(msg);
+                    } else {
+                        // TODO: do something more graceful.
+                        finish();
+                    }
+                }
+            } catch (FormatException e) {
+                Log.e(LOG_TAG, "Unable to parse tag for editing.", e);
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+    }
+
+    private void resolveIntent() {
+        Intent intent = getIntent();
+
+        if (Intent.ACTION_SEND.equals(intent.getAction()) && !mParsedIntent) {
+            if (buildFromSendIntent(intent)) {
+                return;
+            }
+
+            mParsedIntent = true;
+            return;
+
+        }
+
+        Uri uri = intent.getData();
+        if (uri != null) {
+            // Edit existing tag.
+            new LoadTagTask().execute(uri);
+        }
+        // else, new tag - do nothing.
+    }
+
+    private void populateFromMessage(NdefMessage refMessage) {
+        // Locally stored message.
+        ParsedNdefMessage parsed = NdefMessageParser.parse(refMessage);
+        List<ParsedNdefRecord> records = parsed.getRecords();
+
+        // TODO: loosen this restriction. Just check the type of the first record.
+        // There is always a "Text" record for a My Tag.
+        if (records.size() < 1) {
+            Log.w(LOG_TAG, "Message not in expected format");
+            return;
+        }
+        mTextView.setText(((TextRecord) records.get(0)).getText());
+
+        mRecords.clear();
+        for (int i = 1, len = records.size(); i < len; i++) {
+            RecordEditInfo editInfo = records.get(i).getEditInfo(this);
+            if (editInfo != null) {
+                addRecord(editInfo);
+            }
+        }
+        rebuildChildViews();
+    }
+
+    /**
+     * Populates the editor from extras in a given {@link Intent}
+     * @param intent the {@link Intent} to parse.
+     * @return whether or not the {@link Intent} could be handled.
+     */
+    private boolean buildFromSendIntent(final Intent intent) {
+        String type = intent.getType();
+
+        if ("text/plain".equals(type)) {
+            String title = getIntent().getStringExtra(Intent.EXTRA_SUBJECT);
+            mTextView.setText((title == null) ? "" : title);
+
+            String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
+            try {
+                URL parsed = new URL(text);
+
+                // Valid URL.
+                mTextView.setText("");
+                mRecords.add(new UriRecord.UriRecordEditInfo(text));
+                rebuildChildViews();
+                return true;
+
+            } catch (MalformedURLException ex) {
+                // Ignore. Just treat as plain text.
+                mTextView.setText((text == null) ? "" : text);
+            }
+
+        } else if ("text/x-vcard".equals(type)) {
+            Uri stream = (Uri) getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
+            if (stream != null) {
+                RecordEditInfo editInfo = VCardRecord.editInfoForUri(stream);
+                if (editInfo != null) {
+                    mRecords.add(editInfo);
+                    rebuildChildViews();
+                    return true;
+                }
+            }
+        }
+
+        // TODO: handle images.
+
+        return false;
+    }
+
+    /**
+     * Saves the content of the tag.
+     */
+    private void saveAndFinish() {
+        String text = mTextView.getText().toString();
+        Locale locale = getResources().getConfiguration().locale;
+        ArrayList<NdefRecord> values = Lists.newArrayList(
+                TextRecord.newTextRecord(text, locale)
+        );
+
+        values.addAll(getValues());
+
+        NdefMessage msg = new NdefMessage(values.toArray(new NdefRecord[values.size()]));
+        Intent result = new Intent();
+        result.putExtra(EXTRA_RESULT_MSG, msg);
+        setResult(RESULT_OK, result);
+        finish();
+    }
+
+    @Override
+    public void onClick(View target) {
+        switch (target.getId()) {
+            case R.id.add_content_target:
+                showAddContentDialog();
+                break;
+            case R.id.save:
+                saveAndFinish();
+                break;
+            case R.id.cancel:
+                finish();
+                break;
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.menu, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.help:
+                HelpUtils.openHelp(this);
+                return true;
+
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
 }
diff --git a/src/com/android/apps/tag/MyTagActivity.java b/src/com/android/apps/tag/MyTagActivity.java
deleted file mode 100644 (file)
index 0ba621d..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * 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.message.NdefMessageParser;
-import com.android.apps.tag.message.ParsedNdefMessage;
-import com.android.apps.tag.record.ParsedNdefRecord;
-import com.android.apps.tag.record.RecordEditInfo;
-import com.android.apps.tag.record.TextRecord;
-import com.android.apps.tag.record.UriRecord;
-import com.android.apps.tag.record.VCardRecord;
-import com.google.common.collect.Lists;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.nfc.NdefMessage;
-import android.nfc.NdefRecord;
-import android.nfc.NfcAdapter;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.CheckBox;
-import android.widget.EditText;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Editor {@link Activity} for the tag that can be programmed into the device.
- */
-public class MyTagActivity extends EditTagActivity implements OnClickListener {
-
-    private static final String LOG_TAG = "TagEditor";
-
-    private EditText mTextView;
-    private CheckBox mEnabled;
-
-    /**
-     * Whether or not data was already parsed from an {@link Intent}. This happens when the user
-     * shares data via the My tag feature.
-     */
-    private boolean mParsedIntent = false;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.my_tag_activity);
-
-        findViewById(R.id.toggle_enabled_target).setOnClickListener(this);
-        findViewById(R.id.add_content_target).setOnClickListener(this);
-
-        mTextView = (EditText) findViewById(R.id.input_tag_text);
-        mEnabled = (CheckBox) findViewById(R.id.toggle_enabled_checkbox);
-
-        populateEditor();
-    }
-
-    private void populateEditor() {
-        NdefMessage localMessage = NfcAdapter.getDefaultAdapter(this).getLocalNdefMessage();
-
-        if (Intent.ACTION_SEND.equals(getIntent().getAction()) && !mParsedIntent) {
-            if (localMessage != null) {
-                // TODO: prompt user for confirmation about wiping their old tag.
-            }
-
-            if (buildFromIntent(getIntent())) {
-                return;
-            }
-
-            mParsedIntent = true;
-
-        } else if (localMessage == null) {
-            mEnabled.setChecked(false);
-            return;
-
-        } else {
-            // Locally stored message.
-            ParsedNdefMessage parsed = NdefMessageParser.parse(localMessage);
-            List<ParsedNdefRecord> records = parsed.getRecords();
-
-            // There is always a "Text" record for a My Tag.
-            if (records.size() < 1) {
-                Log.w(LOG_TAG, "Local record not in expected format");
-                return;
-            }
-            mEnabled.setChecked(true);
-            mTextView.setText(((TextRecord) records.get(0)).getText());
-
-            mRecords.clear();
-            for (int i = 1, len = records.size(); i < len; i++) {
-                RecordEditInfo editInfo = records.get(i).getEditInfo(this);
-                if (editInfo != null) {
-                    addRecord(editInfo);
-                }
-            }
-            rebuildChildViews();
-        }
-    }
-
-    /**
-     * Populates the editor from extras in a given {@link Intent}
-     * @param intent the {@link Intent} to parse.
-     * @return whether or not the {@link Intent} could be handled.
-     */
-    private boolean buildFromIntent(final Intent intent) {
-        String type = intent.getType();
-
-        if ("text/plain".equals(type)) {
-            String title = getIntent().getStringExtra(Intent.EXTRA_SUBJECT);
-            mTextView.setText((title == null) ? "" : title);
-
-            String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
-            try {
-                URL parsed = new URL(text);
-
-                // Valid URL.
-                mTextView.setText("");
-                mRecords.add(new UriRecord.UriRecordEditInfo(text));
-                rebuildChildViews();
-
-            } catch (MalformedURLException ex) {
-                // Ignore. Just treat as plain text.
-                mTextView.setText((text == null) ? "" : text);
-            }
-
-            mEnabled.setChecked(true);
-            onSave();
-            return true;
-
-        } else if ("text/x-vcard".equals(type)) {
-            Uri stream = (Uri) getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
-            if (stream != null) {
-                RecordEditInfo editInfo = VCardRecord.editInfoForUri(stream);
-                if (editInfo != null) {
-                    mRecords.add(editInfo);
-                    rebuildChildViews();
-                }
-            }
-        }
-        // TODO: handle images.
-        return false;
-    }
-
-    /**
-     * Persists content to store.
-     */
-    private void onSave() {
-        String text = mTextView.getText().toString();
-        NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
-
-        if (!mEnabled.isChecked()) {
-            nfc.setLocalNdefMessage(null);
-            return;
-        }
-
-        Locale locale = getResources().getConfiguration().locale;
-        ArrayList<NdefRecord> values = Lists.newArrayList(
-                TextRecord.newTextRecord(text, locale)
-        );
-
-        values.addAll(getValues());
-
-        Log.d(LOG_TAG, "Writing local NdefMessage from tag app....");
-        nfc.setLocalNdefMessage(new NdefMessage(values.toArray(new NdefRecord[values.size()])));
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        onSave();
-    }
-
-    @Override
-    public void onClick(View target) {
-        switch (target.getId()) {
-            case R.id.toggle_enabled_target:
-                boolean enabled = !mEnabled.isChecked();
-                mEnabled.setChecked(enabled);
-
-                // TODO: Persist to some store.
-                if (enabled) {
-                    onSave();
-                } else {
-                    NfcAdapter.getDefaultAdapter(this).setLocalNdefMessage(null);
-                }
-                break;
-
-            case R.id.add_content_target:
-                showAddContentDialog();
-                break;
-        }
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        getMenuInflater().inflate(R.menu.menu, menu);
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case R.id.help:
-                HelpUtils.openHelp(this);
-                return true;
-
-            default:
-                return super.onOptionsItemSelected(item);
-        }
-    }
-}
diff --git a/src/com/android/apps/tag/MyTagList.java b/src/com/android/apps/tag/MyTagList.java
new file mode 100644 (file)
index 0000000..94a35e0
--- /dev/null
@@ -0,0 +1,441 @@
+/*
+ * 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.provider.TagContract.NdefMessages;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.CharArrayBuffer;
+import android.database.Cursor;
+import android.nfc.FormatException;
+import android.nfc.NdefMessage;
+import android.nfc.NfcAdapter;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.CheckBox;
+import android.widget.CursorAdapter;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Displays the list of tags that can be set as "My tag", and allows the user to select the
+ * active tag that the device shares.
+ */
+public class MyTagList extends Activity implements OnItemClickListener, View.OnClickListener {
+
+    static final String TAG = "TagList";
+
+    private static final int REQUEST_EDIT = 0;
+    private static final int DIALOG_ID_SELECT_ACTIVE_TAG = 0;
+
+    private static final String BUNDLE_KEY_TAG_ID_IN_EDIT = "tag-edit";
+    private static final String PREF_KEY_ACTIVE_TAG = "active-my-tag";
+
+    private View mSelectActiveTagAnchor;
+    private View mActiveTagDetails;
+    private CheckBox mEnabled;
+    private ListView mList;
+
+    private TagAdapter mAdapter;
+    private long mActiveTagId;
+    private NdefMessage mActiveTag;
+
+    private WeakReference<SelectActiveTagDialog> mSelectActiveTagDialog;
+    private long mTagIdInEdit = -1;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.my_tag_activity);
+
+        if (savedInstanceState != null) {
+            mTagIdInEdit = savedInstanceState.getLong(BUNDLE_KEY_TAG_ID_IN_EDIT, -1);
+        }
+
+        // Set up the check box to toggle My tag sharing.
+        mEnabled = (CheckBox) findViewById(R.id.toggle_enabled_checkbox);
+        mEnabled.setChecked(false);  // Set after initial data load completes.
+        findViewById(R.id.toggle_enabled_target).setOnClickListener(this);
+
+        // Setup the active tag selector.
+        mActiveTagDetails = findViewById(R.id.active_tag_details);
+        mSelectActiveTagAnchor = findViewById(R.id.choose_my_tag);
+        findViewById(R.id.active_tag).setOnClickListener(this);
+        updateActiveTagView(null);  // Filled in after initial data load.
+
+        mActiveTagId = getPreferences(Context.MODE_PRIVATE).getLong(PREF_KEY_ACTIVE_TAG, -1);
+
+        // Setup the list
+        mAdapter = new TagAdapter(this);
+        mList = (ListView) findViewById(android.R.id.list);
+        mList.setAdapter(mAdapter);
+        mList.setOnItemClickListener(this);
+        registerForContextMenu(mList);
+        findViewById(R.id.add_tag).setOnClickListener(this);
+
+        // Kick off an async task to load the tags.
+        new TagLoaderTask().execute((Void[]) null);
+    }
+
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+        mTagIdInEdit = -1;
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putLong(BUNDLE_KEY_TAG_ID_IN_EDIT, mTagIdInEdit);
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (mAdapter != null) {
+            mAdapter.changeCursor(null);
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        // TODO: use implicit Intent?
+        Intent intent = new Intent(this, EditTagActivity.class);
+        intent.setData(ContentUris.withAppendedId(NdefMessages.CONTENT_URI, id));
+        mTagIdInEdit = id;
+        startActivityForResult(intent, REQUEST_EDIT);
+    }
+
+    public void setEmptyView() {
+        // TODO: set empty view.
+    }
+
+    public interface TagQuery {
+        static final String[] PROJECTION = new String[] {
+                NdefMessages._ID, // 0
+                NdefMessages.DATE, // 1
+                NdefMessages.TITLE, // 2
+                NdefMessages.BYTES, // 3
+        };
+
+        static final int COLUMN_ID = 0;
+        static final int COLUMN_DATE = 1;
+        static final int COLUMN_TITLE = 2;
+        static final int COLUMN_BYTES = 3;
+    }
+
+    /**
+     * Asynchronously loads the tags info from the database.
+     */
+    final class TagLoaderTask extends AsyncTask<Void, Void, Cursor> {
+        @Override
+        public Cursor doInBackground(Void... args) {
+            // Don't setup the empty view until after the first load
+            // so the empty text doesn't flash when first loading the
+            // activity.
+            mList.setEmptyView(null);
+            Cursor cursor = getContentResolver().query(
+                    NdefMessages.CONTENT_URI,
+                    TagQuery.PROJECTION,
+                    NdefMessages.IS_MY_TAG + "=1",
+                    null, NdefMessages.DATE + " DESC");
+
+            // Ensure the cursor executes and fills its window
+            if (cursor != null) cursor.getCount();
+            return cursor;
+        }
+
+        @Override
+        protected void onPostExecute(Cursor cursor) {
+            mAdapter.changeCursor(cursor);
+
+            if (cursor == null || cursor.getCount() == 0) {
+                setEmptyView();
+            } else {
+                // Find the active tag.
+                if (mActiveTagId != -1) {
+                    cursor.moveToPosition(-1);
+                    while (cursor.moveToNext()) {
+                        if (mActiveTagId == cursor.getLong(TagQuery.COLUMN_ID)) {
+                            selectActiveTag(cursor.getPosition());
+
+                            // If there was an existing shared tag, we update the contents, since
+                            // the active tag contents may have been changed. This also forces the
+                            // active tag to be in sync with what the NfcAdapter.
+                            if (NfcAdapter.getDefaultAdapter(MyTagList.this)
+                                    .getLocalNdefMessage() != null) {
+                                enableSharing();
+                            }
+                            break;
+                        }
+                    }
+                }
+            }
+
+
+            SelectActiveTagDialog dialog = (mSelectActiveTagDialog == null)
+                    ? null : mSelectActiveTagDialog.get();
+            if (dialog != null) {
+                dialog.setData(cursor);
+            }
+        }
+    }
+
+    /**
+     * Struct to hold pointers to views in the list items to save time at view binding time.
+     */
+    static final class ViewHolder {
+        public CharArrayBuffer titleBuffer;
+        public TextView mainLine;
+    }
+
+    /**
+     * Adapter to display the the My tag entries.
+     */
+    public class TagAdapter extends CursorAdapter {
+        private final LayoutInflater mInflater;
+
+        public TagAdapter(Context context) {
+            super(context, null, false);
+            mInflater = LayoutInflater.from(context);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            ViewHolder holder = (ViewHolder) view.getTag();
+
+            CharArrayBuffer buf = holder.titleBuffer;
+            cursor.copyStringToBuffer(TagQuery.COLUMN_TITLE, buf);
+            holder.mainLine.setText(buf.data, 0, buf.sizeCopied);
+        }
+
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+            View view = mInflater.inflate(R.layout.tag_list_item, null);
+
+            // Cache items for the view
+            ViewHolder holder = new ViewHolder();
+            holder.titleBuffer = new CharArrayBuffer(64);
+            holder.mainLine = (TextView) view.findViewById(R.id.title);
+            view.findViewById(R.id.date).setVisibility(View.GONE);
+            view.setTag(holder);
+
+            return view;
+        }
+
+        @Override
+        public void onContentChanged() {
+            // Kick off an async query to refresh the list
+            new TagLoaderTask().execute((Void[]) null);
+        }
+    }
+
+    @Override
+    public void onClick(View target) {
+        switch (target.getId()) {
+            case R.id.toggle_enabled_target:
+                boolean enabled = !mEnabled.isChecked();
+                if (enabled) {
+                    if (mActiveTag != null) {
+                        enableSharing();
+                        return;
+                    }
+                    // TODO: just disable the checkbox when no tag is set
+                    Toast.makeText(
+                            this,
+                            "You must select a tag to share first.",
+                            Toast.LENGTH_SHORT).show();
+                }
+
+                disableSharing();
+                break;
+
+            case R.id.add_tag:
+                // TODO: use implicit intents.
+                Intent intent = new Intent(this, EditTagActivity.class);
+                startActivityForResult(intent, REQUEST_EDIT);
+                break;
+
+            case R.id.active_tag:
+                showDialog(DIALOG_ID_SELECT_ACTIVE_TAG);
+                break;
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_EDIT && resultCode == RESULT_OK) {
+            NdefMessage msg = (NdefMessage) Preconditions.checkNotNull(
+                    data.getParcelableExtra(EditTagActivity.EXTRA_RESULT_MSG));
+
+            if (mTagIdInEdit != -1) {
+                TagService.updateMyMessage(this, mTagIdInEdit, msg);
+            } else {
+                TagService.saveMyMessages(this, new NdefMessage[] { msg });
+            }
+        }
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id, Bundle args) {
+        if (id == DIALOG_ID_SELECT_ACTIVE_TAG) {
+            mSelectActiveTagDialog = new WeakReference<SelectActiveTagDialog>(
+                    new SelectActiveTagDialog(this, mAdapter.getCursor()));
+            return mSelectActiveTagDialog.get();
+        }
+        return super.onCreateDialog(id, args);
+    }
+
+    /**
+     * Selects the tag to be used as the "My tag" shared tag.
+     *
+     * This does not necessarily persist the selection to the {@code NfcAdapter}. That must be done
+     * via {@link #enableSharing}. However, it will call {@link #disableSharing} if the tag
+     * is invalid.
+     */
+    private void selectActiveTag(int position) {
+        Cursor cursor = mAdapter.getCursor();
+        if (cursor != null && cursor.moveToPosition(position)) {
+            mActiveTagId = cursor.getLong(TagQuery.COLUMN_ID);
+
+            try {
+                mActiveTag = new NdefMessage(cursor.getBlob(TagQuery.COLUMN_BYTES));
+
+                // Persist active tag info to preferences.
+                getPreferences(Context.MODE_PRIVATE)
+                        .edit()
+                        .putLong(PREF_KEY_ACTIVE_TAG, mActiveTagId)
+                        .apply();
+
+                // Notify NFC adapter of the My tag contents.
+                updateActiveTagView(cursor.getString(TagQuery.COLUMN_TITLE));
+
+            } catch (FormatException e) {
+                // TODO: handle.
+                disableSharing();
+            }
+        } else {
+            updateActiveTagView(null);
+            disableSharing();
+        }
+    }
+
+    private void enableSharing() {
+        mEnabled.setChecked(true);
+        NfcAdapter.getDefaultAdapter(this).setLocalNdefMessage(
+            Preconditions.checkNotNull(mActiveTag));
+    }
+
+    private void disableSharing() {
+        mEnabled.setChecked(false);
+        NfcAdapter.getDefaultAdapter(this).setLocalNdefMessage(null);
+    }
+
+    private void updateActiveTagView(String title) {
+        if (title == null) {
+            mActiveTagDetails.setVisibility(View.GONE);
+            mSelectActiveTagAnchor.setVisibility(View.VISIBLE);
+        } else {
+            mActiveTagDetails.setVisibility(View.VISIBLE);
+            ((TextView) mActiveTagDetails.findViewById(R.id.active_tag_title)).setText(title);
+            mSelectActiveTagAnchor.setVisibility(View.GONE);
+        }
+    }
+
+    class SelectActiveTagDialog extends AlertDialog
+            implements DialogInterface.OnClickListener, OnItemClickListener {
+
+        private final ArrayList<HashMap<String, String>> mData;
+        private final SimpleAdapter mSelectAdapter;
+
+        protected SelectActiveTagDialog(Context context, Cursor cursor) {
+            super(context);
+
+            setTitle(context.getResources().getString(R.string.choose_my_tag));
+            LayoutInflater inflater = LayoutInflater.from(context);
+            ListView list = new ListView(MyTagList.this);
+
+            mData = Lists.newArrayList();
+            mSelectAdapter = new SimpleAdapter(
+                    context,
+                    mData,
+                    android.R.layout.simple_list_item_1,
+                    new String[] { "title" },
+                    new int[] { android.R.id.text1 });
+
+            list.setAdapter(mSelectAdapter);
+            list.setOnItemClickListener(this);
+            setView(list);
+            setIcon(0);
+            setButton(
+                    DialogInterface.BUTTON_POSITIVE,
+                    context.getString(android.R.string.cancel),
+                    this);
+
+            setData(cursor);
+        }
+
+        public void setData(final Cursor cursor) {
+            if ((cursor == null) || (cursor.getCount() == 0)) {
+                cancel();
+                return;
+            }
+            mData.clear();
+
+            cursor.moveToPosition(-1);
+            while (cursor.moveToNext()) {
+                mData.add(new HashMap<String, String>() {{
+                    put("title", cursor.getString(MyTagList.TagQuery.COLUMN_TITLE));
+                }});
+            }
+
+            mSelectAdapter.notifyDataSetChanged();
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            cancel();
+        }
+
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            selectActiveTag(position);
+            enableSharing();
+            cancel();
+        }
+    }
+}
index a514946..cff0f90 100644 (file)
@@ -63,7 +63,7 @@ public class TagBrowserActivity extends TabActivity implements DialogInterface.O
         tabHost.addTab(tabHost.newTabSpec("mytag")
                 .setIndicator(getText(R.string.tab_my_tag),
                         res.getDrawable(R.drawable.ic_tab_my_tag))
-                .setContent(new Intent().setClass(this, MyTagActivity.class)));
+                .setContent(new Intent().setClass(this, MyTagList.class)));
 
         SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE);
         if (!preferences.getBoolean(PREF_KEY_SHOW_INTRO, false)) {
index ccb20ba..f3d9c82 100644 (file)
@@ -17,6 +17,7 @@
 package com.android.apps.tag;
 
 import com.android.apps.tag.provider.TagContract.NdefMessages;
+import com.android.apps.tag.provider.TagProvider;
 
 import android.app.Activity;
 import android.app.ListActivity;
@@ -117,11 +118,13 @@ public class TagList extends ListActivity implements OnClickListener {
     final class TagLoaderTask extends AsyncTask<Void, Void, Cursor> {
         @Override
         public Cursor doInBackground(Void... args) {
-            String selection = mShowStarredOnly ? NdefMessages.STARRED + "=1" : null;
+            String starred = mShowStarredOnly ? NdefMessages.STARRED + "=1" : null;
+            String notMyTag = NdefMessages.IS_MY_TAG + "!=1";
+
             Cursor cursor = getContentResolver().query(
                     NdefMessages.CONTENT_URI,
                     TagQuery.PROJECTION,
-                    selection,
+                    TagProvider.concatenateWhere(starred, notMyTag),
                     null, NdefMessages.DATE + " DESC");
 
             // Ensure the cursor executes and fills its window
index dee2f00..5847bc2 100644 (file)
@@ -38,6 +38,8 @@ public class TagService extends IntentService {
     private static final String EXTRA_UNSTAR_URI = "remove_star";
     private static final String EXTRA_STARRED = "starred";
     private static final String EXTRA_PENDING_INTENT = "pending";
+    private static final String EXTRA_SAVE_IN_MY_TAGS = "my_tags";
+    private static final String EXTRA_REPLACE_ID = "replace";
 
     private static final boolean DEBUG = true;
 
@@ -51,19 +53,32 @@ public class TagService extends IntentService {
             Parcelable[] msgs = intent.getParcelableArrayExtra(EXTRA_SAVE_MSGS);
             NdefMessage msg = (NdefMessage) msgs[0];
 
-            ContentValues values = NdefMessages.toValues(this, msg, false, System.currentTimeMillis());
-            Uri uri = getContentResolver().insert(NdefMessages.CONTENT_URI, values);
-
-            if (intent.hasExtra(EXTRA_PENDING_INTENT)) {
-                Intent result = new Intent();
-                result.setData(uri);
-
-                PendingIntent pending = (PendingIntent) intent.getParcelableExtra(EXTRA_PENDING_INTENT);
-
-                try {
-                    pending.send(this, 0, result);
-                } catch (CanceledException e) {
-                    if (DEBUG) Log.d(TAG, "Pending intent was canceled.");
+            ContentValues values = NdefMessages.toValues(
+                    this, msg,
+                    intent.getBooleanExtra(EXTRA_STARRED, false),
+                    intent.getBooleanExtra(EXTRA_SAVE_IN_MY_TAGS, false),
+                    System.currentTimeMillis());
+
+            if (intent.hasExtra(EXTRA_REPLACE_ID)) {
+                long id = intent.getLongExtra(EXTRA_REPLACE_ID, 0);
+                String where = NdefMessages._ID + "=" + id;
+                getContentResolver().update(NdefMessages.CONTENT_URI, values, where, null);
+            } else {
+                Uri uri = getContentResolver().insert(NdefMessages.CONTENT_URI, values);
+
+                if (intent.hasExtra(EXTRA_PENDING_INTENT)) {
+                    Intent result = new Intent();
+                    result.setData(uri);
+
+                    PendingIntent pending = (PendingIntent) intent.getParcelableExtra(
+                            EXTRA_PENDING_INTENT);
+                    if (pending != null) {
+                        try {
+                            pending.send(this, 0, result);
+                        } catch (CanceledException e) {
+                            if (DEBUG) Log.d(TAG, "Pending intent was canceled.");
+                        }
+                    }
                 }
             }
 
@@ -100,6 +115,21 @@ public class TagService extends IntentService {
         context.startService(intent);
     }
 
+    public static void saveMyMessages(Context context, NdefMessage[] msgs) {
+        Intent intent = new Intent(context, TagService.class);
+        intent.putExtra(TagService.EXTRA_SAVE_MSGS, msgs);
+        intent.putExtra(TagService.EXTRA_SAVE_IN_MY_TAGS, true);
+        context.startService(intent);
+    }
+
+    public static void updateMyMessage(Context context, long id, NdefMessage msg) {
+        Intent intent = new Intent(context, TagService.class);
+        intent.putExtra(TagService.EXTRA_SAVE_MSGS, new NdefMessage[] { msg });
+        intent.putExtra(TagService.EXTRA_SAVE_IN_MY_TAGS, true);
+        intent.putExtra(TagService.EXTRA_REPLACE_ID, id);
+        context.startService(intent);
+    }
+
     public static void delete(Context context, Uri uri) {
         Intent intent = new Intent(context, TagService.class);
         intent.putExtra(TagService.EXTRA_DELETE_URI, uri);
index acc487e..2d66c7f 100644 (file)
@@ -61,7 +61,7 @@ public class TagContract {
         public static final String BYTES = "bytes";
         public static final String DATE = "date";
         public static final String STARRED = "starred";
-
+        public static final String IS_MY_TAG = "mytag";
 
         public static class MIME implements OpenableColumns {
             public static final String CONTENT_DIRECTORY_MIME = "mime";
@@ -72,12 +72,14 @@ public class TagContract {
         /**
          * Converts an NdefMessage to ContentValues that can be insrted into this table.
          */
-        public static ContentValues toValues(Context context, NdefMessage msg, boolean isStarred, long date) {
+        public static ContentValues toValues(Context context, NdefMessage msg, boolean isStarred,
+                boolean isMyTag, long date) {
             ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
             ContentValues values = new ContentValues();
             values.put(BYTES, msg.toByteArray());
             values.put(DATE, date);
             values.put(STARRED, isStarred ? 1 : 0);
+            values.put(IS_MY_TAG, isMyTag ? 1 : 0);
             values.put(TITLE, parsedMsg.getSnippet(context, Locale.getDefault()));
             return values;
         }
index 3958b87..55e9275 100644 (file)
@@ -29,7 +29,7 @@ import android.database.sqlite.SQLiteOpenHelper;
 public class TagDBHelper extends SQLiteOpenHelper {
 
     private static final String DATABASE_NAME = "tags.db";
-    private static final int DATABASE_VERSION = 14;
+    private static final int DATABASE_VERSION = 15;
 
     public static final String TABLE_NAME_NDEF_MESSAGES = "ndef_msgs";
 
@@ -50,7 +50,8 @@ public class TagDBHelper extends SQLiteOpenHelper {
                 NdefMessages.DATE + " INTEGER NOT NULL, " +
                 NdefMessages.TITLE + " TEXT NOT NULL DEFAULT ''," +
                 NdefMessages.BYTES + " BLOB NOT NULL, " +
-                NdefMessages.STARRED + " INTEGER NOT NULL DEFAULT 0" +  // boolean
+                NdefMessages.STARRED + " INTEGER NOT NULL DEFAULT 0," +  // boolean
+                NdefMessages.IS_MY_TAG + " INTEGER NOT NULL DEFAULT 0" + // boolean
                 ");");
     }