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
17package android.nfc.tech;
18
19import android.nfc.ErrorCodes;
20import android.nfc.FormatException;
21import android.nfc.INfcTag;
22import android.nfc.NdefMessage;
23import android.nfc.Tag;
24import android.nfc.TagLostException;
25import android.os.Bundle;
26import android.os.RemoteException;
27import android.util.Log;
28
29import 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 */
71public 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 4 */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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