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