• Home
  • 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 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 
SmartPoster(UriRecord uri, TextRecord title, RecommendedAction action, String type)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 
getUriRecord()92     public UriRecord getUriRecord() {
93         return mUriRecord;
94     }
95 
96     /**
97      * Returns the title of the smart poster. This may be {@code null}.
98      */
getTitle()99     public TextRecord getTitle() {
100         return mTitleRecord;
101     }
102 
parse(NdefRecord record)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 
parse(NdefRecord[] recordsRaw)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 
isPoster(NdefRecord record)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 
getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset)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      */
getFirstIfExists(Iterable<?> elements, Class<T> type)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()) {
action.getByte()174                 builder.put(action.getByte(), action);
175             }
176             LOOKUP = builder.build();
177         }
178 
179         private final byte mAction;
180 
RecommendedAction(byte val)181         private RecommendedAction(byte val) {
182             this.mAction = val;
183         }
184 
getByte()185         private byte getByte() {
186             return mAction;
187         }
188     }
189 
getByType(byte[] type, NdefRecord[] records)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 
parseRecommendedAction(NdefRecord[] records)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 
parseType(NdefRecord[] records)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