From 41681e0be1d6ba0005f8c6174e2335e647a0fa22 Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Mon, 4 Oct 2010 09:34:39 -0700 Subject: [PATCH] new SaveTag activity, additional utilities The SaveTag activity will receive an intent with the contents of the NdefRecord, and will be responsible for commiting that record to the database. Add NdefUtil, which can convert an NdefRecord to a URI, and vise-versa. Change-Id: Icf8682a00774da6e246961d34de5bc5baa7550cb --- apps/Tag/Android.mk | 3 +- apps/Tag/AndroidManifest.xml | 1 + .../src/com/android/apps/tag/NdefUtil.java | 150 ++++++++++++++++++ .../Tag/src/com/android/apps/tag/SaveTag.java | 52 ++++++ .../src/com/android/apps/tag/TagDBHelper.java | 56 +++++-- .../Tag/src/com/android/apps/tag/TagList.java | 23 ++- apps/Tag/src/com/android/apps/tag/TagUi.java | 26 --- apps/Tag/src/com/android/apps/tag/Tags.java | 4 + .../apps/tag/TagBroadcastReceiverTest.java | 44 ----- 9 files changed, 269 insertions(+), 90 deletions(-) create mode 100644 apps/Tag/src/com/android/apps/tag/NdefUtil.java create mode 100644 apps/Tag/src/com/android/apps/tag/SaveTag.java delete mode 100644 apps/Tag/src/com/android/apps/tag/TagUi.java delete mode 100644 apps/Tag/tests/src/com/android/apps/tag/TagBroadcastReceiverTest.java diff --git a/apps/Tag/Android.mk b/apps/Tag/Android.mk index 949cd3f6b..f069cd859 100644 --- a/apps/Tag/Android.mk +++ b/apps/Tag/Android.mk @@ -3,11 +3,12 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_JAVA_LIBRARIES := guava + # Only compile source java files in this apk. LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_PACKAGE_NAME := TagApp -# LOCAL_PROGUARD_EABLED := disabled # LOCAL_SDK_VERSION := current diff --git a/apps/Tag/AndroidManifest.xml b/apps/Tag/AndroidManifest.xml index 3388a3031..13fedddd8 100644 --- a/apps/Tag/AndroidManifest.xml +++ b/apps/Tag/AndroidManifest.xml @@ -31,6 +31,7 @@ + diff --git a/apps/Tag/src/com/android/apps/tag/NdefUtil.java b/apps/Tag/src/com/android/apps/tag/NdefUtil.java new file mode 100644 index 000000000..f2b01bbbe --- /dev/null +++ b/apps/Tag/src/com/android/apps/tag/NdefUtil.java @@ -0,0 +1,150 @@ +/* + * 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.google.common.base.Preconditions; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.primitives.Bytes; +import com.trustedlogic.trustednfc.android.NdefRecord; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.Charsets; +import java.util.Arrays; + +/** + * Utilities for dealing with conversions to and from NdefRecords. + * + * TODO: Possibly move this class into core Android. + */ +public class NdefUtil { + private static final byte[] EMPTY = new byte[0]; + + /** + * NFC Forum "URI Record Type Definition" + * + * 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() + .put((byte) 0x00, "") + .put((byte) 0x01, "http://www.") + .put((byte) 0x02, "https://www.") + .put((byte) 0x03, "http://") + .put((byte) 0x04, "https://") + .put((byte) 0x05, "tel:") + .put((byte) 0x06, "mailto:") + .put((byte) 0x07, "ftp://anonymous:anonymous@") + .put((byte) 0x08, "ftp://ftp.") + .put((byte) 0x09, "ftps://") + .put((byte) 0x0A, "sftp://") + .put((byte) 0x0B, "smb://") + .put((byte) 0x0C, "nfs://") + .put((byte) 0x0D, "ftp://") + .put((byte) 0x0E, "dav://") + .put((byte) 0x0F, "news:") + .put((byte) 0x10, "telnet://") + .put((byte) 0x11, "imap:") + .put((byte) 0x12, "rtsp://") + .put((byte) 0x13, "urn:") + .put((byte) 0x14, "pop:") + .put((byte) 0x15, "sip:") + .put((byte) 0x16, "sips:") + .put((byte) 0x17, "tftp:") + .put((byte) 0x18, "btspp://") + .put((byte) 0x19, "btl2cap://") + .put((byte) 0x1A, "btgoep://") + .put((byte) 0x1B, "tcpobex://") + .put((byte) 0x1C, "irdaobex://") + .put((byte) 0x1D, "file://") + .put((byte) 0x1E, "urn:epc:id:") + .put((byte) 0x1F, "urn:epc:tag:") + .put((byte) 0x20, "urn:epc:pat:") + .put((byte) 0x21, "urn:epc:raw:") + .put((byte) 0x22, "urn:epc:") + .put((byte) 0x23, "urn:nfc:") + .build(); + + /** + * Create a new {@link NdefRecord} containing the supplied {@link URI}. + */ + public static NdefRecord toUriRecord(URI uri) { + byte[] uriBytes = uri.toString().getBytes(Charsets.UTF_8); + + /* + * We prepend 0x00 to the bytes of the URI to indicate that this + * is the entire URI, and we are not taking advantage of the + * URI shortening rules in the NFC Forum URI spec section 3.2.2. + * This produces a NdefRecord which is slightly larger than + * necessary. + * + * In the future, we should use the URI shortening rules in 3.2.2 + * to create a smaller NdefRecord. + */ + byte[] payload = Bytes.concat(new byte[] { 0x00 }, uriBytes); + + return new NdefRecord(NdefRecord.TNF_WELL_KNOWN_TYPE, + NdefRecord.TYPE_URI, EMPTY, payload); + } + + /** + * 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 { + Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN_TYPE); + Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.TYPE_URI)); + + byte[] payload = record.getPayload(); + + /* + * payload[0] contains the URI Identifier Code, per the + * NFC Forum "URI Record Type Definition" section 3.2.2. + * + * payload[1]...payload[payload.length - 1] contains the rest of + * the URI. + */ + + String prefix = URI_PREFIX_MAP.get(payload[0]); + byte[] fullUri = Bytes.concat( + prefix.getBytes(Charsets.UTF_8), + Arrays.copyOfRange(payload, 1, payload.length - 1)); + + return new URI(new String(fullUri, Charsets.UTF_8)); + } + + public static boolean isURI(NdefRecord record) { + try { + toURI(record); + return true; + } catch (IllegalArgumentException e) { + return false; + } catch (URISyntaxException e) { + return false; + } + } +} diff --git a/apps/Tag/src/com/android/apps/tag/SaveTag.java b/apps/Tag/src/com/android/apps/tag/SaveTag.java new file mode 100644 index 000000000..877f9a711 --- /dev/null +++ b/apps/Tag/src/com/android/apps/tag/SaveTag.java @@ -0,0 +1,52 @@ +/* + * 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.os.Bundle; +import android.widget.Toast; + +/** + * @author nnk@google.com (Nick Kralevich) + */ +public class SaveTag extends Activity implements DialogInterface.OnClickListener { + + @Override + protected void onStart() { + super.onStart(); + showDialog(1); + String s = getIntent().getExtras().toString(); + 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(); + } +} diff --git a/apps/Tag/src/com/android/apps/tag/TagDBHelper.java b/apps/Tag/src/com/android/apps/tag/TagDBHelper.java index 15c18f3ba..a105d5c78 100644 --- a/apps/Tag/src/com/android/apps/tag/TagDBHelper.java +++ b/apps/Tag/src/com/android/apps/tag/TagDBHelper.java @@ -19,6 +19,13 @@ package com.android.apps.tag; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteStatement; +import com.google.common.annotations.VisibleForTesting; +import com.trustedlogic.trustednfc.android.NdefMessage; +import com.trustedlogic.trustednfc.android.NdefRecord; + +import java.net.URI; +import java.util.Date; /** * @author nnk@google.com (Nick Kralevich) @@ -26,28 +33,51 @@ import android.database.sqlite.SQLiteOpenHelper; public class TagDBHelper extends SQLiteOpenHelper { private static final int DATABASE_VERSION = 1; - private static final String TABLE_CREATE = "create table Tags (" - + "_id INTEGER PRIMARY KEY ASC, " - + "description TEXT, " - + "date TEXT" + + private static final String NDEF_MSG = "create table NdefMessage (" + + "_id INTEGER NOT NULL, " + + "bytes TEXT NOT NULL, " // TODO: This should be a blob + + "date TEXT NOT NULL, " + + "PRIMARY KEY(_id)" + ")"; - private static final String FAKE_DATA = - "INSERT INTO Tags (description) values ('hello world')"; - - private static final String FAKE_DATA2 = - "INSERT INTO Tags (description) values ('hi world')"; + private static final String INSERT = + "INSERT INTO NdefMessage (bytes, date) values (?, ?)"; public TagDBHelper(Context context) { - super(context, "Tags.db", null, DATABASE_VERSION); + this(context, "Tags.db"); + } + + @VisibleForTesting + public TagDBHelper(Context context, String dbFile) { + super(context, dbFile, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { - db.execSQL(TABLE_CREATE); - db.execSQL(FAKE_DATA); - db.execSQL(FAKE_DATA2); + db.execSQL(NDEF_MSG); + + // A fake message containing 1 URL + NdefMessage msg1 = new NdefMessage(new NdefRecord[] { + NdefUtil.toUriRecord(URI.create("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")) + }); + + insert(db, msg1); + insert(db, msg2); + } + + private void insert(SQLiteDatabase db, NdefMessage msg) { + SQLiteStatement stmt = db.compileStatement(INSERT); + stmt.bindString(1, new String(msg.toByteArray())); // TODO: This should be a blob + stmt.bindString(2, new Date().toString()); + stmt.executeInsert(); } @Override diff --git a/apps/Tag/src/com/android/apps/tag/TagList.java b/apps/Tag/src/com/android/apps/tag/TagList.java index f8a93e692..daaa9c47e 100644 --- a/apps/Tag/src/com/android/apps/tag/TagList.java +++ b/apps/Tag/src/com/android/apps/tag/TagList.java @@ -34,24 +34,24 @@ import android.widget.Toast; */ public class TagList extends ListActivity implements DialogInterface.OnClickListener { + private SQLiteDatabase db; + private Cursor cursor; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Toast.makeText(getBaseContext(), "entered method", Toast.LENGTH_SHORT).show(); - SQLiteDatabase db = new TagDBHelper(this.getBaseContext()).getReadableDatabase(); - Cursor c = db.query("Tags", new String[] { "_id", "description" }, null, null, null, null, null); + db = new TagDBHelper(getBaseContext()).getReadableDatabase(); + cursor = db.query("NdefMessage", new String[] { "_id", "bytes" }, null, null, null, null, null); SimpleCursorAdapter sca = new SimpleCursorAdapter(this, android.R.layout.two_line_list_item, - c, - new String[] { "description" }, + cursor, + new String[] { "bytes" }, new int[] { android.R.id.text1 }); setListAdapter(sca); registerForContextMenu(getListView()); - c.close(); - db.close(); Toast.makeText(getBaseContext(), "exit method", Toast.LENGTH_SHORT).show(); } @@ -73,6 +73,17 @@ public class TagList extends ListActivity implements DialogInterface.OnClickList .create(); } + @Override + protected void onDestroy() { + if (cursor != null) { + cursor.close(); + } + if (db != null) { + db.close(); + } + super.onDestroy(); + } + @Override protected void onListItemClick(ListView l, View v, int position, long id) { showDialog(1); diff --git a/apps/Tag/src/com/android/apps/tag/TagUi.java b/apps/Tag/src/com/android/apps/tag/TagUi.java deleted file mode 100644 index 98f384244..000000000 --- a/apps/Tag/src/com/android/apps/tag/TagUi.java +++ /dev/null @@ -1,26 +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; - - -/** - * @author nnk@google.com (Nick Kralevich) - */ -public class TagUi { - - -} diff --git a/apps/Tag/src/com/android/apps/tag/Tags.java b/apps/Tag/src/com/android/apps/tag/Tags.java index 781e05e21..d9fa2e79e 100644 --- a/apps/Tag/src/com/android/apps/tag/Tags.java +++ b/apps/Tag/src/com/android/apps/tag/Tags.java @@ -32,6 +32,10 @@ public class Tags 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(); diff --git a/apps/Tag/tests/src/com/android/apps/tag/TagBroadcastReceiverTest.java b/apps/Tag/tests/src/com/android/apps/tag/TagBroadcastReceiverTest.java deleted file mode 100644 index e030b6aae..000000000 --- a/apps/Tag/tests/src/com/android/apps/tag/TagBroadcastReceiverTest.java +++ /dev/null @@ -1,44 +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.Intent; -import android.test.ActivityInstrumentationTestCase2; - -/** - * @author nnk@google.com (Nick Kralevich) - */ -public class TagBroadcastReceiverTest extends ActivityInstrumentationTestCase2 { - /** - * Creates an {@link ActivityInstrumentationTestCase2} for the {@link Tags} activity. - */ - public TagBroadcastReceiverTest() { - super(Tags.class); - } - - public void testWrongMessage() { - TagBroadcastReceiver receiver = new TagBroadcastReceiver(); - Intent i = new Intent().setAction("BOGUS"); - receiver.onReceive(getActivity().getBaseContext(), i); - assertDatabaseNoChange(receiver); - } - - private void assertDatabaseNoChange(TagBroadcastReceiver receiver) { - // TODO: implement - } - -}