NFC sample app
Cherry-picking NFC sample into Honeycomb branch, since it wasn't merged during it's original commit to Gingerbread. Change-Id: I79308f04c1b2edd6cb5bb675fa13b9e5083a8432
This commit is contained in:
committed by
Trevor Johns
parent
d24e1aeaa6
commit
6aa608a559
@@ -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<ParsedNdefRecord> parse(NdefMessage message) {
|
||||
return getRecords(message.getRecords());
|
||||
}
|
||||
|
||||
public static List<ParsedNdefRecord> getRecords(NdefRecord[] records) {
|
||||
List<ParsedNdefRecord> elements = new ArrayList<ParsedNdefRecord>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
124
samples/NFCDemo/src/com/example/android/nfc/TagViewer.java
Normal file
124
samples/NFCDemo/src/com/example/android/nfc/TagViewer.java
Normal file
@@ -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<ParsedNdefRecord> 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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<ParsedNdefRecord> 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> T getFirstIfExists(Iterable<?> elements, Class<T> type) {
|
||||
Iterable<T> 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<Byte, RecommendedAction> LOOKUP;
|
||||
static {
|
||||
ImmutableMap.Builder<Byte, RecommendedAction> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Byte, String> URI_PREFIX_MAP = ImmutableBiMap.<Byte, String>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];
|
||||
}
|
||||
@@ -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<TagDescription> 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<TagDescription> adapter = new ArrayAdapter<TagDescription>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
Reference in New Issue
Block a user