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