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