A bunch of work on the UI.
- Change the tab order and remember the current tab - Proper icon for the starred tab, renamed from saved - Set the proper sort order for the tag lists - Very rough first pass at full screen tag viewer - Hookup the delete button in the tag viewer - Store the snippets for tags in the database - Added view creation logic to the parsed messages and records so they can render themselves - Make the URI records look much better - For URI records if there are multiple activities that can handle the URI build one item per activity and bypass the activity chooser - Pretty print sms[to] and tel URIs - Hookup URI entries in the viewer to launch the activities - Implement the spec for saving tags and timing out the viewer for scanned tags - Made a few more strings localizable Change-Id: I6bdb8adf52445499c62a1b046f99d5b119aff068
This commit is contained in:
@@ -23,6 +23,7 @@
|
|||||||
package="com.android.apps.tag"
|
package="com.android.apps.tag"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
|
||||||
<application android:label="Tags">
|
<application android:label="Tags">
|
||||||
@@ -39,7 +40,7 @@
|
|||||||
<activity android:name="TagList" />
|
<activity android:name="TagList" />
|
||||||
|
|
||||||
<activity android:name="TagViewer"
|
<activity android:name="TagViewer"
|
||||||
android:theme="@android:style/Theme.Dialog"
|
android:theme="@android:style/Theme.NoTitleBar"
|
||||||
>
|
>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.nfc.action.NDEF_TAG_DISCOVERED"/>
|
<action android:name="android.nfc.action.NDEF_TAG_DISCOVERED"/>
|
||||||
@@ -47,5 +48,7 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service android:name="TagService" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.9 KiB |
BIN
apps/Tag/res/drawable-hdpi/ic_tab_selected_starred.png
Normal file
BIN
apps/Tag/res/drawable-hdpi/ic_tab_selected_starred.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
apps/Tag/res/drawable-hdpi/ic_tab_unselected_starred.png
Normal file
BIN
apps/Tag/res/drawable-hdpi/ic_tab_unselected_starred.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 568 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
BIN
apps/Tag/res/drawable-mdpi/ic_tab_selected_starred.png
Normal file
BIN
apps/Tag/res/drawable-mdpi/ic_tab_selected_starred.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 941 B |
BIN
apps/Tag/res/drawable-mdpi/ic_tab_unselected_starred.png
Normal file
BIN
apps/Tag/res/drawable-mdpi/ic_tab_unselected_starred.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 459 B |
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Copyright (C) 2010 The Android Open Source Project
|
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list"
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
android:layout_width="match_parent"
|
<item android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/ic_tab_selected_starred" />
|
||||||
android:layout_height="match_parent"
|
<item android:drawable="@drawable/ic_tab_unselected_starred" />
|
||||||
android:orientation="vertical"
|
</selector>
|
||||||
/>
|
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@android:id/tabcontent"
|
android:id="@android:id/tabcontent"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent" />
|
||||||
android:padding="5dp" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</TabHost>
|
</TabHost>
|
||||||
|
|||||||
55
apps/Tag/res/layout/tag_uri.xml
Normal file
55
apps/Tag/res/layout/tag_uri.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||||
|
|
||||||
|
android:paddingTop="4dip"
|
||||||
|
android:paddingBottom="4dip"
|
||||||
|
>
|
||||||
|
|
||||||
|
<ImageView android:id="@+id/icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
|
||||||
|
android:paddingLeft="8dip"
|
||||||
|
android:paddingRight="8dip"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/primary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_toRightOf="@id/icon"
|
||||||
|
android:layout_marginTop="4dip"
|
||||||
|
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/secondary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/primary"
|
||||||
|
android:layout_alignLeft="@id/primary"
|
||||||
|
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
118
apps/Tag/res/layout/tag_viewer.xml
Normal file
118
apps/Tag/res/layout/tag_viewer.xml
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?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:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
|
||||||
|
android:orientation="vertical"
|
||||||
|
>
|
||||||
|
<!-- Title -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dip"
|
||||||
|
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@android:color/black"
|
||||||
|
>
|
||||||
|
|
||||||
|
<ImageView android:id="@+id/icon"
|
||||||
|
android:layout_width="32dip"
|
||||||
|
android:layout_height="32dip"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/title"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CheckBox android:id="@+id/star"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
|
||||||
|
style="?android:attr/starStyle"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dip"
|
||||||
|
android:layout_weight="1"
|
||||||
|
|
||||||
|
android:background="@android:color/white"
|
||||||
|
>
|
||||||
|
|
||||||
|
<LinearLayout android:id="@+id/list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
android:orientation="vertical"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<!-- Bottom button area -->
|
||||||
|
|
||||||
|
<TextView android:id="@+id/cancel_help_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
android:paddingLeft="4dip"
|
||||||
|
android:paddingRight="4dip"
|
||||||
|
android:text="@string/cancel_help_text"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:background="@android:color/black"
|
||||||
|
android:gravity="center"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
android:orientation="horizontal"
|
||||||
|
style="@style/ButtonBar"
|
||||||
|
>
|
||||||
|
|
||||||
|
<Button android:id="@+id/btn_delete"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
|
||||||
|
android:text="@string/button_delete"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button android:id="@+id/btn_cancel"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
|
||||||
|
android:text="@android:string/cancel"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -14,16 +14,29 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<resources>
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
|
||||||
<string name="hello_activity_text_text">Hello, World!</string>
|
|
||||||
<string name="help_and_info">help and info</string>
|
|
||||||
<string name="saved">Saved</string>
|
|
||||||
|
|
||||||
<!-- The title of the tab that displays all recently scanned NFC tags -->
|
<!-- The title of the tab that displays all recently scanned NFC tags -->
|
||||||
<string name="tab_recent">Recent</string>
|
<string name="tab_tags">Tags</string>
|
||||||
|
|
||||||
<!-- The title of the tab that displays all saved NFC tags -->
|
<!-- The title of the tab that displays all starred NFC tags -->
|
||||||
<string name="tab_saved">Saved</string>
|
<string name="tab_starred">Starred</string>
|
||||||
|
|
||||||
|
<!-- The title displayed for unknown tag types -->
|
||||||
|
<string name="tag_unknown">Unknown tag type</string>
|
||||||
|
|
||||||
|
<!-- The title displayed for an empty tag -->
|
||||||
|
<string name="tag_empty">Empty tag</string>
|
||||||
|
|
||||||
|
<!-- Button label indicating that the user wants to delete a tag -->
|
||||||
|
<string name="button_delete">Delete</string>
|
||||||
|
|
||||||
|
<!-- String describing that if the user doesn't want to save a tag they should touch the button labeled Cancel. The text for the cancel button comes from the system string android.R.string.cancel. -->
|
||||||
|
<string name="cancel_help_text">To skip adding this tag to your collection, press Cancel</string>
|
||||||
|
|
||||||
|
<!-- String displayed for an action to send a text message to a phone number -->
|
||||||
|
<string name="action_text">Text <xliff:g id="phone_number">%s</xliff:g></string>
|
||||||
|
|
||||||
|
<!-- String displayed for an action to call a phone number -->
|
||||||
|
<string name="action_call">Call <xliff:g id="phone_number">%s</xliff:g></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -14,9 +14,12 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<resources>
|
||||||
<item
|
<style name="ButtonBar">
|
||||||
android:id="@+id/help_info_menu_item"
|
<item name="android:paddingTop">5dip</item>
|
||||||
android:icon="@android:drawable/ic_menu_help"
|
<item name="android:paddingLeft">4dip</item>
|
||||||
android:title="@string/help_and_info" />
|
<item name="android:paddingRight">4dip</item>
|
||||||
</menu>
|
<item name="android:paddingBottom">1dip</item>
|
||||||
|
<item name="android:background">@android:color/black</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -16,12 +16,11 @@
|
|||||||
|
|
||||||
package com.android.apps.tag;
|
package com.android.apps.tag;
|
||||||
|
|
||||||
|
import com.android.apps.tag.TagDBHelper.NdefMessagesTable;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.nfc.FormatException;
|
|
||||||
import android.nfc.NdefMessage;
|
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -29,12 +28,6 @@ import android.widget.Adapter;
|
|||||||
import android.widget.CursorAdapter;
|
import android.widget.CursorAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.apps.tag.TagDBHelper.NdefMessagesTable;
|
|
||||||
import com.android.apps.tag.message.NdefMessageParser;
|
|
||||||
import com.android.apps.tag.message.ParsedNdefMessage;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom {@link Adapter} that renders tag entries for a list.
|
* A custom {@link Adapter} that renders tag entries for a list.
|
||||||
*/
|
*/
|
||||||
@@ -52,19 +45,7 @@ public class TagAdapter extends CursorAdapter {
|
|||||||
TextView mainLine = (TextView) view.findViewById(R.id.title);
|
TextView mainLine = (TextView) view.findViewById(R.id.title);
|
||||||
TextView dateLine = (TextView) view.findViewById(R.id.date);
|
TextView dateLine = (TextView) view.findViewById(R.id.date);
|
||||||
|
|
||||||
NdefMessage msg = null;
|
mainLine.setText(cursor.getString(cursor.getColumnIndex(NdefMessagesTable.TITLE)));
|
||||||
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 {
|
|
||||||
ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
|
|
||||||
mainLine.setText(parsedMsg.getSnippet(Locale.getDefault()));
|
|
||||||
}
|
|
||||||
dateLine.setText(DateUtils.getRelativeTimeSpanString(
|
dateLine.setText(DateUtils.getRelativeTimeSpanString(
|
||||||
context, cursor.getLong(cursor.getColumnIndex(NdefMessagesTable.DATE))));
|
context, cursor.getLong(cursor.getColumnIndex(NdefMessagesTable.DATE))));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ package com.android.apps.tag;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.TabActivity;
|
import android.app.TabActivity;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.TabHost;
|
import android.widget.TabHost;
|
||||||
@@ -36,16 +38,34 @@ public class TagBrowserActivity extends TabActivity {
|
|||||||
Resources res = getResources();
|
Resources res = getResources();
|
||||||
TabHost tabHost = getTabHost();
|
TabHost tabHost = getTabHost();
|
||||||
|
|
||||||
TabHost.TabSpec spec1 = tabHost.newTabSpec("saved")
|
tabHost.addTab(tabHost.newTabSpec("tags")
|
||||||
.setIndicator(getText(R.string.tab_saved), res.getDrawable(R.drawable.ic_menu_tag))
|
.setIndicator(getText(R.string.tab_tags),
|
||||||
|
res.getDrawable(R.drawable.ic_menu_tag))
|
||||||
|
.setContent(new Intent().setClass(this, TagList.class)));
|
||||||
|
|
||||||
|
tabHost.addTab(tabHost.newTabSpec("starred")
|
||||||
|
.setIndicator(getText(R.string.tab_starred),
|
||||||
|
res.getDrawable(R.drawable.ic_tab_starred))
|
||||||
.setContent(new Intent().setClass(this, TagList.class)
|
.setContent(new Intent().setClass(this, TagList.class)
|
||||||
.putExtra(TagList.EXTRA_SHOW_SAVED_ONLY, true));
|
.putExtra(TagList.EXTRA_SHOW_STARRED_ONLY, true)));
|
||||||
tabHost.addTab(spec1);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
// Restore the last active tab
|
||||||
|
SharedPreferences prefs = getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||||
|
getTabHost().setCurrentTabByTag(prefs.getString("tab", "tags"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
// Save the active tab
|
||||||
|
SharedPreferences.Editor edit = getSharedPreferences("prefs", Context.MODE_PRIVATE).edit();
|
||||||
|
edit.putString("tab", getTabHost().getCurrentTabTag());
|
||||||
|
edit.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ import android.nfc.FormatException;
|
|||||||
import android.nfc.NdefMessage;
|
import android.nfc.NdefMessage;
|
||||||
import android.nfc.NdefRecord;
|
import android.nfc.NdefRecord;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import com.android.apps.tag.message.NdefMessageParser;
|
||||||
|
import com.android.apps.tag.message.ParsedNdefMessage;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,7 +37,7 @@ import com.google.common.annotations.VisibleForTesting;
|
|||||||
public class TagDBHelper extends SQLiteOpenHelper {
|
public class TagDBHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
private static final String DATABASE_NAME = "tags.db";
|
private static final String DATABASE_NAME = "tags.db";
|
||||||
private static final int DATABASE_VERSION = 3;
|
private static final int DATABASE_VERSION = 5;
|
||||||
|
|
||||||
public interface NdefMessagesTable {
|
public interface NdefMessagesTable {
|
||||||
public static final String TABLE_NAME = "nedf_msg";
|
public static final String TABLE_NAME = "nedf_msg";
|
||||||
@@ -42,11 +46,13 @@ public class TagDBHelper extends SQLiteOpenHelper {
|
|||||||
public static final String TITLE = "title";
|
public static final String TITLE = "title";
|
||||||
public static final String BYTES = "bytes";
|
public static final String BYTES = "bytes";
|
||||||
public static final String DATE = "date";
|
public static final String DATE = "date";
|
||||||
public static final String SAVED = "saved";
|
public static final String STARRED = "starred";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TagDBHelper sInstance;
|
private static TagDBHelper sInstance;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
public static synchronized TagDBHelper getInstance(Context context) {
|
public static synchronized TagDBHelper getInstance(Context context) {
|
||||||
if (sInstance == null) {
|
if (sInstance == null) {
|
||||||
sInstance = new TagDBHelper(context.getApplicationContext());
|
sInstance = new TagDBHelper(context.getApplicationContext());
|
||||||
@@ -56,11 +62,13 @@ public class TagDBHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
private TagDBHelper(Context context) {
|
private TagDBHelper(Context context) {
|
||||||
this(context, DATABASE_NAME);
|
this(context, DATABASE_NAME);
|
||||||
|
mContext = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
TagDBHelper(Context context, String dbFile) {
|
TagDBHelper(Context context, String dbFile) {
|
||||||
super(context, dbFile, null, DATABASE_VERSION);
|
super(context, dbFile, null, DATABASE_VERSION);
|
||||||
|
mContext = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,12 +78,12 @@ public class TagDBHelper extends SQLiteOpenHelper {
|
|||||||
NdefMessagesTable.TITLE + " TEXT NOT NULL DEFAULT ''," +
|
NdefMessagesTable.TITLE + " TEXT NOT NULL DEFAULT ''," +
|
||||||
NdefMessagesTable.BYTES + " BLOB NOT NULL, " +
|
NdefMessagesTable.BYTES + " BLOB NOT NULL, " +
|
||||||
NdefMessagesTable.DATE + " INTEGER NOT NULL, " +
|
NdefMessagesTable.DATE + " INTEGER NOT NULL, " +
|
||||||
NdefMessagesTable.SAVED + " INTEGER NOT NULL DEFAULT 0" + // boolean
|
NdefMessagesTable.STARRED + " INTEGER NOT NULL DEFAULT 0" + // boolean
|
||||||
");");
|
");");
|
||||||
|
|
||||||
db.execSQL("CREATE INDEX msgIndex ON " + NdefMessagesTable.TABLE_NAME + " (" +
|
db.execSQL("CREATE INDEX msgIndex ON " + NdefMessagesTable.TABLE_NAME + " (" +
|
||||||
NdefMessagesTable.DATE + " DESC, " +
|
NdefMessagesTable.DATE + " DESC, " +
|
||||||
NdefMessagesTable.SAVED + " ASC" +
|
NdefMessagesTable.STARRED + " ASC" +
|
||||||
")");
|
")");
|
||||||
|
|
||||||
addTestData(db);
|
addTestData(db);
|
||||||
@@ -114,15 +122,18 @@ public class TagDBHelper extends SQLiteOpenHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insertNdefMessage(SQLiteDatabase db, NdefMessage msg, boolean isSaved) {
|
public void insertNdefMessage(SQLiteDatabase db, NdefMessage msg, boolean isStarred) {
|
||||||
|
ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
|
||||||
SQLiteStatement stmt = null;
|
SQLiteStatement stmt = null;
|
||||||
try {
|
try {
|
||||||
stmt = db.compileStatement("INSERT INTO " + NdefMessagesTable.TABLE_NAME +
|
stmt = db.compileStatement("INSERT INTO " + NdefMessagesTable.TABLE_NAME +
|
||||||
"(" + NdefMessagesTable.BYTES + ", " + NdefMessagesTable.DATE + ", " +
|
"(" + NdefMessagesTable.BYTES + ", " + NdefMessagesTable.DATE + ", " +
|
||||||
NdefMessagesTable.SAVED + ") values (?, ?, ?)");
|
NdefMessagesTable.STARRED + "," + NdefMessagesTable.TITLE + ") " +
|
||||||
|
"values (?, ?, ?, ?)");
|
||||||
stmt.bindBlob(1, msg.toByteArray());
|
stmt.bindBlob(1, msg.toByteArray());
|
||||||
stmt.bindLong(2, System.currentTimeMillis());
|
stmt.bindLong(2, System.currentTimeMillis());
|
||||||
stmt.bindLong(3, isSaved ? 1 : 0);
|
stmt.bindLong(3, isStarred ? 1 : 0);
|
||||||
|
stmt.bindString(4, parsedMsg.getSnippet(mContext, Locale.getDefault()));
|
||||||
stmt.executeInsert();
|
stmt.executeInsert();
|
||||||
} finally {
|
} finally {
|
||||||
if (stmt != null) stmt.close();
|
if (stmt != null) stmt.close();
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import com.android.apps.tag.TagDBHelper.NdefMessagesTable;
|
|||||||
public class TagList extends ListActivity implements DialogInterface.OnClickListener {
|
public class TagList extends ListActivity implements DialogInterface.OnClickListener {
|
||||||
static final String TAG = "TagList";
|
static final String TAG = "TagList";
|
||||||
|
|
||||||
static final String EXTRA_SHOW_SAVED_ONLY = "show_saved_only";
|
static final String EXTRA_SHOW_STARRED_ONLY = "show_starred_only";
|
||||||
|
|
||||||
SQLiteDatabase mDatabase;
|
SQLiteDatabase mDatabase;
|
||||||
TagAdapter mAdapter;
|
TagAdapter mAdapter;
|
||||||
@@ -50,9 +50,9 @@ public class TagList extends ListActivity implements DialogInterface.OnClickList
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
boolean showSavedOnly = getIntent().getBooleanExtra(EXTRA_SHOW_SAVED_ONLY, false);
|
boolean showStarredOnly = getIntent().getBooleanExtra(EXTRA_SHOW_STARRED_ONLY, false);
|
||||||
mDatabase = TagDBHelper.getInstance(this).getReadableDatabase();
|
mDatabase = TagDBHelper.getInstance(this).getReadableDatabase();
|
||||||
String selection = showSavedOnly ? NdefMessagesTable.SAVED + "=1" : null;
|
String selection = showStarredOnly ? NdefMessagesTable.STARRED + "=1" : null;
|
||||||
|
|
||||||
new TagLoaderTask().execute(selection);
|
new TagLoaderTask().execute(selection);
|
||||||
mAdapter = new TagAdapter(this);
|
mAdapter = new TagAdapter(this);
|
||||||
@@ -119,7 +119,7 @@ public class TagList extends ListActivity implements DialogInterface.OnClickList
|
|||||||
NdefMessagesTable.DATE,
|
NdefMessagesTable.DATE,
|
||||||
NdefMessagesTable.TITLE },
|
NdefMessagesTable.TITLE },
|
||||||
selection,
|
selection,
|
||||||
null, null, null, null);
|
null, null, null, NdefMessagesTable.DATE + " DESC");
|
||||||
cursor.getCount();
|
cursor.getCount();
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|||||||
58
apps/Tag/src/com/android/apps/tag/TagService.java
Normal file
58
apps/Tag/src/com/android/apps/tag/TagService.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.IntentService;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.nfc.NdefMessage;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
public class TagService extends IntentService {
|
||||||
|
public static final String EXTRA_SAVE_MSGS = "msgs";
|
||||||
|
public static final String EXTRA_DELETE_ID = "delete";
|
||||||
|
|
||||||
|
public TagService() {
|
||||||
|
super("SaveTagService");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHandleIntent(Intent intent) {
|
||||||
|
TagDBHelper helper = TagDBHelper.getInstance(this);
|
||||||
|
SQLiteDatabase db = helper.getWritableDatabase();
|
||||||
|
if (intent.hasExtra(EXTRA_SAVE_MSGS)) {
|
||||||
|
Parcelable[] parcels = intent.getParcelableArrayExtra(EXTRA_SAVE_MSGS);
|
||||||
|
db.beginTransaction();
|
||||||
|
try {
|
||||||
|
for (Parcelable parcel : parcels) {
|
||||||
|
helper.insertNdefMessage(db, (NdefMessage) parcel, false);
|
||||||
|
}
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
} finally {
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (intent.hasExtra(EXTRA_DELETE_ID)) {
|
||||||
|
long id = intent.getLongExtra(EXTRA_DELETE_ID, 0);
|
||||||
|
db.delete(NdefMessagesTable.TABLE_NAME, NdefMessagesTable._ID + "=?",
|
||||||
|
new String[] { Long.toString(id) });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,39 +16,57 @@
|
|||||||
|
|
||||||
package com.android.apps.tag;
|
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 android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.nfc.NdefMessage;
|
import android.nfc.NdefMessage;
|
||||||
import android.nfc.NdefTag;
|
import android.nfc.NdefTag;
|
||||||
import android.nfc.NfcAdapter;
|
import android.nfc.NfcAdapter;
|
||||||
import android.os.AsyncTask;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ContextThemeWrapper;
|
import android.view.ContextThemeWrapper;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.apps.tag.message.NdefMessageParser;
|
|
||||||
import com.android.apps.tag.message.ParsedNdefMessage;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link Activity} which handles a broadcast of a new tag that the device just discovered.
|
* An {@link Activity} which handles a broadcast of a new tag that the device just discovered.
|
||||||
*/
|
*/
|
||||||
public class TagViewer extends Activity {
|
public class TagViewer extends Activity implements OnClickListener, Handler.Callback {
|
||||||
static final String TAG = "SaveTag";
|
static final String TAG = "SaveTag";
|
||||||
static final String EXTRA_TAG_DB_ID = "db_id";
|
static final String EXTRA_TAG_DB_ID = "db_id";
|
||||||
static final String EXTRA_MESSAGE = "msg";
|
static final String EXTRA_MESSAGE = "msg";
|
||||||
|
|
||||||
|
/** This activity will finish itself in this amount of time if the user doesn't do anything. */
|
||||||
|
static final int ACTIVITY_TIMEOUT_MS = 10 * 1000;
|
||||||
|
|
||||||
long mTagDatabaseId;
|
long mTagDatabaseId;
|
||||||
|
ImageView mIcon;
|
||||||
|
TextView mTitle;
|
||||||
|
CheckBox mStar;
|
||||||
|
Button mDeleteButton;
|
||||||
|
Button mCancelButton;
|
||||||
|
NdefMessage[] mMessagesToSave = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onStart();
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
|
||||||
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
|
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
|
||||||
@@ -58,6 +76,18 @@ public class TagViewer extends Activity {
|
|||||||
| WindowManager.LayoutParams.FLAG_DIM_BEHIND
|
| WindowManager.LayoutParams.FLAG_DIM_BEHIND
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setContentView(R.layout.tag_viewer);
|
||||||
|
|
||||||
|
mTitle = (TextView) findViewById(R.id.title);
|
||||||
|
mIcon = (ImageView) findViewById(R.id.icon);
|
||||||
|
mStar = (CheckBox) findViewById(R.id.star);
|
||||||
|
mDeleteButton = (Button) findViewById(R.id.btn_delete);
|
||||||
|
mCancelButton = (Button) findViewById(R.id.btn_cancel);
|
||||||
|
|
||||||
|
mDeleteButton.setOnClickListener(this);
|
||||||
|
mCancelButton.setOnClickListener(this);
|
||||||
|
mIcon.setImageResource(R.drawable.ic_launcher_nfc);
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
NdefMessage[] msgs = null;
|
NdefMessage[] msgs = null;
|
||||||
NdefTag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
NdefTag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||||
@@ -68,10 +98,18 @@ public class TagViewer extends Activity {
|
|||||||
if (msg != null) {
|
if (msg != null) {
|
||||||
msgs = new NdefMessage[] { msg };
|
msgs = new NdefMessage[] { msg };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide the text about saving the tag, it's already in the database
|
||||||
|
findViewById(R.id.cancel_help_text).setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
msgs = tag.getNdefMessages();
|
msgs = tag.getNdefMessages();
|
||||||
// TODO use a service to avoid the process getting reaped during saving
|
mDeleteButton.setVisibility(View.GONE);
|
||||||
new SaveTagTask().execute(msgs);
|
|
||||||
|
// Set a timer on this activity since it wasn't created by the user
|
||||||
|
new Handler(this).sendEmptyMessageDelayed(0, ACTIVITY_TIMEOUT_MS);
|
||||||
|
|
||||||
|
// Save the messages that were just scanned
|
||||||
|
mMessagesToSave = msgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msgs == null || msgs.length == 0) {
|
if (msgs == null || msgs.length == 0) {
|
||||||
@@ -80,43 +118,72 @@ public class TagViewer extends Activity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Context contentContext = new ContextThemeWrapper(this, android.R.style.Theme_Light);
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(contentContext);
|
||||||
|
LinearLayout list = (LinearLayout) findViewById(R.id.list);
|
||||||
|
|
||||||
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);
|
buildTagViews(list, inflater, msgs);
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(getTitle())) {
|
||||||
|
// There isn't a snippet for this tag, use a default title
|
||||||
|
setTitle(R.string.tag_unknown);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildTagViews(LinearLayout list, LayoutInflater inflater, NdefMessage[] msgs) {
|
private void buildTagViews(LinearLayout list, LayoutInflater inflater, NdefMessage[] msgs) {
|
||||||
// The body of the dialog should use the light theme
|
if (msgs == null || msgs.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Build the views from the logical records in the messages
|
// Build the views from the logical records in the messages
|
||||||
for (NdefMessage msg : msgs) {
|
NdefMessage msg = msgs[0];
|
||||||
|
|
||||||
|
// Set the title to be the snippet of the message
|
||||||
ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
|
ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
|
||||||
TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false);
|
setTitle(parsedMsg.getSnippet(this, Locale.getDefault()));
|
||||||
text.setText(parsedMsg.getSnippet(Locale.getDefault()));
|
|
||||||
list.addView(text);
|
// Build views for all of the sub records
|
||||||
|
for (ParsedNdefRecord record : parsedMsg.getRecords()) {
|
||||||
|
list.addView(record.getView(this, inflater, list));
|
||||||
|
inflater.inflate(R.layout.tag_divider, list, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class SaveTagTask extends AsyncTask<NdefMessage, Void, Void> {
|
|
||||||
@Override
|
@Override
|
||||||
public Void doInBackground(NdefMessage... msgs) {
|
public void setTitle(CharSequence title) {
|
||||||
TagDBHelper helper = TagDBHelper.getInstance(TagViewer.this);
|
mTitle.setText(title);
|
||||||
SQLiteDatabase db = helper.getWritableDatabase();
|
|
||||||
db.beginTransaction();
|
|
||||||
try {
|
|
||||||
for (NdefMessage msg : msgs) {
|
|
||||||
helper.insertNdefMessage(db, msg, false);
|
|
||||||
}
|
}
|
||||||
db.setTransactionSuccessful();
|
|
||||||
} finally {
|
@Override
|
||||||
db.endTransaction();
|
public void onClick(View view) {
|
||||||
}
|
if (view == mDeleteButton) {
|
||||||
return null;
|
Intent save = new Intent(this, TagService.class);
|
||||||
|
save.putExtra(TagService.EXTRA_DELETE_ID, mTagDatabaseId);
|
||||||
|
startService(save);
|
||||||
|
finish();
|
||||||
|
} else if (view == mCancelButton) {
|
||||||
|
mMessagesToSave = null;
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
if (mMessagesToSave != null) {
|
||||||
|
saveMessages(mMessagesToSave);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveMessages(NdefMessage[] msgs) {
|
||||||
|
Intent save = new Intent(this, TagService.class);
|
||||||
|
save.putExtra(TagService.EXTRA_SAVE_MSGS, msgs);
|
||||||
|
startService(save);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleMessage(Message msg) {
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,18 +16,26 @@
|
|||||||
|
|
||||||
package com.android.apps.tag.message;
|
package com.android.apps.tag.message;
|
||||||
|
|
||||||
|
import com.android.apps.tag.R;
|
||||||
|
import com.android.apps.tag.record.ParsedNdefRecord;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A parsed message containing no elements.
|
* A parsed message containing no elements.
|
||||||
*/
|
*/
|
||||||
class EmptyMessage implements ParsedNdefMessage {
|
class EmptyMessage extends ParsedNdefMessage {
|
||||||
|
|
||||||
/* package private */ EmptyMessage() { }
|
/* package private */ EmptyMessage() {
|
||||||
|
super(new ArrayList<ParsedNdefRecord>());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSnippet(Locale locale) {
|
public String getSnippet(Context context, Locale locale) {
|
||||||
return "Empty Tag"; // TODO: localize
|
return context.getString(R.string.tag_empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -47,13 +47,13 @@ public class NdefMessageParser {
|
|||||||
|
|
||||||
if (elements.size() == 1) {
|
if (elements.size() == 1) {
|
||||||
if (first instanceof SmartPoster) {
|
if (first instanceof SmartPoster) {
|
||||||
return new SmartPosterMessage((SmartPoster) first);
|
return new SmartPosterMessage((SmartPoster) first, elements);
|
||||||
}
|
}
|
||||||
if (first instanceof TextRecord) {
|
if (first instanceof TextRecord) {
|
||||||
return new TextMessage((TextRecord) first);
|
return new TextMessage((TextRecord) first, elements);
|
||||||
}
|
}
|
||||||
if (first instanceof UriRecord) {
|
if (first instanceof UriRecord) {
|
||||||
return new UriMessage((UriRecord) first);
|
return new UriMessage((UriRecord) first, elements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,20 +16,39 @@
|
|||||||
|
|
||||||
package com.android.apps.tag.message;
|
package com.android.apps.tag.message;
|
||||||
|
|
||||||
|
import com.android.apps.tag.record.ParsedNdefRecord;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A parsed version of an {@link android.nfc.NdefMessage}
|
* A parsed version of an {@link android.nfc.NdefMessage}
|
||||||
*/
|
*/
|
||||||
public interface ParsedNdefMessage {
|
public abstract class ParsedNdefMessage {
|
||||||
|
|
||||||
|
private List<ParsedNdefRecord> mRecords;
|
||||||
|
|
||||||
|
public ParsedNdefMessage(List<ParsedNdefRecord> records) {
|
||||||
|
mRecords = ImmutableList.copyOf(records);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of parsed records on this message.
|
||||||
|
*/
|
||||||
|
public List<ParsedNdefRecord> getRecords() {
|
||||||
|
return mRecords;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the snippet information associated with the NdefMessage
|
* Returns the snippet information associated with the NdefMessage
|
||||||
* most appropriate for the given {@code locale}.
|
* most appropriate for the given {@code locale}.
|
||||||
*/
|
*/
|
||||||
public String getSnippet(Locale locale);
|
public abstract String getSnippet(Context context, Locale locale);
|
||||||
|
|
||||||
// TODO: Determine if this is the best place for holding whether
|
// TODO: Determine if this is the best place for holding whether
|
||||||
// the user has starred this parsed message.
|
// the user has starred this parsed message.
|
||||||
public boolean isStarred();
|
public abstract boolean isStarred();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,27 +16,32 @@
|
|||||||
|
|
||||||
package com.android.apps.tag.message;
|
package com.android.apps.tag.message;
|
||||||
|
|
||||||
|
import com.android.apps.tag.record.ParsedNdefRecord;
|
||||||
import com.android.apps.tag.record.SmartPoster;
|
import com.android.apps.tag.record.SmartPoster;
|
||||||
import com.android.apps.tag.record.TextRecord;
|
import com.android.apps.tag.record.TextRecord;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message consisting of one {@link SmartPoster} object.
|
* A message consisting of one {@link SmartPoster} object.
|
||||||
*/
|
*/
|
||||||
class SmartPosterMessage implements ParsedNdefMessage {
|
class SmartPosterMessage extends ParsedNdefMessage {
|
||||||
private final SmartPoster mPoster;
|
private final SmartPoster mPoster;
|
||||||
|
|
||||||
SmartPosterMessage(SmartPoster poster) {
|
SmartPosterMessage(SmartPoster poster, List<ParsedNdefRecord> records) {
|
||||||
|
super(Preconditions.checkNotNull(records));
|
||||||
mPoster = Preconditions.checkNotNull(poster);
|
mPoster = Preconditions.checkNotNull(poster);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSnippet(Locale locale) {
|
public String getSnippet(Context context, Locale locale) {
|
||||||
TextRecord title = mPoster.getTitle();
|
TextRecord title = mPoster.getTitle();
|
||||||
if (title == null) {
|
if (title == null) {
|
||||||
return mPoster.getUriRecord().getUri().toString();
|
return mPoster.getUriRecord().getPrettyUriString(context);
|
||||||
}
|
}
|
||||||
return title.getText();
|
return title.getText();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,23 +16,28 @@
|
|||||||
|
|
||||||
package com.android.apps.tag.message;
|
package com.android.apps.tag.message;
|
||||||
|
|
||||||
|
import com.android.apps.tag.record.ParsedNdefRecord;
|
||||||
import com.android.apps.tag.record.TextRecord;
|
import com.android.apps.tag.record.TextRecord;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message containing one text element
|
* A message containing one text element
|
||||||
*/
|
*/
|
||||||
class TextMessage implements ParsedNdefMessage {
|
class TextMessage extends ParsedNdefMessage {
|
||||||
private final TextRecord mRecord;
|
private final TextRecord mRecord;
|
||||||
|
|
||||||
TextMessage(TextRecord record) {
|
TextMessage(TextRecord record, List<ParsedNdefRecord> records) {
|
||||||
|
super(Preconditions.checkNotNull(records));
|
||||||
mRecord = Preconditions.checkNotNull(record);
|
mRecord = Preconditions.checkNotNull(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSnippet(Locale locale) {
|
public String getSnippet(Context context, Locale locale) {
|
||||||
return mRecord.getText();
|
return mRecord.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,26 +16,27 @@
|
|||||||
|
|
||||||
package com.android.apps.tag.message;
|
package com.android.apps.tag.message;
|
||||||
|
|
||||||
|
import com.android.apps.tag.R;
|
||||||
import com.android.apps.tag.record.ParsedNdefRecord;
|
import com.android.apps.tag.record.ParsedNdefRecord;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The catchall parsed message format for when nothing else better applies.
|
* The catchall parsed message format for when nothing else better applies.
|
||||||
*/
|
*/
|
||||||
class UnknownMessage implements ParsedNdefMessage {
|
class UnknownMessage extends ParsedNdefMessage {
|
||||||
|
|
||||||
private final ImmutableList<ParsedNdefRecord> mRecords;
|
UnknownMessage(List<ParsedNdefRecord> records) {
|
||||||
|
super(Preconditions.checkNotNull(records));
|
||||||
UnknownMessage(Iterable<ParsedNdefRecord> records) {
|
|
||||||
mRecords = ImmutableList.copyOf(records);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSnippet(Locale locale) {
|
public String getSnippet(Context context, Locale locale) {
|
||||||
// TODO: localize
|
return context.getString(R.string.tag_unknown);
|
||||||
return "Unknown record type with " + mRecords.size() + " elements.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -16,26 +16,31 @@
|
|||||||
|
|
||||||
package com.android.apps.tag.message;
|
package com.android.apps.tag.message;
|
||||||
|
|
||||||
|
import com.android.apps.tag.record.ParsedNdefRecord;
|
||||||
import com.android.apps.tag.record.UriRecord;
|
import com.android.apps.tag.record.UriRecord;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link ParsedNdefMessage} consisting of one {@link UriRecord}.
|
* A {@link ParsedNdefMessage} consisting of one {@link UriRecord}.
|
||||||
*/
|
*/
|
||||||
class UriMessage implements ParsedNdefMessage {
|
class UriMessage extends ParsedNdefMessage {
|
||||||
|
|
||||||
private final UriRecord mRecord;
|
private final UriRecord mRecord;
|
||||||
|
|
||||||
UriMessage(UriRecord record) {
|
UriMessage(UriRecord record, List<ParsedNdefRecord> records) {
|
||||||
|
super(Preconditions.checkNotNull(records));
|
||||||
mRecord = Preconditions.checkNotNull(record);
|
mRecord = Preconditions.checkNotNull(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSnippet(Locale locale) {
|
public String getSnippet(Context context, Locale locale) {
|
||||||
// URIs cannot be localized
|
// URIs cannot be localized
|
||||||
return mRecord.getUri().toString();
|
return mRecord.getPrettyUriString(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -16,6 +16,11 @@
|
|||||||
|
|
||||||
package com.android.apps.tag.record;
|
package com.android.apps.tag.record;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: come up with a better name.
|
* TODO: come up with a better name.
|
||||||
*/
|
*/
|
||||||
@@ -23,4 +28,9 @@ public interface ParsedNdefRecord {
|
|||||||
|
|
||||||
// Just a placeholder for now. Probably not needed nor desired.
|
// Just a placeholder for now. Probably not needed nor desired.
|
||||||
public String getRecordType();
|
public String getRecordType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a view to display this record.
|
||||||
|
*/
|
||||||
|
public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,25 @@
|
|||||||
|
|
||||||
package com.android.apps.tag.record;
|
package com.android.apps.tag.record;
|
||||||
|
|
||||||
import android.nfc.FormatException;
|
import com.android.apps.tag.R;
|
||||||
import android.nfc.NdefMessage;
|
|
||||||
import android.nfc.NdefRecord;
|
|
||||||
|
|
||||||
import com.android.apps.tag.message.NdefMessageParser;
|
import com.android.apps.tag.message.NdefMessageParser;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.nfc.FormatException;
|
||||||
|
import android.nfc.NdefMessage;
|
||||||
|
import android.nfc.NdefRecord;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,4 +114,23 @@ public class SmartPoster implements ParsedNdefRecord {
|
|||||||
public String getRecordType() {
|
public String getRecordType() {
|
||||||
return "SmartPoster";
|
return "SmartPoster";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
|
||||||
|
if (mTitleRecord != null) {
|
||||||
|
// Build a container to hold the title and the URI
|
||||||
|
LinearLayout container = new LinearLayout(activity);
|
||||||
|
container.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
container.setLayoutParams(new LayoutParams(
|
||||||
|
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
|
container.addView(mTitleRecord.getView(activity, inflater, container));
|
||||||
|
inflater.inflate(R.layout.tag_divider, container);
|
||||||
|
container.addView(mUriRecord.getView(activity, inflater, container));
|
||||||
|
return container;
|
||||||
|
} else {
|
||||||
|
// Just a URI, return a view for it directly
|
||||||
|
return mUriRecord.getView(activity, inflater, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,16 @@
|
|||||||
|
|
||||||
package com.android.apps.tag.record;
|
package com.android.apps.tag.record;
|
||||||
|
|
||||||
import android.nfc.NdefRecord;
|
import com.android.apps.tag.R;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.nfc.NdefRecord;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@@ -42,6 +48,13 @@ public class TextRecord implements ParsedNdefRecord {
|
|||||||
return "Text";
|
return "Text";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
|
||||||
|
TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false);
|
||||||
|
text.setText(mText);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
public String getText() {
|
public String getText() {
|
||||||
return mText;
|
return mText;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,22 +16,48 @@
|
|||||||
|
|
||||||
package com.android.apps.tag.record;
|
package com.android.apps.tag.record;
|
||||||
|
|
||||||
import android.net.Uri;
|
import com.android.apps.tag.R;
|
||||||
import android.nfc.NdefRecord;
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.BiMap;
|
import com.google.common.collect.BiMap;
|
||||||
import com.google.common.collect.ImmutableBiMap;
|
import com.google.common.collect.ImmutableBiMap;
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ActivityInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.nfc.NdefRecord;
|
||||||
|
import android.telephony.PhoneNumberUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.nio.charset.Charsets;
|
import java.nio.charset.Charsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A parsed record containing a Uri.
|
* A parsed record containing a Uri.
|
||||||
*/
|
*/
|
||||||
public class UriRecord implements ParsedNdefRecord {
|
public class UriRecord implements ParsedNdefRecord, OnClickListener {
|
||||||
private static final byte[] EMPTY = new byte[0];
|
private static final class ClickInfo {
|
||||||
|
public Activity activity;
|
||||||
|
public Intent intent;
|
||||||
|
|
||||||
|
public ClickInfo(Activity activity, Intent intent) {
|
||||||
|
this.activity = activity;
|
||||||
|
this.intent = intent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NFC Forum "URI Record Type Definition"
|
* NFC Forum "URI Record Type Definition"
|
||||||
@@ -89,6 +115,96 @@ public class UriRecord implements ParsedNdefRecord {
|
|||||||
return "Uri";
|
return "Uri";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Intent getIntentForUri() {
|
||||||
|
String scheme = mUri.getScheme();
|
||||||
|
if ("tel".equals(scheme)) {
|
||||||
|
return new Intent(Intent.ACTION_CALL, mUri);
|
||||||
|
} else if ("sms".equals(scheme) || "smsto".equals(scheme)) {
|
||||||
|
return new Intent(Intent.ACTION_SENDTO, mUri);
|
||||||
|
} else {
|
||||||
|
return new Intent(Intent.ACTION_VIEW, mUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrettyUriString(Context context) {
|
||||||
|
String scheme = mUri.getScheme();
|
||||||
|
boolean tel = "tel".equals(scheme);
|
||||||
|
boolean sms = "sms".equals(scheme) || "smsto".equals(scheme);
|
||||||
|
if (tel || sms) {
|
||||||
|
String ssp = mUri.getSchemeSpecificPart();
|
||||||
|
int offset = ssp.indexOf('?');
|
||||||
|
if (offset >= 0) {
|
||||||
|
ssp = ssp.substring(0, offset);
|
||||||
|
}
|
||||||
|
if (tel) {
|
||||||
|
return context.getString(R.string.action_call, PhoneNumberUtils.formatNumber(ssp));
|
||||||
|
} else {
|
||||||
|
return context.getString(R.string.action_text, PhoneNumberUtils.formatNumber(ssp));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return mUri.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent) {
|
||||||
|
Intent intent = getIntentForUri();
|
||||||
|
PackageManager pm = activity.getPackageManager();
|
||||||
|
List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
|
||||||
|
int numActivities = activities.size();
|
||||||
|
if (numActivities == 0) {
|
||||||
|
TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false);
|
||||||
|
text.setText(mUri.toString());
|
||||||
|
return text;
|
||||||
|
} else if (numActivities == 1) {
|
||||||
|
return buildActivityView(activity, activities.get(0), pm, inflater, parent);
|
||||||
|
} else {
|
||||||
|
// Build a container to hold the multiple entries
|
||||||
|
LinearLayout container = new LinearLayout(activity);
|
||||||
|
container.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
container.setLayoutParams(new LayoutParams(
|
||||||
|
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
|
// Create an entry for each activity that can handle the URI
|
||||||
|
for (ResolveInfo resolveInfo : activities) {
|
||||||
|
if (container.getChildCount() > 0) {
|
||||||
|
inflater.inflate(R.layout.tag_divider, container);
|
||||||
|
}
|
||||||
|
container.addView(buildActivityView(activity, resolveInfo, pm, inflater, container));
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private View buildActivityView(Activity activity, ResolveInfo resolveInfo, PackageManager pm,
|
||||||
|
LayoutInflater inflater, ViewGroup parent) {
|
||||||
|
Intent intent = getIntentForUri();
|
||||||
|
ActivityInfo activityInfo = resolveInfo.activityInfo;
|
||||||
|
intent.setComponent(new ComponentName(activityInfo.packageName, activityInfo.name));
|
||||||
|
|
||||||
|
View item = inflater.inflate(R.layout.tag_uri, parent, false);
|
||||||
|
item.setOnClickListener(this);
|
||||||
|
item.setTag(new ClickInfo(activity, intent));
|
||||||
|
|
||||||
|
ImageView icon = (ImageView) item.findViewById(R.id.icon);
|
||||||
|
icon.setImageDrawable(resolveInfo.loadIcon(pm));
|
||||||
|
|
||||||
|
TextView text = (TextView) item.findViewById(R.id.secondary);
|
||||||
|
text.setText(resolveInfo.loadLabel(pm));
|
||||||
|
|
||||||
|
text = (TextView) item.findViewById(R.id.primary);
|
||||||
|
text.setText(getPrettyUriString(activity));
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
ClickInfo info = (ClickInfo) view.getTag();
|
||||||
|
info.activity.startActivity(info.intent);
|
||||||
|
info.activity.finish();
|
||||||
|
}
|
||||||
|
|
||||||
public Uri getUri() {
|
public Uri getUri() {
|
||||||
return mUri;
|
return mUri;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user