Ndef.java revision faca12adc62d148505fadfd286e6a2752c197fa0
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>Does not cause any RF activity and does not block.
180     * @return NDEF Message read from the tag at discovery time
181     */
182    public NdefMessage getCachedNdefMessage() {
183        return mNdefMsg;
184    }
185
186    /**
187     * Get the NDEF tag type.
188     *
189     * <p>Returns one of {@link #NFC_FORUM_TYPE_1}, {@link #NFC_FORUM_TYPE_2},
190     * {@link #NFC_FORUM_TYPE_3}, {@link #NFC_FORUM_TYPE_4},
191     * {@link #MIFARE_CLASSIC} or another NDEF tag type that has not yet been
192     * formalized in this Android API.
193     *
194     * <p>Does not cause any RF activity and does not block.
195     *
196     * @return a string representing the NDEF tag type
197     */
198    public String getType() {
199        switch (mNdefType) {
200            case TYPE_1:
201                return NFC_FORUM_TYPE_1;
202            case TYPE_2:
203                return NFC_FORUM_TYPE_2;
204            case TYPE_3:
205                return NFC_FORUM_TYPE_3;
206            case TYPE_4:
207                return NFC_FORUM_TYPE_4;
208            case TYPE_MIFARE_CLASSIC:
209                return MIFARE_CLASSIC;
210            case TYPE_ICODE_SLI:
211                return ICODE_SLI;
212            default:
213                return UNKNOWN;
214        }
215    }
216
217    /**
218     * Get the maximum NDEF message size in bytes.
219     *
220     * <p>Does not cause any RF activity and does not block.
221     *
222     * @return size in bytes
223     */
224    public int getMaxSize() {
225        return mMaxNdefSize;
226    }
227
228    /**
229     * Determine if the tag is writable.
230     *
231     * <p>NFC Forum tags can be in read-only or read-write states.
232     *
233     * <p>Does not cause any RF activity and does not block.
234     *
235     * <p>Requires {@link android.Manifest.permission#NFC} permission.
236     *
237     * @return true if the tag is writable
238     */
239    public boolean isWritable() {
240        return (mCardState == NDEF_MODE_READ_WRITE);
241    }
242
243    /**
244     * Read the current {@link android.nfc.NdefMessage} on this tag.
245     *
246     * <p>This always reads the current NDEF Message stored on the tag.
247     *
248     * <p>This is an I/O operation and will block until complete. It must
249     * not be called from the main application thread. A blocked call will be canceled with
250     * {@link IOException} if {@link #close} is called from another thread.
251     *
252     * @return the NDEF Message, never null
253     * @throws TagLostException if the tag leaves the field
254     * @throws IOException if there is an I/O failure, or the operation is canceled
255     * @throws FormatException if the NDEF Message on the tag is malformed
256     */
257    public NdefMessage getNdefMessage() throws IOException, FormatException {
258        checkConnected();
259
260        try {
261            INfcTag tagService = mTag.getTagService();
262            int serviceHandle = mTag.getServiceHandle();
263            if (tagService.isNdef(serviceHandle)) {
264                NdefMessage msg = tagService.ndefRead(serviceHandle);
265                if (msg == null) {
266                    int errorCode = tagService.getLastError(serviceHandle);
267                    switch (errorCode) {
268                        case ErrorCodes.ERROR_IO:
269                            throw new IOException();
270                        case ErrorCodes.ERROR_INVALID_PARAM:
271                            throw new FormatException();
272                        default:
273                            // Should not happen
274                            throw new IOException();
275                    }
276                }
277                return msg;
278            } else {
279                return null;
280            }
281        } catch (RemoteException e) {
282            Log.e(TAG, "NFC service dead", e);
283            return null;
284        }
285    }
286
287    /**
288     * Overwrite the {@link NdefMessage} on this tag.
289     *
290     * <p>This is an I/O operation and will block until complete. It must
291     * not be called from the main application thread. A blocked call will be canceled with
292     * {@link IOException} if {@link #close} is called from another thread.
293     *
294     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
295     *
296     * @param msg the NDEF Message to write, must not be null
297     * @throws TagLostException if the tag leaves the field
298     * @throws IOException if there is an I/O failure, or the operation is canceled
299     * @throws FormatException if the NDEF Message to write is malformed
300     */
301    public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException {
302        checkConnected();
303
304        try {
305            INfcTag tagService = mTag.getTagService();
306            int serviceHandle = mTag.getServiceHandle();
307            if (tagService.isNdef(serviceHandle)) {
308                int errorCode = tagService.ndefWrite(serviceHandle, msg);
309                switch (errorCode) {
310                    case ErrorCodes.SUCCESS:
311                        break;
312                    case ErrorCodes.ERROR_IO:
313                        throw new IOException();
314                    case ErrorCodes.ERROR_INVALID_PARAM:
315                        throw new FormatException();
316                    default:
317                        // Should not happen
318                        throw new IOException();
319                }
320            }
321            else {
322                throw new IOException("Tag is not ndef");
323            }
324        } catch (RemoteException e) {
325            Log.e(TAG, "NFC service dead", e);
326        }
327    }
328
329    /**
330     * Indicates whether a tag can be made read-only with {@link #makeReadOnly()}.
331     *
332     * <p>Does not cause any RF activity and does not block.
333     *
334     * @return true if it is possible to make this tag read-only
335     */
336    public boolean canMakeReadOnly() {
337        INfcTag tagService = mTag.getTagService();
338        try {
339            return tagService.canMakeReadOnly(mNdefType);
340        } catch (RemoteException e) {
341            Log.e(TAG, "NFC service dead", e);
342            return false;
343        }
344    }
345
346    /**
347     * Make a tag read-only.
348     *
349     * <p>This sets the CC field to indicate the tag is read-only,
350     * and where possible permanently sets the lock bits to prevent
351     * any further modification of the memory.
352     * <p>This is a one-way process and cannot be reverted!
353     *
354     * <p>This is an I/O operation and will block until complete. It must
355     * not be called from the main application thread. A blocked call will be canceled with
356     * {@link IOException} if {@link #close} is called from another thread.
357     *
358     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
359     *
360     * @return true on success, false if it is not possible to make this tag read-only
361     * @throws TagLostException if the tag leaves the field
362     * @throws IOException if there is an I/O failure, or the operation is canceled
363     */
364    public boolean makeReadOnly() throws IOException {
365        checkConnected();
366
367        try {
368            INfcTag tagService = mTag.getTagService();
369            if (tagService.isNdef(mTag.getServiceHandle())) {
370                int errorCode = tagService.ndefMakeReadOnly(mTag.getServiceHandle());
371                switch (errorCode) {
372                    case ErrorCodes.SUCCESS:
373                        return true;
374                    case ErrorCodes.ERROR_IO:
375                        throw new IOException();
376                    case ErrorCodes.ERROR_INVALID_PARAM:
377                        return false;
378                    default:
379                        // Should not happen
380                        throw new IOException();
381                }
382           }
383           else {
384               throw new IOException("Tag is not ndef");
385           }
386        } catch (RemoteException e) {
387            Log.e(TAG, "NFC service dead", e);
388            return false;
389        }
390    }
391}
392