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 final NfcEvent mDefaultEvent; // cached NfcEvent (its currently always the same) 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; 61 public NfcApplicationState(Application app) { 62 this.app = app; 63 } 64 public void register() { 65 refCount++; 66 if (refCount == 1) { 67 this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this); 68 } 69 } 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 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 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 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 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 } 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 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 */ 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 */ 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 182 synchronized NfcActivityState findResumedActivityState() { 183 for (NfcActivityState state : mActivities) { 184 if (state.resumed) { 185 return state; 186 } 187 } 188 return null; 189 } 190 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 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 mDefaultEvent = new NfcEvent(mAdapter); 204 } 205 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 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 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 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 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 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 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 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 */ 339 void requestNfcServiceCallback() { 340 try { 341 NfcAdapter.sService.setAppCallback(this); 342 } catch (RemoteException e) { 343 mAdapter.attemptDeadServiceRecovery(e); 344 } 345 } 346 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 357 public BeamShareData createBeamShareData() { 358 NfcAdapter.CreateNdefMessageCallback ndefCallback; 359 NfcAdapter.CreateBeamUrisCallback urisCallback; 360 NdefMessage message; 361 Activity activity; 362 Uri[] uris; 363 int flags; 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 376 // Make callbacks without lock 377 if (ndefCallback != null) { 378 message = ndefCallback.createNdefMessage(mDefaultEvent); 379 } 380 if (urisCallback != null) { 381 uris = urisCallback.createBeamUris(mDefaultEvent); 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, UserHandle.myUserId()); 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 return new BeamShareData(message, uris, UserHandle.CURRENT, flags); 411 } 412 413 /** Callback from NFC service, usually on binder thread */ 414 @Override 415 public void onNdefPushComplete() { 416 NfcAdapter.OnNdefPushCompleteCallback callback; 417 synchronized (NfcActivityManager.this) { 418 NfcActivityState state = findResumedActivityState(); 419 if (state == null) return; 420 421 callback = state.onNdefPushCompleteCallback; 422 } 423 424 // Make callback without lock 425 if (callback != null) { 426 callback.onNdefPushComplete(mDefaultEvent); 427 } 428 } 429 430 @Override 431 public void onTagDiscovered(Tag tag) throws RemoteException { 432 NfcAdapter.ReaderCallback callback; 433 synchronized (NfcActivityManager.this) { 434 NfcActivityState state = findResumedActivityState(); 435 if (state == null) return; 436 437 callback = state.readerCallback; 438 } 439 440 // Make callback without lock 441 if (callback != null) { 442 callback.onTagDiscovered(tag); 443 } 444 445 } 446 /** Callback from Activity life-cycle, on main thread */ 447 @Override 448 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ } 449 450 /** Callback from Activity life-cycle, on main thread */ 451 @Override 452 public void onActivityStarted(Activity activity) { /* NO-OP */ } 453 454 /** Callback from Activity life-cycle, on main thread */ 455 @Override 456 public void onActivityResumed(Activity activity) { 457 int readerModeFlags = 0; 458 Bundle readerModeExtras = null; 459 Binder token; 460 synchronized (NfcActivityManager.this) { 461 NfcActivityState state = findActivityState(activity); 462 if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state); 463 if (state == null) return; 464 state.resumed = true; 465 token = state.token; 466 readerModeFlags = state.readerModeFlags; 467 readerModeExtras = state.readerModeExtras; 468 } 469 if (readerModeFlags != 0) { 470 setReaderMode(token, readerModeFlags, readerModeExtras); 471 } 472 requestNfcServiceCallback(); 473 } 474 475 /** Callback from Activity life-cycle, on main thread */ 476 @Override 477 public void onActivityPaused(Activity activity) { 478 boolean readerModeFlagsSet; 479 Binder token; 480 synchronized (NfcActivityManager.this) { 481 NfcActivityState state = findActivityState(activity); 482 if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state); 483 if (state == null) return; 484 state.resumed = false; 485 token = state.token; 486 readerModeFlagsSet = state.readerModeFlags != 0; 487 } 488 if (readerModeFlagsSet) { 489 // Restore default p2p modes 490 setReaderMode(token, 0, null); 491 } 492 } 493 494 /** Callback from Activity life-cycle, on main thread */ 495 @Override 496 public void onActivityStopped(Activity activity) { /* NO-OP */ } 497 498 /** Callback from Activity life-cycle, on main thread */ 499 @Override 500 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ } 501 502 /** Callback from Activity life-cycle, on main thread */ 503 @Override 504 public void onActivityDestroyed(Activity activity) { 505 synchronized (NfcActivityManager.this) { 506 NfcActivityState state = findActivityState(activity); 507 if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state); 508 if (state != null) { 509 // release all associated references 510 destroyActivityState(activity); 511 } 512 } 513 } 514 515 } 516