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 android.nfc.tech;
18 
19 import android.nfc.ErrorCodes;
20 import android.nfc.FormatException;
21 import android.nfc.INfcTag;
22 import android.nfc.NdefMessage;
23 import android.nfc.Tag;
24 import android.nfc.TagLostException;
25 import android.os.Bundle;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import java.io.IOException;
30 
31 /**
32  * Provides access to NDEF content and operations on a {@link Tag}.
33  *
34  * <p>Acquire a {@link Ndef} object using {@link #get}.
35  *
36  * <p>NDEF is an NFC Forum data format. The data formats are implemented in
37  * {@link android.nfc.NdefMessage} and
38  * {@link android.nfc.NdefRecord}. This class provides methods to
39  * retrieve and modify the {@link android.nfc.NdefMessage}
40  * on a tag.
41  *
42  * <p>There are currently four NFC Forum standardized tag types that can be
43  * formatted to contain NDEF data.
44  * <ul>
45  * <li>NFC Forum Type 1 Tag ({@link #NFC_FORUM_TYPE_1}), such as the Innovision Topaz
46  * <li>NFC Forum Type 2 Tag ({@link #NFC_FORUM_TYPE_2}), such as the NXP MIFARE Ultralight
47  * <li>NFC Forum Type 3 Tag ({@link #NFC_FORUM_TYPE_3}), such as Sony Felica
48  * <li>NFC Forum Type 4 Tag ({@link #NFC_FORUM_TYPE_4}), such as NXP MIFARE Desfire
49  * </ul>
50  * It is mandatory for all Android devices with NFC to correctly enumerate
51  * {@link Ndef} on NFC Forum Tag Types 1-4, and implement all NDEF operations
52  * as defined in this class.
53  *
54  * <p>Some vendors have their own well defined specifications for storing NDEF data
55  * on tags that do not fall into the above categories. Android devices with NFC
56  * should enumerate and implement {@link Ndef} under these vendor specifications
57  * where possible, but it is not mandatory. {@link #getType} returns a String
58  * describing this specification, for example {@link #MIFARE_CLASSIC} is
59  * <code>com.nxp.ndef.mifareclassic</code>.
60  *
61  * <p>Android devices that support MIFARE Classic must also correctly
62  * implement {@link Ndef} on MIFARE Classic tags formatted to NDEF.
63  *
64  * <p>For guaranteed compatibility across all Android devices with NFC, it is
65  * recommended to use NFC Forum Types 1-4 in new deployments of NFC tags
66  * with NDEF payload. Vendor NDEF formats will not work on all Android devices.
67  *
68  * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
69  * require the {@link android.Manifest.permission#NFC} permission.
70  */
71 public final class Ndef extends BasicTagTechnology {
72     private static final String TAG = "NFC";
73 
74     /** @hide */
75     public static final int NDEF_MODE_READ_ONLY = 1;
76     /** @hide */
77     public static final int NDEF_MODE_READ_WRITE = 2;
78     /** @hide */
79     public static final int NDEF_MODE_UNKNOWN = 3;
80 
81     /** @hide */
82     public static final String EXTRA_NDEF_MSG = "ndefmsg";
83 
84     /** @hide */
85     public static final String EXTRA_NDEF_MAXLENGTH = "ndefmaxlength";
86 
87     /** @hide */
88     public static final String EXTRA_NDEF_CARDSTATE = "ndefcardstate";
89 
90     /** @hide */
91     public static final String EXTRA_NDEF_TYPE = "ndeftype";
92 
93     /** @hide */
94     public static final int TYPE_OTHER = -1;
95     /** @hide */
96     public static final int TYPE_1 = 1;
97     /** @hide */
98     public static final int TYPE_2 = 2;
99     /** @hide */
100     public static final int TYPE_3 = 3;
101     /** @hide */
102     public static final int TYPE_4 = 4;
103     /** @hide */
104     public static final int TYPE_MIFARE_CLASSIC = 101;
105     /** @hide */
106     public static final int TYPE_ICODE_SLI = 102;
107 
108     /** @hide */
109     public static final String UNKNOWN = "android.ndef.unknown";
110 
111     /** NFC Forum Tag Type 1 */
112     public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
113     /** NFC Forum Tag Type 2 */
114     public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
115     /** NFC Forum Tag Type 3 */
116     public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
117     /** NFC Forum Tag Type 4 */
118     public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
119     /** NDEF on MIFARE Classic */
120     public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
121     /**
122      * NDEF on iCODE SLI
123      * @hide
124      */
125     public static final String ICODE_SLI = "com.nxp.ndef.icodesli";
126 
127     private final int mMaxNdefSize;
128     private final int mCardState;
129     private final NdefMessage mNdefMsg;
130     private final int mNdefType;
131 
132     /**
133      * Get an instance of {@link Ndef} for the given tag.
134      *
135      * <p>Returns null if {@link Ndef} was not enumerated in {@link Tag#getTechList}.
136      * This indicates the tag is not NDEF formatted, or that this tag
137      * is NDEF formatted but under a vendor specification that this Android
138      * device does not implement.
139      *
140      * <p>Does not cause any RF activity and does not block.
141      *
142      * @param tag an NDEF compatible tag
143      * @return Ndef object
144      */
get(Tag tag)145     public static Ndef get(Tag tag) {
146         if (!tag.hasTech(TagTechnology.NDEF)) return null;
147         try {
148             return new Ndef(tag);
149         } catch (RemoteException e) {
150             return null;
151         }
152     }
153 
154     /**
155      * Internal constructor, to be used by NfcAdapter
156      * @hide
157      */
Ndef(Tag tag)158     public Ndef(Tag tag) throws RemoteException {
159         super(tag, TagTechnology.NDEF);
160         Bundle extras = tag.getTechExtras(TagTechnology.NDEF);
161         if (extras != null) {
162             mMaxNdefSize = extras.getInt(EXTRA_NDEF_MAXLENGTH);
163             mCardState = extras.getInt(EXTRA_NDEF_CARDSTATE);
164             mNdefMsg = extras.getParcelable(EXTRA_NDEF_MSG, android.nfc.NdefMessage.class);
165             mNdefType = extras.getInt(EXTRA_NDEF_TYPE);
166         } else {
167             throw new NullPointerException("NDEF tech extras are null.");
168         }
169 
170     }
171 
172     /**
173      * Get the {@link NdefMessage} that was read from the tag at discovery time.
174      *
175      * <p>If the NDEF Message is modified by an I/O operation then it
176      * will not be updated here, this function only returns what was discovered
177      * when the tag entered the field.
178      * <p>Note that this method may return null if the tag was in the
179      * INITIALIZED state as defined by NFC Forum, as in this state the
180      * tag is formatted to support NDEF but does not contain a message yet.
181      * <p>Does not cause any RF activity and does not block.
182      * @return NDEF Message read from the tag at discovery time, can be null
183      */
getCachedNdefMessage()184     public NdefMessage getCachedNdefMessage() {
185         return mNdefMsg;
186     }
187 
188     /**
189      * Get the NDEF tag type.
190      *
191      * <p>Returns one of {@link #NFC_FORUM_TYPE_1}, {@link #NFC_FORUM_TYPE_2},
192      * {@link #NFC_FORUM_TYPE_3}, {@link #NFC_FORUM_TYPE_4},
193      * {@link #MIFARE_CLASSIC} or another NDEF tag type that has not yet been
194      * formalized in this Android API.
195      *
196      * <p>Does not cause any RF activity and does not block.
197      *
198      * @return a string representing the NDEF tag type
199      */
getType()200     public String getType() {
201         switch (mNdefType) {
202             case TYPE_1:
203                 return NFC_FORUM_TYPE_1;
204             case TYPE_2:
205                 return NFC_FORUM_TYPE_2;
206             case TYPE_3:
207                 return NFC_FORUM_TYPE_3;
208             case TYPE_4:
209                 return NFC_FORUM_TYPE_4;
210             case TYPE_MIFARE_CLASSIC:
211                 return MIFARE_CLASSIC;
212             case TYPE_ICODE_SLI:
213                 return ICODE_SLI;
214             default:
215                 return UNKNOWN;
216         }
217     }
218 
219     /**
220      * Get the maximum NDEF message size in bytes.
221      *
222      * <p>Does not cause any RF activity and does not block.
223      *
224      * @return size in bytes
225      */
getMaxSize()226     public int getMaxSize() {
227         return mMaxNdefSize;
228     }
229 
230     /**
231      * Determine if the tag is writable.
232      *
233      * <p>NFC Forum tags can be in read-only or read-write states.
234      *
235      * <p>Does not cause any RF activity and does not block.
236      *
237      * <p>Requires {@link android.Manifest.permission#NFC} permission.
238      *
239      * @return true if the tag is writable
240      */
isWritable()241     public boolean isWritable() {
242         return (mCardState == NDEF_MODE_READ_WRITE);
243     }
244 
245     /**
246      * Read the current {@link android.nfc.NdefMessage} on this tag.
247      *
248      * <p>This always reads the current NDEF Message stored on the tag.
249      *
250      * <p>Note that this method may return null if the tag was in the
251      * INITIALIZED state as defined by NFC Forum, as in that state the
252      * tag is formatted to support NDEF but does not contain a message yet.
253      *
254      * <p>This is an I/O operation and will block until complete. It must
255      * not be called from the main application thread. A blocked call will be canceled with
256      * {@link IOException} if {@link #close} is called from another thread.
257      *
258      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
259      *
260      * @return the NDEF Message, can be null
261      * @throws TagLostException if the tag leaves the field
262      * @throws IOException if there is an I/O failure, or the operation is canceled
263      * @throws FormatException if the NDEF Message on the tag is malformed
264      * @throws SecurityException if the tag object is reused after the tag has left the field
265      */
getNdefMessage()266     public NdefMessage getNdefMessage() throws IOException, FormatException {
267         checkConnected();
268 
269         try {
270             INfcTag tagService = mTag.getTagService();
271             if (tagService == null) {
272                 throw new IOException("Mock tags don't support this operation.");
273             }
274             int serviceHandle = mTag.getServiceHandle();
275             if (tagService.isNdef(serviceHandle)) {
276                 NdefMessage msg = tagService.ndefRead(serviceHandle);
277                 if (msg == null && !tagService.isPresent(serviceHandle)) {
278                     throw new TagLostException();
279                 }
280                 return msg;
281             } else if (!tagService.isPresent(serviceHandle)) {
282                 throw new TagLostException();
283             } else {
284                 return null;
285             }
286         } catch (RemoteException e) {
287             Log.e(TAG, "NFC service dead", e);
288             return null;
289         }
290     }
291 
292     /**
293      * Overwrite the {@link NdefMessage} on this tag.
294      *
295      * <p>This is an I/O operation and will block until complete. It must
296      * not be called from the main application thread. A blocked call will be canceled with
297      * {@link IOException} if {@link #close} is called from another thread.
298      *
299      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
300      *
301      * @param msg the NDEF Message to write, must not be null
302      * @throws TagLostException if the tag leaves the field
303      * @throws IOException if there is an I/O failure, or the operation is canceled
304      * @throws FormatException if the NDEF Message to write is malformed
305      * @throws SecurityException if the tag object is reused after the tag has left the field
306      */
writeNdefMessage(NdefMessage msg)307     public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException {
308         checkConnected();
309 
310         try {
311             INfcTag tagService = mTag.getTagService();
312             if (tagService == null) {
313                 throw new IOException("Mock tags don't support this operation.");
314             }
315             int serviceHandle = mTag.getServiceHandle();
316             if (tagService.isNdef(serviceHandle)) {
317                 int errorCode = tagService.ndefWrite(serviceHandle, msg);
318                 switch (errorCode) {
319                     case ErrorCodes.SUCCESS:
320                         break;
321                     case ErrorCodes.ERROR_IO:
322                         throw new IOException();
323                     case ErrorCodes.ERROR_INVALID_PARAM:
324                         throw new FormatException();
325                     default:
326                         // Should not happen
327                         throw new IOException();
328                 }
329             }
330             else {
331                 throw new IOException("Tag is not ndef");
332             }
333         } catch (RemoteException e) {
334             Log.e(TAG, "NFC service dead", e);
335         }
336     }
337 
338     /**
339      * Indicates whether a tag can be made read-only with {@link #makeReadOnly()}.
340      *
341      * <p>Does not cause any RF activity and does not block.
342      *
343      * @return true if it is possible to make this tag read-only
344      * @throws SecurityException if the tag object is reused after the tag has left the field
345      */
canMakeReadOnly()346     public boolean canMakeReadOnly() {
347         INfcTag tagService = mTag.getTagService();
348         if (tagService == null) {
349             return false;
350         }
351         try {
352             return tagService.canMakeReadOnly(mNdefType);
353         } catch (RemoteException e) {
354             Log.e(TAG, "NFC service dead", e);
355             return false;
356         }
357     }
358 
359     /**
360      * Make a tag read-only.
361      *
362      * <p>This sets the CC field to indicate the tag is read-only,
363      * and where possible permanently sets the lock bits to prevent
364      * any further modification of the memory.
365      * <p>This is a one-way process and cannot be reverted!
366      *
367      * <p>This is an I/O operation and will block until complete. It must
368      * not be called from the main application thread. A blocked call will be canceled with
369      * {@link IOException} if {@link #close} is called from another thread.
370      *
371      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
372      *
373      * @return true on success, false if it is not possible to make this tag read-only
374      * @throws TagLostException if the tag leaves the field
375      * @throws IOException if there is an I/O failure, or the operation is canceled
376      * @throws SecurityException if the tag object is reused after the tag has left the field
377      */
makeReadOnly()378     public boolean makeReadOnly() throws IOException {
379         checkConnected();
380 
381         try {
382             INfcTag tagService = mTag.getTagService();
383             if (tagService == null) {
384                 return false;
385             }
386             if (tagService.isNdef(mTag.getServiceHandle())) {
387                 int errorCode = tagService.ndefMakeReadOnly(mTag.getServiceHandle());
388                 switch (errorCode) {
389                     case ErrorCodes.SUCCESS:
390                         return true;
391                     case ErrorCodes.ERROR_IO:
392                         throw new IOException();
393                     case ErrorCodes.ERROR_INVALID_PARAM:
394                         return false;
395                     default:
396                         // Should not happen
397                         throw new IOException();
398                 }
399            }
400            else {
401                throw new IOException("Tag is not ndef");
402            }
403         } catch (RemoteException e) {
404             Log.e(TAG, "NFC service dead", e);
405             return false;
406         }
407     }
408 }
409