NfcAdapter.java revision d88e9aa575eb3a9d20cdb0e8918d54993e1ce1e0
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; 18 19import android.annotation.SdkConstant; 20import android.annotation.SdkConstant.SdkConstantType; 21import android.app.Activity; 22import android.app.ActivityThread; 23import android.app.OnActivityPausedListener; 24import android.app.PendingIntent; 25import android.content.Context; 26import android.content.IntentFilter; 27import android.content.pm.IPackageManager; 28import android.content.pm.PackageManager; 29import android.os.IBinder; 30import android.os.Parcel; 31import android.os.RemoteException; 32import android.os.ServiceManager; 33import android.util.Log; 34 35/** 36 * Represents the device's local NFC adapter. 37 * <p> 38 * Use the helper {@link #getDefaultAdapter(Context)} to get the default NFC 39 * adapter for this Android device. 40 */ 41public final class NfcAdapter { 42 private static final String TAG = "NFC"; 43 44 /** 45 * Intent to start an activity when a tag with NDEF payload is discovered. 46 * If the tag has and NDEF payload this intent is started before 47 * {@link #ACTION_TECHNOLOGY_DISCOVERED}. 48 * 49 * If any activities respond to this intent neither 50 * {@link #ACTION_TECHNOLOGY_DISCOVERED} or {@link #ACTION_TAG_DISCOVERED} will be started. 51 */ 52 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 53 public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED"; 54 55 /** 56 * Intent to started when a tag is discovered. The data URI is formated as 57 * {@code vnd.android.nfc://tag/} with the path having a directory entry for each technology 58 * in the {@link Tag#getTechList()} is sorted ascending order. 59 * 60 * This intent is started after {@link #ACTION_NDEF_DISCOVERED} and before 61 * {@link #ACTION_TAG_DISCOVERED} 62 * 63 * If any activities respond to this intent {@link #ACTION_TAG_DISCOVERED} will not be started. 64 */ 65 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 66 public static final String ACTION_TECHNOLOGY_DISCOVERED = "android.nfc.action.TECH_DISCOVERED"; 67 68 /** 69 * Intent to start an activity when a tag is discovered. 70 */ 71 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 72 public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED"; 73 74 /** 75 * Broadcast to only the activity that handles ACTION_TAG_DISCOVERED 76 * @hide 77 */ 78 public static final String ACTION_TAG_LEFT_FIELD = "android.nfc.action.TAG_LOST"; 79 80 /** 81 * Mandatory Tag extra for the ACTION_TAG intents. 82 */ 83 public static final String EXTRA_TAG = "android.nfc.extra.TAG"; 84 85 /** 86 * Optional NdefMessage[] extra for the ACTION_TAG intents. 87 */ 88 public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES"; 89 90 /** 91 * Optional byte[] extra for the tag identifier. 92 */ 93 public static final String EXTRA_ID = "android.nfc.extra.ID"; 94 95 /** 96 * Broadcast Action: a transaction with a secure element has been detected. 97 * <p> 98 * Always contains the extra field 99 * {@link android.nfc.NfcAdapter#EXTRA_AID} 100 * @hide 101 */ 102 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 103 public static final String ACTION_TRANSACTION_DETECTED = 104 "android.nfc.action.TRANSACTION_DETECTED"; 105 106 /** 107 * Broadcast Action: an RF field ON has been detected. 108 * @hide 109 */ 110 public static final String ACTION_RF_FIELD_ON_DETECTED = 111 "android.nfc.action.RF_FIELD_ON_DETECTED"; 112 113 /** 114 * Broadcast Action: an RF Field OFF has been detected. 115 * @hide 116 */ 117 public static final String ACTION_RF_FIELD_OFF_DETECTED = 118 "android.nfc.action.RF_FIELD_OFF_DETECTED"; 119 120 /** 121 * Broadcast Action: an adapter's state changed between enabled and disabled. 122 * 123 * The new value is stored in the extra EXTRA_NEW_BOOLEAN_STATE and just contains 124 * whether it's enabled or disabled, not including any information about whether it's 125 * actively enabling or disabling. 126 * 127 * @hide 128 */ 129 public static final String ACTION_ADAPTER_STATE_CHANGE = 130 "android.nfc.action.ADAPTER_STATE_CHANGE"; 131 132 /** 133 * The Intent extra for ACTION_ADAPTER_STATE_CHANGE, saying what the new state is. 134 * 135 * @hide 136 */ 137 public static final String EXTRA_NEW_BOOLEAN_STATE = "android.nfc.isEnabled"; 138 139 /** 140 * Mandatory byte array extra field in 141 * {@link android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED}. 142 * <p> 143 * Contains the AID of the applet involved in the transaction. 144 * @hide 145 */ 146 public static final String EXTRA_AID = "android.nfc.extra.AID"; 147 148 /** 149 * LLCP link status: The LLCP link is activated. 150 * @hide 151 */ 152 public static final int LLCP_LINK_STATE_ACTIVATED = 0; 153 154 /** 155 * LLCP link status: The LLCP link is deactivated. 156 * @hide 157 */ 158 public static final int LLCP_LINK_STATE_DEACTIVATED = 1; 159 160 /** 161 * Broadcast Action: the LLCP link state changed. 162 * <p> 163 * Always contains the extra field 164 * {@link android.nfc.NfcAdapter#EXTRA_LLCP_LINK_STATE_CHANGED}. 165 * @hide 166 */ 167 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 168 public static final String ACTION_LLCP_LINK_STATE_CHANGED = 169 "android.nfc.action.LLCP_LINK_STATE_CHANGED"; 170 171 /** 172 * Used as int extra field in 173 * {@link android.nfc.NfcAdapter#ACTION_LLCP_LINK_STATE_CHANGED}. 174 * <p> 175 * It contains the new state of the LLCP link. 176 * @hide 177 */ 178 public static final String EXTRA_LLCP_LINK_STATE_CHANGED = "android.nfc.extra.LLCP_LINK_STATE"; 179 180 /** 181 * Tag Reader Discovery mode 182 * @hide 183 */ 184 private static final int DISCOVERY_MODE_TAG_READER = 0; 185 186 /** 187 * NFC-IP1 Peer-to-Peer mode Enables the manager to act as a peer in an 188 * NFC-IP1 communication. Implementations should not assume that the 189 * controller will end up behaving as an NFC-IP1 target or initiator and 190 * should handle both cases, depending on the type of the remote peer type. 191 * @hide 192 */ 193 private static final int DISCOVERY_MODE_NFCIP1 = 1; 194 195 /** 196 * Card Emulation mode Enables the manager to act as an NFC tag. Provided 197 * that a Secure Element (an UICC for instance) is connected to the NFC 198 * controller through its SWP interface, it can be exposed to the outside 199 * NFC world and be addressed by external readers the same way they would 200 * with a tag. 201 * <p> 202 * Which Secure Element is exposed is implementation-dependent. 203 * 204 * @hide 205 */ 206 private static final int DISCOVERY_MODE_CARD_EMULATION = 2; 207 208 209 // Guarded by NfcAdapter.class 210 private static boolean sIsInitialized = false; 211 212 // Final after first constructor, except for 213 // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort 214 // recovery 215 private static INfcAdapter sService; 216 private static INfcTag sTagService; 217 218 /** 219 * Helper to check if this device has FEATURE_NFC, but without using 220 * a context. 221 * Equivalent to 222 * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC) 223 */ 224 private static boolean hasNfcFeature() { 225 IPackageManager pm = ActivityThread.getPackageManager(); 226 if (pm == null) { 227 Log.e(TAG, "Cannot get package manager, assuming no NFC feature"); 228 return false; 229 } 230 try { 231 return pm.hasSystemFeature(PackageManager.FEATURE_NFC); 232 } catch (RemoteException e) { 233 Log.e(TAG, "Package manager query failed, assuming no NFC feature", e); 234 return false; 235 } 236 } 237 238 private static synchronized INfcAdapter setupService() { 239 if (!sIsInitialized) { 240 sIsInitialized = true; 241 242 /* is this device meant to have NFC */ 243 if (!hasNfcFeature()) { 244 Log.v(TAG, "this device does not have NFC support"); 245 return null; 246 } 247 248 sService = getServiceInterface(); 249 if (sService == null) { 250 Log.e(TAG, "could not retrieve NFC service"); 251 return null; 252 } 253 try { 254 sTagService = sService.getNfcTagInterface(); 255 } catch (RemoteException e) { 256 Log.e(TAG, "could not retrieve NFC Tag service"); 257 return null; 258 } 259 } 260 return sService; 261 } 262 263 /** get handle to NFC service interface */ 264 private static INfcAdapter getServiceInterface() { 265 /* get a handle to NFC service */ 266 IBinder b = ServiceManager.getService("nfc"); 267 if (b == null) { 268 return null; 269 } 270 return INfcAdapter.Stub.asInterface(b); 271 } 272 273 /** 274 * Helper to get the default NFC Adapter. 275 * <p> 276 * Most Android devices will only have one NFC Adapter (NFC Controller). 277 * <p> 278 * This helper is the equivalent of: 279 * <pre>{@code 280 * NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE); 281 * NfcAdapter adapter = manager.getDefaultAdapter(); 282 * }</pre> 283 * @param context the calling application's context 284 * 285 * @return the default NFC adapter, or null if no NFC adapter exists 286 */ 287 public static NfcAdapter getDefaultAdapter(Context context) { 288 /* use getSystemService() instead of just instantiating to take 289 * advantage of the context's cached NfcManager & NfcAdapter */ 290 NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE); 291 return manager.getDefaultAdapter(); 292 } 293 294 /** 295 * Get a handle to the default NFC Adapter on this Android device. 296 * <p> 297 * Most Android devices will only have one NFC Adapter (NFC Controller). 298 * 299 * @return the default NFC adapter, or null if no NFC adapter exists 300 * @deprecated use {@link #getDefaultAdapter(Context)} 301 */ 302 @Deprecated 303 public static NfcAdapter getDefaultAdapter() { 304 Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " + 305 "NfcAdapter.getDefaultAdapter(Context) instead", new Exception()); 306 return new NfcAdapter(null); 307 } 308 309 /*package*/ NfcAdapter(Context context) { 310 if (setupService() == null) { 311 throw new UnsupportedOperationException(); 312 } 313 } 314 315 /** 316 * Returns the binder interface to the service. 317 * @hide 318 */ 319 public INfcAdapter getService() { 320 isEnabled(); // NOP call to recover sService if it is stale 321 return sService; 322 } 323 324 /** 325 * Returns the binder interface to the tag service. 326 * @hide 327 */ 328 public INfcTag getTagService() { 329 isEnabled(); // NOP call to recover sTagService if it is stale 330 return sTagService; 331 } 332 333 /** 334 * NFC service dead - attempt best effort recovery 335 * @hide 336 */ 337 public void attemptDeadServiceRecovery(Exception e) { 338 Log.e(TAG, "NFC service dead - attempting to recover", e); 339 INfcAdapter service = getServiceInterface(); 340 if (service == null) { 341 Log.e(TAG, "could not retrieve NFC service during service recovery"); 342 // nothing more can be done now, sService is still stale, we'll hit 343 // this recovery path again later 344 return; 345 } 346 // assigning to sService is not thread-safe, but this is best-effort code 347 // and on a well-behaved system should never happen 348 sService = service; 349 try { 350 sTagService = service.getNfcTagInterface(); 351 } catch (RemoteException ee) { 352 Log.e(TAG, "could not retrieve NFC tag service during service recovery"); 353 // nothing more can be done now, sService is still stale, we'll hit 354 // this recovery path again later 355 } 356 357 return; 358 } 359 360 /** 361 * Return true if this NFC Adapter has any features enabled. 362 * <p> 363 * If this method returns false, then applications should request the user 364 * turn on NFC tag discovery in Settings. 365 * <p> 366 * If this method returns false, the NFC hardware is guaranteed not to 367 * perform or respond to any NFC communication. 368 * 369 * @return true if this NFC Adapter is enabled to discover new tags 370 */ 371 public boolean isEnabled() { 372 try { 373 return sService.isEnabled(); 374 } catch (RemoteException e) { 375 attemptDeadServiceRecovery(e); 376 return false; 377 } 378 } 379 380 /** 381 * Enable NFC hardware. 382 * <p> 383 * NOTE: may block for ~second or more. Poor API. Avoid 384 * calling from the UI thread. 385 * 386 * @hide 387 */ 388 public boolean enable() { 389 try { 390 return sService.enable(); 391 } catch (RemoteException e) { 392 attemptDeadServiceRecovery(e); 393 return false; 394 } 395 } 396 397 /** 398 * Disable NFC hardware. 399 * No NFC features will work after this call, and the hardware 400 * will not perform or respond to any NFC communication. 401 * <p> 402 * NOTE: may block for ~second or more. Poor API. Avoid 403 * calling from the UI thread. 404 * 405 * @hide 406 */ 407 public boolean disable() { 408 try { 409 return sService.disable(); 410 } catch (RemoteException e) { 411 attemptDeadServiceRecovery(e); 412 return false; 413 } 414 } 415 416 /** 417 * Enables foreground dispatching to the given Activity. This will force all NFC Intents that 418 * match the given filters to be delivered to the activity bypassing the standard dispatch 419 * mechanism. If no IntentFilters are given all the PendingIntent will be invoked for every 420 * dispatch Intent. 421 * 422 * This method must be called from the main thread. 423 * 424 * @param activity the Activity to dispatch to 425 * @param intent the PendingIntent to start for the dispatch 426 * @param filters the IntentFilters to override dispatching for, or null to always dispatch 427 * @throws IllegalStateException 428 */ 429 public void enableForegroundDispatch(Activity activity, PendingIntent intent, 430 IntentFilter[] filters, String[][] techLists) { 431 if (activity == null || intent == null) { 432 throw new NullPointerException(); 433 } 434 if (!activity.isResumed()) { 435 throw new IllegalStateException("Foregorund dispatching can only be enabled " + 436 "when your activity is resumed"); 437 } 438 try { 439 TechListParcel parcel = null; 440 if (techLists != null && techLists.length > 0) { 441 parcel = new TechListParcel(techLists); 442 } 443 ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity, 444 mForegroundDispatchListener); 445 sService.enableForegroundDispatch(activity.getComponentName(), intent, filters, 446 parcel); 447 } catch (RemoteException e) { 448 attemptDeadServiceRecovery(e); 449 } 450 } 451 452 /** 453 * Disables foreground activity dispatching setup with 454 * {@link #enableForegroundDispatch}. 455 * 456 * <p>This must be called before the Activity returns from 457 * it's <code>onPause()</code> or this method will throw an IllegalStateException. 458 * 459 * <p>This method must be called from the main thread. 460 */ 461 public void disableForegroundDispatch(Activity activity) { 462 ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity, 463 mForegroundDispatchListener); 464 disableForegroundDispatchInternal(activity, false); 465 } 466 467 OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() { 468 @Override 469 public void onPaused(Activity activity) { 470 disableForegroundDispatchInternal(activity, true); 471 } 472 }; 473 474 void disableForegroundDispatchInternal(Activity activity, boolean force) { 475 try { 476 sService.disableForegroundDispatch(activity.getComponentName()); 477 if (!force && !activity.isResumed()) { 478 throw new IllegalStateException("You must disable forgeground dispatching " + 479 "while your activity is still resumed"); 480 } 481 } catch (RemoteException e) { 482 attemptDeadServiceRecovery(e); 483 } 484 } 485 486 /** 487 * Enable NDEF message push over P2P while this Activity is in the foreground. For this to 488 * function properly the other NFC device being scanned must support the "com.android.npp" 489 * NDEF push protocol. 490 * 491 * <p><em>NOTE</em> While foreground NDEF push is active standard tag dispatch is disabled. 492 * Only the foreground activity may receive tag discovered dispatches via 493 * {@link #enableForegroundDispatch}. 494 */ 495 public void enableForegroundNdefPush(Activity activity, NdefMessage msg) { 496 if (activity == null || msg == null) { 497 throw new NullPointerException(); 498 } 499 if (!activity.isResumed()) { 500 throw new IllegalStateException("Foregorund NDEF push can only be enabled " + 501 "when your activity is resumed"); 502 } 503 try { 504 ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity, 505 mForegroundNdefPushListener); 506 sService.enableForegroundNdefPush(activity.getComponentName(), msg); 507 } catch (RemoteException e) { 508 attemptDeadServiceRecovery(e); 509 } 510 } 511 512 /** 513 * Disables foreground NDEF push setup with 514 * {@link #enableForegroundNdefPush}. 515 * 516 * <p>This must be called before the Activity returns from 517 * it's <code>onPause()</code> or this method will throw an IllegalStateException. 518 * 519 * <p>This method must be called from the main thread. 520 */ 521 public void disableForegroundNdefPush(Activity activity) { 522 ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity, 523 mForegroundNdefPushListener); 524 disableForegroundNdefPushInternal(activity, false); 525 } 526 527 OnActivityPausedListener mForegroundNdefPushListener = new OnActivityPausedListener() { 528 @Override 529 public void onPaused(Activity activity) { 530 disableForegroundNdefPushInternal(activity, true); 531 } 532 }; 533 534 void disableForegroundNdefPushInternal(Activity activity, boolean force) { 535 try { 536 sService.disableForegroundNdefPush(activity.getComponentName()); 537 if (!force && !activity.isResumed()) { 538 throw new IllegalStateException("You must disable forgeground NDEF push " + 539 "while your activity is still resumed"); 540 } 541 } catch (RemoteException e) { 542 attemptDeadServiceRecovery(e); 543 } 544 } 545 546 /** 547 * Set the NDEF Message that this NFC adapter should appear as to Tag 548 * readers. 549 * <p> 550 * Any Tag reader can read the contents of the local tag when it is in 551 * proximity, without any further user confirmation. 552 * <p> 553 * The implementation of this method must either 554 * <ul> 555 * <li>act as a passive tag containing this NDEF message 556 * <li>provide the NDEF message on over LLCP to peer NFC adapters 557 * </ul> 558 * The NDEF message is preserved across reboot. 559 * <p>Requires {@link android.Manifest.permission#NFC} permission. 560 * 561 * @param message NDEF message to make public 562 * @hide 563 */ 564 public void setLocalNdefMessage(NdefMessage message) { 565 try { 566 sService.localSet(message); 567 } catch (RemoteException e) { 568 attemptDeadServiceRecovery(e); 569 } 570 } 571 572 /** 573 * Get the NDEF Message that this adapter appears as to Tag readers. 574 * <p>Requires {@link android.Manifest.permission#NFC} permission. 575 * 576 * @return NDEF Message that is publicly readable 577 * @hide 578 */ 579 public NdefMessage getLocalNdefMessage() { 580 try { 581 return sService.localGet(); 582 } catch (RemoteException e) { 583 attemptDeadServiceRecovery(e); 584 return null; 585 } 586 } 587 588 /** 589 * Create an Nfc Secure Element Connection 590 * @hide 591 */ 592 public NfcSecureElement createNfcSecureElementConnection() { 593 try { 594 return new NfcSecureElement(sService.getNfcSecureElementInterface()); 595 } catch (RemoteException e) { 596 Log.e(TAG, "createNfcSecureElementConnection failed", e); 597 return null; 598 } 599 } 600} 601