Home | History | Annotate | Download | only in record
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.example.android.nfc.record;
     17 
     18 import android.app.Activity;
     19 import android.nfc.FormatException;
     20 import android.nfc.NdefMessage;
     21 import android.nfc.NdefRecord;
     22 import android.view.LayoutInflater;
     23 import android.view.View;
     24 import android.view.ViewGroup;
     25 import android.view.ViewGroup.LayoutParams;
     26 import android.widget.LinearLayout;
     27 
     28 import com.google.common.base.Charsets;
     29 import com.google.common.base.Preconditions;
     30 import com.google.common.collect.ImmutableMap;
     31 import com.google.common.collect.Iterables;
     32 
     33 import com.example.android.nfc.NdefMessageParser;
     34 import com.example.android.nfc.R;
     35 
     36 import java.util.Arrays;
     37 import java.util.NoSuchElementException;
     38 
     39 /**
     40  * A representation of an NFC Forum "Smart Poster".
     41  */
     42 public class SmartPoster implements ParsedNdefRecord {
     43 
     44     /**
     45      * NFC Forum Smart Poster Record Type Definition section 3.2.1.
     46      *
     47      * "The Title record for the service (there can be many of these in
     48      * different languages, but a language MUST NOT be repeated). This record is
     49      * optional."
     50      */
     51     private final TextRecord mTitleRecord;
     52 
     53     /**
     54      * NFC Forum Smart Poster Record Type Definition section 3.2.1.
     55      *
     56      * "The URI record. This is the core of the Smart Poster, and all other
     57      * records are just metadata about this record. There MUST be one URI record
     58      * and there MUST NOT be more than one."
     59      */
     60     private final UriRecord mUriRecord;
     61 
     62     /**
     63      * NFC Forum Smart Poster Record Type Definition section 3.2.1.
     64      *
     65      * "The Action record. This record describes how the service should be
     66      * treated. For example, the action may indicate that the device should save
     67      * the URI as a bookmark or open a browser. The Action record is optional.
     68      * If it does not exist, the device may decide what to do with the service.
     69      * If the action record exists, it should be treated as a strong suggestion;
     70      * the UI designer may ignore it, but doing so will induce a different user
     71      * experience from device to device."
     72      */
     73     private final RecommendedAction mAction;
     74 
     75     /**
     76      * NFC Forum Smart Poster Record Type Definition section 3.2.1.
     77      *
     78      * "The Type record. If the URI references an external entity (e.g., via a
     79      * URL), the Type record may be used to declare the MIME type of the entity.
     80      * This can be used to tell the mobile device what kind of an object it can
     81      * expect before it opens the connection. The Type record is optional."
     82      */
     83     private final String mType;
     84 
     85     private SmartPoster(UriRecord uri, TextRecord title, RecommendedAction action, String type) {
     86         mUriRecord = Preconditions.checkNotNull(uri);
     87         mTitleRecord = title;
     88         mAction = Preconditions.checkNotNull(action);
     89         mType = type;
     90     }
     91 
     92     public UriRecord getUriRecord() {
     93         return mUriRecord;
     94     }
     95 
     96     /**
     97      * Returns the title of the smart poster. This may be {@code null}.
     98      */
     99     public TextRecord getTitle() {
    100         return mTitleRecord;
    101     }
    102 
    103     public static SmartPoster parse(NdefRecord record) {
    104         Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN);
    105         Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER));
    106         try {
    107             NdefMessage subRecords = new NdefMessage(record.getPayload());
    108             return parse(subRecords.getRecords());
    109         } catch (FormatException e) {
    110             throw new IllegalArgumentException(e);
    111         }
    112     }
    113 
    114     public static SmartPoster parse(NdefRecord[] recordsRaw) {
    115         try {
    116             Iterable<ParsedNdefRecord> records = NdefMessageParser.getRecords(recordsRaw);
    117             UriRecord uri = Iterables.getOnlyElement(Iterables.filter(records, UriRecord.class));
    118             TextRecord title = getFirstIfExists(records, TextRecord.class);
    119             RecommendedAction action = parseRecommendedAction(recordsRaw);
    120             String type = parseType(recordsRaw);
    121             return new SmartPoster(uri, title, action, type);
    122         } catch (NoSuchElementException e) {
    123             throw new IllegalArgumentException(e);
    124         }
    125     }
    126 
    127     public static boolean isPoster(NdefRecord record) {
    128         try {
    129             parse(record);
    130             return true;
    131         } catch (IllegalArgumentException e) {
    132             return false;
    133         }
    134     }
    135 
    136     public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
    137         if (mTitleRecord != null) {
    138             // Build a container to hold the title and the URI
    139             LinearLayout container = new LinearLayout(activity);
    140             container.setOrientation(LinearLayout.VERTICAL);
    141             container.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
    142                 LayoutParams.WRAP_CONTENT));
    143             container.addView(mTitleRecord.getView(activity, inflater, container, offset));
    144             inflater.inflate(R.layout.tag_divider, container);
    145             container.addView(mUriRecord.getView(activity, inflater, container, offset));
    146             return container;
    147         } else {
    148             // Just a URI, return a view for it directly
    149             return mUriRecord.getView(activity, inflater, parent, offset);
    150         }
    151     }
    152 
    153     /**
    154      * Returns the first element of {@code elements} which is an instance of
    155      * {@code type}, or {@code null} if no such element exists.
    156      */
    157     private static <T> T getFirstIfExists(Iterable<?> elements, Class<T> type) {
    158         Iterable<T> filtered = Iterables.filter(elements, type);
    159         T instance = null;
    160         if (!Iterables.isEmpty(filtered)) {
    161             instance = Iterables.get(filtered, 0);
    162         }
    163         return instance;
    164     }
    165 
    166     private enum RecommendedAction {
    167         UNKNOWN((byte) -1), DO_ACTION((byte) 0), SAVE_FOR_LATER((byte) 1), OPEN_FOR_EDITING(
    168             (byte) 2);
    169 
    170         private static final ImmutableMap<Byte, RecommendedAction> LOOKUP;
    171         static {
    172             ImmutableMap.Builder<Byte, RecommendedAction> builder = ImmutableMap.builder();
    173             for (RecommendedAction action : RecommendedAction.values()) {
    174                 builder.put(action.getByte(), action);
    175             }
    176             LOOKUP = builder.build();
    177         }
    178 
    179         private final byte mAction;
    180 
    181         private RecommendedAction(byte val) {
    182             this.mAction = val;
    183         }
    184 
    185         private byte getByte() {
    186             return mAction;
    187         }
    188     }
    189 
    190     private static NdefRecord getByType(byte[] type, NdefRecord[] records) {
    191         for (NdefRecord record : records) {
    192             if (Arrays.equals(type, record.getType())) {
    193                 return record;
    194             }
    195         }
    196         return null;
    197     }
    198 
    199     private static final byte[] ACTION_RECORD_TYPE = new byte[] {'a', 'c', 't'};
    200 
    201     private static RecommendedAction parseRecommendedAction(NdefRecord[] records) {
    202         NdefRecord record = getByType(ACTION_RECORD_TYPE, records);
    203         if (record == null) {
    204             return RecommendedAction.UNKNOWN;
    205         }
    206         byte action = record.getPayload()[0];
    207         if (RecommendedAction.LOOKUP.containsKey(action)) {
    208             return RecommendedAction.LOOKUP.get(action);
    209         }
    210         return RecommendedAction.UNKNOWN;
    211     }
    212 
    213     private static final byte[] TYPE_TYPE = new byte[] {'t'};
    214 
    215     private static String parseType(NdefRecord[] records) {
    216         NdefRecord type = getByType(TYPE_TYPE, records);
    217         if (type == null) {
    218             return null;
    219         }
    220         return new String(type.getPayload(), Charsets.UTF_8);
    221     }
    222 }
    223