diff --git a/apps/Tag/AndroidManifest.xml b/apps/Tag/AndroidManifest.xml index 4414d7360..ba01fff2b 100644 --- a/apps/Tag/AndroidManifest.xml +++ b/apps/Tag/AndroidManifest.xml @@ -20,26 +20,33 @@ own application, the package name must be changed from "com.example.*" to come from a domain that you own or have control over. --> + package="com.android.apps.tag" +> + + + + - + - - - + + + - + + - + - - diff --git a/apps/Tag/res/values/styles.xml b/apps/Tag/res/layout/tag_divider.xml similarity index 77% rename from apps/Tag/res/values/styles.xml rename to apps/Tag/res/layout/tag_divider.xml index 40e29fb1d..b6b1b7cd4 100644 --- a/apps/Tag/res/values/styles.xml +++ b/apps/Tag/res/layout/tag_divider.xml @@ -14,10 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - + \ No newline at end of file diff --git a/apps/Tag/res/layout/tag_text.xml b/apps/Tag/res/layout/tag_text.xml new file mode 100644 index 000000000..903865055 --- /dev/null +++ b/apps/Tag/res/layout/tag_text.xml @@ -0,0 +1,26 @@ + + + + \ No newline at end of file diff --git a/apps/Tag/res/layout/tag_viewer_list.xml b/apps/Tag/res/layout/tag_viewer_list.xml new file mode 100644 index 000000000..3d322d539 --- /dev/null +++ b/apps/Tag/res/layout/tag_viewer_list.xml @@ -0,0 +1,21 @@ + + + + diff --git a/apps/Tag/res/values/strings.xml b/apps/Tag/res/values/strings.xml index 3d0432765..9b736784e 100644 --- a/apps/Tag/res/values/strings.xml +++ b/apps/Tag/res/values/strings.xml @@ -20,4 +20,10 @@ help and info Saved + + Recent + + + Saved + diff --git a/apps/Tag/src/com/android/apps/tag/NdefUtil.java b/apps/Tag/src/com/android/apps/tag/NdefUtil.java index 87efb9e43..40f5bcf7e 100644 --- a/apps/Tag/src/com/android/apps/tag/NdefUtil.java +++ b/apps/Tag/src/com/android/apps/tag/NdefUtil.java @@ -16,17 +16,17 @@ package com.android.apps.tag; -import android.util.Log; import com.google.common.base.Preconditions; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.Iterables; import com.google.common.primitives.Bytes; + +import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import java.io.UnsupportedEncodingException; -import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charsets; import java.util.ArrayList; @@ -47,8 +47,7 @@ public class NdefUtil { * This is a mapping of "URI Identifier Codes" to URI string prefixes, * per section 3.2.2 of the NFC Forum URI Record Type Definition document. */ - private static final - BiMap URI_PREFIX_MAP = ImmutableBiMap.builder() + private static final BiMap URI_PREFIX_MAP = ImmutableBiMap.builder() .put((byte) 0x00, "") .put((byte) 0x01, "http://www.") .put((byte) 0x02, "https://www.") @@ -88,9 +87,9 @@ public class NdefUtil { .build(); /** - * Create a new {@link NdefRecord} containing the supplied {@link URI}. + * Create a new {@link NdefRecord} containing the supplied {@link Uri}. */ - public static NdefRecord toUriRecord(URI uri) { + public static NdefRecord toUriRecord(Uri uri) { byte[] uriBytes = uri.toString().getBytes(Charsets.UTF_8); /* @@ -110,18 +109,16 @@ public class NdefUtil { } /** - * Convert {@link NdefRecord} into a {@link URI}. + * Convert {@link NdefRecord} into a {@link Uri}. * * TODO: This class does not handle NdefRecords where the TNF * (Type Name Format) of the class is {@link NdefRecord#TNF_ABSOLUTE_URI}. * This should be fixed. * - * @throws URISyntaxException if the {@code NdefRecord} contains an - * invalid URI. * @throws IllegalArgumentException if the NdefRecord is not a * record containing a URI. */ - public static URI toURI(NdefRecord record) throws URISyntaxException { + public static Uri toUri(NdefRecord record) { Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN); Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_URI)); @@ -140,17 +137,15 @@ public class NdefUtil { prefix.getBytes(Charsets.UTF_8), Arrays.copyOfRange(payload, 1, payload.length)); - return new URI(new String(fullUri, Charsets.UTF_8)); + return Uri.parse(new String(fullUri, Charsets.UTF_8)); } - public static boolean isURI(NdefRecord record) { + public static boolean isUri(NdefRecord record) { try { - toURI(record); + toUri(record); return true; } catch (IllegalArgumentException e) { return false; - } catch (URISyntaxException e) { - return false; } } @@ -207,33 +202,29 @@ public class NdefUtil { return Iterables.filter(getObjects(message), String.class); } - public static Iterable getURIs(NdefMessage message) { - return Iterables.filter(getObjects(message), URI.class); + public static Iterable getUris(NdefMessage message) { + return Iterables.filter(getObjects(message), Uri.class); } /** * Parse the provided {@code NdefMessage}, extracting all known * objects from the message. Typically this list will consist of - * {@link String}s corresponding to NDEF text records, or {@link URI}s + * {@link String}s corresponding to NDEF text records, or {@link Uri}s * corresponding to NDEF URI records. *

* TODO: Is this API too generic? Should we keep it? */ - private static Iterable getObjects(NdefMessage message) { - try { - List retval = new ArrayList(); - for (NdefRecord record : message.getRecords()) { - if (isURI(record)) { - retval.add(toURI(record)); - } else if (isText(record)) { - retval.add(toText(record)); - } else if (SmartPoster.isPoster(record)) { - retval.add(SmartPoster.from(record)); - } + public static Iterable getObjects(NdefMessage message) { + List retval = new ArrayList(); + for (NdefRecord record : message.getRecords()) { + if (isUri(record)) { + retval.add(toUri(record)); + } else if (isText(record)) { + retval.add(toText(record)); + } else if (SmartPoster.isPoster(record)) { + retval.add(SmartPoster.from(record)); } - return retval; - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); } + return retval; } } diff --git a/apps/Tag/src/com/android/apps/tag/SaveTag.java b/apps/Tag/src/com/android/apps/tag/SaveTag.java deleted file mode 100644 index 6a5f66fcc..000000000 --- a/apps/Tag/src/com/android/apps/tag/SaveTag.java +++ /dev/null @@ -1,96 +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 android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.nfc.NdefMessage; -import android.nfc.NdefTag; -import android.nfc.NfcAdapter; -import android.os.Bundle; -import android.util.Log; -import android.view.WindowManager; -import android.widget.Toast; - -/** - * An {@code Activity} which handles a broadcast of a new tag that the device just discovered. - */ -public class SaveTag extends Activity implements DialogInterface.OnClickListener { - private static final String TAG = "SaveTag"; - - @Override - protected void onStart() { - super.onStart(); - - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON - | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON - | WindowManager.LayoutParams.FLAG_DIM_BEHIND - ); - - showDialog(1); - NdefTag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG); - NdefMessage[] msgs = tag.getNdefMessages(); - - if (msgs.length == 0) { - Log.d(TAG, "No NDEF messages"); - return; - } - if (msgs.length > 1) { - Log.d(TAG, "Multiple NDEF messages, only saving first"); - } - String s = toHexString(msgs[0].toByteArray()); - Log.d("SaveTag", s); - Toast.makeText(this.getBaseContext(), "SaveTag: " + s, Toast.LENGTH_SHORT).show(); - } - - @Override - protected Dialog onCreateDialog(int id, Bundle args) { - return new AlertDialog.Builder(this) - .setTitle("Welcome! T2000 Festival") - .setPositiveButton("Save", this) - .setNegativeButton("Cancel", this) - .create(); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - - @Override - protected void onStop() { - super.onStop(); - dismissDialog(1); - } - - private static final char[] hexDigits = "0123456789abcdef".toCharArray(); - - private static String toHexString(byte[] bytes) { - StringBuilder sb = new StringBuilder(3 * bytes.length); - for (byte b : bytes) { - sb.append("(byte) 0x") - .append(hexDigits[(b >> 4) & 0xf]) - .append(hexDigits[b & 0xf]).append(", "); - } - return sb.toString(); - } -} diff --git a/apps/Tag/src/com/android/apps/tag/SmartPoster.java b/apps/Tag/src/com/android/apps/tag/SmartPoster.java index 1e107235b..dd6f518fb 100644 --- a/apps/Tag/src/com/android/apps/tag/SmartPoster.java +++ b/apps/Tag/src/com/android/apps/tag/SmartPoster.java @@ -18,13 +18,15 @@ package com.android.apps.tag; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; + +import android.net.Uri; +import android.nfc.FormatException; import android.nfc.NdefMessage; import android.nfc.NdefRecord; -import android.nfc.FormatException; + +import java.util.Arrays; import javax.annotation.Nullable; -import java.net.URI; -import java.util.Arrays; /** * A representation of an NFC Forum "Smart Poster". @@ -39,7 +41,7 @@ public class SmartPoster { * This record is optional." */ - private final String titleRecord; + private final String mTitleRecord; /** * NFC Forum Smart Poster Record Type Definition section 3.2.1. @@ -48,22 +50,22 @@ public class SmartPoster { * records are just metadata about this record. There MUST be one URI * record and there MUST NOT be more than one." */ - private final URI uriRecord; + private final Uri mUriRecord; - private SmartPoster(URI uri, @Nullable String title) { - uriRecord = Preconditions.checkNotNull(uri); - titleRecord = title; + private SmartPoster(Uri uri, @Nullable String title) { + mUriRecord = Preconditions.checkNotNull(uri); + mTitleRecord = title; } - public URI getURI() { - return uriRecord; + public Uri getUri() { + return mUriRecord; } /** - * Returns the title of the smartposter. This may be {@code null}. + * Returns the title of the smart poster. This may be {@code null}. */ public String getTitle() { - return titleRecord; + return mTitleRecord; } public static SmartPoster from(NdefRecord record) { @@ -71,7 +73,7 @@ public class SmartPoster { Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER)); try { NdefMessage subRecords = new NdefMessage(record.getPayload()); - URI uri = Iterables.getOnlyElement(NdefUtil.getURIs(subRecords)); + Uri uri = Iterables.getOnlyElement(NdefUtil.getUris(subRecords)); Iterable textFields = NdefUtil.getTextFields(subRecords); String title = null; if (!Iterables.isEmpty(textFields)) { diff --git a/apps/Tag/src/com/android/apps/tag/TagAdapter.java b/apps/Tag/src/com/android/apps/tag/TagAdapter.java new file mode 100644 index 000000000..f69c3ea4a --- /dev/null +++ b/apps/Tag/src/com/android/apps/tag/TagAdapter.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.apps.tag; + +import com.android.apps.tag.TagDBHelper.NdefMessagesTable; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.text.format.DateUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Adapter; +import android.widget.CursorAdapter; +import android.widget.TextView; + +/** + * A custom {@link Adapter} that renders tag entries for a list. + */ +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) { + TextView mainLine = (TextView) view.findViewById(R.id.title); + TextView dateLine = (TextView) view.findViewById(R.id.date); + + NdefMessage msg = null; + try { + msg = new NdefMessage(cursor.getBlob(cursor.getColumnIndex(NdefMessagesTable.BYTES))); + } catch (FormatException e) { + Log.e("foo", "poorly formatted message", e); + } + + if (msg == null) { + mainLine.setText("Invalid tag"); + } else { + try { + SmartPoster poster = SmartPoster.from(msg.getRecords()[0]); + mainLine.setText(poster.getTitle()); + } catch (IllegalArgumentException e) { + // Not a smart poster + NdefRecord record = msg.getRecords()[0]; + Uri uri = null; + try { + uri = NdefUtil.toUri(record); + mainLine.setText(uri.toString()); + } catch (IllegalArgumentException e2) { + mainLine.setText("Not a smart poster or URL"); + } + } + } + dateLine.setText(DateUtils.getRelativeTimeSpanString( + context, cursor.getLong(cursor.getColumnIndex(NdefMessagesTable.DATE)))); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.tag_list_item, null); + } +} diff --git a/apps/Tag/src/com/android/apps/tag/TagBroadcastReceiver.java b/apps/Tag/src/com/android/apps/tag/TagBroadcastReceiver.java deleted file mode 100644 index b5ef1e740..000000000 --- a/apps/Tag/src/com/android/apps/tag/TagBroadcastReceiver.java +++ /dev/null @@ -1,43 +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 android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.nfc.NdefTag; - -import android.nfc.NdefMessage; -import android.nfc.NfcAdapter; - -/** - * When we receive a new NDEF tag, start the activity to - * process the tag. - */ -public class TagBroadcastReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(NfcAdapter.ACTION_NDEF_TAG_DISCOVERED)) { - NdefTag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - Intent i = new Intent(context, SaveTag.class) - .putExtra(NfcAdapter.EXTRA_TAG, tag) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - } - } -} diff --git a/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java b/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java index a4cbb86a6..38d3bfab8 100644 --- a/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java +++ b/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java @@ -16,6 +16,7 @@ package com.android.apps.tag; +import android.app.Activity; import android.app.TabActivity; import android.content.Intent; import android.content.res.Resources; @@ -23,34 +24,27 @@ import android.os.Bundle; import android.widget.TabHost; /** - * A browsing {@code Activity} that displays the saved tags in categories under tabs. + * A browsing {@link Activity} that displays the saved tags in categories under tabs. */ public class TagBrowserActivity extends TabActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // While we're doing development, delete the database every time we start. - getBaseContext().getDatabasePath("Tags.db").delete(); - setContentView(R.layout.main); Resources res = getResources(); TabHost tabHost = getTabHost(); - Intent i = new Intent().setClass(this, TagList.class); - Intent iSavedList = new Intent().setClass(this, TagList.class) - .putExtra(TagList.SHOW_SAVED_ONLY, true); - Intent iRecentList = new Intent().setClass(this, TagList.class); - - TabHost.TabSpec spec1 = tabHost.newTabSpec("1") - .setIndicator("Saved", res.getDrawable(R.drawable.ic_menu_tag)) - .setContent(iSavedList); + TabHost.TabSpec spec1 = tabHost.newTabSpec("saved") + .setIndicator(getText(R.string.tab_saved), res.getDrawable(R.drawable.ic_menu_tag)) + .setContent(new Intent().setClass(this, TagList.class) + .putExtra(TagList.EXTRA_SHOW_SAVED_ONLY, true)); tabHost.addTab(spec1); - TabHost.TabSpec spec2 = tabHost.newTabSpec("2") - .setIndicator("Recent", res.getDrawable(R.drawable.ic_menu_desk_clock)) - .setContent(iRecentList); + TabHost.TabSpec spec2 = tabHost.newTabSpec("recent") + .setIndicator(getText(R.string.tab_recent), res.getDrawable(R.drawable.ic_menu_desk_clock)) + .setContent(new Intent().setClass(this, TagList.class)); tabHost.addTab(spec2); } } diff --git a/apps/Tag/src/com/android/apps/tag/TagCursorAdapter.java b/apps/Tag/src/com/android/apps/tag/TagCursorAdapter.java deleted file mode 100644 index a658268e6..000000000 --- a/apps/Tag/src/com/android/apps/tag/TagCursorAdapter.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2010 Google Inc. All Rights Reserved. - -package com.android.apps.tag; - -import android.content.Context; -import android.database.Cursor; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Adapter; -import android.widget.CursorAdapter; -import android.widget.TextView; - -/** - * A custom {@link Adapter} that renders tag entries for a list. - */ -public class TagCursorAdapter extends CursorAdapter { - - private final LayoutInflater mInflater; - - public TagCursorAdapter(Context context, Cursor c) { - super(context, c); - - mInflater = LayoutInflater.from(context); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - TextView mainLine = (TextView) view.findViewById(R.id.title); - TextView dateLine = (TextView) view.findViewById(R.id.date); - - // TODO(benkomalo): either write a cursor abstraction, or use constants for column indices. - mainLine.setText(cursor.getString(cursor.getColumnIndex("bytes"))); - dateLine.setText(DateUtils.getRelativeTimeSpanString( - context, cursor.getLong(cursor.getColumnIndex("date")))); - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.tag_list_item, null); - } -} diff --git a/apps/Tag/src/com/android/apps/tag/TagDBHelper.java b/apps/Tag/src/com/android/apps/tag/TagDBHelper.java index 06fa9e001..654cb47f6 100644 --- a/apps/Tag/src/com/android/apps/tag/TagDBHelper.java +++ b/apps/Tag/src/com/android/apps/tag/TagDBHelper.java @@ -22,34 +22,32 @@ import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; +import android.net.Uri; import android.nfc.FormatException; import android.nfc.NdefMessage; import android.nfc.NdefRecord; -import java.net.URI; -import java.util.Date; - /** * Database utilities for the saved tags. */ public class TagDBHelper extends SQLiteOpenHelper { - private static final int DATABASE_VERSION = 1; + private static final String DATABASE_NAME = "tags.db"; + private static final int DATABASE_VERSION = 3; - private static final String NDEF_MSG = "create table NdefMessage (" - + "_id INTEGER NOT NULL, " - + "bytes BLOB NOT NULL, " - + "date INTEGER NOT NULL, " - + "saved TEXT NOT NULL default 0," // boolean - + "PRIMARY KEY(_id)" - + ")"; - - private static final String INSERT = - "INSERT INTO NdefMessage (bytes, date, saved) values (?, ?, ?)"; + public interface NdefMessagesTable { + public static final String TABLE_NAME = "nedf_msg"; + public static final String _ID = "_id"; + public static final String TITLE = "title"; + public static final String BYTES = "bytes"; + public static final String DATE = "date"; + public static final String SAVED = "saved"; + } + /** * A real NFC tag containing an NFC "smart poster". This smart poster - * consists of the text "NFC Forum Type 4 Tag" in english combined with + * consists of the text "NFC Forum Type 4 Tag" in English combined with * the URL "http://www.nxp.com/nfc" */ public static final byte[] REAL_NFC_MSG = new byte[] { @@ -115,53 +113,85 @@ public class TagDBHelper extends SQLiteOpenHelper { // end smart poster payload }; - public TagDBHelper(Context context) { - this(context, "Tags.db"); + private static TagDBHelper sInstance; + + public static synchronized TagDBHelper getInstance(Context context) { + if (sInstance == null) { + sInstance = new TagDBHelper(context.getApplicationContext()); + } + return sInstance; + } + + private TagDBHelper(Context context) { + this(context, DATABASE_NAME); } @VisibleForTesting - public TagDBHelper(Context context, String dbFile) { + TagDBHelper(Context context, String dbFile) { super(context, dbFile, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { - db.execSQL(NDEF_MSG); + db.execSQL("CREATE TABLE " + NdefMessagesTable.TABLE_NAME + " (" + + NdefMessagesTable._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + NdefMessagesTable.TITLE + " TEXT NOT NULL DEFAULT ''," + + NdefMessagesTable.BYTES + " BLOB NOT NULL, " + + NdefMessagesTable.DATE + " INTEGER NOT NULL, " + + NdefMessagesTable.SAVED + " INTEGER NOT NULL DEFAULT 0" + // boolean + ");"); + db.execSQL("CREATE INDEX msgIndex ON " + NdefMessagesTable.TABLE_NAME + " (" + + NdefMessagesTable.DATE + " DESC, " + + NdefMessagesTable.SAVED + " ASC" + + ")"); + + addTestData(db); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // Drop everything and recreate it for now + db.execSQL("DROP TABLE IF EXISTS " + NdefMessagesTable.TABLE_NAME); + onCreate(db); + } + + private void addTestData(SQLiteDatabase db) { // A fake message containing 1 URL NdefMessage msg1 = new NdefMessage(new NdefRecord[] { - NdefUtil.toUriRecord(URI.create("http://www.google.com")) + NdefUtil.toUriRecord(Uri.parse("http://www.google.com")) }); // A fake message containing 2 URLs NdefMessage msg2 = new NdefMessage(new NdefRecord[] { - NdefUtil.toUriRecord(URI.create("http://www.youtube.com")), - NdefUtil.toUriRecord(URI.create("http://www.android.com")) + NdefUtil.toUriRecord(Uri.parse("http://www.youtube.com")), + NdefUtil.toUriRecord(Uri.parse("http://www.android.com")) }); - insert(db, msg1, false); - insert(db, msg2, true); + insertNdefMessage(db, msg1, false); + insertNdefMessage(db, msg2, true); try { // A real message obtained from an NFC Forum Type 4 tag. NdefMessage msg3 = new NdefMessage(REAL_NFC_MSG); - insert(db, msg3, false); + insertNdefMessage(db, msg3, false); } catch (FormatException e) { throw new RuntimeException(e); } } - private void insert(SQLiteDatabase db, NdefMessage msg, boolean isSaved) { - SQLiteStatement stmt = db.compileStatement(INSERT); - stmt.bindString(1, new String(msg.toByteArray())); // TODO: This should be a blob - stmt.bindLong(2, System.currentTimeMillis()); - String isSavedStr = isSaved ? "1" : "0"; - stmt.bindString(3, isSavedStr); - stmt.executeInsert(); - stmt.close(); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + public void insertNdefMessage(SQLiteDatabase db, NdefMessage msg, boolean isSaved) { + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement("INSERT INTO " + NdefMessagesTable.TABLE_NAME + + "(" + NdefMessagesTable.BYTES + ", " + NdefMessagesTable.DATE + ", " + + NdefMessagesTable.SAVED + ") values (?, ?, ?)"); + stmt.bindBlob(1, msg.toByteArray()); + stmt.bindLong(2, System.currentTimeMillis()); + stmt.bindLong(3, isSaved ? 1 : 0); + stmt.executeInsert(); + } finally { + if (stmt != null) stmt.close(); + } } } diff --git a/apps/Tag/src/com/android/apps/tag/TagList.java b/apps/Tag/src/com/android/apps/tag/TagList.java index 369ef6557..45f6f6577 100644 --- a/apps/Tag/src/com/android/apps/tag/TagList.java +++ b/apps/Tag/src/com/android/apps/tag/TagList.java @@ -16,42 +16,47 @@ package com.android.apps.tag; +import com.android.apps.tag.TagDBHelper.NdefMessagesTable; + +import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.ListActivity; import android.content.DialogInterface; +import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.os.AsyncTask; import android.os.Bundle; +import android.util.Log; import android.view.Menu; import android.view.View; import android.widget.ListView; -import android.widget.SimpleCursorAdapter; /** - * An {@code Activity} that displays a flat list of tags that can be "opened". + * An {@link Activity} that displays a flat list of tags that can be "opened". */ public class TagList extends ListActivity implements DialogInterface.OnClickListener { - private SQLiteDatabase db; - private Cursor cursor; - static final String SHOW_SAVED_ONLY = "show_saved_only"; + static final String TAG = "TagList"; + + static final String EXTRA_SHOW_SAVED_ONLY = "show_saved_only"; + + SQLiteDatabase mDatabase; + TagAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - boolean showSavedOnly = getIntent().getBooleanExtra(SHOW_SAVED_ONLY, false); - db = new TagDBHelper(getBaseContext()).getReadableDatabase(); - String selection = showSavedOnly ? "saved=1" : null; + boolean showSavedOnly = getIntent().getBooleanExtra(EXTRA_SHOW_SAVED_ONLY, false); + mDatabase = TagDBHelper.getInstance(this).getReadableDatabase(); + String selection = showSavedOnly ? NdefMessagesTable.SAVED + "=1" : null; - // TODO: Use an AsyncQueryHandler so that DB queries are not done on UI thread. - cursor = db.query( - "NdefMessage", - new String[] { "_id", "bytes", "date" }, - selection, - null, null, null, null); - - setListAdapter(new TagCursorAdapter(this, cursor)); + new TagLoaderTask().execute(selection); + mAdapter = new TagAdapter(this); + setListAdapter(mAdapter); registerForContextMenu(getListView()); } @@ -75,22 +80,53 @@ public class TagList extends ListActivity implements DialogInterface.OnClickList @Override protected void onDestroy() { - if (cursor != null) { - cursor.close(); - } - if (db != null) { - db.close(); + if (mAdapter != null) { + mAdapter.changeCursor(null); } super.onDestroy(); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { - showDialog(1); - super.onListItemClick(l, v, position, id); + Cursor cursor = mAdapter.getCursor(); + cursor.moveToPosition(position); + byte[] tagBytes = cursor.getBlob(cursor.getColumnIndexOrThrow(NdefMessagesTable.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; + } } @Override public void onClick(DialogInterface dialog, int which) { } + + final class TagLoaderTask extends AsyncTask { + @Override + public Cursor doInBackground(String... args) { + String selection = args[0]; + Cursor cursor = mDatabase.query( + NdefMessagesTable.TABLE_NAME, + new String[] { + NdefMessagesTable._ID, + NdefMessagesTable.BYTES, + NdefMessagesTable.DATE, + NdefMessagesTable.TITLE }, + selection, + null, null, null, null); + cursor.getCount(); + return cursor; + } + + @Override + protected void onPostExecute(Cursor cursor) { + mAdapter.changeCursor(cursor); + } + } } diff --git a/apps/Tag/src/com/android/apps/tag/TagViewer.java b/apps/Tag/src/com/android/apps/tag/TagViewer.java new file mode 100644 index 000000000..21f64e161 --- /dev/null +++ b/apps/Tag/src/com/android/apps/tag/TagViewer.java @@ -0,0 +1,137 @@ +/* + * 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 android.app.Activity; +import android.content.Intent; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Color; +import android.net.Uri; +import android.nfc.NdefMessage; +import android.nfc.NdefTag; +import android.nfc.NfcAdapter; +import android.os.AsyncTask; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * An {@link Activity} which handles a broadcast of a new tag that the device just discovered. + */ +public class TagViewer extends Activity { + static final String TAG = "SaveTag"; + static final String EXTRA_TAG_DB_ID = "db_id"; + static final String EXTRA_MESSAGE = "msg"; + + long mTagDatabaseId; + + @Override + protected void onStart() { + super.onStart(); + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + | WindowManager.LayoutParams.FLAG_DIM_BEHIND + ); + + 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 }; + } + } else { + msgs = tag.getNdefMessages(); + // TODO use a service to avoid the process getting reaped during saving + new SaveTagTask().execute(msgs); + } + + if (msgs == null || msgs.length == 0) { + Log.e(TAG, "No NDEF messages"); + finish(); + return; + } + + + LayoutInflater inflater = LayoutInflater.from( + new ContextThemeWrapper(this, android.R.style.Theme_Light)); + LinearLayout list = (LinearLayout) inflater.inflate(R.layout.tag_viewer_list, null, false); + // TODO figure out why the background isn't white, the CTW should force that... + list.setBackgroundColor(Color.WHITE); + setContentView(list); + buildTagViews(list, inflater, msgs); + } + + private void buildTagViews(LinearLayout list, LayoutInflater inflater, NdefMessage[] msgs) { + // The body of the dialog should use the light theme + + // Build the views from the logical records in the messages + boolean first = true; + for (NdefMessage msg : msgs) { + Iterable objects = NdefUtil.getObjects(msg); + for (Object object : objects) { + if (!first) { + list.addView(inflater.inflate(R.layout.tag_divider, list, false)); + first = false; + } + + if (object instanceof String) { + TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false); + text.setText((CharSequence) object); + list.addView(text); + } else if (object instanceof Uri) { + TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false); + text.setText(object.toString()); + list.addView(text); + } else if (object instanceof SmartPoster) { + TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false); + SmartPoster poster = (SmartPoster) object; + text.setText(poster.getTitle()); + list.addView(text); + } + } + } + } + + final class SaveTagTask extends AsyncTask { + @Override + public Void doInBackground(NdefMessage... msgs) { + TagDBHelper helper = TagDBHelper.getInstance(TagViewer.this); + SQLiteDatabase db = helper.getWritableDatabase(); + db.beginTransaction(); + try { + for (NdefMessage msg : msgs) { + helper.insertNdefMessage(db, msg, false); + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + return null; + } + } +} diff --git a/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java b/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java index b7eb86973..e55e07d2d 100644 --- a/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java +++ b/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java @@ -28,6 +28,6 @@ public class SmartPosterTest extends AndroidTestCase { SmartPoster poster = SmartPoster.from(msg.getRecords()[0]); assertEquals("NFC Forum Type 4 Tag", poster.getTitle()); - assertEquals("http://www.nxp.com/nfc", poster.getURI().toString()); + assertEquals("http://www.nxp.com/nfc", poster.getUri().toString()); } }