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.NfcAdapter; 24 import android.nfc.Tag; 25 import android.nfc.TagLostException; 26 import android.os.Bundle; 27 import android.os.RemoteException; 28 import android.util.Log; 29 30 import 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 */ 72 public 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 NDEF compatible tag 144 * @return Ndef 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>Note that this method may return null if the tag was in the 180 * INITIALIZED state as defined by NFC Forum, as in this state the 181 * tag is formatted to support NDEF but does not contain a message yet. 182 * <p>Does not cause any RF activity and does not block. 183 * @return NDEF Message read from the tag at discovery time, can be null 184 */ 185 public NdefMessage getCachedNdefMessage() { 186 return mNdefMsg; 187 } 188 189 /** 190 * Get the NDEF tag type. 191 * 192 * <p>Returns one of {@link #NFC_FORUM_TYPE_1}, {@link #NFC_FORUM_TYPE_2}, 193 * {@link #NFC_FORUM_TYPE_3}, {@link #NFC_FORUM_TYPE_4}, 194 * {@link #MIFARE_CLASSIC} or another NDEF tag type that has not yet been 195 * formalized in this Android API. 196 * 197 * <p>Does not cause any RF activity and does not block. 198 * 199 * @return a string representing the NDEF tag type 200 */ 201 public String getType() { 202 switch (mNdefType) { 203 case TYPE_1: 204 return NFC_FORUM_TYPE_1; 205 case TYPE_2: 206 return NFC_FORUM_TYPE_2; 207 case TYPE_3: 208 return NFC_FORUM_TYPE_3; 209 case TYPE_4: 210 return NFC_FORUM_TYPE_4; 211 case TYPE_MIFARE_CLASSIC: 212 return MIFARE_CLASSIC; 213 case TYPE_ICODE_SLI: 214 return ICODE_SLI; 215 default: 216 return UNKNOWN; 217 } 218 } 219 220 /** 221 * Get the maximum NDEF message size in bytes. 222 * 223 * <p>Does not cause any RF activity and does not block. 224 * 225 * @return size in bytes 226 */ 227 public int getMaxSize() { 228 return mMaxNdefSize; 229 } 230 231 /** 232 * Determine if the tag is writable. 233 * 234 * <p>NFC Forum tags can be in read-only or read-write states. 235 * 236 * <p>Does not cause any RF activity and does not block. 237 * 238 * <p>Requires {@link android.Manifest.permission#NFC} permission. 239 * 240 * @return true if the tag is writable 241 */ 242 public boolean isWritable() { 243 return (mCardState == NDEF_MODE_READ_WRITE); 244 } 245 246 /** 247 * Read the current {@link android.nfc.NdefMessage} on this tag. 248 * 249 * <p>This always reads the current NDEF Message stored on the tag. 250 * 251 * <p>Note that this method may return null if the tag was in the 252 * INITIALIZED state as defined by NFC Forum, as in that state the 253 * tag is formatted to support NDEF but does not contain a message yet. 254 * 255 * <p>This is an I/O operation and will block until complete. It must 256 * not be called from the main application thread. A blocked call will be canceled with 257 * {@link IOException} if {@link #close} is called from another thread. 258 * 259 * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 260 * 261 * @return the NDEF Message, can be null 262 * @throws TagLostException if the tag leaves the field 263 * @throws IOException if there is an I/O failure, or the operation is canceled 264 * @throws FormatException if the NDEF Message on the tag is malformed 265 */ 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 { 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