blob: d0d943c5de7eb8d7d2e3a6c577a3a968f0867c00 [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;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -070022import android.os.Binder;
Nick Pelly8ce7a272012-03-21 15:14:09 -070023import android.os.Bundle;
Nick Pellyc84c89a2011-08-22 22:27:11 -070024import android.os.RemoteException;
25import android.util.Log;
26
Nick Pelly8ce7a272012-03-21 15:14:09 -070027import java.util.ArrayList;
28import java.util.LinkedList;
29import java.util.List;
Nick Pellyc84c89a2011-08-22 22:27:11 -070030
31/**
32 * Manages NFC API's that are coupled to the life-cycle of an Activity.
33 *
Nick Pelly8ce7a272012-03-21 15:14:09 -070034 * <p>Uses {@link Application#registerActivityLifecycleCallbacks} to hook
35 * into activity life-cycle events such as onPause() and onResume().
Nick Pellyc84c89a2011-08-22 22:27:11 -070036 *
37 * @hide
38 */
Nick Pelly8ce7a272012-03-21 15:14:09 -070039public final class NfcActivityManager extends INdefPushCallback.Stub
40 implements Application.ActivityLifecycleCallbacks {
Nick Pellyc84c89a2011-08-22 22:27:11 -070041 static final String TAG = NfcAdapter.TAG;
42 static final Boolean DBG = false;
43
44 final NfcAdapter mAdapter;
Nick Pelly8ce7a272012-03-21 15:14:09 -070045 final NfcEvent mDefaultEvent; // cached NfcEvent (its currently always the same)
46
47 // All objects in the lists are protected by this
48 final List<NfcApplicationState> mApps; // Application(s) that have NFC state. Usually one
49 final List<NfcActivityState> mActivities; // Activities that have NFC state
50
51 /**
52 * NFC State associated with an {@link Application}.
53 */
54 class NfcApplicationState {
55 int refCount = 0;
56 final Application app;
57 public NfcApplicationState(Application app) {
58 this.app = app;
59 }
60 public void register() {
61 refCount++;
62 if (refCount == 1) {
63 this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
64 }
65 }
66 public void unregister() {
67 refCount--;
68 if (refCount == 0) {
69 this.app.unregisterActivityLifecycleCallbacks(NfcActivityManager.this);
70 } else if (refCount < 0) {
71 Log.e(TAG, "-ve refcount for " + app);
72 }
73 }
74 }
75
76 NfcApplicationState findAppState(Application app) {
77 for (NfcApplicationState appState : mApps) {
78 if (appState.app == app) {
79 return appState;
80 }
81 }
82 return null;
83 }
84
85 void registerApplication(Application app) {
86 NfcApplicationState appState = findAppState(app);
87 if (appState == null) {
88 appState = new NfcApplicationState(app);
89 mApps.add(appState);
90 }
91 appState.register();
92 }
93
94 void unregisterApplication(Application app) {
95 NfcApplicationState appState = findAppState(app);
96 if (appState == null) {
97 Log.e(TAG, "app was not registered " + app);
98 return;
99 }
100 appState.unregister();
101 }
Nick Pellyc84c89a2011-08-22 22:27:11 -0700102
103 /**
104 * NFC state associated with an {@link Activity}
105 */
106 class NfcActivityState {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700107 boolean resumed = false;
108 Activity activity;
109 NdefMessage ndefMessage = null; // static NDEF message
110 NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
111 NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700112 NfcAdapter.CreateBeamUrisCallback uriCallback = null;
113 Uri[] uris = null;
Martijn Coenen1fa2aff2013-02-27 09:21:22 -0800114 int flags = 0;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700115 int readerModeFlags = 0;
116 Binder token;
117
Nick Pelly8ce7a272012-03-21 15:14:09 -0700118 public NfcActivityState(Activity activity) {
119 if (activity.getWindow().isDestroyed()) {
120 throw new IllegalStateException("activity is already destroyed");
121 }
Martijn Coenen20fe5372012-04-05 10:50:05 -0700122 // Check if activity is resumed right now, as we will not
123 // immediately get a callback for that.
124 resumed = activity.isResumed();
125
Nick Pelly8ce7a272012-03-21 15:14:09 -0700126 this.activity = activity;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700127 this.token = new Binder();
Nick Pelly8ce7a272012-03-21 15:14:09 -0700128 registerApplication(activity.getApplication());
129 }
130 public void destroy() {
131 unregisterApplication(activity.getApplication());
132 resumed = false;
133 activity = null;
134 ndefMessage = null;
135 ndefMessageCallback = null;
136 onNdefPushCompleteCallback = null;
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700137 uriCallback = null;
138 uris = null;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700139 readerModeFlags = 0;
140 token = null;
Nick Pelly8ce7a272012-03-21 15:14:09 -0700141 }
Nick Pellyc84c89a2011-08-22 22:27:11 -0700142 @Override
143 public String toString() {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700144 StringBuilder s = new StringBuilder("[").append(" ");
Nick Pellyc84c89a2011-08-22 22:27:11 -0700145 s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700146 s.append(uriCallback).append(" ");
147 if (uris != null) {
148 for (Uri uri : uris) {
149 s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
150 }
151 }
Nick Pellyc84c89a2011-08-22 22:27:11 -0700152 return s.toString();
153 }
154 }
155
Nick Pelly8ce7a272012-03-21 15:14:09 -0700156 /** find activity state from mActivities */
157 synchronized NfcActivityState findActivityState(Activity activity) {
158 for (NfcActivityState state : mActivities) {
159 if (state.activity == activity) {
160 return state;
161 }
162 }
163 return null;
Nick Pellyc84c89a2011-08-22 22:27:11 -0700164 }
165
Nick Pelly8ce7a272012-03-21 15:14:09 -0700166 /** find or create activity state from mActivities */
167 synchronized NfcActivityState getActivityState(Activity activity) {
168 NfcActivityState state = findActivityState(activity);
169 if (state == null) {
170 state = new NfcActivityState(activity);
171 mActivities.add(state);
Nick Pellyc84c89a2011-08-22 22:27:11 -0700172 }
173 return state;
174 }
175
Nick Pelly8ce7a272012-03-21 15:14:09 -0700176 synchronized NfcActivityState findResumedActivityState() {
177 for (NfcActivityState state : mActivities) {
178 if (state.resumed) {
179 return state;
180 }
181 }
182 return null;
183 }
184
185 synchronized void destroyActivityState(Activity activity) {
186 NfcActivityState activityState = findActivityState(activity);
187 if (activityState != null) {
188 activityState.destroy();
189 mActivities.remove(activityState);
190 }
191 }
192
193 public NfcActivityManager(NfcAdapter adapter) {
194 mAdapter = adapter;
195 mActivities = new LinkedList<NfcActivityState>();
196 mApps = new ArrayList<NfcApplicationState>(1); // Android VM usually has 1 app
197 mDefaultEvent = new NfcEvent(mAdapter);
198 }
199
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700200 public void enableReaderMode(Activity activity, int flags) {
201 boolean isResumed;
202 Binder token;
203 synchronized (NfcActivityManager.this) {
204 NfcActivityState state = getActivityState(activity);
205 state.readerModeFlags = flags;
206 token = state.token;
207 isResumed = state.resumed;
208 }
209 if (isResumed) {
210 setReaderMode(token, flags);
211 }
212 }
213
214 public void disableReaderMode(Activity activity) {
215 boolean isResumed;
216 Binder token;
217 synchronized (NfcActivityManager.this) {
218 NfcActivityState state = getActivityState(activity);
219 state.readerModeFlags = 0;
220 token = state.token;
221 isResumed = state.resumed;
222 }
223 if (isResumed) {
224 setReaderMode(token, 0);
225 }
226
227 }
228
229 public void setReaderMode(Binder token, int flags) {
230 if (DBG) Log.d(TAG, "Setting reader mode");
231 try {
232 NfcAdapter.sService.setReaderMode(token, flags);
233 } catch (RemoteException e) {
234 mAdapter.attemptDeadServiceRecovery(e);
235 }
236 }
237
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700238 public void setNdefPushContentUri(Activity activity, Uri[] uris) {
Nick Pelly1d7e9062012-04-03 17:46:00 -0700239 boolean isResumed;
240 synchronized (NfcActivityManager.this) {
241 NfcActivityState state = getActivityState(activity);
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700242 state.uris = uris;
243 isResumed = state.resumed;
244 }
245 if (isResumed) {
Martijn Coenen1360c552013-01-07 16:34:20 -0800246 requestNfcServiceCallback();
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700247 }
248 }
249
250
251 public void setNdefPushContentUriCallback(Activity activity,
252 NfcAdapter.CreateBeamUrisCallback callback) {
253 boolean isResumed;
254 synchronized (NfcActivityManager.this) {
255 NfcActivityState state = getActivityState(activity);
256 state.uriCallback = callback;
Nick Pelly1d7e9062012-04-03 17:46:00 -0700257 isResumed = state.resumed;
258 }
259 if (isResumed) {
Martijn Coenen1360c552013-01-07 16:34:20 -0800260 requestNfcServiceCallback();
Nick Pelly1d7e9062012-04-03 17:46:00 -0700261 }
262 }
263
Martijn Coenen1fa2aff2013-02-27 09:21:22 -0800264 public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700265 boolean isResumed;
266 synchronized (NfcActivityManager.this) {
267 NfcActivityState state = getActivityState(activity);
268 state.ndefMessage = message;
Martijn Coenen1fa2aff2013-02-27 09:21:22 -0800269 state.flags = flags;
Nick Pelly8ce7a272012-03-21 15:14:09 -0700270 isResumed = state.resumed;
271 }
272 if (isResumed) {
Martijn Coenen1360c552013-01-07 16:34:20 -0800273 requestNfcServiceCallback();
Nick Pelly8ce7a272012-03-21 15:14:09 -0700274 }
275 }
276
277 public void setNdefPushMessageCallback(Activity activity,
Martijn Coenen1fa2aff2013-02-27 09:21:22 -0800278 NfcAdapter.CreateNdefMessageCallback callback, int flags) {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700279 boolean isResumed;
280 synchronized (NfcActivityManager.this) {
281 NfcActivityState state = getActivityState(activity);
282 state.ndefMessageCallback = callback;
Martijn Coenen1fa2aff2013-02-27 09:21:22 -0800283 state.flags = flags;
Nick Pelly8ce7a272012-03-21 15:14:09 -0700284 isResumed = state.resumed;
285 }
286 if (isResumed) {
Martijn Coenen1360c552013-01-07 16:34:20 -0800287 requestNfcServiceCallback();
Nick Pelly8ce7a272012-03-21 15:14:09 -0700288 }
289 }
290
291 public void setOnNdefPushCompleteCallback(Activity activity,
292 NfcAdapter.OnNdefPushCompleteCallback callback) {
293 boolean isResumed;
294 synchronized (NfcActivityManager.this) {
295 NfcActivityState state = getActivityState(activity);
296 state.onNdefPushCompleteCallback = callback;
297 isResumed = state.resumed;
298 }
299 if (isResumed) {
Martijn Coenen1360c552013-01-07 16:34:20 -0800300 requestNfcServiceCallback();
Nick Pellyc84c89a2011-08-22 22:27:11 -0700301 }
302 }
303
304 /**
Nick Pelly8ce7a272012-03-21 15:14:09 -0700305 * Request or unrequest NFC service callbacks for NDEF push.
306 * Makes IPC call - do not hold lock.
Nick Pellyc84c89a2011-08-22 22:27:11 -0700307 */
Martijn Coenen1360c552013-01-07 16:34:20 -0800308 void requestNfcServiceCallback() {
Nick Pellyc84c89a2011-08-22 22:27:11 -0700309 try {
Martijn Coenen1360c552013-01-07 16:34:20 -0800310 NfcAdapter.sService.setNdefPushCallback(this);
Nick Pellyc84c89a2011-08-22 22:27:11 -0700311 } catch (RemoteException e) {
312 mAdapter.attemptDeadServiceRecovery(e);
313 }
314 }
315
Nick Pelly8ce7a272012-03-21 15:14:09 -0700316 /** Callback from NFC service, usually on binder thread */
Nick Pellyc84c89a2011-08-22 22:27:11 -0700317 @Override
Martijn Coenen1fa2aff2013-02-27 09:21:22 -0800318 public BeamShareData createBeamShareData() {
319 NfcAdapter.CreateNdefMessageCallback ndefCallback;
320 NfcAdapter.CreateBeamUrisCallback urisCallback;
Nick Pelly8ce7a272012-03-21 15:14:09 -0700321 NdefMessage message;
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700322 Uri[] uris;
Martijn Coenen1fa2aff2013-02-27 09:21:22 -0800323 int flags;
Nick Pelly1d7e9062012-04-03 17:46:00 -0700324 synchronized (NfcActivityManager.this) {
325 NfcActivityState state = findResumedActivityState();
326 if (state == null) return null;
Martijn Coenen1fa2aff2013-02-27 09:21:22 -0800327
328 ndefCallback = state.ndefMessageCallback;
329 urisCallback = state.uriCallback;
330 message = state.ndefMessage;
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700331 uris = state.uris;
Martijn Coenen1fa2aff2013-02-27 09:21:22 -0800332 flags = state.flags;
Martijn Coenen20e8dd92012-04-12 16:37:18 -0700333 }
Martijn Coenen1fa2aff2013-02-27 09:21:22 -0800334
335 // Make callbacks without lock
336 if (ndefCallback != null) {
337 message = ndefCallback.createNdefMessage(mDefaultEvent);
338 }
339 if (urisCallback != null) {
340 uris = urisCallback.createBeamUris(mDefaultEvent);
Martijn Coenen2c103112012-05-15 10:32:15 -0700341 if (uris != null) {
342 for (Uri uri : uris) {
343 if (uri == null) {
344 Log.e(TAG, "Uri not allowed to be null.");
345 return null;
346 }
347 String scheme = uri.getScheme();
348 if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
349 !scheme.equalsIgnoreCase("content"))) {
350 Log.e(TAG, "Uri needs to have " +
351 "either scheme file or scheme content");
352 return null;
353 }
354 }
355 }
Nick Pelly1d7e9062012-04-03 17:46:00 -0700356 }
Martijn Coenen1fa2aff2013-02-27 09:21:22 -0800357
358 return new BeamShareData(message, uris, flags);
Nick Pelly1d7e9062012-04-03 17:46:00 -0700359 }
Nick Pelly1d7e9062012-04-03 17:46:00 -0700360
Nick Pelly1d7e9062012-04-03 17:46:00 -0700361 /** Callback from NFC service, usually on binder thread */
362 @Override
Nick Pellyc84c89a2011-08-22 22:27:11 -0700363 public void onNdefPushComplete() {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700364 NfcAdapter.OnNdefPushCompleteCallback callback;
Nick Pellyc84c89a2011-08-22 22:27:11 -0700365 synchronized (NfcActivityManager.this) {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700366 NfcActivityState state = findResumedActivityState();
367 if (state == null) return;
368
369 callback = state.onNdefPushCompleteCallback;
Nick Pellyc84c89a2011-08-22 22:27:11 -0700370 }
371
Nick Pelly8ce7a272012-03-21 15:14:09 -0700372 // Make callback without lock
Nick Pellyc84c89a2011-08-22 22:27:11 -0700373 if (callback != null) {
374 callback.onNdefPushComplete(mDefaultEvent);
375 }
376 }
Martijn Coenen3433a8a2011-09-01 19:18:02 -0700377
Nick Pelly8ce7a272012-03-21 15:14:09 -0700378 /** Callback from Activity life-cycle, on main thread */
379 @Override
380 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
381
382 /** Callback from Activity life-cycle, on main thread */
383 @Override
384 public void onActivityStarted(Activity activity) { /* NO-OP */ }
385
386 /** Callback from Activity life-cycle, on main thread */
387 @Override
388 public void onActivityResumed(Activity activity) {
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700389 int readerModeFlags = 0;
390 Binder token;
Nick Pelly8ce7a272012-03-21 15:14:09 -0700391 synchronized (NfcActivityManager.this) {
392 NfcActivityState state = findActivityState(activity);
393 if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
394 if (state == null) return;
395 state.resumed = true;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700396 token = state.token;
397 readerModeFlags = state.readerModeFlags;
398 }
399 if (readerModeFlags != 0) {
400 setReaderMode(token, readerModeFlags);
Nick Pelly8ce7a272012-03-21 15:14:09 -0700401 }
Martijn Coenen1360c552013-01-07 16:34:20 -0800402 requestNfcServiceCallback();
Nick Pelly8ce7a272012-03-21 15:14:09 -0700403 }
404
405 /** Callback from Activity life-cycle, on main thread */
406 @Override
407 public void onActivityPaused(Activity activity) {
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700408 boolean readerModeFlagsSet;
409 Binder token;
Nick Pelly8ce7a272012-03-21 15:14:09 -0700410 synchronized (NfcActivityManager.this) {
411 NfcActivityState state = findActivityState(activity);
412 if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
413 if (state == null) return;
414 state.resumed = false;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700415 token = state.token;
416 readerModeFlagsSet = state.readerModeFlags != 0;
417 }
418 if (readerModeFlagsSet) {
419 // Restore default p2p modes
420 setReaderMode(token, 0);
Nick Pelly8ce7a272012-03-21 15:14:09 -0700421 }
Nick Pelly8ce7a272012-03-21 15:14:09 -0700422 }
423
424 /** Callback from Activity life-cycle, on main thread */
425 @Override
426 public void onActivityStopped(Activity activity) { /* NO-OP */ }
427
428 /** Callback from Activity life-cycle, on main thread */
429 @Override
430 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
431
432 /** Callback from Activity life-cycle, on main thread */
433 @Override
434 public void onActivityDestroyed(Activity activity) {
435 synchronized (NfcActivityManager.this) {
436 NfcActivityState state = findActivityState(activity);
437 if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
438 if (state != null) {
439 // release all associated references
440 destroyActivityState(activity);
441 }
442 }
443 }
Nick Pellyc84c89a2011-08-22 22:27:11 -0700444}