From 150dd1693ff06a7683076eebe669277631dedf06 Mon Sep 17 00:00:00 2001 From: Megha Joshi Date: Tue, 2 Nov 2010 23:27:24 -0700 Subject: [PATCH] NFC sample app (DO NOT MERGE) Change-Id: I1d1d67bbc6760cea1a0c6eccfa4d9f7b1398cd4d --- samples/NFCDemo/Android.mk | 18 ++ samples/NFCDemo/AndroidManifest.xml | 49 ++++ samples/NFCDemo/_index.html | 129 ++++++++++ samples/NFCDemo/res/drawable/icon.png | Bin 0 -> 2574 bytes samples/NFCDemo/res/layout/tag_divider.xml | 22 ++ samples/NFCDemo/res/layout/tag_text.xml | 28 +++ samples/NFCDemo/res/layout/tag_viewer.xml | 32 +++ .../res/raw/discovered_tag_notification.ogg | Bin 0 -> 7871 bytes samples/NFCDemo/res/values/strings.xml | 28 +++ .../android/nfc/NdefMessageParser.java | 57 +++++ .../com/example/android/nfc/TagViewer.java | 124 ++++++++++ .../android/nfc/record/ParsedNdefRecord.java | 33 +++ .../android/nfc/record/SmartPoster.java | 222 ++++++++++++++++++ .../android/nfc/record/TextRecord.java | 104 ++++++++ .../example/android/nfc/record/UriRecord.java | 159 +++++++++++++ .../nfc/simulator/FakeTagsActivity.java | 108 +++++++++ .../nfc/simulator/MockNdefMessages.java | 59 +++++ 17 files changed, 1172 insertions(+) create mode 100644 samples/NFCDemo/Android.mk create mode 100644 samples/NFCDemo/AndroidManifest.xml create mode 100644 samples/NFCDemo/_index.html create mode 100644 samples/NFCDemo/res/drawable/icon.png create mode 100644 samples/NFCDemo/res/layout/tag_divider.xml create mode 100644 samples/NFCDemo/res/layout/tag_text.xml create mode 100644 samples/NFCDemo/res/layout/tag_viewer.xml create mode 100755 samples/NFCDemo/res/raw/discovered_tag_notification.ogg create mode 100644 samples/NFCDemo/res/values/strings.xml create mode 100644 samples/NFCDemo/src/com/example/android/nfc/NdefMessageParser.java create mode 100644 samples/NFCDemo/src/com/example/android/nfc/TagViewer.java create mode 100644 samples/NFCDemo/src/com/example/android/nfc/record/ParsedNdefRecord.java create mode 100644 samples/NFCDemo/src/com/example/android/nfc/record/SmartPoster.java create mode 100644 samples/NFCDemo/src/com/example/android/nfc/record/TextRecord.java create mode 100644 samples/NFCDemo/src/com/example/android/nfc/record/UriRecord.java create mode 100644 samples/NFCDemo/src/com/example/android/nfc/simulator/FakeTagsActivity.java create mode 100644 samples/NFCDemo/src/com/example/android/nfc/simulator/MockNdefMessages.java 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: +

+ +

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: +

+ +

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 +

+

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 0000000000000000000000000000000000000000..a07c69fa5a0f4da5d5efe96eea12a543154dbab6 GIT binary patch literal 2574 zcmV+p3i0)cP)Q`Og{P|8RRXpj5bgrSmEzSMfBn+{{vpNxw?;5UX;iv9sYxy_`IQHs$i<61a_iv^L>h8s-`D(`e@|IgS*Fj zNGM876Gf;3D8*1UX9a%v>yJKD*QkCwW2AirU(L{qNA)JghmGItc;(H<$!ABY&gBy1vJIEUj-b8%el*o|VkG)LqNx#TG>Jvj^jIte!!+RY z)T4j$7+PoF1AkRBf}R#^T=-q|PaK1$c<4UH)Hpq3$4WA|xtr!ZQLC=*vNE>O6E9kp+5X0eKB$6>C(lPwI@3#oY zhS_%x7e|j!$yG?ECXmh~EH~^OeuK}+sWoJse3Z3?ha3n`MM9KvA?uqpEnBg4Q46)7 zM$p%a$@l;+O}vfvx%XjH`}a{(-HHth9!JaUwV0*VqGR48^gWNYN<&~7x)y$e!X>e` zZ5!6KZoxbKuV9XUDI%#M1~IVh?pNSdeb~6@$y`v|yk=XK+fHxnDqnUK4&=QRNyIVf zYbDM*cI>~qIy*a7=z7uqkw@agd(<=y-Q7L!ty_23SGdXmahO<;N=wB+j;lNm%=OHC zy zU|>La6h%92y4IPufI$9>Xu!@y`TaNgtg&41@PwMwBdmSm7)xAWDLoqjZ==P2#*k7! z3o1)cVSI3KP_!?d8G^Lg0FtLXC~JYdxi|c%h~lXEixY=%VSFF@!*3&&9>(Rb|iK54Cx5;s~PY5iaV1het%w`dgQFBAJ;aFK zImQC}(|QaCFYUm1JVfzSc)ebv=)ObI)0jwJb``}Zj9J0n0Xgn*Zc(rFM9$xh_makZbm-at_v5^SW zM1y1SW@%+FuIy*WR)i3A2N_q;(YO`O!A|Ts^%z}9ZepCj3ytlw#x%N_fNrKKtPh`< z|1{UqF`4LxHaCQ79+E=uUXCOZ35jAMRz%R%0(P!0FMv=sk>Nr8%+OzY^c-M9@+fz=G`qa@v4sF5u-2289-#$**LWnyNNDwDf1( zkUiMnw|y$tn>pQP=Vn!#|17L^5AGrjtBkN$D@v)Z7LXc5EFhLB4<;7Wehh)CMqX|W zqsiZaO^benJ_hwa&V0ub$-_HUk**?g6fm9|!@kguU6*zhK)$qn-<3*kFrYPIaqR=V zUaUvk>@F_89b@tHs8R!*QKY;INJ<2_U+K6Ca3e9Gsl2{qY0%a7J?uICWgHuLfj+MB z=GkAN1&ifT#2u}B+2S#~$5jA(Qn^;H%CCmIae4AE-Dsng|Hl*Ov!z72k3ZnJs{pp| z+pW`DDueC#mEWOf=ucJ!dTL}hzOeiS-i?m2E;`EKz4<&Lu~NnW?peqVU^@<+T3KKu z{yrI%Qy-Z%HEvLUz}n^~m?7x`xuCtNR#L2En!T>dQtIKdS#V-Hzt3RtwTeYtmQ&dR z6qXZvac*oc@BUYEH%@Ylv_1&tSjkbzzU6*h1(3^C`;1z;g_SmOtclS?KWk2VYE zM*oS<=C483XckW?GN|1jfh3Ro(h + + + \ 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 0000000000000000000000000000000000000000..7bf5df7efd94e60aa8dee94eedcfc1321d8470bf GIT binary patch literal 7871 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 literal 0 HcmV?d00001 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}; +}