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 int flags = 0; 114 public NfcActivityState(Activity activity) { 115 if (activity.getWindow().isDestroyed()) { 116 throw new IllegalStateException("activity is already destroyed"); 117 } 118 // Check if activity is resumed right now, as we will not 119 // immediately get a callback for that. 120 resumed = activity.isResumed(); 121 122 this.activity = activity; 123 registerApplication(activity.getApplication()); 124 } 125 public void destroy() { 126 unregisterApplication(activity.getApplication()); 127 resumed = false; 128 activity = null; 129 ndefMessage = null; 130 ndefMessageCallback = null; 131 onNdefPushCompleteCallback = null; 132 uriCallback = null; 133 uris = null; 134 } 135 @Override 136 public String toString() { 137 StringBuilder s = new StringBuilder("[").append(" "); 138 s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" "); 139 s.append(uriCallback).append(" "); 140 if (uris != null) { 141 for (Uri uri : uris) { 142 s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]"); 143 } 144 } 145 return s.toString(); 146 } 147 } 148 149 /** find activity state from mActivities */ 150 synchronized NfcActivityState findActivityState(Activity activity) { 151 for (NfcActivityState state : mActivities) { 152 if (state.activity == activity) { 153 return state; 154 } 155 } 156 return null; 157 } 158 159 /** find or create activity state from mActivities */ 160 synchronized NfcActivityState getActivityState(Activity activity) { 161 NfcActivityState state = findActivityState(activity); 162 if (state == null) { 163 state = new NfcActivityState(activity); 164 mActivities.add(state); 165 } 166 return state; 167 } 168 169 synchronized NfcActivityState findResumedActivityState() { 170 for (NfcActivityState state : mActivities) { 171 if (state.resumed) { 172 return state; 173 } 174 } 175 return null; 176 } 177 178 synchronized void destroyActivityState(Activity activity) { 179 NfcActivityState activityState = findActivityState(activity); 180 if (activityState != null) { 181 activityState.destroy(); 182 mActivities.remove(activityState); 183 } 184 } 185 186 public NfcActivityManager(NfcAdapter adapter) { 187 mAdapter = adapter; 188 mActivities = new LinkedList<NfcActivityState>(); 189 mApps = new ArrayList<NfcApplicationState>(1); // Android VM usually has 1 app 190 mDefaultEvent = new NfcEvent(mAdapter); 191 } 192 193 public void setNdefPushContentUri(Activity activity, Uri[] uris) { 194 boolean isResumed; 195 synchronized (NfcActivityManager.this) { 196 NfcActivityState state = getActivityState(activity); 197 state.uris = uris; 198 isResumed = state.resumed; 199 } 200 if (isResumed) { 201 requestNfcServiceCallback(); 202 } 203 } 204 205 206 public void setNdefPushContentUriCallback(Activity activity, 207 NfcAdapter.CreateBeamUrisCallback callback) { 208 boolean isResumed; 209 synchronized (NfcActivityManager.this) { 210 NfcActivityState state = getActivityState(activity); 211 state.uriCallback = callback; 212 isResumed = state.resumed; 213 } 214 if (isResumed) { 215 requestNfcServiceCallback(); 216 } 217 } 218 219 public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) { 220 boolean isResumed; 221 synchronized (NfcActivityManager.this) { 222 NfcActivityState state = getActivityState(activity); 223 state.ndefMessage = message; 224 state.flags = flags; 225 isResumed = state.resumed; 226 } 227 if (isResumed) { 228 requestNfcServiceCallback(); 229 } 230 } 231 232 public void setNdefPushMessageCallback(Activity activity, 233 NfcAdapter.CreateNdefMessageCallback callback, int flags) { 234 boolean isResumed; 235 synchronized (NfcActivityManager.this) { 236 NfcActivityState state = getActivityState(activity); 237 state.ndefMessageCallback = callback; 238 state.flags = flags; 239 isResumed = state.resumed; 240 } 241 if (isResumed) { 242 requestNfcServiceCallback(); 243 } 244 } 245 246 public void setOnNdefPushCompleteCallback(Activity activity, 247 NfcAdapter.OnNdefPushCompleteCallback callback) { 248 boolean isResumed; 249 synchronized (NfcActivityManager.this) { 250 NfcActivityState state = getActivityState(activity); 251 state.onNdefPushCompleteCallback = callback; 252 isResumed = state.resumed; 253 } 254 if (isResumed) { 255 requestNfcServiceCallback(); 256 } 257 } 258 259 /** 260 * Request or unrequest NFC service callbacks for NDEF push. 261 * Makes IPC call - do not hold lock. 262 */ 263 void requestNfcServiceCallback() { 264 try { 265 NfcAdapter.sService.setNdefPushCallback(this); 266 } catch (RemoteException e) { 267 mAdapter.attemptDeadServiceRecovery(e); 268 } 269 } 270 271 /** Callback from NFC service, usually on binder thread */ 272 @Override 273 public BeamShareData createBeamShareData() { 274 NfcAdapter.CreateNdefMessageCallback ndefCallback; 275 NfcAdapter.CreateBeamUrisCallback urisCallback; 276 NdefMessage message; 277 Uri[] uris; 278 int flags; 279 synchronized (NfcActivityManager.this) { 280 NfcActivityState state = findResumedActivityState(); 281 if (state == null) return null; 282 283 ndefCallback = state.ndefMessageCallback; 284 urisCallback = state.uriCallback; 285 message = state.ndefMessage; 286 uris = state.uris; 287 flags = state.flags; 288 } 289 290 // Make callbacks without lock 291 if (ndefCallback != null) { 292 message = ndefCallback.createNdefMessage(mDefaultEvent); 293 } 294 if (urisCallback != null) { 295 uris = urisCallback.createBeamUris(mDefaultEvent); 296 if (uris != null) { 297 for (Uri uri : uris) { 298 if (uri == null) { 299 Log.e(TAG, "Uri not allowed to be null."); 300 return null; 301 } 302 String scheme = uri.getScheme(); 303 if (scheme == null || (!scheme.equalsIgnoreCase("file") && 304 !scheme.equalsIgnoreCase("content"))) { 305 Log.e(TAG, "Uri needs to have " + 306 "either scheme file or scheme content"); 307 return null; 308 } 309 } 310 } 311 } 312 313 return new BeamShareData(message, uris, flags); 314 } 315 316 /** Callback from NFC service, usually on binder thread */ 317 @Override 318 public void onNdefPushComplete() { 319 NfcAdapter.OnNdefPushCompleteCallback callback; 320 synchronized (NfcActivityManager.this) { 321 NfcActivityState state = findResumedActivityState(); 322 if (state == null) return; 323 324 callback = state.onNdefPushCompleteCallback; 325 } 326 327 // Make callback without lock 328 if (callback != null) { 329 callback.onNdefPushComplete(mDefaultEvent); 330 } 331 } 332 333 /** Callback from Activity life-cycle, on main thread */ 334 @Override 335 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ } 336 337 /** Callback from Activity life-cycle, on main thread */ 338 @Override 339 public void onActivityStarted(Activity activity) { /* NO-OP */ } 340 341 /** Callback from Activity life-cycle, on main thread */ 342 @Override 343 public void onActivityResumed(Activity activity) { 344 synchronized (NfcActivityManager.this) { 345 NfcActivityState state = findActivityState(activity); 346 if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state); 347 if (state == null) return; 348 state.resumed = true; 349 } 350 requestNfcServiceCallback(); 351 } 352 353 /** Callback from Activity life-cycle, on main thread */ 354 @Override 355 public void onActivityPaused(Activity activity) { 356 synchronized (NfcActivityManager.this) { 357 NfcActivityState state = findActivityState(activity); 358 if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state); 359 if (state == null) return; 360 state.resumed = false; 361 } 362 } 363 364 /** Callback from Activity life-cycle, on main thread */ 365 @Override 366 public void onActivityStopped(Activity activity) { /* NO-OP */ } 367 368 /** Callback from Activity life-cycle, on main thread */ 369 @Override 370 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ } 371 372 /** Callback from Activity life-cycle, on main thread */ 373 @Override 374 public void onActivityDestroyed(Activity activity) { 375 synchronized (NfcActivityManager.this) { 376 NfcActivityState state = findActivityState(activity); 377 if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state); 378 if (state != null) { 379 // release all associated references 380 destroyActivityState(activity); 381 } 382 } 383 } 384 385 } 386