1 /* 2 * Copyright (C) 2011 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; 18 19 import android.app.Activity; 20 import android.app.Application; 21 import android.content.ContentProvider; 22 import android.content.Intent; 23 import android.net.Uri; 24 import android.nfc.NfcAdapter.ReaderCallback; 25 import android.os.Binder; 26 import android.os.Bundle; 27 import android.os.RemoteException; 28 import android.os.UserHandle; 29 import android.util.Log; 30 31 import java.util.ArrayList; 32 import java.util.LinkedList; 33 import java.util.List; 34 35 /** 36 * Manages NFC API's that are coupled to the life-cycle of an Activity. 37 * 38 * <p>Uses {@link Application#registerActivityLifecycleCallbacks} to hook 39 * into activity life-cycle events such as onPause() and onResume(). 40 * 41 * @hide 42 */ 43 public final class NfcActivityManager extends IAppCallback.Stub 44 implements Application.ActivityLifecycleCallbacks { 45 static final String TAG = NfcAdapter.TAG; 46 static final Boolean DBG = false; 47 48 final NfcAdapter mAdapter; 49 50 // All objects in the lists are protected by this 51 final List<NfcApplicationState> mApps; // Application(s) that have NFC state. Usually one 52 final List<NfcActivityState> mActivities; // Activities that have NFC state 53 54 /** 55 * NFC State associated with an {@link Application}. 56 */ 57 class NfcApplicationState { 58 int refCount = 0; 59 final Application app; NfcApplicationState(Application app)60 public NfcApplicationState(Application app) { 61 this.app = app; 62 } register()63 public void register() { 64 refCount++; 65 if (refCount == 1) { 66 this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this); 67 } 68 } unregister()69 public void unregister() { 70 refCount--; 71 if (refCount == 0) { 72 this.app.unregisterActivityLifecycleCallbacks(NfcActivityManager.this); 73 } else if (refCount < 0) { 74 Log.e(TAG, "-ve refcount for " + app); 75 } 76 } 77 } 78 findAppState(Application app)79 NfcApplicationState findAppState(Application app) { 80 for (NfcApplicationState appState : mApps) { 81 if (appState.app == app) { 82 return appState; 83 } 84 } 85 return null; 86 } 87 registerApplication(Application app)88 void registerApplication(Application app) { 89 NfcApplicationState appState = findAppState(app); 90 if (appState == null) { 91 appState = new NfcApplicationState(app); 92 mApps.add(appState); 93 } 94 appState.register(); 95 } 96 unregisterApplication(Application app)97 void unregisterApplication(Application app) { 98 NfcApplicationState appState = findAppState(app); 99 if (appState == null) { 100 Log.e(TAG, "app was not registered " + app); 101 return; 102 } 103 appState.unregister(); 104 } 105 106 /** 107 * NFC state associated with an {@link Activity} 108 */ 109 class NfcActivityState { 110 boolean resumed = false; 111 Activity activity; 112 NdefMessage ndefMessage = null; // static NDEF message 113 NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null; 114 NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null; 115 NfcAdapter.CreateBeamUrisCallback uriCallback = null; 116 Uri[] uris = null; 117 int flags = 0; 118 int readerModeFlags = 0; 119 NfcAdapter.ReaderCallback readerCallback = null; 120 Bundle readerModeExtras = null; 121 Binder token; 122 NfcActivityState(Activity activity)123 public NfcActivityState(Activity activity) { 124 if (activity.getWindow().isDestroyed()) { 125 throw new IllegalStateException("activity is already destroyed"); 126 } 127 // Check if activity is resumed right now, as we will not 128 // immediately get a callback for that. 129 resumed = activity.isResumed(); 130 131 this.activity = activity; 132 this.token = new Binder(); 133 registerApplication(activity.getApplication()); 134 } destroy()135 public void destroy() { 136 unregisterApplication(activity.getApplication()); 137 resumed = false; 138 activity = null; 139 ndefMessage = null; 140 ndefMessageCallback = null; 141 onNdefPushCompleteCallback = null; 142 uriCallback = null; 143 uris = null; 144 readerModeFlags = 0; 145 token = null; 146 } 147 @Override toString()148 public String toString() { 149 StringBuilder s = new StringBuilder("[").append(" "); 150 s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" "); 151 s.append(uriCallback).append(" "); 152 if (uris != null) { 153 for (Uri uri : uris) { 154 s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]"); 155 } 156 } 157 return s.toString(); 158 } 159 } 160 161 /** find activity state from mActivities */ findActivityState(Activity activity)162 synchronized NfcActivityState findActivityState(Activity activity) { 163 for (NfcActivityState state : mActivities) { 164 if (state.activity == activity) { 165 return state; 166 } 167 } 168 return null; 169 } 170 171 /** find or create activity state from mActivities */ getActivityState(Activity activity)172 synchronized NfcActivityState getActivityState(Activity activity) { 173 NfcActivityState state = findActivityState(activity); 174 if (state == null) { 175 state = new NfcActivityState(activity); 176 mActivities.add(state); 177 } 178 return state; 179 } 180 findResumedActivityState()181 synchronized NfcActivityState findResumedActivityState() { 182 for (NfcActivityState state : mActivities) { 183 if (state.resumed) { 184 return state; 185 } 186 } 187 return null; 188 } 189 destroyActivityState(Activity activity)190 synchronized void destroyActivityState(Activity activity) { 191 NfcActivityState activityState = findActivityState(activity); 192 if (activityState != null) { 193 activityState.destroy(); 194 mActivities.remove(activityState); 195 } 196 } 197 NfcActivityManager(NfcAdapter adapter)198 public NfcActivityManager(NfcAdapter adapter) { 199 mAdapter = adapter; 200 mActivities = new LinkedList<NfcActivityState>(); 201 mApps = new ArrayList<NfcApplicationState>(1); // Android VM usually has 1 app 202 } 203 enableReaderMode(Activity activity, ReaderCallback callback, int flags, Bundle extras)204 public void enableReaderMode(Activity activity, ReaderCallback callback, int flags, 205 Bundle extras) { 206 boolean isResumed; 207 Binder token; 208 synchronized (NfcActivityManager.this) { 209 NfcActivityState state = getActivityState(activity); 210 state.readerCallback = callback; 211 state.readerModeFlags = flags; 212 state.readerModeExtras = extras; 213 token = state.token; 214 isResumed = state.resumed; 215 } 216 if (isResumed) { 217 setReaderMode(token, flags, extras); 218 } 219 } 220 disableReaderMode(Activity activity)221 public void disableReaderMode(Activity activity) { 222 boolean isResumed; 223 Binder token; 224 synchronized (NfcActivityManager.this) { 225 NfcActivityState state = getActivityState(activity); 226 state.readerCallback = null; 227 state.readerModeFlags = 0; 228 state.readerModeExtras = null; 229 token = state.token; 230 isResumed = state.resumed; 231 } 232 if (isResumed) { 233 setReaderMode(token, 0, null); 234 } 235 236 } 237 setReaderMode(Binder token, int flags, Bundle extras)238 public void setReaderMode(Binder token, int flags, Bundle extras) { 239 if (DBG) Log.d(TAG, "Setting reader mode"); 240 try { 241 NfcAdapter.sService.setReaderMode(token, this, flags, extras); 242 } catch (RemoteException e) { 243 mAdapter.attemptDeadServiceRecovery(e); 244 } 245 } 246 setNdefPushContentUri(Activity activity, Uri[] uris)247 public void setNdefPushContentUri(Activity activity, Uri[] uris) { 248 boolean isResumed; 249 synchronized (NfcActivityManager.this) { 250 NfcActivityState state = getActivityState(activity); 251 state.uris = uris; 252 isResumed = state.resumed; 253 } 254 if (isResumed) { 255 // requestNfcServiceCallback() verifies permission also 256 requestNfcServiceCallback(); 257 } else { 258 // Crash API calls early in case NFC permission is missing 259 verifyNfcPermission(); 260 } 261 } 262 263 setNdefPushContentUriCallback(Activity activity, NfcAdapter.CreateBeamUrisCallback callback)264 public void setNdefPushContentUriCallback(Activity activity, 265 NfcAdapter.CreateBeamUrisCallback callback) { 266 boolean isResumed; 267 synchronized (NfcActivityManager.this) { 268 NfcActivityState state = getActivityState(activity); 269 state.uriCallback = callback; 270 isResumed = state.resumed; 271 } 272 if (isResumed) { 273 // requestNfcServiceCallback() verifies permission also 274 requestNfcServiceCallback(); 275 } else { 276 // Crash API calls early in case NFC permission is missing 277 verifyNfcPermission(); 278 } 279 } 280 setNdefPushMessage(Activity activity, NdefMessage message, int flags)281 public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) { 282 boolean isResumed; 283 synchronized (NfcActivityManager.this) { 284 NfcActivityState state = getActivityState(activity); 285 state.ndefMessage = message; 286 state.flags = flags; 287 isResumed = state.resumed; 288 } 289 if (isResumed) { 290 // requestNfcServiceCallback() verifies permission also 291 requestNfcServiceCallback(); 292 } else { 293 // Crash API calls early in case NFC permission is missing 294 verifyNfcPermission(); 295 } 296 } 297 setNdefPushMessageCallback(Activity activity, NfcAdapter.CreateNdefMessageCallback callback, int flags)298 public void setNdefPushMessageCallback(Activity activity, 299 NfcAdapter.CreateNdefMessageCallback callback, int flags) { 300 boolean isResumed; 301 synchronized (NfcActivityManager.this) { 302 NfcActivityState state = getActivityState(activity); 303 state.ndefMessageCallback = callback; 304 state.flags = flags; 305 isResumed = state.resumed; 306 } 307 if (isResumed) { 308 // requestNfcServiceCallback() verifies permission also 309 requestNfcServiceCallback(); 310 } else { 311 // Crash API calls early in case NFC permission is missing 312 verifyNfcPermission(); 313 } 314 } 315 setOnNdefPushCompleteCallback(Activity activity, NfcAdapter.OnNdefPushCompleteCallback callback)316 public void setOnNdefPushCompleteCallback(Activity activity, 317 NfcAdapter.OnNdefPushCompleteCallback callback) { 318 boolean isResumed; 319 synchronized (NfcActivityManager.this) { 320 NfcActivityState state = getActivityState(activity); 321 state.onNdefPushCompleteCallback = callback; 322 isResumed = state.resumed; 323 } 324 if (isResumed) { 325 // requestNfcServiceCallback() verifies permission also 326 requestNfcServiceCallback(); 327 } else { 328 // Crash API calls early in case NFC permission is missing 329 verifyNfcPermission(); 330 } 331 } 332 333 /** 334 * Request or unrequest NFC service callbacks. 335 * Makes IPC call - do not hold lock. 336 */ requestNfcServiceCallback()337 void requestNfcServiceCallback() { 338 try { 339 NfcAdapter.sService.setAppCallback(this); 340 } catch (RemoteException e) { 341 mAdapter.attemptDeadServiceRecovery(e); 342 } 343 } 344 verifyNfcPermission()345 void verifyNfcPermission() { 346 try { 347 NfcAdapter.sService.verifyNfcPermission(); 348 } catch (RemoteException e) { 349 mAdapter.attemptDeadServiceRecovery(e); 350 } 351 } 352 353 /** Callback from NFC service, usually on binder thread */ 354 @Override createBeamShareData(byte peerLlcpVersion)355 public BeamShareData createBeamShareData(byte peerLlcpVersion) { 356 NfcAdapter.CreateNdefMessageCallback ndefCallback; 357 NfcAdapter.CreateBeamUrisCallback urisCallback; 358 NdefMessage message; 359 Activity activity; 360 Uri[] uris; 361 int flags; 362 NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion); 363 synchronized (NfcActivityManager.this) { 364 NfcActivityState state = findResumedActivityState(); 365 if (state == null) return null; 366 367 ndefCallback = state.ndefMessageCallback; 368 urisCallback = state.uriCallback; 369 message = state.ndefMessage; 370 uris = state.uris; 371 flags = state.flags; 372 activity = state.activity; 373 } 374 final long ident = Binder.clearCallingIdentity(); 375 try { 376 // Make callbacks without lock 377 if (ndefCallback != null) { 378 message = ndefCallback.createNdefMessage(event); 379 } 380 if (urisCallback != null) { 381 uris = urisCallback.createBeamUris(event); 382 if (uris != null) { 383 ArrayList<Uri> validUris = new ArrayList<Uri>(); 384 for (Uri uri : uris) { 385 if (uri == null) { 386 Log.e(TAG, "Uri not allowed to be null."); 387 continue; 388 } 389 String scheme = uri.getScheme(); 390 if (scheme == null || (!scheme.equalsIgnoreCase("file") && 391 !scheme.equalsIgnoreCase("content"))) { 392 Log.e(TAG, "Uri needs to have " + 393 "either scheme file or scheme content"); 394 continue; 395 } 396 uri = ContentProvider.maybeAddUserId(uri, activity.getUserId()); 397 validUris.add(uri); 398 } 399 400 uris = validUris.toArray(new Uri[validUris.size()]); 401 } 402 } 403 if (uris != null && uris.length > 0) { 404 for (Uri uri : uris) { 405 // Grant the NFC process permission to read these URIs 406 activity.grantUriPermission("com.android.nfc", uri, 407 Intent.FLAG_GRANT_READ_URI_PERMISSION); 408 } 409 } 410 } finally { 411 Binder.restoreCallingIdentity(ident); 412 } 413 return new BeamShareData(message, uris, activity.getUser(), flags); 414 } 415 416 /** Callback from NFC service, usually on binder thread */ 417 @Override onNdefPushComplete(byte peerLlcpVersion)418 public void onNdefPushComplete(byte peerLlcpVersion) { 419 NfcAdapter.OnNdefPushCompleteCallback callback; 420 synchronized (NfcActivityManager.this) { 421 NfcActivityState state = findResumedActivityState(); 422 if (state == null) return; 423 424 callback = state.onNdefPushCompleteCallback; 425 } 426 NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion); 427 // Make callback without lock 428 if (callback != null) { 429 callback.onNdefPushComplete(event); 430 } 431 } 432 433 @Override onTagDiscovered(Tag tag)434 public void onTagDiscovered(Tag tag) throws RemoteException { 435 NfcAdapter.ReaderCallback callback; 436 synchronized (NfcActivityManager.this) { 437 NfcActivityState state = findResumedActivityState(); 438 if (state == null) return; 439 440 callback = state.readerCallback; 441 } 442 443 // Make callback without lock 444 if (callback != null) { 445 callback.onTagDiscovered(tag); 446 } 447 448 } 449 /** Callback from Activity life-cycle, on main thread */ 450 @Override onActivityCreated(Activity activity, Bundle savedInstanceState)451 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ } 452 453 /** Callback from Activity life-cycle, on main thread */ 454 @Override onActivityStarted(Activity activity)455 public void onActivityStarted(Activity activity) { /* NO-OP */ } 456 457 /** Callback from Activity life-cycle, on main thread */ 458 @Override onActivityResumed(Activity activity)459 public void onActivityResumed(Activity activity) { 460 int readerModeFlags = 0; 461 Bundle readerModeExtras = null; 462 Binder token; 463 synchronized (NfcActivityManager.this) { 464 NfcActivityState state = findActivityState(activity); 465 if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state); 466 if (state == null) return; 467 state.resumed = true; 468 token = state.token; 469 readerModeFlags = state.readerModeFlags; 470 readerModeExtras = state.readerModeExtras; 471 } 472 if (readerModeFlags != 0) { 473 setReaderMode(token, readerModeFlags, readerModeExtras); 474 } 475 requestNfcServiceCallback(); 476 } 477 478 /** Callback from Activity life-cycle, on main thread */ 479 @Override onActivityPaused(Activity activity)480 public void onActivityPaused(Activity activity) { 481 boolean readerModeFlagsSet; 482 Binder token; 483 synchronized (NfcActivityManager.this) { 484 NfcActivityState state = findActivityState(activity); 485 if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state); 486 if (state == null) return; 487 state.resumed = false; 488 token = state.token; 489 readerModeFlagsSet = state.readerModeFlags != 0; 490 } 491 if (readerModeFlagsSet) { 492 // Restore default p2p modes 493 setReaderMode(token, 0, null); 494 } 495 } 496 497 /** Callback from Activity life-cycle, on main thread */ 498 @Override onActivityStopped(Activity activity)499 public void onActivityStopped(Activity activity) { /* NO-OP */ } 500 501 /** Callback from Activity life-cycle, on main thread */ 502 @Override onActivitySaveInstanceState(Activity activity, Bundle outState)503 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ } 504 505 /** Callback from Activity life-cycle, on main thread */ 506 @Override onActivityDestroyed(Activity activity)507 public void onActivityDestroyed(Activity activity) { 508 synchronized (NfcActivityManager.this) { 509 NfcActivityState state = findActivityState(activity); 510 if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state); 511 if (state != null) { 512 // release all associated references 513 destroyActivityState(activity); 514 } 515 } 516 } 517 518 } 519