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 NfcActivityState(Activity activity)115 public NfcActivityState(Activity activity) { 116 if (activity.isDestroyed()) { 117 throw new IllegalStateException("activity is already destroyed"); 118 } 119 // Check if activity is resumed right now, as we will not 120 // immediately get a callback for that. 121 resumed = activity.isResumed(); 122 123 this.activity = activity; 124 this.token = new Binder(); 125 registerApplication(activity.getApplication()); 126 } destroy()127 public void destroy() { 128 unregisterApplication(activity.getApplication()); 129 resumed = false; 130 activity = null; 131 readerCallback = null; 132 readerModeFlags = 0; 133 readerModeExtras = null; 134 token = null; 135 } 136 @Override toString()137 public String toString() { 138 StringBuilder s = new StringBuilder("["); 139 s.append(readerCallback); 140 s.append("]"); 141 return s.toString(); 142 } 143 } 144 145 /** find activity state from mActivities */ findActivityState(Activity activity)146 synchronized NfcActivityState findActivityState(Activity activity) { 147 for (NfcActivityState state : mActivities) { 148 if (state.activity == activity) { 149 return state; 150 } 151 } 152 return null; 153 } 154 155 /** find or create activity state from mActivities */ getActivityState(Activity activity)156 synchronized NfcActivityState getActivityState(Activity activity) { 157 NfcActivityState state = findActivityState(activity); 158 if (state == null) { 159 state = new NfcActivityState(activity); 160 mActivities.add(state); 161 } 162 return state; 163 } 164 findResumedActivityState()165 synchronized NfcActivityState findResumedActivityState() { 166 for (NfcActivityState state : mActivities) { 167 if (state.resumed) { 168 return state; 169 } 170 } 171 return null; 172 } 173 destroyActivityState(Activity activity)174 synchronized void destroyActivityState(Activity activity) { 175 NfcActivityState activityState = findActivityState(activity); 176 if (activityState != null) { 177 activityState.destroy(); 178 mActivities.remove(activityState); 179 } 180 } 181 NfcActivityManager(NfcAdapter adapter)182 public NfcActivityManager(NfcAdapter adapter) { 183 mAdapter = adapter; 184 mActivities = new LinkedList<NfcActivityState>(); 185 mApps = new ArrayList<NfcApplicationState>(1); // Android VM usually has 1 app 186 } 187 enableReaderMode(Activity activity, ReaderCallback callback, int flags, Bundle extras)188 public void enableReaderMode(Activity activity, ReaderCallback callback, int flags, 189 Bundle extras) { 190 boolean isResumed; 191 Binder token; 192 synchronized (NfcActivityManager.this) { 193 NfcActivityState state = getActivityState(activity); 194 state.readerCallback = callback; 195 state.readerModeFlags = flags; 196 state.readerModeExtras = extras; 197 token = state.token; 198 isResumed = state.resumed; 199 } 200 if (isResumed) { 201 setReaderMode(token, flags, extras); 202 } 203 } 204 disableReaderMode(Activity activity)205 public void disableReaderMode(Activity activity) { 206 boolean isResumed; 207 Binder token; 208 synchronized (NfcActivityManager.this) { 209 NfcActivityState state = getActivityState(activity); 210 state.readerCallback = null; 211 state.readerModeFlags = 0; 212 state.readerModeExtras = null; 213 token = state.token; 214 isResumed = state.resumed; 215 } 216 if (isResumed) { 217 setReaderMode(token, 0, null); 218 } 219 220 } 221 setReaderMode(Binder token, int flags, Bundle extras)222 public void setReaderMode(Binder token, int flags, Bundle extras) { 223 if (DBG) Log.d(TAG, "Setting reader mode"); 224 try { 225 NfcAdapter.sService.setReaderMode(token, this, flags, extras); 226 } catch (RemoteException e) { 227 mAdapter.attemptDeadServiceRecovery(e); 228 } 229 } 230 231 /** 232 * Request or unrequest NFC service callbacks. 233 * Makes IPC call - do not hold lock. 234 */ requestNfcServiceCallback()235 void requestNfcServiceCallback() { 236 try { 237 NfcAdapter.sService.setAppCallback(this); 238 } catch (RemoteException e) { 239 mAdapter.attemptDeadServiceRecovery(e); 240 } 241 } 242 verifyNfcPermission()243 void verifyNfcPermission() { 244 try { 245 NfcAdapter.sService.verifyNfcPermission(); 246 } catch (RemoteException e) { 247 mAdapter.attemptDeadServiceRecovery(e); 248 } 249 } 250 251 @Override onTagDiscovered(Tag tag)252 public void onTagDiscovered(Tag tag) throws RemoteException { 253 NfcAdapter.ReaderCallback callback; 254 synchronized (NfcActivityManager.this) { 255 NfcActivityState state = findResumedActivityState(); 256 if (state == null) return; 257 258 callback = state.readerCallback; 259 } 260 261 // Make callback without lock 262 if (callback != null) { 263 callback.onTagDiscovered(tag); 264 } 265 266 } 267 /** Callback from Activity life-cycle, on main thread */ 268 @Override onActivityCreated(Activity activity, Bundle savedInstanceState)269 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ } 270 271 /** Callback from Activity life-cycle, on main thread */ 272 @Override onActivityStarted(Activity activity)273 public void onActivityStarted(Activity activity) { /* NO-OP */ } 274 275 /** Callback from Activity life-cycle, on main thread */ 276 @Override onActivityResumed(Activity activity)277 public void onActivityResumed(Activity activity) { 278 int readerModeFlags = 0; 279 Bundle readerModeExtras = null; 280 Binder token; 281 synchronized (NfcActivityManager.this) { 282 NfcActivityState state = findActivityState(activity); 283 if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state); 284 if (state == null) return; 285 state.resumed = true; 286 token = state.token; 287 readerModeFlags = state.readerModeFlags; 288 readerModeExtras = state.readerModeExtras; 289 } 290 if (readerModeFlags != 0) { 291 setReaderMode(token, readerModeFlags, readerModeExtras); 292 } 293 requestNfcServiceCallback(); 294 } 295 296 /** Callback from Activity life-cycle, on main thread */ 297 @Override onActivityPaused(Activity activity)298 public void onActivityPaused(Activity activity) { 299 boolean readerModeFlagsSet; 300 Binder token; 301 synchronized (NfcActivityManager.this) { 302 NfcActivityState state = findActivityState(activity); 303 if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state); 304 if (state == null) return; 305 state.resumed = false; 306 token = state.token; 307 readerModeFlagsSet = state.readerModeFlags != 0; 308 } 309 if (readerModeFlagsSet) { 310 // Restore default p2p modes 311 setReaderMode(token, 0, null); 312 } 313 } 314 315 /** Callback from Activity life-cycle, on main thread */ 316 @Override onActivityStopped(Activity activity)317 public void onActivityStopped(Activity activity) { /* NO-OP */ } 318 319 /** Callback from Activity life-cycle, on main thread */ 320 @Override onActivitySaveInstanceState(Activity activity, Bundle outState)321 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ } 322 323 /** Callback from Activity life-cycle, on main thread */ 324 @Override onActivityDestroyed(Activity activity)325 public void onActivityDestroyed(Activity activity) { 326 synchronized (NfcActivityManager.this) { 327 NfcActivityState state = findActivityState(activity); 328 if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state); 329 if (state != null) { 330 // release all associated references 331 destroyActivityState(activity); 332 } 333 } 334 } 335 336 } 337