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