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);
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      */
getNdefMessage()265     public NdefMessage getNdefMessage() throws IOException, FormatException {
266         checkConnected();
267 
268         try {
269             INfcTag tagService = mTag.getTagService();
270             if (tagService == null) {
271                 throw new IOException("Mock tags don't support this operation.");
272             }
273             int serviceHandle = mTag.getServiceHandle();
274             if (tagService.isNdef(serviceHandle)) {
275                 NdefMessage msg = tagService.ndefRead(serviceHandle);
276                 if (msg == null && !tagService.isPresent(serviceHandle)) {
277                     throw new TagLostException();
278                 }
279                 return msg;
280             } else if (!tagService.isPresent(serviceHandle)) {
281                 throw new TagLostException();
282             } else {
283                 return null;
284             }
285         } catch (RemoteException e) {
286             Log.e(TAG, "NFC service dead", e);
287             return null;
288         }
289     }
290 
291     /**
292      * Overwrite the {@link NdefMessage} on this tag.
293      *
294      * <p>This is an I/O operation and will block until complete. It must
295      * not be called from the main application thread. A blocked call will be canceled with
296      * {@link IOException} if {@link #close} is called from another thread.
297      *
298      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
299      *
300      * @param msg the NDEF Message to write, must not be null
301      * @throws TagLostException if the tag leaves the field
302      * @throws IOException if there is an I/O failure, or the operation is canceled
303      * @throws FormatException if the NDEF Message to write is malformed
304      */
writeNdefMessage(NdefMessage msg)305     public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException {
306         checkConnected();
307 
308         try {
309             INfcTag tagService = mTag.getTagService();
310             if (tagService == null) {
311                 throw new IOException("Mock tags don't support this operation.");
312             }
313             int serviceHandle = mTag.getServiceHandle();
314             if (tagService.isNdef(serviceHandle)) {
315                 int errorCode = tagService.ndefWrite(serviceHandle, msg);
316                 switch (errorCode) {
317                     case ErrorCodes.SUCCESS:
318                         break;
319                     case ErrorCodes.ERROR_IO:
320                         throw new IOException();
321                     case ErrorCodes.ERROR_INVALID_PARAM:
322                         throw new FormatException();
323                     default:
324                         // Should not happen
325                         throw new IOException();
326                 }
327             }
328             else {
329                 throw new IOException("Tag is not ndef");
330             }
331         } catch (RemoteException e) {
332             Log.e(TAG, "NFC service dead", e);
333         }
334     }
335 
336     /**
337      * Indicates whether a tag can be made read-only with {@link #makeReadOnly()}.
338      *
339      * <p>Does not cause any RF activity and does not block.
340      *
341      * @return true if it is possible to make this tag read-only
342      */
canMakeReadOnly()343     public boolean canMakeReadOnly() {
344         INfcTag tagService = mTag.getTagService();
345         if (tagService == null) {
346             return false;
347         }
348         try {
349             return tagService.canMakeReadOnly(mNdefType);
350         } catch (RemoteException e) {
351             Log.e(TAG, "NFC service dead", e);
352             return false;
353         }
354     }
355 
356     /**
357      * Make a tag read-only.
358      *
359      * <p>This sets the CC field to indicate the tag is read-only,
360      * and where possible permanently sets the lock bits to prevent
361      * any further modification of the memory.
362      * <p>This is a one-way process and cannot be reverted!
363      *
364      * <p>This is an I/O operation and will block until complete. It must
365      * not be called from the main application thread. A blocked call will be canceled with
366      * {@link IOException} if {@link #close} is called from another thread.
367      *
368      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
369      *
370      * @return true on success, false if it is not possible to make this tag read-only
371      * @throws TagLostException if the tag leaves the field
372      * @throws IOException if there is an I/O failure, or the operation is canceled
373      */
makeReadOnly()374     public boolean makeReadOnly() throws IOException {
375         checkConnected();
376 
377         try {
378             INfcTag tagService = mTag.getTagService();
379             if (tagService == null) {
380                 return false;
381             }
382             if (tagService.isNdef(mTag.getServiceHandle())) {
383                 int errorCode = tagService.ndefMakeReadOnly(mTag.getServiceHandle());
384                 switch (errorCode) {
385                     case ErrorCodes.SUCCESS:
386                         return true;
387                     case ErrorCodes.ERROR_IO:
388                         throw new IOException();
389                     case ErrorCodes.ERROR_INVALID_PARAM:
390                         return false;
391                     default:
392                         // Should not happen
393                         throw new IOException();
394                 }
395            }
396            else {
397                throw new IOException("Tag is not ndef");
398            }
399         } catch (RemoteException e) {
400             Log.e(TAG, "NFC service dead", e);
401             return false;
402         }
403     }
404 }
405