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