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