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