Ndef.java revision 28319c0cec94977682db32b949628a8e4b8183dc
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>
70 * Use of this class requires the {@link android.Manifest.permission#NFC}
71 * permission.
72 */
73public final class Ndef extends BasicTagTechnology {
74    private static final String TAG = "NFC";
75
76    /** @hide */
77    public static final int NDEF_MODE_READ_ONLY = 1;
78    /** @hide */
79    public static final int NDEF_MODE_READ_WRITE = 2;
80    /** @hide */
81    public static final int NDEF_MODE_UNKNOWN = 3;
82
83    /** @hide */
84    public static final String EXTRA_NDEF_MSG = "ndefmsg";
85
86    /** @hide */
87    public static final String EXTRA_NDEF_MAXLENGTH = "ndefmaxlength";
88
89    /** @hide */
90    public static final String EXTRA_NDEF_CARDSTATE = "ndefcardstate";
91
92    /** @hide */
93    public static final String EXTRA_NDEF_TYPE = "ndeftype";
94
95    /** @hide */
96    public static final int TYPE_OTHER = -1;
97    /** @hide */
98    public static final int TYPE_1 = 1;
99    /** @hide */
100    public static final int TYPE_2 = 2;
101    /** @hide */
102    public static final int TYPE_3 = 3;
103    /** @hide */
104    public static final int TYPE_4 = 4;
105    /** @hide */
106    public static final int TYPE_MIFARE_CLASSIC = 101;
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    private final int mMaxNdefSize;
123    private final int mCardState;
124    private final NdefMessage mNdefMsg;
125    private final int mNdefType;
126
127    /**
128     * Get an instance of {@link Ndef} for the given tag.
129     *
130     * <p>Returns null if {@link Ndef} was not enumerated in {@link Tag#getTechList}.
131     * This indicates the tag is not NDEF formatted, or that this tag
132     * is NDEF formatted but under a vendor specification that this Android
133     * device does not implement.
134     *
135     * <p>Does not cause any RF activity and does not block.
136     *
137     * @param tag an MIFARE Classic compatible tag
138     * @return MIFARE Classic object
139     */
140    public static Ndef get(Tag tag) {
141        if (!tag.hasTech(TagTechnology.NDEF)) return null;
142        try {
143            return new Ndef(tag);
144        } catch (RemoteException e) {
145            return null;
146        }
147    }
148
149    /**
150     * Internal constructor, to be used by NfcAdapter
151     * @hide
152     */
153    public Ndef(Tag tag) throws RemoteException {
154        super(tag, TagTechnology.NDEF);
155        Bundle extras = tag.getTechExtras(TagTechnology.NDEF);
156        if (extras != null) {
157            mMaxNdefSize = extras.getInt(EXTRA_NDEF_MAXLENGTH);
158            mCardState = extras.getInt(EXTRA_NDEF_CARDSTATE);
159            mNdefMsg = extras.getParcelable(EXTRA_NDEF_MSG);
160            mNdefType = extras.getInt(EXTRA_NDEF_TYPE);
161        } else {
162            throw new NullPointerException("NDEF tech extras are null.");
163        }
164
165    }
166
167    /**
168     * Get the {@link NdefMessage} that was read from the tag at discovery time.
169     *
170     * <p>If the NDEF Message is modified by an I/O operation then it
171     * will not be updated here, this function only returns what was discovered
172     * when the tag entered the field.
173     * <p>Does not cause any RF activity and does not block.
174     * @return NDEF Message read from the tag at discovery time
175     */
176    public NdefMessage getCachedNdefMessage() {
177        return mNdefMsg;
178    }
179
180    /**
181     * Get the NDEF tag type.
182     *
183     * <p>Returns one of {@link #NFC_FORUM_TYPE_1}, {@link #NFC_FORUM_TYPE_2},
184     * {@link #NFC_FORUM_TYPE_3}, {@link #NFC_FORUM_TYPE_4},
185     * {@link #MIFARE_CLASSIC} or another NDEF tag type that has not yet been
186     * formalized in this Android API.
187     *
188     * <p>Does not cause any RF activity and does not block.
189     *
190     * @return a string representing the NDEF tag type
191     */
192    public String getType() {
193        switch (mNdefType) {
194            case TYPE_1:
195                return NFC_FORUM_TYPE_1;
196            case TYPE_2:
197                return NFC_FORUM_TYPE_2;
198            case TYPE_3:
199                return NFC_FORUM_TYPE_3;
200            case TYPE_4:
201                return NFC_FORUM_TYPE_4;
202            case TYPE_MIFARE_CLASSIC:
203                return MIFARE_CLASSIC;
204            default:
205                return UNKNOWN;
206        }
207    }
208
209    /**
210     * Get the maximum NDEF message size in bytes.
211     *
212     * <p>Does not cause any RF activity and does not block.
213     *
214     * @return size in bytes
215     */
216    public int getMaxSize() {
217        return mMaxNdefSize;
218    }
219
220    /**
221     * Determine if the tag is writable.
222     *
223     * <p>NFC Forum tags can be in read-only or read-write states.
224     *
225     * <p>Does not cause any RF activity and does not block.
226     *
227     * <p>Requires {@link android.Manifest.permission#NFC} permission.
228     *
229     * @return true if the tag is writable
230     */
231    public boolean isWritable() {
232        return (mCardState == NDEF_MODE_READ_WRITE);
233    }
234
235    /**
236     * Read the current {@link android.nfc.NdefMessage} on this tag.
237     *
238     * <p>This always reads the current NDEF Message stored on the tag.
239     *
240     * <p>This is an I/O operation and will block until complete. It must
241     * not be called from the main application thread. A blocked call will be canceled with
242     * {@link IOException} if {@link #close} is called from another thread.
243     *
244     * @return the NDEF Message, never null
245     * @throws TagLostException if the tag leaves the field
246     * @throws IOException if there is an I/O failure, or the operation is canceled
247     * @throws FormatException if the NDEF Message on the tag is malformed
248     */
249    public NdefMessage getNdefMessage() throws IOException, FormatException {
250        checkConnected();
251
252        try {
253            INfcTag tagService = mTag.getTagService();
254            int serviceHandle = mTag.getServiceHandle();
255            if (tagService.isNdef(serviceHandle)) {
256                NdefMessage msg = tagService.ndefRead(serviceHandle);
257                if (msg == null) {
258                    int errorCode = tagService.getLastError(serviceHandle);
259                    switch (errorCode) {
260                        case ErrorCodes.ERROR_IO:
261                            throw new IOException();
262                        case ErrorCodes.ERROR_INVALID_PARAM:
263                            throw new FormatException();
264                        default:
265                            // Should not happen
266                            throw new IOException();
267                    }
268                }
269                return msg;
270            } else {
271                return null;
272            }
273        } catch (RemoteException e) {
274            Log.e(TAG, "NFC service dead", e);
275            return null;
276        }
277    }
278
279    /**
280     * Overwrite the {@link NdefMessage} on this tag.
281     *
282     * <p>This is an I/O operation and will block until complete. It must
283     * not be called from the main application thread. A blocked call will be canceled with
284     * {@link IOException} if {@link #close} is called from another thread.
285     *
286     * @param msg the NDEF Message to write, must not be null
287     * @throws TagLostException if the tag leaves the field
288     * @throws IOException if there is an I/O failure, or the operation is canceled
289     * @throws FormatException if the NDEF Message to write is malformed
290     */
291    public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException {
292        checkConnected();
293
294        try {
295            INfcTag tagService = mTag.getTagService();
296            int serviceHandle = mTag.getServiceHandle();
297            if (tagService.isNdef(serviceHandle)) {
298                int errorCode = tagService.ndefWrite(serviceHandle, msg);
299                switch (errorCode) {
300                    case ErrorCodes.SUCCESS:
301                        break;
302                    case ErrorCodes.ERROR_IO:
303                        throw new IOException();
304                    case ErrorCodes.ERROR_INVALID_PARAM:
305                        throw new FormatException();
306                    default:
307                        // Should not happen
308                        throw new IOException();
309                }
310            }
311            else {
312                throw new IOException("Tag is not ndef");
313            }
314        } catch (RemoteException e) {
315            Log.e(TAG, "NFC service dead", e);
316        }
317    }
318
319    /**
320     * Indicates whether a tag can be made read-only with {@link #makeReadOnly()}.
321     *
322     * <p>Does not cause any RF activity and does not block.
323     *
324     * @return true if it is possible to make this tag read-only
325     */
326    public boolean canMakeReadOnly() {
327        if (mNdefType == TYPE_1 || mNdefType == TYPE_2) {
328            return true;
329        } else {
330            return false;
331        }
332    }
333
334    /**
335     * Make a tag read-only.
336     *
337     * <p>This sets the CC field to indicate the tag is read-only,
338     * and where possible permanently sets the lock bits to prevent
339     * any further modification of the memory.
340     * <p>This is a one-way process and cannot be reverted!
341     *
342     * <p>This is an I/O operation and will block until complete. It must
343     * not be called from the main application thread. A blocked call will be canceled with
344     * {@link IOException} if {@link #close} is called from another thread.
345     *
346     * @return true on success, false if it is not possible to make this tag read-only
347     * @throws TagLostException if the tag leaves the field
348     * @throws IOException if there is an I/O failure, or the operation is canceled
349     */
350    public boolean makeReadOnly() throws IOException {
351        checkConnected();
352
353        try {
354            INfcTag tagService = mTag.getTagService();
355            if (tagService.isNdef(mTag.getServiceHandle())) {
356                int errorCode = tagService.ndefMakeReadOnly(mTag.getServiceHandle());
357                switch (errorCode) {
358                    case ErrorCodes.SUCCESS:
359                        return true;
360                    case ErrorCodes.ERROR_IO:
361                        throw new IOException();
362                    case ErrorCodes.ERROR_INVALID_PARAM:
363                        return false;
364                    default:
365                        // Should not happen
366                        throw new IOException();
367                }
368           }
369           else {
370               throw new IOException("Tag is not ndef");
371           }
372        } catch (RemoteException e) {
373            Log.e(TAG, "NFC service dead", e);
374            return false;
375        }
376    }
377}
378