NfcActivityManager.java revision 56f299b87a891db38691cc0260f761c3ce8ef9ac
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.ContentProvider; 22import android.content.Intent; 23import android.net.Uri; 24import android.nfc.NfcAdapter.ReaderCallback; 25import android.os.Binder; 26import android.os.Bundle; 27import android.os.RemoteException; 28import android.os.UserHandle; 29import android.util.Log; 30 31import java.util.ArrayList; 32import java.util.LinkedList; 33import 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 */ 43public 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(); 258 } 259 } 260 261 262 public void setNdefPushContentUriCallback(Activity activity, 263 NfcAdapter.CreateBeamUrisCallback callback) { 264 boolean isResumed; 265 synchronized (NfcActivityManager.this) { 266 NfcActivityState state = getActivityState(activity); 267 state.uriCallback = callback; 268 isResumed = state.resumed; 269 } 270 if (isResumed) { 271 requestNfcServiceCallback(); 272 } 273 } 274 275 public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) { 276 boolean isResumed; 277 synchronized (NfcActivityManager.this) { 278 NfcActivityState state = getActivityState(activity); 279 state.ndefMessage = message; 280 state.flags = flags; 281 isResumed = state.resumed; 282 } 283 if (isResumed) { 284 requestNfcServiceCallback(); 285 } 286 } 287 288 public void setNdefPushMessageCallback(Activity activity, 289 NfcAdapter.CreateNdefMessageCallback callback, int flags) { 290 boolean isResumed; 291 synchronized (NfcActivityManager.this) { 292 NfcActivityState state = getActivityState(activity); 293 state.ndefMessageCallback = callback; 294 state.flags = flags; 295 isResumed = state.resumed; 296 } 297 if (isResumed) { 298 requestNfcServiceCallback(); 299 } 300 } 301 302 public void setOnNdefPushCompleteCallback(Activity activity, 303 NfcAdapter.OnNdefPushCompleteCallback callback) { 304 boolean isResumed; 305 synchronized (NfcActivityManager.this) { 306 NfcActivityState state = getActivityState(activity); 307 state.onNdefPushCompleteCallback = callback; 308 isResumed = state.resumed; 309 } 310 if (isResumed) { 311 requestNfcServiceCallback(); 312 } 313 } 314 315 /** 316 * Request or unrequest NFC service callbacks. 317 * Makes IPC call - do not hold lock. 318 */ 319 void requestNfcServiceCallback() { 320 try { 321 NfcAdapter.sService.setAppCallback(this); 322 } catch (RemoteException e) { 323 mAdapter.attemptDeadServiceRecovery(e); 324 } 325 } 326 327 /** Callback from NFC service, usually on binder thread */ 328 @Override 329 public BeamShareData createBeamShareData() { 330 NfcAdapter.CreateNdefMessageCallback ndefCallback; 331 NfcAdapter.CreateBeamUrisCallback urisCallback; 332 NdefMessage message; 333 Activity activity; 334 Uri[] uris; 335 int flags; 336 synchronized (NfcActivityManager.this) { 337 NfcActivityState state = findResumedActivityState(); 338 if (state == null) return null; 339 340 ndefCallback = state.ndefMessageCallback; 341 urisCallback = state.uriCallback; 342 message = state.ndefMessage; 343 uris = state.uris; 344 flags = state.flags; 345 activity = state.activity; 346 } 347 348 // Make callbacks without lock 349 if (ndefCallback != null) { 350 message = ndefCallback.createNdefMessage(mDefaultEvent); 351 } 352 if (urisCallback != null) { 353 uris = urisCallback.createBeamUris(mDefaultEvent); 354 if (uris != null) { 355 ArrayList<Uri> validUris = new ArrayList<Uri>(); 356 for (Uri uri : uris) { 357 if (uri == null) { 358 Log.e(TAG, "Uri not allowed to be null."); 359 continue; 360 } 361 String scheme = uri.getScheme(); 362 if (scheme == null || (!scheme.equalsIgnoreCase("file") && 363 !scheme.equalsIgnoreCase("content"))) { 364 Log.e(TAG, "Uri needs to have " + 365 "either scheme file or scheme content"); 366 continue; 367 } 368 uri = ContentProvider.maybeAddUserId(uri, UserHandle.myUserId()); 369 validUris.add(uri); 370 } 371 372 uris = validUris.toArray(new Uri[validUris.size()]); 373 } 374 } 375 if (uris != null && uris.length > 0) { 376 for (Uri uri : uris) { 377 // Grant the NFC process permission to read these URIs 378 activity.grantUriPermission("com.android.nfc", uri, 379 Intent.FLAG_GRANT_READ_URI_PERMISSION); 380 } 381 } 382 return new BeamShareData(message, uris, UserHandle.CURRENT, flags); 383 } 384 385 /** Callback from NFC service, usually on binder thread */ 386 @Override 387 public void onNdefPushComplete() { 388 NfcAdapter.OnNdefPushCompleteCallback callback; 389 synchronized (NfcActivityManager.this) { 390 NfcActivityState state = findResumedActivityState(); 391 if (state == null) return; 392 393 callback = state.onNdefPushCompleteCallback; 394 } 395 396 // Make callback without lock 397 if (callback != null) { 398 callback.onNdefPushComplete(mDefaultEvent); 399 } 400 } 401 402 @Override 403 public void onTagDiscovered(Tag tag) throws RemoteException { 404 NfcAdapter.ReaderCallback callback; 405 synchronized (NfcActivityManager.this) { 406 NfcActivityState state = findResumedActivityState(); 407 if (state == null) return; 408 409 callback = state.readerCallback; 410 } 411 412 // Make callback without lock 413 if (callback != null) { 414 callback.onTagDiscovered(tag); 415 } 416 417 } 418 /** Callback from Activity life-cycle, on main thread */ 419 @Override 420 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ } 421 422 /** Callback from Activity life-cycle, on main thread */ 423 @Override 424 public void onActivityStarted(Activity activity) { /* NO-OP */ } 425 426 /** Callback from Activity life-cycle, on main thread */ 427 @Override 428 public void onActivityResumed(Activity activity) { 429 int readerModeFlags = 0; 430 Bundle readerModeExtras = null; 431 Binder token; 432 synchronized (NfcActivityManager.this) { 433 NfcActivityState state = findActivityState(activity); 434 if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state); 435 if (state == null) return; 436 state.resumed = true; 437 token = state.token; 438 readerModeFlags = state.readerModeFlags; 439 readerModeExtras = state.readerModeExtras; 440 } 441 if (readerModeFlags != 0) { 442 setReaderMode(token, readerModeFlags, readerModeExtras); 443 } 444 requestNfcServiceCallback(); 445 } 446 447 /** Callback from Activity life-cycle, on main thread */ 448 @Override 449 public void onActivityPaused(Activity activity) { 450 boolean readerModeFlagsSet; 451 Binder token; 452 synchronized (NfcActivityManager.this) { 453 NfcActivityState state = findActivityState(activity); 454 if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state); 455 if (state == null) return; 456 state.resumed = false; 457 token = state.token; 458 readerModeFlagsSet = state.readerModeFlags != 0; 459 } 460 if (readerModeFlagsSet) { 461 // Restore default p2p modes 462 setReaderMode(token, 0, null); 463 } 464 } 465 466 /** Callback from Activity life-cycle, on main thread */ 467 @Override 468 public void onActivityStopped(Activity activity) { /* NO-OP */ } 469 470 /** Callback from Activity life-cycle, on main thread */ 471 @Override 472 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ } 473 474 /** Callback from Activity life-cycle, on main thread */ 475 @Override 476 public void onActivityDestroyed(Activity activity) { 477 synchronized (NfcActivityManager.this) { 478 NfcActivityState state = findActivityState(activity); 479 if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state); 480 if (state != null) { 481 // release all associated references 482 destroyActivityState(activity); 483 } 484 } 485 } 486 487} 488