support more SmartPoster fields.
Nick Kralevich [Fri, 22 Oct 2010 16:57:54 +0000 (09:57 -0700)]
Change-Id: I308eb23d8a3df8d5fcecc69b155699ddcfe70203

src/com/android/apps/tag/message/NdefMessageParser.java
src/com/android/apps/tag/record/SmartPoster.java

index 95fc3ad..a1012b1 100644 (file)
@@ -63,8 +63,12 @@ public class NdefMessageParser {
     }
 
     public static List<ParsedNdefRecord> getRecords(NdefMessage message) {
+        return getRecords(message.getRecords());
+    }
+
+    public static List<ParsedNdefRecord> getRecords(NdefRecord[] records) {
         List<ParsedNdefRecord> elements = new ArrayList<ParsedNdefRecord>();
-        for (NdefRecord record : message.getRecords()) {
+        for (NdefRecord record : records) {
             if (UriRecord.isUri(record)) {
                 elements.add(UriRecord.parse(record));
             } else if (TextRecord.isText(record)) {
index 4ea08be..dda86af 100644 (file)
@@ -18,7 +18,9 @@ package com.android.apps.tag.record;
 
 import com.android.apps.tag.R;
 import com.android.apps.tag.message.NdefMessageParser;
+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 android.app.Activity;
@@ -60,9 +62,50 @@ public class SmartPoster implements ParsedNdefRecord {
      */
     private final UriRecord mUriRecord;
 
-    private SmartPoster(UriRecord uri, @Nullable TextRecord title) {
+    /**
+     * NFC Forum Smart Poster Record Type Definition section 3.2.1.
+     *
+     * "The Icon record. A Smart Poster may include an icon by including one
+     * or many MIME-typed image records within the Smart Poster. If the
+     * device supports images, it SHOULD select and display one of these,
+     * depending on the device capabilities. The device SHOULD display only
+     * one. The Icon record is optional."
+     */
+    private final ImageRecord mImageRecord;
+
+    /**
+     * 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, @Nullable TextRecord title,
+            @Nullable ImageRecord image, RecommendedAction action,
+            @Nullable String type) {
         mUriRecord = Preconditions.checkNotNull(uri);
         mTitleRecord = title;
+        mImageRecord = image;
+        mAction = Preconditions.checkNotNull(action);
+        mType = type;
     }
 
     public UriRecord getUriRecord() {
@@ -81,20 +124,22 @@ public class SmartPoster implements ParsedNdefRecord {
         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);
+        }
+    }
 
-            Iterable<ParsedNdefRecord> records = NdefMessageParser.getRecords(subRecords);
-
+    public static SmartPoster parse(NdefRecord[] recordsRaw) {
+        try {
+            Iterable<ParsedNdefRecord> records = NdefMessageParser.getRecords(recordsRaw);
             UriRecord uri = Iterables.getOnlyElement(Iterables.filter(records, UriRecord.class));
-            Iterable<TextRecord> textFields = Iterables.filter(records, TextRecord.class);
-
-            TextRecord title = null;
-            if (!Iterables.isEmpty(textFields)) {
-                title = Iterables.get(textFields, 0);
-            }
+            TextRecord title = getFirstIfExists(records, TextRecord.class);
+            ImageRecord image = getFirstIfExists(records, ImageRecord.class);
+            RecommendedAction action = parseRecommendedAction(recordsRaw);
+            String type = parseType(recordsRaw);
 
-            return new SmartPoster(uri, title);
-        } catch (FormatException e) {
-            throw new IllegalArgumentException(e);
+            return new SmartPoster(uri, title, image, action, type);
         } catch (NoSuchElementException e) {
             throw new IllegalArgumentException(e);
         }
@@ -127,4 +172,73 @@ public class SmartPoster implements ParsedNdefRecord {
             return mUriRecord.getView(activity, inflater, parent);
         }
     }
+
+    /**
+     * 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);
+    }
 }