diff --git a/samples/NFCDemo/Android.mk b/samples/NFCDemo/Android.mk
new file mode 100644
index 000000000..60815a207
--- /dev/null
+++ b/samples/NFCDemo/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples tests
+
+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 := NFCDemo
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/NFCDemo/AndroidManifest.xml b/samples/NFCDemo/AndroidManifest.xml
new file mode 100644
index 000000000..044690b75
--- /dev/null
+++ b/samples/NFCDemo/AndroidManifest.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/NFCDemo/_index.html b/samples/NFCDemo/_index.html
new file mode 100644
index 000000000..d1c53f17d
--- /dev/null
+++ b/samples/NFCDemo/_index.html
@@ -0,0 +1,129 @@
+
+ Near-field Communication or NFC is a standard defined by the
+ NFC Forum
+ .
+ NFC Data Exchange Format (NDEF) defines a common data format between NFC-compliant devices and tags.
+ This demo application shows how to read a NDEF Tags using using Android 2.3 SDK APIs.
+ The NFC Tags consist of data encoded in NDEF Message format specified by NFC Forum Type 2 Specification.
+ Each NDEF message consists of one or more NDEF Records.
+
+ You need a NFC compliant device and a NFC compliant Tag to use this sample app. Or else, you could use
+ the FakeTagsActivity displayed at launch of this sample app, to generate fake Tag broadcasts from the emulator.
+
+
+The application includes:
+
+
+ -
+
+
TagViewer
+
+
+ — an
+ Activity
+ that handles a broadcast of a new tag that the device
+ just discovered, parses it, and displays its record contents in a
+ ListActivity
+
+
+ -
+
+
NdefMessageParser
+
+
+ — parses the record type of records within the NDEF message.
+
+ -
+
+
ParsedNdefRecord
+
+
+ — an interface implemented by all parsed NdefRecord types.
+
+ -
+
+
SmartPoster
+
+
+ — a representation of an NFC Forum Smart Poster Record Type.
+
+ -
+
+
TextRecord
+
+
+ — a representation of an NFC Forum Text Record Type.
+
+ -
+
+
UriRecord
+
+
+ — a representation of an NFC Forum Uri Record Type.
+
+ -
+
+
FakeTagsActivity
+
+
+ — A activity that launches tags as if they had been scanned.
+ This is useful if you don't have access to NFC enabled device or tag.
+
+ -
+
+
MockNdefMessages
+
+
+ — this class provides a list of fake NFC Ndef format Tags.
+
+
+
+If you are developing an application that uses the NFC API, remember that the feature
+ is supported only on Android 2.3 (API level 9) and higher versions of the platform. Also,
+ among devices running Android 2.3 (API level 9) or higher, not all devices will offer NFC
+ support. To ensure that your application can only be installed on devices that are capable
+ of supporting NFC, remember to add the following to the application's manifest before
+ publishing to Android Market:
+
+
+ -
+
<uses-sdk android:minSdkVersion="9" />
+ ,
+ which indicates to Android Market and the platform that your application requires
+ Android 2.3 or higher. For more information, see
+ API Levels
+
+ and the documentation for the
+
+ <uses-sdk>
+
+ element.
+
+
+To control how Android Market filters your application
+ from devices that do not support NFC, remember to add the following to the application's manifest
+
+ -
+
<uses-feature android:name="android.hardware.nfc" />
+ ,
+ which tells Android Market that your application uses the NFC API. The declaration
+ should include an
+ android:required
+ attribute that indicates whether you want
+ Android Market to filter the application from devices that do not offer NFC support. Other
+ <uses-feature>
+ declarations may also be needed, depending on your
+ implementation. For more information, see the documentation for the
+
+ <uses-feature>
+
+ element.
+
+
+For more information about using the NFC API, see the
+
+ android.nfc
+
+ documentation.
+
+
diff --git a/samples/NFCDemo/res/drawable/icon.png b/samples/NFCDemo/res/drawable/icon.png
new file mode 100644
index 000000000..a07c69fa5
Binary files /dev/null and b/samples/NFCDemo/res/drawable/icon.png differ
diff --git a/samples/NFCDemo/res/layout/tag_divider.xml b/samples/NFCDemo/res/layout/tag_divider.xml
new file mode 100644
index 000000000..b6b1b7cd4
--- /dev/null
+++ b/samples/NFCDemo/res/layout/tag_divider.xml
@@ -0,0 +1,22 @@
+
+
+
+
\ No newline at end of file
diff --git a/samples/NFCDemo/res/layout/tag_text.xml b/samples/NFCDemo/res/layout/tag_text.xml
new file mode 100644
index 000000000..59cbbfbc9
--- /dev/null
+++ b/samples/NFCDemo/res/layout/tag_text.xml
@@ -0,0 +1,28 @@
+
+
+
+
\ No newline at end of file
diff --git a/samples/NFCDemo/res/layout/tag_viewer.xml b/samples/NFCDemo/res/layout/tag_viewer.xml
new file mode 100644
index 000000000..a429ef95d
--- /dev/null
+++ b/samples/NFCDemo/res/layout/tag_viewer.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/NFCDemo/res/raw/discovered_tag_notification.ogg b/samples/NFCDemo/res/raw/discovered_tag_notification.ogg
new file mode 100755
index 000000000..7bf5df7ef
Binary files /dev/null and b/samples/NFCDemo/res/raw/discovered_tag_notification.ogg differ
diff --git a/samples/NFCDemo/res/values/strings.xml b/samples/NFCDemo/res/values/strings.xml
new file mode 100644
index 000000000..251d2f66c
--- /dev/null
+++ b/samples/NFCDemo/res/values/strings.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ NFCDemo
+
+
+ New tag collected
+
+
+ Text
+
+
diff --git a/samples/NFCDemo/src/com/example/android/nfc/NdefMessageParser.java b/samples/NFCDemo/src/com/example/android/nfc/NdefMessageParser.java
new file mode 100644
index 000000000..7372810b3
--- /dev/null
+++ b/samples/NFCDemo/src/com/example/android/nfc/NdefMessageParser.java
@@ -0,0 +1,57 @@
+/*
+ * 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.example.android.nfc;
+
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+
+import com.example.android.nfc.record.ParsedNdefRecord;
+import com.example.android.nfc.record.SmartPoster;
+import com.example.android.nfc.record.TextRecord;
+import com.example.android.nfc.record.UriRecord;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for creating {@link ParsedNdefMessage}s.
+ */
+public class NdefMessageParser {
+
+ // Utility class
+ private NdefMessageParser() {
+
+ }
+
+ /** Parse an NdefMessage */
+ public static List parse(NdefMessage message) {
+ return getRecords(message.getRecords());
+ }
+
+ public static List getRecords(NdefRecord[] records) {
+ List elements = new ArrayList();
+ for (NdefRecord record : records) {
+ if (UriRecord.isUri(record)) {
+ elements.add(UriRecord.parse(record));
+ } else if (TextRecord.isText(record)) {
+ elements.add(TextRecord.parse(record));
+ } else if (SmartPoster.isPoster(record)) {
+ elements.add(SmartPoster.parse(record));
+ }
+ }
+ return elements;
+ }
+}
diff --git a/samples/NFCDemo/src/com/example/android/nfc/TagViewer.java b/samples/NFCDemo/src/com/example/android/nfc/TagViewer.java
new file mode 100644
index 000000000..01dc0bd80
--- /dev/null
+++ b/samples/NFCDemo/src/com/example/android/nfc/TagViewer.java
@@ -0,0 +1,124 @@
+/*
+ * 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.example.android.nfc;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.example.android.nfc.record.ParsedNdefRecord;
+
+import java.util.List;
+
+/**
+ * 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 = "ViewTag";
+
+ /**
+ * This activity will finish itself in this amount of time if the user
+ * doesn't do anything.
+ */
+ static final int ACTIVITY_TIMEOUT_MS = 1 * 1000;
+
+ TextView mTitle;
+
+ LinearLayout mTagContent;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.tag_viewer);
+ mTagContent = (LinearLayout) findViewById(R.id.list);
+ mTitle = (TextView) findViewById(R.id.title);
+ resolveIntent(getIntent());
+ }
+
+ void resolveIntent(Intent intent) {
+ // Parse the intent
+ String action = intent.getAction();
+ if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
+ // When a tag is discovered we send it to the service to be save. We
+ // include a PendingIntent for the service to call back onto. This
+ // will cause this activity to be restarted with onNewIntent(). At
+ // that time we read it from the database and view it.
+ Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
+ NdefMessage[] msgs;
+ if (rawMsgs != null) {
+ msgs = new NdefMessage[rawMsgs.length];
+ for (int i = 0; i < rawMsgs.length; i++) {
+ msgs[i] = (NdefMessage) rawMsgs[i];
+ }
+ } else {
+ // Unknown tag type
+ byte[] empty = new byte[] {};
+ NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
+ NdefMessage msg = new NdefMessage(new NdefRecord[] {record});
+ msgs = new NdefMessage[] {msg};
+ }
+ // Setup the views
+ setTitle(R.string.title_scanned_tag);
+ buildTagViews(msgs);
+ } else {
+ Log.e(TAG, "Unknown intent " + intent);
+ finish();
+ return;
+ }
+ }
+
+ void buildTagViews(NdefMessage[] msgs) {
+ if (msgs == null || msgs.length == 0) {
+ return;
+ }
+ LayoutInflater inflater = LayoutInflater.from(this);
+ LinearLayout content = mTagContent;
+ // Clear out any old views in the content area, for example if you scan
+ // two tags in a row.
+ content.removeAllViews();
+ // Parse the first message in the list
+ // Build views for all of the sub records
+ List records = NdefMessageParser.parse(msgs[0]);
+ final int size = records.size();
+ for (int i = 0; i < size; i++) {
+ ParsedNdefRecord record = records.get(i);
+ content.addView(record.getView(this, inflater, content, i));
+ inflater.inflate(R.layout.tag_divider, content, true);
+ }
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ setIntent(intent);
+ resolveIntent(intent);
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mTitle.setText(title);
+ }
+}
diff --git a/samples/NFCDemo/src/com/example/android/nfc/record/ParsedNdefRecord.java b/samples/NFCDemo/src/com/example/android/nfc/record/ParsedNdefRecord.java
new file mode 100644
index 000000000..b706ae8cb
--- /dev/null
+++ b/samples/NFCDemo/src/com/example/android/nfc/record/ParsedNdefRecord.java
@@ -0,0 +1,33 @@
+/*
+ * 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.example.android.nfc.record;
+
+import android.app.Activity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+
+public interface ParsedNdefRecord {
+
+ /**
+ * Returns a view to display this record.
+ */
+ public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent,
+ int offset);
+
+}
diff --git a/samples/NFCDemo/src/com/example/android/nfc/record/SmartPoster.java b/samples/NFCDemo/src/com/example/android/nfc/record/SmartPoster.java
new file mode 100644
index 000000000..f86d3ef63
--- /dev/null
+++ b/samples/NFCDemo/src/com/example/android/nfc/record/SmartPoster.java
@@ -0,0 +1,222 @@
+/*
+ * 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.example.android.nfc.record;
+
+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 com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+import com.example.android.nfc.NdefMessageParser;
+import com.example.android.nfc.R;
+
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+
+/**
+ * A representation of an NFC Forum "Smart Poster".
+ */
+public class SmartPoster implements ParsedNdefRecord {
+
+ /**
+ * NFC Forum Smart Poster Record Type Definition section 3.2.1.
+ *
+ * "The Title record for the service (there can be many of these in
+ * different languages, but a language MUST NOT be repeated). This record is
+ * optional."
+ */
+ private final TextRecord mTitleRecord;
+
+ /**
+ * NFC Forum Smart Poster Record Type Definition section 3.2.1.
+ *
+ * "The URI record. This is the core of the Smart Poster, and all other
+ * records are just metadata about this record. There MUST be one URI record
+ * and there MUST NOT be more than one."
+ */
+ private final UriRecord mUriRecord;
+
+ /**
+ * NFC Forum Smart Poster Record Type Definition section 3.2.1.
+ *
+ * "The Action record. This record describes how the service should be
+ * treated. For example, the action may indicate that the device should save
+ * the URI as a bookmark or open a browser. The Action record is optional.
+ * If it does not exist, the device may decide what to do with the service.
+ * If the action record exists, it should be treated as a strong suggestion;
+ * the UI designer may ignore it, but doing so will induce a different user
+ * experience from device to device."
+ */
+ private final RecommendedAction mAction;
+
+ /**
+ * NFC Forum Smart Poster Record Type Definition section 3.2.1.
+ *
+ * "The Type record. If the URI references an external entity (e.g., via a
+ * URL), the Type record may be used to declare the MIME type of the entity.
+ * This can be used to tell the mobile device what kind of an object it can
+ * expect before it opens the connection. The Type record is optional."
+ */
+ private final String mType;
+
+ private SmartPoster(UriRecord uri, TextRecord title, RecommendedAction action, String type) {
+ mUriRecord = Preconditions.checkNotNull(uri);
+ mTitleRecord = title;
+ mAction = Preconditions.checkNotNull(action);
+ mType = type;
+ }
+
+ public UriRecord getUriRecord() {
+ return mUriRecord;
+ }
+
+ /**
+ * Returns the title of the smart poster. This may be {@code null}.
+ */
+ public TextRecord getTitle() {
+ return mTitleRecord;
+ }
+
+ public static SmartPoster parse(NdefRecord record) {
+ Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN);
+ Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER));
+ try {
+ NdefMessage subRecords = new NdefMessage(record.getPayload());
+ return parse(subRecords.getRecords());
+ } catch (FormatException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static SmartPoster parse(NdefRecord[] recordsRaw) {
+ try {
+ Iterable records = NdefMessageParser.getRecords(recordsRaw);
+ UriRecord uri = Iterables.getOnlyElement(Iterables.filter(records, UriRecord.class));
+ TextRecord title = getFirstIfExists(records, TextRecord.class);
+ RecommendedAction action = parseRecommendedAction(recordsRaw);
+ String type = parseType(recordsRaw);
+ return new SmartPoster(uri, title, action, type);
+ } catch (NoSuchElementException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static boolean isPoster(NdefRecord record) {
+ try {
+ parse(record);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
+ 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, offset));
+ inflater.inflate(R.layout.tag_divider, container);
+ container.addView(mUriRecord.getView(activity, inflater, container, offset));
+ return container;
+ } else {
+ // Just a URI, return a view for it directly
+ return mUriRecord.getView(activity, inflater, parent, offset);
+ }
+ }
+
+ /**
+ * Returns the first element of {@code elements} which is an instance of
+ * {@code type}, or {@code null} if no such element exists.
+ */
+ private static T getFirstIfExists(Iterable> elements, Class type) {
+ Iterable filtered = Iterables.filter(elements, type);
+ T instance = null;
+ if (!Iterables.isEmpty(filtered)) {
+ instance = Iterables.get(filtered, 0);
+ }
+ return instance;
+ }
+
+ private enum RecommendedAction {
+ UNKNOWN((byte) -1), DO_ACTION((byte) 0), SAVE_FOR_LATER((byte) 1), OPEN_FOR_EDITING(
+ (byte) 2);
+
+ private static final ImmutableMap LOOKUP;
+ static {
+ ImmutableMap.Builder builder = ImmutableMap.builder();
+ for (RecommendedAction action : RecommendedAction.values()) {
+ builder.put(action.getByte(), action);
+ }
+ LOOKUP = builder.build();
+ }
+
+ private final byte mAction;
+
+ private RecommendedAction(byte val) {
+ this.mAction = val;
+ }
+
+ private byte getByte() {
+ return mAction;
+ }
+ }
+
+ private static NdefRecord getByType(byte[] type, NdefRecord[] records) {
+ for (NdefRecord record : records) {
+ if (Arrays.equals(type, record.getType())) {
+ return record;
+ }
+ }
+ return null;
+ }
+
+ private static final byte[] ACTION_RECORD_TYPE = new byte[] {'a', 'c', 't'};
+
+ private static RecommendedAction parseRecommendedAction(NdefRecord[] records) {
+ NdefRecord record = getByType(ACTION_RECORD_TYPE, records);
+ if (record == null) {
+ return RecommendedAction.UNKNOWN;
+ }
+ byte action = record.getPayload()[0];
+ if (RecommendedAction.LOOKUP.containsKey(action)) {
+ return RecommendedAction.LOOKUP.get(action);
+ }
+ return RecommendedAction.UNKNOWN;
+ }
+
+ private static final byte[] TYPE_TYPE = new byte[] {'t'};
+
+ private static String parseType(NdefRecord[] records) {
+ NdefRecord type = getByType(TYPE_TYPE, records);
+ if (type == null) {
+ return null;
+ }
+ return new String(type.getPayload(), Charsets.UTF_8);
+ }
+}
diff --git a/samples/NFCDemo/src/com/example/android/nfc/record/TextRecord.java b/samples/NFCDemo/src/com/example/android/nfc/record/TextRecord.java
new file mode 100644
index 000000000..8041cca9b
--- /dev/null
+++ b/samples/NFCDemo/src/com/example/android/nfc/record/TextRecord.java
@@ -0,0 +1,104 @@
+/*
+ * 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.example.android.nfc.record;
+
+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 com.google.common.base.Preconditions;
+
+import com.example.android.nfc.R;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+/**
+ * An NFC Text Record
+ */
+public class TextRecord implements ParsedNdefRecord {
+
+ /** ISO/IANA language code */
+ private final String mLanguageCode;
+
+ private final String mText;
+
+ private TextRecord(String languageCode, String text) {
+ mLanguageCode = Preconditions.checkNotNull(languageCode);
+ mText = Preconditions.checkNotNull(text);
+ }
+
+ public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
+ TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false);
+ text.setText(mText);
+ return text;
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * Returns the ISO/IANA language code associated with this text element.
+ */
+ public String getLanguageCode() {
+ return mLanguageCode;
+ }
+
+ // TODO: deal with text fields which span multiple NdefRecords
+ public static TextRecord parse(NdefRecord record) {
+ Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN);
+ Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT));
+ try {
+ byte[] payload = record.getPayload();
+ /*
+ * payload[0] contains the "Status Byte Encodings" field, per the
+ * NFC Forum "Text Record Type Definition" section 3.2.1.
+ *
+ * bit7 is the Text Encoding Field.
+ *
+ * if (Bit_7 == 0): The text is encoded in UTF-8 if (Bit_7 == 1):
+ * The text is encoded in UTF16
+ *
+ * Bit_6 is reserved for future use and must be set to zero.
+ *
+ * Bits 5 to 0 are the length of the IANA language code.
+ */
+ String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16";
+ int languageCodeLength = payload[0] & 0077;
+ String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
+ String text =
+ new String(payload, languageCodeLength + 1,
+ payload.length - languageCodeLength - 1, textEncoding);
+ return new TextRecord(languageCode, text);
+ } catch (UnsupportedEncodingException e) {
+ // should never happen unless we get a malformed tag.
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static boolean isText(NdefRecord record) {
+ try {
+ parse(record);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+}
diff --git a/samples/NFCDemo/src/com/example/android/nfc/record/UriRecord.java b/samples/NFCDemo/src/com/example/android/nfc/record/UriRecord.java
new file mode 100644
index 000000000..452ebb282
--- /dev/null
+++ b/samples/NFCDemo/src/com/example/android/nfc/record/UriRecord.java
@@ -0,0 +1,159 @@
+/*
+ * 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.example.android.nfc.record;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.nfc.NdefRecord;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+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.example.android.nfc.R;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+/**
+ * A parsed record containing a Uri.
+ */
+public class UriRecord implements ParsedNdefRecord {
+
+ private static final String TAG = "UriRecord";
+
+ public static final String RECORD_TYPE = "UriRecord";
+
+ /**
+ * 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();
+
+ private final Uri mUri;
+
+ private UriRecord(Uri uri) {
+ this.mUri = Preconditions.checkNotNull(uri);
+ }
+
+ public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
+ TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false);
+ text.setText(mUri.toString());
+ return text;
+ }
+
+ public Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * Convert {@link android.nfc.NdefRecord} into a {@link android.net.Uri}.
+ * This will handle both TNF_WELL_KNOWN / RTD_URI and TNF_ABSOLUTE_URI.
+ *
+ * @throws IllegalArgumentException if the NdefRecord is not a record
+ * containing a URI.
+ */
+ public static UriRecord parse(NdefRecord record) {
+ short tnf = record.getTnf();
+ if (tnf == NdefRecord.TNF_WELL_KNOWN) {
+ return parseWellKnown(record);
+ } else if (tnf == NdefRecord.TNF_ABSOLUTE_URI) {
+ return parseAbsolute(record);
+ }
+ throw new IllegalArgumentException("Unknown TNF " + tnf);
+ }
+
+ /** Parse and absolute URI record */
+ private static UriRecord parseAbsolute(NdefRecord record) {
+ byte[] payload = record.getPayload();
+ Uri uri = Uri.parse(new String(payload, Charset.forName("UTF-8")));
+ return new UriRecord(uri);
+ }
+
+ /** Parse an well known URI record */
+ private static UriRecord parseWellKnown(NdefRecord record) {
+ Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_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(Charset.forName("UTF-8")), Arrays.copyOfRange(payload, 1,
+ payload.length));
+ Uri uri = Uri.parse(new String(fullUri, Charset.forName("UTF-8")));
+ return new UriRecord(uri);
+ }
+
+ public static boolean isUri(NdefRecord record) {
+ try {
+ parse(record);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ private static final byte[] EMPTY = new byte[0];
+}
diff --git a/samples/NFCDemo/src/com/example/android/nfc/simulator/FakeTagsActivity.java b/samples/NFCDemo/src/com/example/android/nfc/simulator/FakeTagsActivity.java
new file mode 100644
index 000000000..313bab499
--- /dev/null
+++ b/samples/NFCDemo/src/com/example/android/nfc/simulator/FakeTagsActivity.java
@@ -0,0 +1,108 @@
+/*
+ * 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.example.android.nfc.simulator;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.primitives.Bytes;
+
+import java.nio.charset.Charset;
+import java.util.Locale;
+
+/**
+ * A activity that launches tags as if they had been scanned.
+ */
+public class FakeTagsActivity extends ListActivity {
+
+ static final String TAG = "FakeTagsActivity";
+
+ static final byte[] UID = new byte[] {0x05, 0x00, 0x03, 0x08};
+
+ ArrayAdapter mAdapter;
+
+ public static NdefRecord newTextRecord(String text, Locale locale, boolean encodeInUtf8) {
+ Preconditions.checkNotNull(text);
+ Preconditions.checkNotNull(locale);
+ final byte[] langBytes = locale.getLanguage().getBytes(Charsets.US_ASCII);
+ final Charset utfEncoding = encodeInUtf8 ? Charsets.UTF_8 : Charset.forName("UTF-16");
+ final byte[] textBytes = text.getBytes(utfEncoding);
+ final int utfBit = encodeInUtf8 ? 0 : (1 << 7);
+ final char status = (char) (utfBit + langBytes.length);
+ final byte[] data = Bytes.concat(new byte[] {(byte) status}, langBytes, textBytes);
+ return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data);
+ }
+
+ public static NdefRecord newMimeRecord(String type, byte[] data) {
+ Preconditions.checkNotNull(type);
+ Preconditions.checkNotNull(data);
+ final byte[] typeBytes = type.getBytes(Charsets.US_ASCII);
+ return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, typeBytes, new byte[0], data);
+ }
+
+ static final class TagDescription {
+
+ public String title;
+
+ public NdefMessage[] msgs;
+
+ public TagDescription(String title, byte[] bytes) {
+ this.title = title;
+ try {
+ msgs = new NdefMessage[] {new NdefMessage(bytes)};
+ } catch (final Exception e) {
+ throw new RuntimeException("Failed to create tag description", e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return title;
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ final ArrayAdapter adapter = new ArrayAdapter(
+ this, android.R.layout.simple_list_item_1, android.R.id.text1);
+ adapter.add(
+ new TagDescription("Broadcast NFC Text Tag", MockNdefMessages.ENGLISH_PLAIN_TEXT));
+ adapter.add(new TagDescription(
+ "Broadcast NFC SmartPoster URL & text", MockNdefMessages.SMART_POSTER_URL_AND_TEXT));
+ adapter.add(new TagDescription(
+ "Broadcast NFC SmartPoster URL", MockNdefMessages.SMART_POSTER_URL_NO_TEXT));
+ setListAdapter(adapter);
+ mAdapter = adapter;
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ final TagDescription description = mAdapter.getItem(position);
+ final Intent intent = new Intent(NfcAdapter.ACTION_TAG_DISCOVERED);
+ intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, description.msgs);
+ startActivity(intent);
+ }
+}
diff --git a/samples/NFCDemo/src/com/example/android/nfc/simulator/MockNdefMessages.java b/samples/NFCDemo/src/com/example/android/nfc/simulator/MockNdefMessages.java
new file mode 100644
index 000000000..52a122f7e
--- /dev/null
+++ b/samples/NFCDemo/src/com/example/android/nfc/simulator/MockNdefMessages.java
@@ -0,0 +1,59 @@
+/*
+ * 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.example.android.nfc.simulator;
+
+/**
+ * This class provides a list of fake NFC Ndef format Tags.
+ */
+public class MockNdefMessages {
+
+ /**
+ * A Smart Poster containing a URL and no text.
+ */
+ public static final byte[] SMART_POSTER_URL_NO_TEXT =
+ new byte[] {(byte) 0xd1, (byte) 0x02, (byte) 0x0f, (byte) 0x53, (byte) 0x70, (byte) 0xd1,
+ (byte) 0x01, (byte) 0x0b, (byte) 0x55, (byte) 0x01, (byte) 0x67, (byte) 0x6f,
+ (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63,
+ (byte) 0x6f, (byte) 0x6d};
+
+ /**
+ * A plain text tag in english.
+ */
+ public static final byte[] ENGLISH_PLAIN_TEXT =
+ new byte[] {(byte) 0xd1, (byte) 0x01, (byte) 0x1c, (byte) 0x54, (byte) 0x02, (byte) 0x65,
+ (byte) 0x6e, (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x20,
+ (byte) 0x72, (byte) 0x61, (byte) 0x6e, (byte) 0x64, (byte) 0x6f, (byte) 0x6d,
+ (byte) 0x20, (byte) 0x65, (byte) 0x6e, (byte) 0x67, (byte) 0x6c, (byte) 0x69,
+ (byte) 0x73, (byte) 0x68, (byte) 0x20, (byte) 0x74, (byte) 0x65, (byte) 0x78,
+ (byte) 0x74, (byte) 0x2e};
+
+ /**
+ * Smart Poster containing a URL and Text.
+ */
+ public static final byte[] SMART_POSTER_URL_AND_TEXT =
+ new byte[] {(byte) 0xd1, (byte) 0x02, (byte) 0x1c, (byte) 0x53, (byte) 0x70, (byte) 0x91,
+ (byte) 0x01, (byte) 0x09, (byte) 0x54, (byte) 0x02, (byte) 0x65, (byte) 0x6e,
+ (byte) 0x47, (byte) 0x6f, (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65,
+ (byte) 0x51, (byte) 0x01, (byte) 0x0b, (byte) 0x55, (byte) 0x01, (byte) 0x67,
+ (byte) 0x6f, (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, (byte) 0x2e,
+ (byte) 0x63, (byte) 0x6f, (byte) 0x6d};
+
+ /**
+ * All the mock Ndef tags.
+ */
+ public static final byte[][] ALL_MOCK_MESSAGES =
+ new byte[][] {SMART_POSTER_URL_NO_TEXT, ENGLISH_PLAIN_TEXT, SMART_POSTER_URL_AND_TEXT};
+}