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