*/
abstract class AbstractNDEFParsedResult extends ParsedReaderResult {
- /**
- * MB = 1 (start of record)
- * ME = 1 (also end of record)
- * CF = 0 (not a chunk)
- * SR = 1 (assume short record)
- * ID = 0 (ID length field omitted)
- * TNF = 0 (= 1, well-known type)
- * 0
- * 1
- */
- private static final int HEADER_VALUE = 0xD1;
- private static final int MASK = 0xFF;
-
AbstractNDEFParsedResult(ParsedReaderResultType type) {
super(type);
}
- static boolean isMaybeNDEF(byte[] bytes) {
- return
- bytes != null &&
- bytes.length >= 4 &&
- ((bytes[0] & MASK) == HEADER_VALUE) &&
- ((bytes[1] & 0xFF) == 1);
- }
-
static String bytesToString(byte[] bytes, int offset, int length, String encoding) {
try {
return new String(bytes, offset, length, encoding);
} catch (UnsupportedEncodingException uee) {
+ // This should only be used when 'encoding' is an encoding that must necessarily
+ // be supported by the JVM, like UTF-8
throw new RuntimeException("Platform does not support required encoding: " + uee);
}
}
public static AddressBookAUParsedResult parse(Result result) {
String rawText = result.getText();
// MEMORY is mandatory; seems like a decent indicator, as does end-of-record separator CR/LF
- if (rawText.indexOf("MEMORY") < 0 || rawText.indexOf("\r\n") < 0) {
+ if (rawText == null || rawText.indexOf("MEMORY") < 0 || rawText.indexOf("\r\n") < 0) {
return null;
}
String[] names = matchMultipleValuePrefix("NAME", 2, rawText);
public static AddressBookDoCoMoParsedResult parse(Result result) {
String rawText = result.getText();
- if (!rawText.startsWith("MECARD:")) {
+ if (rawText == null || !rawText.startsWith("MECARD:")) {
return null;
}
String[] rawName = matchPrefixedField("N:", rawText);
public static BookmarkDoCoMoParsedResult parse(Result result) {
String rawText = result.getText();
- if (!rawText.startsWith("MEBKM:")) {
+ if (rawText == null || !rawText.startsWith("MEBKM:")) {
return null;
}
String title = matchSinglePrefixedField("TITLE:", rawText);
public static EmailAddressParsedResult parse(Result result) {
String rawText = result.getText();
String emailAddress;
- if (rawText.startsWith("mailto:")) {
+ if (rawText != null && rawText.startsWith("mailto:")) {
// If it starts with mailto:, assume it is definitely trying to be an email address
emailAddress = rawText.substring(7);
} else {
public static EmailDoCoMoParsedResult parse(Result result) {
String rawText = result.getText();
- if (!rawText.startsWith("MATMSG:")) {
+ if (rawText == null || !rawText.startsWith("MATMSG:")) {
return null;
}
String[] rawTo = matchPrefixedField("TO:", rawText);
* in a barcode, not "judge" it.
*/
static boolean isBasicallyValidEmailAddress(String email) {
+ if (email == null) {
+ return false;
+ }
int atIndex = email.indexOf('@');
return atIndex >= 0 && email.indexOf('.') > atIndex && email.indexOf(' ') < 0;
}
public static GeoParsedResult parse(Result result) {
String rawText = result.getText();
- if (!rawText.startsWith("geo:")) {
+ if (rawText == null || !rawText.startsWith("geo:")) {
return null;
}
// Drop geo, query portion
--- /dev/null
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * 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.google.zxing.client.result;
+
+/**
+ * <p>Represents a record in an NDEF message. This class only supports certain types
+ * of records -- namely, non-chunked records, where ID length is omitted, and only
+ * "short records".</p>
+ *
+ * @author srowen@google.com (Sean Owen)
+ */
+final class NDEFRecord {
+
+ private static final int SUPPORTED_HEADER_MASK = 0x3F; // 0 0 1 1 1 111 (the bottom 6 bits matter)
+ private static final int SUPPORTED_HEADER = 0x11; // 0 0 0 1 0 001
+
+ public static final String TEXT_WELL_KNOWN_TYPE = "T";
+ public static final String URI_WELL_KNOWN_TYPE = "U";
+ public static final String SMART_POSTER_WELL_KNOWN_TYPE = "Sp";
+ public static final String ACTION_WELL_KNOWN_TYPE = "act";
+
+ private final int header;
+ private final String type;
+ private final byte[] payload;
+ private final int totalRecordLength;
+
+ private NDEFRecord(int header, String type, byte[] payload, int totalRecordLength) {
+ this.header = header;
+ this.type = type;
+ this.payload = payload;
+ this.totalRecordLength = totalRecordLength;
+ }
+
+ static NDEFRecord readRecord(byte[] bytes, int offset) {
+ int header = bytes[offset] & 0xFF;
+ // Does header match what we support in the bits we care about?
+ // XOR figures out where we differ, and if any of those are in the mask, fail
+ if (((header ^ SUPPORTED_HEADER) & SUPPORTED_HEADER_MASK) != 0) {
+ return null;
+ }
+ int typeLength = bytes[offset + 1] & 0xFF;
+
+ int payloadLength = bytes[offset + 2] & 0xFF;
+
+ String type = AbstractNDEFParsedResult.bytesToString(bytes, offset + 3, typeLength, "US-ASCII");
+
+ byte[] payload = new byte[payloadLength];
+ System.arraycopy(bytes, offset + 3 + typeLength, payload, 0, payloadLength);
+
+ return new NDEFRecord(header, type, payload, 3 + typeLength + payloadLength);
+ }
+
+ boolean isMessageBegin() {
+ return (header & 0x80) != 0;
+ }
+
+ boolean isMessageEnd() {
+ return (header & 0x40) != 0;
+ }
+
+ String getType() {
+ return type;
+ }
+
+ byte[] getPayload() {
+ return payload;
+ }
+
+ int getTotalRecordLength() {
+ return totalRecordLength;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * 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.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * <p>Recognizes an NDEF message that encodes information according to the
+ * "Smart Poster Record Type Definition" specification.</p>
+ *
+ * <p>This actually only supports some parts of the Smart Poster format: title,
+ * URI, and action records. Icon records are not supported because the size
+ * of these records are infeasibly large for barcodes. Size and type records
+ * are not supported. Multiple titles are not supported.</p>
+ *
+ * @author srowen@google.com (Sean Owen)
+ */
+public final class NDEFSmartPosterParsedResult extends AbstractNDEFParsedResult {
+
+ public static final int ACTION_UNSPECIFIED = -1;
+ public static final int ACTION_DO = 0;
+ public static final int ACTION_SAVE = 1;
+ public static final int ACTION_OPEN = 2;
+
+ private String title;
+ private String uri;
+ private int action;
+
+ private NDEFSmartPosterParsedResult() {
+ super(ParsedReaderResultType.NDEF_SMART_POSTER);
+ action = ACTION_UNSPECIFIED;
+ }
+
+ public static NDEFSmartPosterParsedResult parse(Result result) {
+ byte[] bytes = result.getRawBytes();
+ if (bytes == null) {
+ return null;
+ }
+ NDEFRecord headerRecord = NDEFRecord.readRecord(bytes, 0);
+ // Yes, header record starts and ends a message
+ if (headerRecord == null || !headerRecord.isMessageBegin() || !headerRecord.isMessageEnd()) {
+ return null;
+ }
+ if (!headerRecord.getType().equals(NDEFRecord.SMART_POSTER_WELL_KNOWN_TYPE)) {
+ return null;
+ }
+
+ int offset = 0;
+ int recordNumber = 0;
+ NDEFRecord ndefRecord = null;
+ byte[] payload = headerRecord.getPayload();
+ NDEFSmartPosterParsedResult smartPosterParsedResult = new NDEFSmartPosterParsedResult();
+
+ while (offset < payload.length && (ndefRecord = NDEFRecord.readRecord(payload, offset)) != null) {
+ if (recordNumber == 0 && !ndefRecord.isMessageBegin()) {
+ return null;
+ }
+ String type = ndefRecord.getType();
+ if (NDEFRecord.TEXT_WELL_KNOWN_TYPE.equals(type)) {
+ String[] languageText = NDEFTextParsedResult.decodeTextPayload(ndefRecord.getPayload());
+ smartPosterParsedResult.title = languageText[1];
+ } else if (NDEFRecord.URI_WELL_KNOWN_TYPE.equals(type)) {
+ smartPosterParsedResult.uri = NDEFURIParsedResult.decodeURIPayload(ndefRecord.getPayload());
+ } else if (NDEFRecord.ACTION_WELL_KNOWN_TYPE.equals(type)) {
+ smartPosterParsedResult.action = ndefRecord.getPayload()[0];
+ }
+ recordNumber++;
+ offset += ndefRecord.getTotalRecordLength();
+ }
+
+ if (recordNumber == 0 || (ndefRecord != null && !ndefRecord.isMessageEnd())) {
+ return null;
+ }
+
+ return smartPosterParsedResult;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getURI() {
+ return uri;
+ }
+
+ public int getAction() {
+ return action;
+ }
+
+ public String getDisplayResult() {
+ if (title == null) {
+ return uri;
+ } else {
+ return title + '\n' + uri;
+ }
+ }
+
+}
\ No newline at end of file
*/
public final class NDEFTextParsedResult extends AbstractNDEFParsedResult {
- private static final byte TEXT_WELL_KNOWN_TYPE = (byte) 0x54;
private final String language;
private final String text;
public static NDEFTextParsedResult parse(Result result) {
byte[] bytes = result.getRawBytes();
- if (!isMaybeNDEF(bytes)) {
+ if (bytes == null) {
return null;
}
-
- int payloadLength = bytes[2] & 0xFF;
-
- // Next 1 byte is type
- if (bytes[3] != TEXT_WELL_KNOWN_TYPE) {
+ NDEFRecord ndefRecord = NDEFRecord.readRecord(bytes, 0);
+ if (ndefRecord == null || !ndefRecord.isMessageBegin() || !ndefRecord.isMessageEnd()) {
+ return null;
+ }
+ if (!ndefRecord.getType().equals(NDEFRecord.TEXT_WELL_KNOWN_TYPE)) {
return null;
}
+ String[] languageText = decodeTextPayload(ndefRecord.getPayload());
+ return new NDEFTextParsedResult(languageText[0], languageText[1]);
+ }
- // Text record
- byte statusByte = bytes[4];
+ static String[] decodeTextPayload(byte[] payload) {
+ byte statusByte = payload[0];
boolean isUTF16 = (statusByte & 0x80) != 0;
int languageLength = statusByte & 0x1F;
-
// language is always ASCII-encoded:
- String language = bytesToString(bytes, 5, languageLength, "US-ASCII");
+ String language = bytesToString(payload, 1, languageLength, "US-ASCII");
String encoding = isUTF16 ? "UTF-16" : "UTF-8";
- String text = bytesToString(bytes, 5 + languageLength, payloadLength - languageLength - 1, encoding);
- return new NDEFTextParsedResult(language, text);
+ String text = bytesToString(payload, 1 + languageLength, payload.length - languageLength - 1, encoding);
+ return new String[] { language, text };
}
public String getLanguage() {
*/
public final class NDEFURIParsedResult extends AbstractNDEFParsedResult {
- private static final byte URI_WELL_KNOWN_TYPE = (byte) 0x55;
-
private static final String[] URI_PREFIXES = new String[] {
null,
"http://www.",
private final String uri;
private NDEFURIParsedResult(String uri) {
- super(ParsedReaderResultType.NDEF_TEXT);
+ super(ParsedReaderResultType.NDEF_URI);
this.uri = uri;
}
public static NDEFURIParsedResult parse(Result result) {
byte[] bytes = result.getRawBytes();
- if (!isMaybeNDEF(bytes)) {
+ if (bytes == null) {
return null;
}
-
- int payloadLength = bytes[2] & 0xFF;
-
- // Next 1 byte is type
- if (bytes[3] != URI_WELL_KNOWN_TYPE) {
+ NDEFRecord ndefRecord = NDEFRecord.readRecord(bytes, 0);
+ if (ndefRecord == null || !ndefRecord.isMessageBegin() || !ndefRecord.isMessageEnd()) {
+ return null;
+ }
+ if (!ndefRecord.getType().equals(NDEFRecord.URI_WELL_KNOWN_TYPE)) {
return null;
}
+ String fullURI = decodeURIPayload(ndefRecord.getPayload());
+ return new NDEFURIParsedResult(fullURI);
+ }
- int identifierCode = bytes[4] & 0xFF;
+ static String decodeURIPayload(byte[] payload) {
+ int identifierCode = payload[0] & 0xFF;
String prefix = null;
if (identifierCode < URI_PREFIXES.length) {
prefix = URI_PREFIXES[identifierCode];
}
-
- String restOfURI = bytesToString(bytes, 5, payloadLength - 1, "UTF-8");
- String fullURI = prefix == null ? restOfURI : prefix + restOfURI;
- return new NDEFURIParsedResult(fullURI);
+ String restOfURI = bytesToString(payload, 1, payload.length - 1, "UTF-8");
+ return prefix == null ? restOfURI : prefix + restOfURI;
}
public String getURI() {
return result;
} else if ((result = UPCParsedResult.parse(theResult)) != null) {
return result;
+ //} else if ((result = NDEFTextParsedResult.parse(theResult)) != null) {
+ // return result;
+ //} else if ((result = NDEFURIParsedResult.parse(theResult)) != null) {
+ // return result;
+ //} else if ((result = NDEFSmartPosterParsedResult.parse(theResult)) != null) {
+ // return result;
}
return TextParsedResult.parse(theResult);
}
// TODO later, add the NDEF types to those actually processed by the clients
public static final ParsedReaderResultType NDEF_TEXT = new ParsedReaderResultType("NDEF_TEXT");
public static final ParsedReaderResultType NDEF_URI = new ParsedReaderResultType("NDEF_URI");
+ public static final ParsedReaderResultType NDEF_SMART_POSTER = new ParsedReaderResultType("NDEF_SMART_POSTER");
private final String name;
public static TelParsedResult parse(Result result) {
String rawText = result.getText();
- if (!rawText.startsWith("tel:")) {
+ if (rawText == null || !rawText.startsWith("tel:")) {
return null;
}
// Drop tel, query portion
return null;
}
String rawText = result.getText();
+ if (rawText == null) {
+ return null;
+ }
int length = rawText.length();
if (length != 12 && length != 13) {
return null;
* need to know when a string is obviously not a URI.
*/
static boolean isBasicallyValidURI(String uri) {
- return uri.indexOf(' ') < 0 && (uri.indexOf(':') >= 0 || uri.indexOf('.') >= 0);
+ return uri != null && uri.indexOf(' ') < 0 && (uri.indexOf(':') >= 0 || uri.indexOf('.') >= 0);
}
}
\ No newline at end of file
public static URLTOParsedResult parse(Result result) {
String rawText = result.getText();
- if (!rawText.startsWith("URLTO:")) {
+ if (rawText == null || !rawText.startsWith("URLTO:")) {
return null;
}
int titleEnd = rawText.indexOf(':', 6);
doTestResult("telephone", ParsedReaderResultType.TEXT);
}
+ /*
+ public void testNDEFText() {
+ doTestResult(new byte[] {(byte)0xD1,(byte)0x01,(byte)0x05,(byte)0x54,
+ (byte)0x02,(byte)0x65,(byte)0x6E,(byte)0x68,
+ (byte)0x69},
+ ParsedReaderResultType.NDEF_TEXT);
+ }
+
+ public void testNDEFURI() {
+ doTestResult(new byte[] {(byte)0xD1,(byte)0x01,(byte)0x08,(byte)0x55,
+ (byte)0x01,(byte)0x6E,(byte)0x66,(byte)0x63,
+ (byte)0x2E,(byte)0x63,(byte)0x6F,(byte)0x6D},
+ ParsedReaderResultType.NDEF_URI);
+ }
+
+ public void testNDEFSmartPoster() {
+ doTestResult(new byte[] {(byte)0xD1,(byte)0x02,(byte)0x2F,(byte)0x53,
+ (byte)0x70,(byte)0x91,(byte)0x01,(byte)0x0E,
+ (byte)0x55,(byte)0x01,(byte)0x6E,(byte)0x66,
+ (byte)0x63,(byte)0x2D,(byte)0x66,(byte)0x6F,
+ (byte)0x72,(byte)0x75,(byte)0x6D,(byte)0x2E,
+ (byte)0x6F,(byte)0x72,(byte)0x67,(byte)0x11,
+ (byte)0x03,(byte)0x01,(byte)0x61,(byte)0x63,
+ (byte)0x74,(byte)0x00,(byte)0x51,(byte)0x01,
+ (byte)0x12,(byte)0x54,(byte)0x05,(byte)0x65,
+ (byte)0x6E,(byte)0x2D,(byte)0x55,(byte)0x53,
+ (byte)0x48,(byte)0x65,(byte)0x6C,(byte)0x6C,
+ (byte)0x6F,(byte)0x2C,(byte)0x20,(byte)0x77,
+ (byte)0x6F,(byte)0x72,(byte)0x6C,(byte)0x64},
+ ParsedReaderResultType.NDEF_SMART_POSTER);
+ }
+ */
+
private static void doTestResult(String text, ParsedReaderResultType type) {
doTestResult(text, type, null);
}
assertEquals(type, result.getType());
}
+ private static void doTestResult(byte[] rawBytes, ParsedReaderResultType type) {
+ Result fakeResult = new Result(null, rawBytes, null, null);
+ ParsedReaderResult result = ParsedReaderResult.parseReaderResult(fakeResult);
+ assertNotNull(result);
+ assertEquals(type, result.getType());
+ }
+
}
\ No newline at end of file