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 * @hide 54 */ findAppState(Application app)55 public NfcApplicationState findAppState(Application app) { 56 for (NfcApplicationState appState : mApps) { 57 if (appState.app == app) { 58 return appState; 59 } 60 } 61 return null; 62 } 63 64 /** 65 * @hide 66 */ registerApplication(Application app)67 public void registerApplication(Application app) { 68 NfcApplicationState appState = findAppState(app); 69 if (appState == null) { 70 appState = new NfcApplicationState(app, this); 71 mApps.add(appState); 72 } 73 appState.register(); 74 } 75 76 /** 77 * @hide 78 */ unregisterApplication(Application app)79 public void unregisterApplication(Application app) { 80 NfcApplicationState appState = findAppState(app); 81 if (appState == null) { 82 Log.e(TAG, "unregisterApplication: app was not registered " + app); 83 return; 84 } 85 appState.unregister(); 86 } 87 88 /** find activity state from mActivities 89 * 90 * @hide 91 */ findActivityState(Activity activity)92 public synchronized NfcActivityState findActivityState(Activity activity) { 93 for (NfcActivityState state : mActivities) { 94 if (state.activity == activity) { 95 return state; 96 } 97 } 98 return null; 99 } 100 101 /** find or create activity state from mActivities 102 * 103 * @hide 104 */ getActivityState(Activity activity)105 public synchronized NfcActivityState getActivityState(Activity activity) { 106 NfcActivityState state = findActivityState(activity); 107 if (state == null) { 108 state = new NfcActivityState(activity, this); 109 mActivities.add(state); 110 } 111 return state; 112 } 113 114 /** 115 * @hide 116 */ findResumedActivityState()117 public synchronized NfcActivityState findResumedActivityState() { 118 for (NfcActivityState state : mActivities) { 119 if (state.resumed) { 120 return state; 121 } 122 } 123 return null; 124 } 125 126 /** 127 * @hide 128 */ destroyActivityState(Activity activity)129 public synchronized void destroyActivityState(Activity activity) { 130 NfcActivityState activityState = findActivityState(activity); 131 if (activityState != null) { 132 activityState.destroy(); 133 mActivities.remove(activityState); 134 } 135 } 136 NfcActivityManager(NfcAdapter adapter)137 public NfcActivityManager(NfcAdapter adapter) { 138 mAdapter = adapter; 139 mActivities = new LinkedList<NfcActivityState>(); 140 mApps = new ArrayList<NfcApplicationState>(1); // Android VM usually has 1 app 141 } 142 enableReaderMode(Activity activity, ReaderCallback callback, int flags, Bundle extras)143 public void enableReaderMode(Activity activity, ReaderCallback callback, int flags, 144 Bundle extras) { 145 boolean isResumed; 146 Binder token; 147 int pollTech, listenTech; 148 synchronized (NfcActivityManager.this) { 149 NfcActivityState state = getActivityState(activity); 150 state.readerCallback = callback; 151 state.readerModeFlags = flags; 152 state.readerModeExtras = extras; 153 pollTech = state.mPollTech; 154 listenTech = state.mListenTech; 155 token = state.token; 156 isResumed = state.resumed; 157 } 158 if (isResumed) { 159 if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH 160 || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) { 161 throw new IllegalStateException( 162 "Cannot be used when alternative DiscoveryTechnology is set"); 163 } else { 164 setReaderMode(token, flags, extras); 165 } 166 } 167 } 168 disableReaderMode(Activity activity)169 public void disableReaderMode(Activity activity) { 170 boolean isResumed; 171 Binder token; 172 synchronized (NfcActivityManager.this) { 173 NfcActivityState state = getActivityState(activity); 174 state.readerCallback = null; 175 state.readerModeFlags = 0; 176 state.readerModeExtras = null; 177 token = state.token; 178 isResumed = state.resumed; 179 } 180 if (isResumed) { 181 setReaderMode(token, 0, null); 182 } 183 184 } 185 setReaderMode(Binder token, int flags, Bundle extras)186 public void setReaderMode(Binder token, int flags, Bundle extras) { 187 if (DBG) Log.d(TAG, "setReaderModee"); 188 NfcAdapter.callService(() -> NfcAdapter.sService.setReaderMode( 189 token, this, flags, extras, mAdapter.getContext().getPackageName())); 190 } 191 192 /** 193 * Request or unrequest NFC service callbacks. 194 * Makes IPC call - do not hold lock. 195 */ requestNfcServiceCallback()196 void requestNfcServiceCallback() { 197 NfcAdapter.callService(() -> NfcAdapter.sService.setAppCallback(this)); 198 } 199 verifyNfcPermission()200 void verifyNfcPermission() { 201 NfcAdapter.callService(() -> NfcAdapter.sService.verifyNfcPermission()); 202 } 203 204 @Override onTagDiscovered(Tag tag)205 public void onTagDiscovered(Tag tag) throws RemoteException { 206 NfcAdapter.ReaderCallback callback; 207 synchronized (NfcActivityManager.this) { 208 NfcActivityState state = findResumedActivityState(); 209 if (state == null) return; 210 211 callback = state.readerCallback; 212 } 213 214 // Make callback without lock 215 if (callback != null) { 216 callback.onTagDiscovered(tag); 217 } 218 219 } 220 /** Callback from Activity life-cycle, on main thread */ 221 @Override onActivityCreated(Activity activity, Bundle savedInstanceState)222 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ } 223 224 /** Callback from Activity life-cycle, on main thread */ 225 @Override onActivityStarted(Activity activity)226 public void onActivityStarted(Activity activity) { /* NO-OP */ } 227 228 /** Callback from Activity life-cycle, on main thread */ 229 @Override onActivityResumed(Activity activity)230 public void onActivityResumed(Activity activity) { 231 int readerModeFlags = 0; 232 Bundle readerModeExtras = null; 233 Binder token; 234 int pollTech; 235 int listenTech; 236 237 synchronized (NfcActivityManager.this) { 238 NfcActivityState state = findActivityState(activity); 239 if (DBG) Log.d(TAG, "onActivityResumed: " + activity + " " + state); 240 if (state == null) return; 241 state.resumed = true; 242 token = state.token; 243 readerModeFlags = state.readerModeFlags; 244 readerModeExtras = state.readerModeExtras; 245 246 pollTech = state.mPollTech; 247 listenTech = state.mListenTech; 248 } 249 if (readerModeFlags != 0) { 250 setReaderMode(token, readerModeFlags, readerModeExtras); 251 } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH 252 || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) { 253 changeDiscoveryTech(token, pollTech, listenTech); 254 } 255 requestNfcServiceCallback(); 256 } 257 258 /** Callback from Activity life-cycle, on main thread */ 259 @Override onActivityPaused(Activity activity)260 public void onActivityPaused(Activity activity) { 261 boolean readerModeFlagsSet; 262 Binder token; 263 int pollTech; 264 int listenTech; 265 266 synchronized (NfcActivityManager.this) { 267 NfcActivityState state = findActivityState(activity); 268 if (DBG) Log.d(TAG, "onActivityPaused: " + activity + " " + state); 269 if (state == null) return; 270 state.resumed = false; 271 token = state.token; 272 readerModeFlagsSet = state.readerModeFlags != 0; 273 274 pollTech = state.mPollTech; 275 listenTech = state.mListenTech; 276 } 277 if (readerModeFlagsSet) { 278 // Restore default p2p modes 279 setReaderMode(token, 0, null); 280 } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH 281 || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) { 282 changeDiscoveryTech(token, 283 NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH); 284 } 285 } 286 287 /** Callback from Activity life-cycle, on main thread */ 288 @Override onActivityStopped(Activity activity)289 public void onActivityStopped(Activity activity) { /* NO-OP */ } 290 291 /** Callback from Activity life-cycle, on main thread */ 292 @Override onActivitySaveInstanceState(Activity activity, Bundle outState)293 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ } 294 295 /** Callback from Activity life-cycle, on main thread */ 296 @Override onActivityDestroyed(Activity activity)297 public void onActivityDestroyed(Activity activity) { 298 synchronized (NfcActivityManager.this) { 299 NfcActivityState state = findActivityState(activity); 300 if (DBG) Log.d(TAG, "onActivityDestroyed: " + activity + " " + state); 301 if (state != null) { 302 // release all associated references 303 destroyActivityState(activity); 304 } 305 } 306 } 307 308 /** setDiscoveryTechnology() implementation */ setDiscoveryTech(Activity activity, int pollTech, int listenTech)309 public void setDiscoveryTech(Activity activity, int pollTech, int listenTech) { 310 boolean isResumed; 311 Binder token; 312 boolean readerModeFlagsSet; 313 synchronized (NfcActivityManager.this) { 314 NfcActivityState state = getActivityState(activity); 315 readerModeFlagsSet = state.readerModeFlags != 0; 316 state.mListenTech = listenTech; 317 state.mPollTech = pollTech; 318 token = state.token; 319 isResumed = state.resumed; 320 } 321 if (!readerModeFlagsSet && isResumed) { 322 changeDiscoveryTech(token, pollTech, listenTech); 323 } else if (readerModeFlagsSet) { 324 throw new IllegalStateException("Cannot be used when the Reader Mode is enabled"); 325 } 326 } 327 328 /** resetDiscoveryTechnology() implementation */ resetDiscoveryTech(Activity activity)329 public void resetDiscoveryTech(Activity activity) { 330 boolean isResumed; 331 Binder token; 332 boolean readerModeFlagsSet; 333 synchronized (NfcActivityManager.this) { 334 NfcActivityState state = getActivityState(activity); 335 state.mListenTech = NfcAdapter.FLAG_USE_ALL_TECH; 336 state.mPollTech = NfcAdapter.FLAG_USE_ALL_TECH; 337 token = state.token; 338 isResumed = state.resumed; 339 } 340 if (isResumed) { 341 changeDiscoveryTech(token, NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH); 342 } 343 344 } 345 changeDiscoveryTech(Binder token, int pollTech, int listenTech)346 private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) { 347 NfcAdapter.callService( 348 () -> NfcAdapter.sService.updateDiscoveryTechnology( 349 token, pollTech, listenTech, mAdapter.getContext().getPackageName())); 350 } 351 352 } 353