blob: b266bb6dd1edc5d0d5db39d22eac3524aa06a4d5 [file] [log] [blame]
Jeff Hamilton6be655c2010-11-12 12:28:16 -06001/*
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
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -060017package android.nfc.tech;
Jeff Hamilton6be655c2010-11-12 12:28:16 -060018
19import android.nfc.ErrorCodes;
20import android.nfc.FormatException;
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -060021import android.nfc.INfcTag;
Jeff Hamilton6be655c2010-11-12 12:28:16 -060022import android.nfc.NdefMessage;
23import android.nfc.NfcAdapter;
24import android.nfc.Tag;
Nick Pelly74fe6c62011-02-02 22:37:40 -080025import android.nfc.TagLostException;
Jeff Hamilton6be655c2010-11-12 12:28:16 -060026import android.os.Bundle;
27import android.os.RemoteException;
Nick Pelly3dd6c452011-01-10 18:14:41 +110028import android.util.Log;
Jeff Hamilton6be655c2010-11-12 12:28:16 -060029
30import java.io.IOException;
31
32/**
Nick Pelly74fe6c62011-02-02 22:37:40 -080033 * Provides access to NDEF content and operations on a {@link Tag}.
Jeff Hamilton6be655c2010-11-12 12:28:16 -060034 *
Nick Pelly74fe6c62011-02-02 22:37:40 -080035 * <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
Nick Pelly39cf3a42011-02-07 17:04:21 +090047 * <li>NFC Forum Type 2 Tag ({@link #NFC_FORUM_TYPE_2}), such as the NXP MIFARE Ultralight
Nick Pelly74fe6c62011-02-02 22:37:40 -080048 * <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.
Jeff Hamilton6be655c2010-11-12 12:28:16 -060068 *
Nick Pelly39cf3a42011-02-07 17:04:21 +090069 * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
70 * require the {@link android.Manifest.permission#NFC} permission.
Jeff Hamilton6be655c2010-11-12 12:28:16 -060071 */
72public final class Ndef extends BasicTagTechnology {
Nick Pelly3dd6c452011-01-10 18:14:41 +110073 private static final String TAG = "NFC";
74
Martijn Coenen3300e4c2010-12-08 23:23:47 -080075 /** @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;
Jeff Hamilton6be655c2010-11-12 12:28:16 -060081
Martijn Coenen72df4ea2010-12-06 21:05:17 -080082 /** @hide */
83 public static final String EXTRA_NDEF_MSG = "ndefmsg";
84
Martijn Coenen6d9fc7e2010-12-07 14:04:28 -080085 /** @hide */
86 public static final String EXTRA_NDEF_MAXLENGTH = "ndefmaxlength";
87
Martijn Coenen3300e4c2010-12-08 23:23:47 -080088 /** @hide */
89 public static final String EXTRA_NDEF_CARDSTATE = "ndefcardstate";
90
Martijn Coenend27ebf12010-12-22 13:51:51 +010091 /** @hide */
92 public static final String EXTRA_NDEF_TYPE = "ndeftype";
93
Nick Pellyf003e262011-01-31 23:27:37 -080094 /** @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;
Jeff Hamilton5644d0e2011-02-24 21:59:52 -0600106 /** @hide */
107 public static final int TYPE_ICODE_SLI = 102;
Nick Pellyf003e262011-01-31 23:27:37 -0800108
109 /** @hide */
110 public static final String UNKNOWN = "android.ndef.unknown";
111
Nick Pelly74fe6c62011-02-02 22:37:40 -0800112 /** NFC Forum Tag Type 1 */
Nick Pellyf003e262011-01-31 23:27:37 -0800113 public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
Nick Pelly74fe6c62011-02-02 22:37:40 -0800114 /** NFC Forum Tag Type 2 */
Nick Pellyf003e262011-01-31 23:27:37 -0800115 public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
Nick Pelly74fe6c62011-02-02 22:37:40 -0800116 /** NFC Forum Tag Type 4 */
Nick Pellyf003e262011-01-31 23:27:37 -0800117 public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
Nick Pelly74fe6c62011-02-02 22:37:40 -0800118 /** NFC Forum Tag Type 4 */
Nick Pellyf003e262011-01-31 23:27:37 -0800119 public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
Nick Pelly74fe6c62011-02-02 22:37:40 -0800120 /** NDEF on MIFARE Classic */
Nick Pellyf003e262011-01-31 23:27:37 -0800121 public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
Jeff Hamilton5644d0e2011-02-24 21:59:52 -0600122 /**
123 * NDEF on iCODE SLI
124 * @hide
125 */
126 public static final String ICODE_SLI = "com.nxp.ndef.icodesli";
Martijn Coenend27ebf12010-12-22 13:51:51 +0100127
Martijn Coenen3300e4c2010-12-08 23:23:47 -0800128 private final int mMaxNdefSize;
129 private final int mCardState;
Martijn Coenene3f63362010-12-13 16:18:41 +0100130 private final NdefMessage mNdefMsg;
Martijn Coenend27ebf12010-12-22 13:51:51 +0100131 private final int mNdefType;
Martijn Coenen6d9fc7e2010-12-07 14:04:28 -0800132
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600133 /**
Nick Pelly74fe6c62011-02-02 22:37:40 -0800134 * Get an instance of {@link Ndef} for the given tag.
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600135 *
Nick Pelly74fe6c62011-02-02 22:37:40 -0800136 * <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
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600145 */
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 /**
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600156 * Internal constructor, to be used by NfcAdapter
157 * @hide
158 */
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600159 public Ndef(Tag tag) throws RemoteException {
160 super(tag, TagTechnology.NDEF);
161 Bundle extras = tag.getTechExtras(TagTechnology.NDEF);
Martijn Coenen6d9fc7e2010-12-07 14:04:28 -0800162 if (extras != null) {
Martijn Coenen3300e4c2010-12-08 23:23:47 -0800163 mMaxNdefSize = extras.getInt(EXTRA_NDEF_MAXLENGTH);
164 mCardState = extras.getInt(EXTRA_NDEF_CARDSTATE);
Martijn Coenene3f63362010-12-13 16:18:41 +0100165 mNdefMsg = extras.getParcelable(EXTRA_NDEF_MSG);
Martijn Coenend27ebf12010-12-22 13:51:51 +0100166 mNdefType = extras.getInt(EXTRA_NDEF_TYPE);
Martijn Coenen6d9fc7e2010-12-07 14:04:28 -0800167 } else {
Martijn Coenen3300e4c2010-12-08 23:23:47 -0800168 throw new NullPointerException("NDEF tech extras are null.");
Martijn Coenen6d9fc7e2010-12-07 14:04:28 -0800169 }
Martijn Coenen3300e4c2010-12-08 23:23:47 -0800170
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600171 }
172
173 /**
Nick Pelly74fe6c62011-02-02 22:37:40 -0800174 * 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
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600181 */
Martijn Coenene3f63362010-12-13 16:18:41 +0100182 public NdefMessage getCachedNdefMessage() {
183 return mNdefMsg;
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600184 }
185
186 /**
Nick Pelly74fe6c62011-02-02 22:37:40 -0800187 * Get the NDEF tag type.
188 *
Nick Pellyddbb2c92011-01-04 17:29:14 +1100189 * <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},
Nick Pelly74fe6c62011-02-02 22:37:40 -0800191 * {@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
Martijn Coenend27ebf12010-12-22 13:51:51 +0100197 */
Nick Pellyf003e262011-01-31 23:27:37 -0800198 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;
Jeff Hamilton5644d0e2011-02-24 21:59:52 -0600210 case TYPE_ICODE_SLI:
211 return ICODE_SLI;
Nick Pellyf003e262011-01-31 23:27:37 -0800212 default:
213 return UNKNOWN;
214 }
Martijn Coenend27ebf12010-12-22 13:51:51 +0100215 }
216
217 /**
Nick Pelly74fe6c62011-02-02 22:37:40 -0800218 * 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
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600223 */
Martijn Coenen3300e4c2010-12-08 23:23:47 -0800224 public int getMaxSize() {
225 return mMaxNdefSize;
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600226 }
227
228 /**
Nick Pelly74fe6c62011-02-02 22:37:40 -0800229 * 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 *
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600235 * <p>Requires {@link android.Manifest.permission#NFC} permission.
Nick Pelly74fe6c62011-02-02 22:37:40 -0800236 *
237 * @return true if the tag is writable
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600238 */
Martijn Coenen3300e4c2010-12-08 23:23:47 -0800239 public boolean isWritable() {
240 return (mCardState == NDEF_MODE_READ_WRITE);
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600241 }
242
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600243 /**
Nick Pelly74fe6c62011-02-02 22:37:40 -0800244 * 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
Martijn Coenene3f63362010-12-13 16:18:41 +0100256 */
257 public NdefMessage getNdefMessage() throws IOException, FormatException {
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100258 checkConnected();
259
Martijn Coenene3f63362010-12-13 16:18:41 +0100260 try {
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600261 INfcTag tagService = mTag.getTagService();
Martijn Coenene3f63362010-12-13 16:18:41 +0100262 int serviceHandle = mTag.getServiceHandle();
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600263 if (tagService.isNdef(serviceHandle)) {
264 NdefMessage msg = tagService.ndefRead(serviceHandle);
Martijn Coenene3f63362010-12-13 16:18:41 +0100265 if (msg == null) {
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600266 int errorCode = tagService.getLastError(serviceHandle);
Martijn Coenene3f63362010-12-13 16:18:41 +0100267 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) {
Nick Pelly3dd6c452011-01-10 18:14:41 +1100282 Log.e(TAG, "NFC service dead", e);
Martijn Coenene3f63362010-12-13 16:18:41 +0100283 return null;
284 }
285 }
Jeff Hamilton3ce864812010-12-18 16:39:37 -0600286
Martijn Coenene3f63362010-12-13 16:18:41 +0100287 /**
Nick Pelly74fe6c62011-02-02 22:37:40 -0800288 * 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 *
Nick Pelly39cf3a42011-02-07 17:04:21 +0900294 * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
295 *
Nick Pelly74fe6c62011-02-02 22:37:40 -0800296 * @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
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600300 */
301 public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException {
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100302 checkConnected();
303
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600304 try {
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600305 INfcTag tagService = mTag.getTagService();
Martijn Coenen01d159a2010-12-16 22:39:20 +0100306 int serviceHandle = mTag.getServiceHandle();
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600307 if (tagService.isNdef(serviceHandle)) {
308 int errorCode = tagService.ndefWrite(serviceHandle, msg);
Martijn Coenen01d159a2010-12-16 22:39:20 +0100309 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");
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600323 }
324 } catch (RemoteException e) {
Nick Pelly3dd6c452011-01-10 18:14:41 +1100325 Log.e(TAG, "NFC service dead", e);
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600326 }
327 }
328
329 /**
Nick Pelly74fe6c62011-02-02 22:37:40 -0800330 * 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
Martijn Coenen25be5362011-01-10 16:12:52 +0100335 */
Nick Pellyf003e262011-01-31 23:27:37 -0800336 public boolean canMakeReadOnly() {
Martijn Coenenfaca12a2011-08-19 14:07:52 +0200337 INfcTag tagService = mTag.getTagService();
338 try {
339 return tagService.canMakeReadOnly(mNdefType);
340 } catch (RemoteException e) {
341 Log.e(TAG, "NFC service dead", e);
Martijn Coenen25be5362011-01-10 16:12:52 +0100342 return false;
343 }
344 }
345
346 /**
Nick Pelly74fe6c62011-02-02 22:37:40 -0800347 * 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 *
Nick Pelly39cf3a42011-02-07 17:04:21 +0900358 * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
359 *
Nick Pelly74fe6c62011-02-02 22:37:40 -0800360 * @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
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600363 */
Nick Pellyf003e262011-01-31 23:27:37 -0800364 public boolean makeReadOnly() throws IOException {
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100365 checkConnected();
366
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600367 try {
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600368 INfcTag tagService = mTag.getTagService();
369 if (tagService.isNdef(mTag.getServiceHandle())) {
370 int errorCode = tagService.ndefMakeReadOnly(mTag.getServiceHandle());
Martijn Coenen07e6f612011-01-14 16:26:45 +0100371 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 }
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600386 } catch (RemoteException e) {
Nick Pelly3dd6c452011-01-10 18:14:41 +1100387 Log.e(TAG, "NFC service dead", e);
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600388 return false;
389 }
390 }
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600391}