NfcActivityManager.java revision d8bcfbadf7037dd2574312a3bfa04dd70d967166
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 17package android.nfc; 18 19import android.app.Activity; 20import android.app.Application; 21import android.content.Intent; 22import android.net.Uri; 23import android.nfc.NfcAdapter.ReaderCallback; 24import android.os.Binder; 25import android.os.Bundle; 26import android.os.RemoteException; 27import android.util.Log; 28 29import java.util.ArrayList; 30import java.util.LinkedList; 31import java.util.List; 32 33/** 34 * Manages NFC API's that are coupled to the life-cycle of an Activity. 35 * 36 * <p>Uses {@link Application#registerActivityLifecycleCallbacks} to hook 37 * into activity life-cycle events such as onPause() and onResume(). 38 * 39 * @hide 40 */ 41public final class NfcActivityManager extends IAppCallback.Stub 42 implements Application.ActivityLifecycleCallbacks { 43 static final String TAG = NfcAdapter.TAG; 44 static final Boolean DBG = false; 45 46 final NfcAdapter mAdapter; 47 final NfcEvent mDefaultEvent; // cached NfcEvent (its currently always the same) 48 49 // All objects in the lists are protected by this 50 final List<NfcApplicationState> mApps; // Application(s) that have NFC state. Usually one 51 final List<NfcActivityState> mActivities; // Activities that have NFC state 52 53 /** 54 * NFC State associated with an {@link Application}. 55 */ 56 class NfcApplicationState { 57 int refCount = 0; 58 final Application app; 59 public NfcApplicationState(Application app) { 60 this.app = app; 61 } 62 public void register() { 63 refCount++; 64 if (refCount == 1) { 65 this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this); 66 } 67 } 68 public void unregister() { 69 refCount--; 70 if (refCount == 0) { 71 this.app.unregisterActivityLifecycleCallbacks(NfcActivityManager.this); 72 } else if (refCount < 0) { 73 Log.e(TAG, "-ve refcount for " + app); 74 } 75 } 76 } 77 78 NfcApplicationState findAppState(Application app) { 79 for (NfcApplicationState appState : mApps) { 80 if (appState.app == app) { 81 return appState; 82 } 83 } 84 return null; 85 } 86 87 void registerApplication(Application app) { 88 NfcApplicationState appState = findAppState(app); 89 if (appState == null) { 90 appState = new NfcApplicationState(app); 91 mApps.add(appState); 92 } 93 appState.register(); 94 } 95 96 void unregisterApplication(Application app) { 97 NfcApplicationState appState = findAppState(app); 98 if (appState == null) { 99 Log.e(TAG, "app was not registered " + app); 100 return; 101 } 102 appState.unregister(); 103 } 104 105 /** 106 * NFC state associated with an {@link Activity} 107 */ 108 class NfcActivityState { 109 boolean resumed = false; 110 Activity activity; 111 NdefMessage ndefMessage = null; // static NDEF message 112 NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null; 113 NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null; 114 NfcAdapter.CreateBeamUrisCallback uriCallback = null; 115 Uri[] uris = null; 116 int flags = 0; 117 int readerModeFlags = 0; 118 NfcAdapter.ReaderCallback readerCallback = null; 119 Bundle readerModeExtras = null; 120 Binder token; 121 122 public NfcActivityState(Activity activity) { 123 if (activity.getWindow().isDestroyed()) { 124 throw new IllegalStateException("activity is already destroyed"); 125 } 126 // Check if activity is resumed right now, as we will not 127 // immediately get a callback for that. 128 resumed = activity.isResumed(); 129 130 this.activity = activity; 131 this.token = new Binder(); 132 registerApplication(activity.getApplication()); 133 } 134 public void destroy() { 135 unregisterApplication(activity.getApplication()); 136 resumed = false; 137 activity = null; 138 ndefMessage = null; 139 ndefMessageCallback = null; 140 onNdefPushCompleteCallback = null; 141 uriCallback = null; 142 uris = null; 143 readerModeFlags = 0; 144 token = null; 145 } 146 @Override 147 public String toString() { 148 StringBuilder s = new StringBuilder("[").append(" "); 149 s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" "); 150 s.append(uriCallback).append(" "); 151 if (uris != null) { 152 for (Uri uri : uris) { 153 s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]"); 154 } 155 } 156 return s.toString(); 157 } 158 } 159 160 /** find activity state from mActivities */ 161 synchronized NfcActivityState findActivityState(Activity activity) { 162 for (NfcActivityState state : mActivities) { 163 if (state.activity == activity) { 164 return state; 165 } 166 } 167 return null; 168 } 169 170 /** find or create activity state from mActivities */ 171 synchronized NfcActivityState getActivityState(Activity activity) { 172 NfcActivityState state = findActivityState(activity); 173 if (state == null) { 174 state = new NfcActivityState(activity); 175 mActivities.add(state); 176 } 177 return state; 178 } 179 180 synchronized NfcActivityState findResumedActivityState() { 181 for (NfcActivityState state : mActivities) { 182 if (state.resumed) { 183 return state; 184 } 185 } 186 return null; 187 } 188 189 synchronized void destroyActivityState(Activity activity) { 190 NfcActivityState activityState = findActivityState(activity); 191 if (activityState != null) { 192 activityState.destroy(); 193 mActivities.remove(activityState); 194 } 195 } 196 197 public NfcActivityManager(NfcAdapter adapter) { 198 mAdapter = adapter; 199 mActivities = new LinkedList<NfcActivityState>(); 200 mApps = new ArrayList<NfcApplicationState>(1); // Android VM usually has 1 app 201 mDefaultEvent = new NfcEvent(mAdapter); 202 } 203 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 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 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 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 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 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 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 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 */ 337 void requestNfcServiceCallback() { 338 try { 339 NfcAdapter.sService.setAppCallback(this); 340 } catch (RemoteException e) { 341 mAdapter.attemptDeadServiceRecovery(e); 342 } 343 } 344 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 355 public BeamShareData createBeamShareData() { 356 NfcAdapter.CreateNdefMessageCallback ndefCallback; 357 NfcAdapter.CreateBeamUrisCallback urisCallback; 358 NdefMessage message; 359 Activity activity; 360 Uri[] uris; 361 int flags; 362 synchronized (NfcActivityManager.this) { 363 NfcActivityState state = findResumedActivityState(); 364 if (state == null) return null; 365 366 ndefCallback = state.ndefMessageCallback; 367 urisCallback = state.uriCallback; 368 message = state.ndefMessage; 369 uris = state.uris; 370 flags = state.flags; 371 activity = state.activity; 372 } 373 374 // Make callbacks without lock 375 if (ndefCallback != null) { 376 message = ndefCallback.createNdefMessage(mDefaultEvent); 377 } 378 if (urisCallback != null) { 379 uris = urisCallback.createBeamUris(mDefaultEvent); 380 if (uris != null) { 381 for (Uri uri : uris) { 382 if (uri == null) { 383 Log.e(TAG, "Uri not allowed to be null."); 384 return null; 385 } 386 String scheme = uri.getScheme(); 387 if (scheme == null || (!scheme.equalsIgnoreCase("file") && 388 !scheme.equalsIgnoreCase("content"))) { 389 Log.e(TAG, "Uri needs to have " + 390 "either scheme file or scheme content"); 391 return null; 392 } 393 } 394 } 395 } 396 if (uris != null && uris.length > 0) { 397 for (Uri uri : uris) { 398 // Grant the NFC process permission to read these URIs 399 activity.grantUriPermission("com.android.nfc", uri, 400 Intent.FLAG_GRANT_READ_URI_PERMISSION); 401 } 402 } 403 return new BeamShareData(message, uris, flags); 404 } 405 406 /** Callback from NFC service, usually on binder thread */ 407 @Override 408 public void onNdefPushComplete() { 409 NfcAdapter.OnNdefPushCompleteCallback callback; 410 synchronized (NfcActivityManager.this) { 411 NfcActivityState state = findResumedActivityState(); 412 if (state == null) return; 413 414 callback = state.onNdefPushCompleteCallback; 415 } 416 417 // Make callback without lock 418 if (callback != null) { 419 callback.onNdefPushComplete(mDefaultEvent); 420 } 421 } 422 423 @Override 424 public void onTagDiscovered(Tag tag) throws RemoteException { 425 NfcAdapter.ReaderCallback callback; 426 synchronized (NfcActivityManager.this) { 427 NfcActivityState state = findResumedActivityState(); 428 if (state == null) return; 429 430 callback = state.readerCallback; 431 } 432 433 // Make callback without lock 434 if (callback != null) { 435 callback.onTagDiscovered(tag); 436 } 437 438 } 439 /** Callback from Activity life-cycle, on main thread */ 440 @Override 441 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ } 442 443 /** Callback from Activity life-cycle, on main thread */ 444 @Override 445 public void onActivityStarted(Activity activity) { /* NO-OP */ } 446 447 /** Callback from Activity life-cycle, on main thread */ 448 @Override 449 public void onActivityResumed(Activity activity) { 450 int readerModeFlags = 0; 451 Bundle readerModeExtras = null; 452 Binder token; 453 synchronized (NfcActivityManager.this) { 454 NfcActivityState state = findActivityState(activity); 455 if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state); 456 if (state == null) return; 457 state.resumed = true; 458 token = state.token; 459 readerModeFlags = state.readerModeFlags; 460 readerModeExtras = state.readerModeExtras; 461 } 462 if (readerModeFlags != 0) { 463 setReaderMode(token, readerModeFlags, readerModeExtras); 464 } 465 requestNfcServiceCallback(); 466 } 467 468 /** Callback from Activity life-cycle, on main thread */ 469 @Override 470 public void onActivityPaused(Activity activity) { 471 boolean readerModeFlagsSet; 472 Binder token; 473 synchronized (NfcActivityManager.this) { 474 NfcActivityState state = findActivityState(activity); 475 if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state); 476 if (state == null) return; 477 state.resumed = false; 478 token = state.token; 479 readerModeFlagsSet = state.readerModeFlags != 0; 480 } 481 if (readerModeFlagsSet) { 482 // Restore default p2p modes 483 setReaderMode(token, 0, null); 484 } 485 } 486 487 /** Callback from Activity life-cycle, on main thread */ 488 @Override 489 public void onActivityStopped(Activity activity) { /* NO-OP */ } 490 491 /** Callback from Activity life-cycle, on main thread */ 492 @Override 493 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ } 494 495 /** Callback from Activity life-cycle, on main thread */ 496 @Override 497 public void onActivityDestroyed(Activity activity) { 498 synchronized (NfcActivityManager.this) { 499 NfcActivityState state = findActivityState(activity); 500 if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state); 501 if (state != null) { 502 // release all associated references 503 destroyActivityState(activity); 504 } 505 } 506 } 507 508} 509