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 17 package android.nfc.tech; 18 19 import android.nfc.ErrorCodes; 20 import android.nfc.FormatException; 21 import android.nfc.INfcTag; 22 import android.nfc.NdefMessage; 23 import android.nfc.Tag; 24 import android.nfc.TagLostException; 25 import android.os.Bundle; 26 import android.os.RemoteException; 27 import android.util.Log; 28 29 import java.io.IOException; 30 31 /** 32 * Provides access to NDEF content and operations on a {@link Tag}. 33 * 34 * <p>Acquire a {@link Ndef} object using {@link #get}. 35 * 36 * <p>NDEF is an NFC Forum data format. The data formats are implemented in 37 * {@link android.nfc.NdefMessage} and 38 * {@link android.nfc.NdefRecord}. This class provides methods to 39 * retrieve and modify the {@link android.nfc.NdefMessage} 40 * on a tag. 41 * 42 * <p>There are currently four NFC Forum standardized tag types that can be 43 * formatted to contain NDEF data. 44 * <ul> 45 * <li>NFC Forum Type 1 Tag ({@link #NFC_FORUM_TYPE_1}), such as the Innovision Topaz 46 * <li>NFC Forum Type 2 Tag ({@link #NFC_FORUM_TYPE_2}), such as the NXP MIFARE Ultralight 47 * <li>NFC Forum Type 3 Tag ({@link #NFC_FORUM_TYPE_3}), such as Sony Felica 48 * <li>NFC Forum Type 4 Tag ({@link #NFC_FORUM_TYPE_4}), such as NXP MIFARE Desfire 49 * </ul> 50 * It is mandatory for all Android devices with NFC to correctly enumerate 51 * {@link Ndef} on NFC Forum Tag Types 1-4, and implement all NDEF operations 52 * as defined in this class. 53 * 54 * <p>Some vendors have their own well defined specifications for storing NDEF data 55 * on tags that do not fall into the above categories. Android devices with NFC 56 * should enumerate and implement {@link Ndef} under these vendor specifications 57 * where possible, but it is not mandatory. {@link #getType} returns a String 58 * describing this specification, for example {@link #MIFARE_CLASSIC} is 59 * <code>com.nxp.ndef.mifareclassic</code>. 60 * 61 * <p>Android devices that support MIFARE Classic must also correctly 62 * implement {@link Ndef} on MIFARE Classic tags formatted to NDEF. 63 * 64 * <p>For guaranteed compatibility across all Android devices with NFC, it is 65 * recommended to use NFC Forum Types 1-4 in new deployments of NFC tags 66 * with NDEF payload. Vendor NDEF formats will not work on all Android devices. 67 * 68 * <p class="note"><strong>Note:</strong> Methods that perform I/O operations 69 * require the {@link android.Manifest.permission#NFC} permission. 70 */ 71 public final class Ndef extends BasicTagTechnology { 72 private static final String TAG = "NFC"; 73 74 /** @hide */ 75 public static final int NDEF_MODE_READ_ONLY = 1; 76 /** @hide */ 77 public static final int NDEF_MODE_READ_WRITE = 2; 78 /** @hide */ 79 public static final int NDEF_MODE_UNKNOWN = 3; 80 81 /** @hide */ 82 public static final String EXTRA_NDEF_MSG = "ndefmsg"; 83 84 /** @hide */ 85 public static final String EXTRA_NDEF_MAXLENGTH = "ndefmaxlength"; 86 87 /** @hide */ 88 public static final String EXTRA_NDEF_CARDSTATE = "ndefcardstate"; 89 90 /** @hide */ 91 public static final String EXTRA_NDEF_TYPE = "ndeftype"; 92 93 /** @hide */ 94 public static final int TYPE_OTHER = -1; 95 /** @hide */ 96 public static final int TYPE_1 = 1; 97 /** @hide */ 98 public static final int TYPE_2 = 2; 99 /** @hide */ 100 public static final int TYPE_3 = 3; 101 /** @hide */ 102 public static final int TYPE_4 = 4; 103 /** @hide */ 104 public static final int TYPE_MIFARE_CLASSIC = 101; 105 /** @hide */ 106 public static final int TYPE_ICODE_SLI = 102; 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 3 */ 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 * NDEF on iCODE SLI 123 * @hide 124 */ 125 public static final String ICODE_SLI = "com.nxp.ndef.icodesli"; 126 127 private final int mMaxNdefSize; 128 private final int mCardState; 129 private final NdefMessage mNdefMsg; 130 private final int mNdefType; 131 132 /** 133 * Get an instance of {@link Ndef} for the given tag. 134 * 135 * <p>Returns null if {@link Ndef} was not enumerated in {@link Tag#getTechList}. 136 * This indicates the tag is not NDEF formatted, or that this tag 137 * is NDEF formatted but under a vendor specification that this Android 138 * device does not implement. 139 * 140 * <p>Does not cause any RF activity and does not block. 141 * 142 * @param tag an NDEF compatible tag 143 * @return Ndef object 144 */ get(Tag tag)145 public static Ndef get(Tag tag) { 146 if (!tag.hasTech(TagTechnology.NDEF)) return null; 147 try { 148 return new Ndef(tag); 149 } catch (RemoteException e) { 150 return null; 151 } 152 } 153 154 /** 155 * Internal constructor, to be used by NfcAdapter 156 * @hide 157 */ Ndef(Tag tag)158 public Ndef(Tag tag) throws RemoteException { 159 super(tag, TagTechnology.NDEF); 160 Bundle extras = tag.getTechExtras(TagTechnology.NDEF); 161 if (extras != null) { 162 mMaxNdefSize = extras.getInt(EXTRA_NDEF_MAXLENGTH); 163 mCardState = extras.getInt(EXTRA_NDEF_CARDSTATE); 164 mNdefMsg = extras.getParcelable(EXTRA_NDEF_MSG, android.nfc.NdefMessage.class); 165 mNdefType = extras.getInt(EXTRA_NDEF_TYPE); 166 } else { 167 throw new NullPointerException("NDEF tech extras are null."); 168 } 169 170 } 171 172 /** 173 * Get the {@link NdefMessage} that was read from the tag at discovery time. 174 * 175 * <p>If the NDEF Message is modified by an I/O operation then it 176 * will not be updated here, this function only returns what was discovered 177 * when the tag entered the field. 178 * <p>Note that this method may return null if the tag was in the 179 * INITIALIZED state as defined by NFC Forum, as in this state the 180 * tag is formatted to support NDEF but does not contain a message yet. 181 * <p>Does not cause any RF activity and does not block. 182 * @return NDEF Message read from the tag at discovery time, can be null 183 */ getCachedNdefMessage()184 public NdefMessage getCachedNdefMessage() { 185 return mNdefMsg; 186 } 187 188 /** 189 * Get the NDEF tag type. 190 * 191 * <p>Returns one of {@link #NFC_FORUM_TYPE_1}, {@link #NFC_FORUM_TYPE_2}, 192 * {@link #NFC_FORUM_TYPE_3}, {@link #NFC_FORUM_TYPE_4}, 193 * {@link #MIFARE_CLASSIC} or another NDEF tag type that has not yet been 194 * formalized in this Android API. 195 * 196 * <p>Does not cause any RF activity and does not block. 197 * 198 * @return a string representing the NDEF tag type 199 */ getType()200 public String getType() { 201 switch (mNdefType) { 202 case TYPE_1: 203 return NFC_FORUM_TYPE_1; 204 case TYPE_2: 205 return NFC_FORUM_TYPE_2; 206 case TYPE_3: 207 return NFC_FORUM_TYPE_3; 208 case TYPE_4: 209 return NFC_FORUM_TYPE_4; 210 case TYPE_MIFARE_CLASSIC: 211 return MIFARE_CLASSIC; 212 case TYPE_ICODE_SLI: 213 return ICODE_SLI; 214 default: 215 return UNKNOWN; 216 } 217 } 218 219 /** 220 * Get the maximum NDEF message size in bytes. 221 * 222 * <p>Does not cause any RF activity and does not block. 223 * 224 * @return size in bytes 225 */ getMaxSize()226 public int getMaxSize() { 227 return mMaxNdefSize; 228 } 229 230 /** 231 * Determine if the tag is writable. 232 * 233 * <p>NFC Forum tags can be in read-only or read-write states. 234 * 235 * <p>Does not cause any RF activity and does not block. 236 * 237 * <p>Requires {@link android.Manifest.permission#NFC} permission. 238 * 239 * @return true if the tag is writable 240 */ isWritable()241 public boolean isWritable() { 242 return (mCardState == NDEF_MODE_READ_WRITE); 243 } 244 245 /** 246 * Read the current {@link android.nfc.NdefMessage} on this tag. 247 * 248 * <p>This always reads the current NDEF Message stored on the tag. 249 * 250 * <p>Note that this method may return null if the tag was in the 251 * INITIALIZED state as defined by NFC Forum, as in that state the 252 * tag is formatted to support NDEF but does not contain a message yet. 253 * 254 * <p>This is an I/O operation and will block until complete. It must 255 * not be called from the main application thread. A blocked call will be canceled with 256 * {@link IOException} if {@link #close} is called from another thread. 257 * 258 * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 259 * 260 * @return the NDEF Message, can be null 261 * @throws TagLostException if the tag leaves the field 262 * @throws IOException if there is an I/O failure, or the operation is canceled 263 * @throws FormatException if the NDEF Message on the tag is malformed 264 * @throws SecurityException if the tag object is reused after the tag has left the field 265 */ getNdefMessage()266 public NdefMessage getNdefMessage() throws IOException, FormatException { 267 checkConnected(); 268 269 try { 270 INfcTag tagService = mTag.getTagService(); 271 if (tagService == null) { 272 throw new IOException("Mock tags don't support this operation."); 273 } 274 int serviceHandle = mTag.getServiceHandle(); 275 if (tagService.isNdef(serviceHandle)) { 276 NdefMessage msg = tagService.ndefRead(serviceHandle); 277 if (msg == null && !tagService.isPresent(serviceHandle)) { 278 throw new TagLostException(); 279 } 280 return msg; 281 } else if (!tagService.isPresent(serviceHandle)) { 282 throw new TagLostException(); 283 } else { 284 return null; 285 } 286 } catch (RemoteException e) { 287 Log.e(TAG, "NFC service dead", e); 288 return null; 289 } 290 } 291 292 /** 293 * Overwrite the {@link NdefMessage} on this tag. 294 * 295 * <p>This is an I/O operation and will block until complete. It must 296 * not be called from the main application thread. A blocked call will be canceled with 297 * {@link IOException} if {@link #close} is called from another thread. 298 * 299 * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 300 * 301 * @param msg the NDEF Message to write, must not be null 302 * @throws TagLostException if the tag leaves the field 303 * @throws IOException if there is an I/O failure, or the operation is canceled 304 * @throws FormatException if the NDEF Message to write is malformed 305 * @throws SecurityException if the tag object is reused after the tag has left the field 306 */ writeNdefMessage(NdefMessage msg)307 public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException { 308 checkConnected(); 309 310 try { 311 INfcTag tagService = mTag.getTagService(); 312 if (tagService == null) { 313 throw new IOException("Mock tags don't support this operation."); 314 } 315 int serviceHandle = mTag.getServiceHandle(); 316 if (tagService.isNdef(serviceHandle)) { 317 int errorCode = tagService.ndefWrite(serviceHandle, msg); 318 switch (errorCode) { 319 case ErrorCodes.SUCCESS: 320 break; 321 case ErrorCodes.ERROR_IO: 322 throw new IOException(); 323 case ErrorCodes.ERROR_INVALID_PARAM: 324 throw new FormatException(); 325 default: 326 // Should not happen 327 throw new IOException(); 328 } 329 } 330 else { 331 throw new IOException("Tag is not ndef"); 332 } 333 } catch (RemoteException e) { 334 Log.e(TAG, "NFC service dead", e); 335 } 336 } 337 338 /** 339 * Indicates whether a tag can be made read-only with {@link #makeReadOnly()}. 340 * 341 * <p>Does not cause any RF activity and does not block. 342 * 343 * @return true if it is possible to make this tag read-only 344 * @throws SecurityException if the tag object is reused after the tag has left the field 345 */ canMakeReadOnly()346 public boolean canMakeReadOnly() { 347 INfcTag tagService = mTag.getTagService(); 348 if (tagService == null) { 349 return false; 350 } 351 try { 352 return tagService.canMakeReadOnly(mNdefType); 353 } catch (RemoteException e) { 354 Log.e(TAG, "NFC service dead", e); 355 return false; 356 } 357 } 358 359 /** 360 * Make a tag read-only. 361 * 362 * <p>This sets the CC field to indicate the tag is read-only, 363 * and where possible permanently sets the lock bits to prevent 364 * any further modification of the memory. 365 * <p>This is a one-way process and cannot be reverted! 366 * 367 * <p>This is an I/O operation and will block until complete. It must 368 * not be called from the main application thread. A blocked call will be canceled with 369 * {@link IOException} if {@link #close} is called from another thread. 370 * 371 * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 372 * 373 * @return true on success, false if it is not possible to make this tag read-only 374 * @throws TagLostException if the tag leaves the field 375 * @throws IOException if there is an I/O failure, or the operation is canceled 376 * @throws SecurityException if the tag object is reused after the tag has left the field 377 */ makeReadOnly()378 public boolean makeReadOnly() throws IOException { 379 checkConnected(); 380 381 try { 382 INfcTag tagService = mTag.getTagService(); 383 if (tagService == null) { 384 return false; 385 } 386 if (tagService.isNdef(mTag.getServiceHandle())) { 387 int errorCode = tagService.ndefMakeReadOnly(mTag.getServiceHandle()); 388 switch (errorCode) { 389 case ErrorCodes.SUCCESS: 390 return true; 391 case ErrorCodes.ERROR_IO: 392 throw new IOException(); 393 case ErrorCodes.ERROR_INVALID_PARAM: 394 return false; 395 default: 396 // Should not happen 397 throw new IOException(); 398 } 399 } 400 else { 401 throw new IOException("Tag is not ndef"); 402 } 403 } catch (RemoteException e) { 404 Log.e(TAG, "NFC service dead", e); 405 return false; 406 } 407 } 408 } 409