Ndef.java revision 39cf3a445e507f219ecc8a476f6038f095d9d520
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
107    /** @hide */
108    public static final String UNKNOWN = "android.ndef.unknown";
109
110    /** NFC Forum Tag Type 1 */
111    public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
112    /** NFC Forum Tag Type 2 */
113    public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
114    /** NFC Forum Tag Type 4 */
115    public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
116    /** NFC Forum Tag Type 4 */
117    public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
118    /** NDEF on MIFARE Classic */
119    public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
120
121    private final int mMaxNdefSize;
122    private final int mCardState;
123    private final NdefMessage mNdefMsg;
124    private final int mNdefType;
125
126    /**
127     * Get an instance of {@link Ndef} for the given tag.
128     *
129     * <p>Returns null if {@link Ndef} was not enumerated in {@link Tag#getTechList}.
130     * This indicates the tag is not NDEF formatted, or that this tag
131     * is NDEF formatted but under a vendor specification that this Android
132     * device does not implement.
133     *
134     * <p>Does not cause any RF activity and does not block.
135     *
136     * @param tag an MIFARE Classic compatible tag
137     * @return MIFARE Classic object
138     */
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     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
287     *
288     * @param msg the NDEF Message to write, must not be null
289     * @throws TagLostException if the tag leaves the field
290     * @throws IOException if there is an I/O failure, or the operation is canceled
291     * @throws FormatException if the NDEF Message to write is malformed
292     */
293    public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException {
294        checkConnected();
295
296        try {
297            INfcTag tagService = mTag.getTagService();
298            int serviceHandle = mTag.getServiceHandle();
299            if (tagService.isNdef(serviceHandle)) {
300                int errorCode = tagService.ndefWrite(serviceHandle, msg);
301                switch (errorCode) {
302                    case ErrorCodes.SUCCESS:
303                        break;
304                    case ErrorCodes.ERROR_IO:
305                        throw new IOException();
306                    case ErrorCodes.ERROR_INVALID_PARAM:
307                        throw new FormatException();
308                    default:
309                        // Should not happen
310                        throw new IOException();
311                }
312            }
313            else {
314                throw new IOException("Tag is not ndef");
315            }
316        } catch (RemoteException e) {
317            Log.e(TAG, "NFC service dead", e);
318        }
319    }
320
321    /**
322     * Indicates whether a tag can be made read-only with {@link #makeReadOnly()}.
323     *
324     * <p>Does not cause any RF activity and does not block.
325     *
326     * @return true if it is possible to make this tag read-only
327     */
328    public boolean canMakeReadOnly() {
329        if (mNdefType == TYPE_1 || mNdefType == TYPE_2) {
330            return true;
331        } else {
332            return false;
333        }
334    }
335
336    /**
337     * Make a tag read-only.
338     *
339     * <p>This sets the CC field to indicate the tag is read-only,
340     * and where possible permanently sets the lock bits to prevent
341     * any further modification of the memory.
342     * <p>This is a one-way process and cannot be reverted!
343     *
344     * <p>This is an I/O operation and will block until complete. It must
345     * not be called from the main application thread. A blocked call will be canceled with
346     * {@link IOException} if {@link #close} is called from another thread.
347     *
348     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
349     *
350     * @return true on success, false if it is not possible to make this tag read-only
351     * @throws TagLostException if the tag leaves the field
352     * @throws IOException if there is an I/O failure, or the operation is canceled
353     */
354    public boolean makeReadOnly() throws IOException {
355        checkConnected();
356
357        try {
358            INfcTag tagService = mTag.getTagService();
359            if (tagService.isNdef(mTag.getServiceHandle())) {
360                int errorCode = tagService.ndefMakeReadOnly(mTag.getServiceHandle());
361                switch (errorCode) {
362                    case ErrorCodes.SUCCESS:
363                        return true;
364                    case ErrorCodes.ERROR_IO:
365                        throw new IOException();
366                    case ErrorCodes.ERROR_INVALID_PARAM:
367                        return false;
368                    default:
369                        // Should not happen
370                        throw new IOException();
371                }
372           }
373           else {
374               throw new IOException("Tag is not ndef");
375           }
376        } catch (RemoteException e) {
377            Log.e(TAG, "NFC service dead", e);
378            return false;
379        }
380    }
381}
382