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.compat.annotation.UnsupportedAppUsage; 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 @UnsupportedAppUsage 46 final NfcAdapter mAdapter; 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; NfcApplicationState(Application app)58 public NfcApplicationState(Application app) { 59 this.app = app; 60 } register()61 public void register() { 62 refCount++; 63 if (refCount == 1) { 64 this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this); 65 } 66 } unregister()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 findAppState(Application app)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 registerApplication(Application app)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 unregisterApplication(Application app)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 NfcAdapter.ReaderCallback readerCallback = null; 111 int readerModeFlags = 0; 112 Bundle readerModeExtras = null; 113 Binder token; 114 115 int mPollTech = NfcAdapter.FLAG_USE_ALL_TECH; 116 int mListenTech = NfcAdapter.FLAG_USE_ALL_TECH; 117 NfcActivityState(Activity activity)118 public NfcActivityState(Activity activity) { 119 if (activity.isDestroyed()) { 120 throw new IllegalStateException("activity is already destroyed"); 121 } 122 // Check if activity is resumed right now, as we will not 123 // immediately get a callback for that. 124 resumed = activity.isResumed(); 125 126 this.activity = activity; 127 this.token = new Binder(); 128 registerApplication(activity.getApplication()); 129 } destroy()130 public void destroy() { 131 unregisterApplication(activity.getApplication()); 132 resumed = false; 133 activity = null; 134 readerCallback = null; 135 readerModeFlags = 0; 136 readerModeExtras = null; 137 token = null; 138 139 mPollTech = NfcAdapter.FLAG_USE_ALL_TECH; 140 mListenTech = NfcAdapter.FLAG_USE_ALL_TECH; 141 } 142 @Override toString()143 public String toString() { 144 StringBuilder s = new StringBuilder("["); 145 s.append(readerCallback); 146 s.append("]"); 147 return s.toString(); 148 } 149 } 150 151 /** find activity state from mActivities */ findActivityState(Activity activity)152 synchronized NfcActivityState findActivityState(Activity activity) { 153 for (NfcActivityState state : mActivities) { 154 if (state.activity == activity) { 155 return state; 156 } 157 } 158 return null; 159 } 160 161 /** find or create activity state from mActivities */ getActivityState(Activity activity)162 synchronized NfcActivityState getActivityState(Activity activity) { 163 NfcActivityState state = findActivityState(activity); 164 if (state == null) { 165 state = new NfcActivityState(activity); 166 mActivities.add(state); 167 } 168 return state; 169 } 170 findResumedActivityState()171 synchronized NfcActivityState findResumedActivityState() { 172 for (NfcActivityState state : mActivities) { 173 if (state.resumed) { 174 return state; 175 } 176 } 177 return null; 178 } 179 destroyActivityState(Activity activity)180 synchronized void destroyActivityState(Activity activity) { 181 NfcActivityState activityState = findActivityState(activity); 182 if (activityState != null) { 183 activityState.destroy(); 184 mActivities.remove(activityState); 185 } 186 } 187 NfcActivityManager(NfcAdapter adapter)188 public NfcActivityManager(NfcAdapter adapter) { 189 mAdapter = adapter; 190 mActivities = new LinkedList<NfcActivityState>(); 191 mApps = new ArrayList<NfcApplicationState>(1); // Android VM usually has 1 app 192 } 193 enableReaderMode(Activity activity, ReaderCallback callback, int flags, Bundle extras)194 public void enableReaderMode(Activity activity, ReaderCallback callback, int flags, 195 Bundle extras) { 196 boolean isResumed; 197 Binder token; 198 int pollTech, listenTech; 199 synchronized (NfcActivityManager.this) { 200 NfcActivityState state = getActivityState(activity); 201 state.readerCallback = callback; 202 state.readerModeFlags = flags; 203 state.readerModeExtras = extras; 204 pollTech = state.mPollTech; 205 listenTech = state.mListenTech; 206 token = state.token; 207 isResumed = state.resumed; 208 } 209 if (isResumed) { 210 if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH 211 || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) { 212 throw new IllegalStateException( 213 "Cannot be used when alternative DiscoveryTechnology is set"); 214 } else { 215 setReaderMode(token, flags, extras); 216 } 217 } 218 } 219 disableReaderMode(Activity activity)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 setReaderMode(Binder token, int flags, Bundle extras)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 /** 247 * Request or unrequest NFC service callbacks. 248 * Makes IPC call - do not hold lock. 249 */ requestNfcServiceCallback()250 void requestNfcServiceCallback() { 251 try { 252 NfcAdapter.sService.setAppCallback(this); 253 } catch (RemoteException e) { 254 mAdapter.attemptDeadServiceRecovery(e); 255 } 256 } 257 verifyNfcPermission()258 void verifyNfcPermission() { 259 try { 260 NfcAdapter.sService.verifyNfcPermission(); 261 } catch (RemoteException e) { 262 mAdapter.attemptDeadServiceRecovery(e); 263 } 264 } 265 266 @Override onTagDiscovered(Tag tag)267 public void onTagDiscovered(Tag tag) throws RemoteException { 268 NfcAdapter.ReaderCallback callback; 269 synchronized (NfcActivityManager.this) { 270 NfcActivityState state = findResumedActivityState(); 271 if (state == null) return; 272 273 callback = state.readerCallback; 274 } 275 276 // Make callback without lock 277 if (callback != null) { 278 callback.onTagDiscovered(tag); 279 } 280 281 } 282 /** Callback from Activity life-cycle, on main thread */ 283 @Override onActivityCreated(Activity activity, Bundle savedInstanceState)284 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ } 285 286 /** Callback from Activity life-cycle, on main thread */ 287 @Override onActivityStarted(Activity activity)288 public void onActivityStarted(Activity activity) { /* NO-OP */ } 289 290 /** Callback from Activity life-cycle, on main thread */ 291 @Override onActivityResumed(Activity activity)292 public void onActivityResumed(Activity activity) { 293 int readerModeFlags = 0; 294 Bundle readerModeExtras = null; 295 Binder token; 296 int pollTech; 297 int listenTech; 298 299 synchronized (NfcActivityManager.this) { 300 NfcActivityState state = findActivityState(activity); 301 if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state); 302 if (state == null) return; 303 state.resumed = true; 304 token = state.token; 305 readerModeFlags = state.readerModeFlags; 306 readerModeExtras = state.readerModeExtras; 307 308 pollTech = state.mPollTech; 309 listenTech = state.mListenTech; 310 } 311 if (readerModeFlags != 0) { 312 setReaderMode(token, readerModeFlags, readerModeExtras); 313 } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH 314 || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) { 315 changeDiscoveryTech(token, pollTech, listenTech); 316 } 317 requestNfcServiceCallback(); 318 } 319 320 /** Callback from Activity life-cycle, on main thread */ 321 @Override onActivityPaused(Activity activity)322 public void onActivityPaused(Activity activity) { 323 boolean readerModeFlagsSet; 324 Binder token; 325 int pollTech; 326 int listenTech; 327 328 synchronized (NfcActivityManager.this) { 329 NfcActivityState state = findActivityState(activity); 330 if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state); 331 if (state == null) return; 332 state.resumed = false; 333 token = state.token; 334 readerModeFlagsSet = state.readerModeFlags != 0; 335 336 pollTech = state.mPollTech; 337 listenTech = state.mListenTech; 338 } 339 if (readerModeFlagsSet) { 340 // Restore default p2p modes 341 setReaderMode(token, 0, null); 342 } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH 343 || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) { 344 changeDiscoveryTech(token, 345 NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH); 346 } 347 } 348 349 /** Callback from Activity life-cycle, on main thread */ 350 @Override onActivityStopped(Activity activity)351 public void onActivityStopped(Activity activity) { /* NO-OP */ } 352 353 /** Callback from Activity life-cycle, on main thread */ 354 @Override onActivitySaveInstanceState(Activity activity, Bundle outState)355 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ } 356 357 /** Callback from Activity life-cycle, on main thread */ 358 @Override onActivityDestroyed(Activity activity)359 public void onActivityDestroyed(Activity activity) { 360 synchronized (NfcActivityManager.this) { 361 NfcActivityState state = findActivityState(activity); 362 if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state); 363 if (state != null) { 364 // release all associated references 365 destroyActivityState(activity); 366 } 367 } 368 } 369 370 /** setDiscoveryTechnology() implementation */ setDiscoveryTech(Activity activity, int pollTech, int listenTech)371 public void setDiscoveryTech(Activity activity, int pollTech, int listenTech) { 372 boolean isResumed; 373 Binder token; 374 boolean readerModeFlagsSet; 375 synchronized (NfcActivityManager.this) { 376 NfcActivityState state = getActivityState(activity); 377 readerModeFlagsSet = state.readerModeFlags != 0; 378 state.mListenTech = listenTech; 379 state.mPollTech = pollTech; 380 token = state.token; 381 isResumed = state.resumed; 382 } 383 if (!readerModeFlagsSet && isResumed) { 384 changeDiscoveryTech(token, pollTech, listenTech); 385 } else if (readerModeFlagsSet) { 386 throw new IllegalStateException("Cannot be used when the Reader Mode is enabled"); 387 } 388 } 389 390 /** resetDiscoveryTechnology() implementation */ resetDiscoveryTech(Activity activity)391 public void resetDiscoveryTech(Activity activity) { 392 boolean isResumed; 393 Binder token; 394 boolean readerModeFlagsSet; 395 synchronized (NfcActivityManager.this) { 396 NfcActivityState state = getActivityState(activity); 397 state.mListenTech = NfcAdapter.FLAG_USE_ALL_TECH; 398 state.mPollTech = NfcAdapter.FLAG_USE_ALL_TECH; 399 token = state.token; 400 isResumed = state.resumed; 401 } 402 if (isResumed) { 403 changeDiscoveryTech(token, NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH); 404 } 405 406 } 407 changeDiscoveryTech(Binder token, int pollTech, int listenTech)408 private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) { 409 try { 410 NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech); 411 } catch (RemoteException e) { 412 mAdapter.attemptDeadServiceRecovery(e); 413 } 414 } 415 416 } 417