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