• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.apps.tag.record;
18 
19 import com.android.apps.tag.R;
20 import com.android.apps.tag.message.NdefMessageParser;
21 import com.google.common.base.Charsets;
22 import com.google.common.base.Preconditions;
23 import com.google.common.collect.ImmutableMap;
24 import com.google.common.collect.Iterables;
25 
26 import android.app.Activity;
27 import android.content.Context;
28 import android.nfc.FormatException;
29 import android.nfc.NdefMessage;
30 import android.nfc.NdefRecord;
31 import android.view.LayoutInflater;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.ViewGroup.LayoutParams;
35 import android.widget.LinearLayout;
36 
37 import java.util.Arrays;
38 import java.util.Locale;
39 import java.util.NoSuchElementException;
40 
41 import javax.annotation.Nullable;
42 
43 /**
44  * A representation of an NFC Forum "Smart Poster".
45  */
46 public class SmartPoster extends ParsedNdefRecord {
47 
48     /**
49      * NFC Forum Smart Poster Record Type Definition section 3.2.1.
50      *
51      * "The Title record for the service (there can be many of these in
52      * different languages, but a language MUST NOT be repeated).
53      * This record is optional."
54 
55      */
56     private final TextRecord mTitleRecord;
57 
58     /**
59      * NFC Forum Smart Poster Record Type Definition section 3.2.1.
60      *
61      * "The URI record. This is the core of the Smart Poster, and all other
62      * records are just metadata about this record. There MUST be one URI
63      * record and there MUST NOT be more than one."
64      */
65     private final UriRecord mUriRecord;
66 
67     /**
68      * NFC Forum Smart Poster Record Type Definition section 3.2.1.
69      *
70      * "The Icon record. A Smart Poster may include an icon by including one
71      * or many MIME-typed image records within the Smart Poster. If the
72      * device supports images, it SHOULD select and display one of these,
73      * depending on the device capabilities. The device SHOULD display only
74      * one. The Icon record is optional."
75      */
76     private final ImageRecord mImageRecord;
77 
78     /**
79      * NFC Forum Smart Poster Record Type Definition section 3.2.1.
80      *
81      * "The Action record. This record describes how the service should be
82      * treated. For example, the action may indicate that the device should
83      * save the URI as a bookmark or open a browser. The Action record is
84      * optional. If it does not exist, the device may decide what to do with
85      * the service. If the action record exists, it should be treated as
86      * a strong suggestion; the UI designer may ignore it, but doing so
87      * will induce a different user experience from device to device."
88      */
89     private final RecommendedAction mAction;
90 
91     /**
92      * NFC Forum Smart Poster Record Type Definition section 3.2.1.
93      *
94      * "The Type record. If the URI references an external entity (e.g., via
95      * a URL), the Type record may be used to declare the MIME type of the
96      * entity. This can be used to tell the mobile device what kind of an
97      * object it can expect before it opens the connection. The Type record
98      * is optional."
99      */
100     private final String mType;
101 
102 
SmartPoster(UriRecord uri, @Nullable TextRecord title, @Nullable ImageRecord image, RecommendedAction action, @Nullable String type)103     private SmartPoster(UriRecord uri, @Nullable TextRecord title,
104             @Nullable ImageRecord image, RecommendedAction action,
105             @Nullable String type) {
106         mUriRecord = Preconditions.checkNotNull(uri);
107         mTitleRecord = title;
108         mImageRecord = image;
109         mAction = Preconditions.checkNotNull(action);
110         mType = type;
111     }
112 
getUriRecord()113     public UriRecord getUriRecord() {
114         return mUriRecord;
115     }
116 
117     /**
118      * Returns the title of the smart poster.  This may be {@code null}.
119      */
getTitle()120     public TextRecord getTitle() {
121         return mTitleRecord;
122     }
123 
parse(NdefRecord record)124     public static SmartPoster parse(NdefRecord record) {
125         Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN);
126         Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER));
127         try {
128             NdefMessage subRecords = new NdefMessage(record.getPayload());
129             return parse(subRecords.getRecords());
130         } catch (FormatException e) {
131             throw new IllegalArgumentException(e);
132         }
133     }
134 
parse(NdefRecord[] recordsRaw)135     public static SmartPoster parse(NdefRecord[] recordsRaw) {
136         try {
137             Iterable<ParsedNdefRecord> records = NdefMessageParser.getRecords(recordsRaw);
138             UriRecord uri = Iterables.getOnlyElement(Iterables.filter(records, UriRecord.class));
139             TextRecord title = getFirstIfExists(records, TextRecord.class);
140             ImageRecord image = getFirstIfExists(records, ImageRecord.class);
141             RecommendedAction action = parseRecommendedAction(recordsRaw);
142             String type = parseType(recordsRaw);
143 
144             return new SmartPoster(uri, title, image, action, type);
145         } catch (NoSuchElementException e) {
146             throw new IllegalArgumentException(e);
147         }
148     }
149 
isPoster(NdefRecord record)150     public static boolean isPoster(NdefRecord record) {
151         try {
152             parse(record);
153             return true;
154         } catch (IllegalArgumentException e) {
155             return false;
156         }
157     }
158 
159     @Override
getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset)160     public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
161         if (mTitleRecord != null) {
162             // Build a container to hold the title and the URI
163             LinearLayout container = new LinearLayout(activity);
164             container.setOrientation(LinearLayout.VERTICAL);
165             container.setLayoutParams(new LayoutParams(
166                     LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
167 
168             container.addView(mTitleRecord.getView(activity, inflater, container, offset));
169             inflater.inflate(R.layout.tag_divider, container);
170             container.addView(mUriRecord.getView(activity, inflater, container, offset));
171             return container;
172         } else {
173             // Just a URI, return a view for it directly
174             return mUriRecord.getView(activity, inflater, parent, offset);
175         }
176     }
177 
178     @Override
getSnippet(Context context, Locale locale)179     public String getSnippet(Context context, Locale locale) {
180         if (mTitleRecord != null) {
181             return mTitleRecord.getText();
182         }
183 
184         return mUriRecord.getPrettyUriString(context);
185     }
186 
187 
188     /**
189      * Returns the first element of {@code elements} which is an instance
190      * of {@code type}, or {@code null} if no such element exists.
191      */
getFirstIfExists(Iterable<?> elements, Class<T> type)192     private static <T> T getFirstIfExists(Iterable<?> elements, Class<T> type) {
193         Iterable<T> filtered = Iterables.filter(elements, type);
194         T instance = null;
195         if (!Iterables.isEmpty(filtered)) {
196             instance = Iterables.get(filtered, 0);
197         }
198         return instance;
199     }
200 
201     private enum RecommendedAction {
202         UNKNOWN((byte) -1), DO_ACTION((byte) 0),
203         SAVE_FOR_LATER((byte) 1), OPEN_FOR_EDITING((byte) 2);
204 
205         private static final ImmutableMap<Byte, RecommendedAction> LOOKUP;
206         static {
207             ImmutableMap.Builder<Byte, RecommendedAction> builder = ImmutableMap.builder();
208             for (RecommendedAction action : RecommendedAction.values()) {
action.getByte()209                 builder.put(action.getByte(), action);
210             }
211             LOOKUP = builder.build();
212         }
213 
214         private final byte mAction;
215 
RecommendedAction(byte val)216         private RecommendedAction(byte val) {
217             this.mAction = val;
218         }
getByte()219         private byte getByte() {
220             return mAction;
221         }
222     }
223 
getByType(byte[] type, NdefRecord[] records)224     private static NdefRecord getByType(byte[] type, NdefRecord[] records) {
225         for (NdefRecord record : records) {
226             if (Arrays.equals(type, record.getType())) {
227                 return record;
228             }
229         }
230         return null;
231     }
232 
233     private static final byte[] ACTION_RECORD_TYPE = new byte[] { 'a', 'c', 't' };
234 
parseRecommendedAction(NdefRecord[] records)235     private static RecommendedAction parseRecommendedAction(NdefRecord[] records) {
236         NdefRecord record = getByType(ACTION_RECORD_TYPE, records);
237         if (record == null) {
238             return RecommendedAction.UNKNOWN;
239         }
240         byte action = record.getPayload()[0];
241         if (RecommendedAction.LOOKUP.containsKey(action)) {
242             return RecommendedAction.LOOKUP.get(action);
243         }
244         return RecommendedAction.UNKNOWN;
245     }
246 
247     private static final byte[] TYPE_TYPE = new byte[] { 't' };
248 
parseType(NdefRecord[] records)249     private static String parseType(NdefRecord[] records) {
250         NdefRecord type = getByType(TYPE_TYPE, records);
251         if (type == null) {
252             return null;
253         }
254         return new String(type.getPayload(), Charsets.UTF_8);
255     }
256 }
257