blob: 53b41d5f42c2fc1d607dc5473c607f057c89faee [file] [log] [blame]
Nick Pellyc84c89a2011-08-22 22:27:11 -07001/*
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
17package android.nfc;
18
19import android.app.Activity;
Nick Pelly8ce7a272012-03-21 15:14:09 -070020import android.app.Application;
Nick Pelly1d7e9062012-04-03 17:46:00 -070021import android.net.Uri;
Nick Pelly8ce7a272012-03-21 15:14:09 -070022import android.os.Bundle;
Nick Pellyc84c89a2011-08-22 22:27:11 -070023import android.os.RemoteException;
24import android.util.Log;
25
Nick Pelly8ce7a272012-03-21 15:14:09 -070026import java.util.ArrayList;
27import java.util.LinkedList;
28import java.util.List;
Nick Pellyc84c89a2011-08-22 22:27:11 -070029
30/**
31 * Manages NFC API's that are coupled to the life-cycle of an Activity.
32 *
Nick Pelly8ce7a272012-03-21 15:14:09 -070033 * <p>Uses {@link Application#registerActivityLifecycleCallbacks} to hook
34 * into activity life-cycle events such as onPause() and onResume().
Nick Pellyc84c89a2011-08-22 22:27:11 -070035 *
36 * @hide
37 */
Nick Pelly8ce7a272012-03-21 15:14:09 -070038public final class NfcActivityManager extends INdefPushCallback.Stub
39 implements Application.ActivityLifecycleCallbacks {
Nick Pellyc84c89a2011-08-22 22:27:11 -070040 static final String TAG = NfcAdapter.TAG;
41 static final Boolean DBG = false;
42
43 final NfcAdapter mAdapter;
Nick Pelly8ce7a272012-03-21 15:14:09 -070044 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 }
Nick Pellyc84c89a2011-08-22 22:27:11 -0700101
102 /**
103 * NFC state associated with an {@link Activity}
104 */
105 class NfcActivityState {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700106 boolean resumed = false;
107 Activity activity;
108 NdefMessage ndefMessage = null; // static NDEF message
109 NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
110 NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700111 NfcAdapter.CreateBeamUrisCallback uriCallback = null;
112 Uri[] uris = null;
Nick Pelly8ce7a272012-03-21 15:14:09 -0700113 public NfcActivityState(Activity activity) {
114 if (activity.getWindow().isDestroyed()) {
115 throw new IllegalStateException("activity is already destroyed");
116 }
Martijn Coenen20fe5372012-04-05 10:50:05 -0700117 // Check if activity is resumed right now, as we will not
118 // immediately get a callback for that.
119 resumed = activity.isResumed();
120
Nick Pelly8ce7a272012-03-21 15:14:09 -0700121 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;
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700131 uriCallback = null;
132 uris = null;
Nick Pelly8ce7a272012-03-21 15:14:09 -0700133 }
Nick Pellyc84c89a2011-08-22 22:27:11 -0700134 @Override
135 public String toString() {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700136 StringBuilder s = new StringBuilder("[").append(" ");
Nick Pellyc84c89a2011-08-22 22:27:11 -0700137 s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700138 s.append(uriCallback).append(" ");
139 if (uris != null) {
140 for (Uri uri : uris) {
141 s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
142 }
143 }
Nick Pellyc84c89a2011-08-22 22:27:11 -0700144 return s.toString();
145 }
146 }
147
Nick Pelly8ce7a272012-03-21 15:14:09 -0700148 /** 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;
Nick Pellyc84c89a2011-08-22 22:27:11 -0700156 }
157
Nick Pelly8ce7a272012-03-21 15:14:09 -0700158 /** 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);
Nick Pellyc84c89a2011-08-22 22:27:11 -0700164 }
165 return state;
166 }
167
Nick Pelly8ce7a272012-03-21 15:14:09 -0700168 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
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700192 public void setNdefPushContentUri(Activity activity, Uri[] uris) {
Nick Pelly1d7e9062012-04-03 17:46:00 -0700193 boolean isResumed;
194 synchronized (NfcActivityManager.this) {
195 NfcActivityState state = getActivityState(activity);
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700196 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;
Nick Pelly1d7e9062012-04-03 17:46:00 -0700211 isResumed = state.resumed;
212 }
213 if (isResumed) {
214 requestNfcServiceCallback(true);
215 }
216 }
217
Nick Pelly8ce7a272012-03-21 15:14:09 -0700218 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);
Nick Pellyc84c89a2011-08-22 22:27:11 -0700253 }
254 }
255
256 /**
Nick Pelly8ce7a272012-03-21 15:14:09 -0700257 * 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
Nick Pellyc84c89a2011-08-22 22:27:11 -0700260 */
Nick Pelly8ce7a272012-03-21 15:14:09 -0700261 void requestNfcServiceCallback(boolean request) {
Nick Pellyc84c89a2011-08-22 22:27:11 -0700262 try {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700263 NfcAdapter.sService.setNdefPushCallback(request ? this : null);
Nick Pellyc84c89a2011-08-22 22:27:11 -0700264 } catch (RemoteException e) {
265 mAdapter.attemptDeadServiceRecovery(e);
266 }
267 }
268
Nick Pelly8ce7a272012-03-21 15:14:09 -0700269 /** Callback from NFC service, usually on binder thread */
Nick Pellyc84c89a2011-08-22 22:27:11 -0700270 @Override
271 public NdefMessage createMessage() {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700272 NfcAdapter.CreateNdefMessageCallback callback;
273 NdefMessage message;
Nick Pellyc84c89a2011-08-22 22:27:11 -0700274 synchronized (NfcActivityManager.this) {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700275 NfcActivityState state = findResumedActivityState();
276 if (state == null) return null;
277
278 callback = state.ndefMessageCallback;
279 message = state.ndefMessage;
Nick Pellyc84c89a2011-08-22 22:27:11 -0700280 }
281
Nick Pelly8ce7a272012-03-21 15:14:09 -0700282 // Make callback without lock
Nick Pellyc84c89a2011-08-22 22:27:11 -0700283 if (callback != null) {
284 return callback.createNdefMessage(mDefaultEvent);
Nick Pelly8ce7a272012-03-21 15:14:09 -0700285 } else {
286 return message;
Nick Pellyc84c89a2011-08-22 22:27:11 -0700287 }
Nick Pellyc84c89a2011-08-22 22:27:11 -0700288 }
289
Nick Pelly8ce7a272012-03-21 15:14:09 -0700290 /** Callback from NFC service, usually on binder thread */
Nick Pellyc84c89a2011-08-22 22:27:11 -0700291 @Override
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700292 public Uri[] getUris() {
293 Uri[] uris;
294 NfcAdapter.CreateBeamUrisCallback callback;
Nick Pelly1d7e9062012-04-03 17:46:00 -0700295 synchronized (NfcActivityManager.this) {
296 NfcActivityState state = findResumedActivityState();
297 if (state == null) return null;
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700298 uris = state.uris;
299 callback = state.uriCallback;
300 }
301 if (callback != null) {
Martijn Coenen2c103112012-05-15 10:32:15 -0700302 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;
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700319 } else {
320 return uris;
Nick Pelly1d7e9062012-04-03 17:46:00 -0700321 }
322 }
Nick Pelly1d7e9062012-04-03 17:46:00 -0700323
Nick Pelly1d7e9062012-04-03 17:46:00 -0700324 /** Callback from NFC service, usually on binder thread */
325 @Override
Nick Pellyc84c89a2011-08-22 22:27:11 -0700326 public void onNdefPushComplete() {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700327 NfcAdapter.OnNdefPushCompleteCallback callback;
Nick Pellyc84c89a2011-08-22 22:27:11 -0700328 synchronized (NfcActivityManager.this) {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700329 NfcActivityState state = findResumedActivityState();
330 if (state == null) return;
331
332 callback = state.onNdefPushCompleteCallback;
Nick Pellyc84c89a2011-08-22 22:27:11 -0700333 }
334
Nick Pelly8ce7a272012-03-21 15:14:09 -0700335 // Make callback without lock
Nick Pellyc84c89a2011-08-22 22:27:11 -0700336 if (callback != null) {
337 callback.onNdefPushComplete(mDefaultEvent);
338 }
339 }
Martijn Coenen3433a8a2011-09-01 19:18:02 -0700340
Nick Pelly8ce7a272012-03-21 15:14:09 -0700341 /** 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 }
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700393
Nick Pellyc84c89a2011-08-22 22:27:11 -0700394}