blob: 0e40db612708785b4da584e62603bd3084d74988 [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;
Artur Satayevbc3d8b92019-12-10 17:47:53 +000021import android.compat.annotation.UnsupportedAppUsage;
Martijn Coenen5b1e0322013-09-02 20:38:47 -070022import android.nfc.NfcAdapter.ReaderCallback;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -070023import android.os.Binder;
Nick Pelly8ce7a272012-03-21 15:14:09 -070024import android.os.Bundle;
Nick Pellyc84c89a2011-08-22 22:27:11 -070025import android.os.RemoteException;
26import android.util.Log;
27
Nick Pelly8ce7a272012-03-21 15:14:09 -070028import java.util.ArrayList;
29import java.util.LinkedList;
30import java.util.List;
Nick Pellyc84c89a2011-08-22 22:27:11 -070031
32/**
33 * Manages NFC API's that are coupled to the life-cycle of an Activity.
34 *
Nick Pelly8ce7a272012-03-21 15:14:09 -070035 * <p>Uses {@link Application#registerActivityLifecycleCallbacks} to hook
36 * into activity life-cycle events such as onPause() and onResume().
Nick Pellyc84c89a2011-08-22 22:27:11 -070037 *
38 * @hide
39 */
Martijn Coenen5b1e0322013-09-02 20:38:47 -070040public final class NfcActivityManager extends IAppCallback.Stub
Nick Pelly8ce7a272012-03-21 15:14:09 -070041 implements Application.ActivityLifecycleCallbacks {
Nick Pellyc84c89a2011-08-22 22:27:11 -070042 static final String TAG = NfcAdapter.TAG;
43 static final Boolean DBG = false;
44
Mathew Inwood1961e1e2018-07-31 16:04:15 +010045 @UnsupportedAppUsage
Nick Pellyc84c89a2011-08-22 22:27:11 -070046 final NfcAdapter mAdapter;
Nick Pelly8ce7a272012-03-21 15:14:09 -070047
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;
58 public NfcApplicationState(Application app) {
59 this.app = app;
60 }
61 public void register() {
62 refCount++;
63 if (refCount == 1) {
64 this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
65 }
66 }
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
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
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
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 }
Nick Pellyc84c89a2011-08-22 22:27:11 -0700103
104 /**
105 * NFC state associated with an {@link Activity}
106 */
107 class NfcActivityState {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700108 boolean resumed = false;
109 Activity activity;
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700110 NfcAdapter.ReaderCallback readerCallback = null;
Henri Chataing6d408b52023-01-30 23:55:09 +0000111 int readerModeFlags = 0;
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700112 Bundle readerModeExtras = null;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700113 Binder token;
114
Alisher Alikhodjaev8802f872023-11-28 14:52:44 -0800115 int mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
116 int mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
117
Nick Pelly8ce7a272012-03-21 15:14:09 -0700118 public NfcActivityState(Activity activity) {
Henri Chataingb658e2d2022-12-23 14:45:42 +0000119 if (activity.isDestroyed()) {
Nick Pelly8ce7a272012-03-21 15:14:09 -0700120 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;
Henri Chataing6d408b52023-01-30 23:55:09 +0000134 readerCallback = null;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700135 readerModeFlags = 0;
Henri Chataing6d408b52023-01-30 23:55:09 +0000136 readerModeExtras = null;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700137 token = null;
Alisher Alikhodjaev8802f872023-11-28 14:52:44 -0800138
139 mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
140 mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
Nick Pelly8ce7a272012-03-21 15:14:09 -0700141 }
Nick Pellyc84c89a2011-08-22 22:27:11 -0700142 @Override
143 public String toString() {
Henri Chataing6d408b52023-01-30 23:55:09 +0000144 StringBuilder s = new StringBuilder("[");
145 s.append(readerCallback);
146 s.append("]");
Nick Pellyc84c89a2011-08-22 22:27:11 -0700147 return s.toString();
148 }
149 }
150
Nick Pelly8ce7a272012-03-21 15:14:09 -0700151 /** find activity state from mActivities */
152 synchronized NfcActivityState findActivityState(Activity activity) {
153 for (NfcActivityState state : mActivities) {
154 if (state.activity == activity) {
155 return state;
156 }
157 }
158 return null;
Nick Pellyc84c89a2011-08-22 22:27:11 -0700159 }
160
Nick Pelly8ce7a272012-03-21 15:14:09 -0700161 /** find or create activity state from mActivities */
162 synchronized NfcActivityState getActivityState(Activity activity) {
163 NfcActivityState state = findActivityState(activity);
164 if (state == null) {
165 state = new NfcActivityState(activity);
166 mActivities.add(state);
Nick Pellyc84c89a2011-08-22 22:27:11 -0700167 }
168 return state;
169 }
170
Nick Pelly8ce7a272012-03-21 15:14:09 -0700171 synchronized NfcActivityState findResumedActivityState() {
172 for (NfcActivityState state : mActivities) {
173 if (state.resumed) {
174 return state;
175 }
176 }
177 return null;
178 }
179
180 synchronized void destroyActivityState(Activity activity) {
181 NfcActivityState activityState = findActivityState(activity);
182 if (activityState != null) {
183 activityState.destroy();
184 mActivities.remove(activityState);
185 }
186 }
187
188 public NfcActivityManager(NfcAdapter adapter) {
189 mAdapter = adapter;
190 mActivities = new LinkedList<NfcActivityState>();
191 mApps = new ArrayList<NfcApplicationState>(1); // Android VM usually has 1 app
Nick Pelly8ce7a272012-03-21 15:14:09 -0700192 }
193
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700194 public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
195 Bundle extras) {
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700196 boolean isResumed;
197 Binder token;
Alisher Alikhodjaev280c0872024-05-21 20:24:19 +0000198 int pollTech, listenTech;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700199 synchronized (NfcActivityManager.this) {
200 NfcActivityState state = getActivityState(activity);
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700201 state.readerCallback = callback;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700202 state.readerModeFlags = flags;
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700203 state.readerModeExtras = extras;
Alisher Alikhodjaev280c0872024-05-21 20:24:19 +0000204 pollTech = state.mPollTech;
205 listenTech = state.mListenTech;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700206 token = state.token;
207 isResumed = state.resumed;
208 }
209 if (isResumed) {
Alisher Alikhodjaev280c0872024-05-21 20:24:19 +0000210 if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
211 || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
212 throw new IllegalStateException(
213 "Cannot be used when alternative DiscoveryTechnology is set");
214 } else {
215 setReaderMode(token, flags, extras);
216 }
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700217 }
218 }
219
220 public void disableReaderMode(Activity activity) {
221 boolean isResumed;
222 Binder token;
223 synchronized (NfcActivityManager.this) {
224 NfcActivityState state = getActivityState(activity);
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700225 state.readerCallback = null;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700226 state.readerModeFlags = 0;
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700227 state.readerModeExtras = null;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700228 token = state.token;
229 isResumed = state.resumed;
230 }
231 if (isResumed) {
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700232 setReaderMode(token, 0, null);
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700233 }
234
235 }
236
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700237 public void setReaderMode(Binder token, int flags, Bundle extras) {
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700238 if (DBG) Log.d(TAG, "Setting reader mode");
239 try {
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700240 NfcAdapter.sService.setReaderMode(token, this, flags, extras);
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700241 } catch (RemoteException e) {
242 mAdapter.attemptDeadServiceRecovery(e);
243 }
244 }
245
Nick Pellyc84c89a2011-08-22 22:27:11 -0700246 /**
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700247 * Request or unrequest NFC service callbacks.
Nick Pelly8ce7a272012-03-21 15:14:09 -0700248 * Makes IPC call - do not hold lock.
Nick Pellyc84c89a2011-08-22 22:27:11 -0700249 */
Martijn Coenen1360c552013-01-07 16:34:20 -0800250 void requestNfcServiceCallback() {
Nick Pellyc84c89a2011-08-22 22:27:11 -0700251 try {
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700252 NfcAdapter.sService.setAppCallback(this);
Nick Pellyc84c89a2011-08-22 22:27:11 -0700253 } catch (RemoteException e) {
254 mAdapter.attemptDeadServiceRecovery(e);
255 }
256 }
257
Martijn Coenend8bcfba2014-11-13 15:00:56 -0800258 void verifyNfcPermission() {
259 try {
260 NfcAdapter.sService.verifyNfcPermission();
261 } catch (RemoteException e) {
262 mAdapter.attemptDeadServiceRecovery(e);
263 }
264 }
265
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700266 @Override
267 public void onTagDiscovered(Tag tag) throws RemoteException {
268 NfcAdapter.ReaderCallback callback;
269 synchronized (NfcActivityManager.this) {
270 NfcActivityState state = findResumedActivityState();
271 if (state == null) return;
272
273 callback = state.readerCallback;
274 }
275
276 // Make callback without lock
277 if (callback != null) {
278 callback.onTagDiscovered(tag);
279 }
280
281 }
Nick Pelly8ce7a272012-03-21 15:14:09 -0700282 /** Callback from Activity life-cycle, on main thread */
283 @Override
284 public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
285
286 /** Callback from Activity life-cycle, on main thread */
287 @Override
288 public void onActivityStarted(Activity activity) { /* NO-OP */ }
289
290 /** Callback from Activity life-cycle, on main thread */
291 @Override
292 public void onActivityResumed(Activity activity) {
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700293 int readerModeFlags = 0;
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700294 Bundle readerModeExtras = null;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700295 Binder token;
Alisher Alikhodjaev8802f872023-11-28 14:52:44 -0800296 int pollTech;
297 int listenTech;
298
Nick Pelly8ce7a272012-03-21 15:14:09 -0700299 synchronized (NfcActivityManager.this) {
300 NfcActivityState state = findActivityState(activity);
301 if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
302 if (state == null) return;
303 state.resumed = true;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700304 token = state.token;
305 readerModeFlags = state.readerModeFlags;
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700306 readerModeExtras = state.readerModeExtras;
Alisher Alikhodjaev8802f872023-11-28 14:52:44 -0800307
308 pollTech = state.mPollTech;
309 listenTech = state.mListenTech;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700310 }
311 if (readerModeFlags != 0) {
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700312 setReaderMode(token, readerModeFlags, readerModeExtras);
Alisher Alikhodjaev8802f872023-11-28 14:52:44 -0800313 } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
314 || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
315 changeDiscoveryTech(token, pollTech, listenTech);
Nick Pelly8ce7a272012-03-21 15:14:09 -0700316 }
Martijn Coenen1360c552013-01-07 16:34:20 -0800317 requestNfcServiceCallback();
Nick Pelly8ce7a272012-03-21 15:14:09 -0700318 }
319
320 /** Callback from Activity life-cycle, on main thread */
321 @Override
322 public void onActivityPaused(Activity activity) {
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700323 boolean readerModeFlagsSet;
324 Binder token;
Alisher Alikhodjaev8802f872023-11-28 14:52:44 -0800325 int pollTech;
326 int listenTech;
327
Nick Pelly8ce7a272012-03-21 15:14:09 -0700328 synchronized (NfcActivityManager.this) {
329 NfcActivityState state = findActivityState(activity);
330 if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
331 if (state == null) return;
332 state.resumed = false;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700333 token = state.token;
334 readerModeFlagsSet = state.readerModeFlags != 0;
Alisher Alikhodjaev8802f872023-11-28 14:52:44 -0800335
336 pollTech = state.mPollTech;
337 listenTech = state.mListenTech;
Martijn Coenenc20ed2f2013-08-27 14:32:53 -0700338 }
339 if (readerModeFlagsSet) {
340 // Restore default p2p modes
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700341 setReaderMode(token, 0, null);
Alisher Alikhodjaev8802f872023-11-28 14:52:44 -0800342 } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
343 || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
344 changeDiscoveryTech(token,
345 NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH);
Nick Pelly8ce7a272012-03-21 15:14:09 -0700346 }
Nick Pelly8ce7a272012-03-21 15:14:09 -0700347 }
348
349 /** Callback from Activity life-cycle, on main thread */
350 @Override
351 public void onActivityStopped(Activity activity) { /* NO-OP */ }
352
353 /** Callback from Activity life-cycle, on main thread */
354 @Override
355 public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
356
357 /** Callback from Activity life-cycle, on main thread */
358 @Override
359 public void onActivityDestroyed(Activity activity) {
360 synchronized (NfcActivityManager.this) {
361 NfcActivityState state = findActivityState(activity);
362 if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
363 if (state != null) {
364 // release all associated references
365 destroyActivityState(activity);
366 }
367 }
368 }
Martijn Coenen5b1e0322013-09-02 20:38:47 -0700369
Alisher Alikhodjaev8802f872023-11-28 14:52:44 -0800370 /** setDiscoveryTechnology() implementation */
371 public void setDiscoveryTech(Activity activity, int pollTech, int listenTech) {
372 boolean isResumed;
373 Binder token;
374 boolean readerModeFlagsSet;
375 synchronized (NfcActivityManager.this) {
376 NfcActivityState state = getActivityState(activity);
377 readerModeFlagsSet = state.readerModeFlags != 0;
378 state.mListenTech = listenTech;
379 state.mPollTech = pollTech;
380 token = state.token;
381 isResumed = state.resumed;
382 }
383 if (!readerModeFlagsSet && isResumed) {
384 changeDiscoveryTech(token, pollTech, listenTech);
385 } else if (readerModeFlagsSet) {
386 throw new IllegalStateException("Cannot be used when the Reader Mode is enabled");
387 }
388 }
389
390 /** resetDiscoveryTechnology() implementation */
391 public void resetDiscoveryTech(Activity activity) {
392 boolean isResumed;
393 Binder token;
394 boolean readerModeFlagsSet;
395 synchronized (NfcActivityManager.this) {
396 NfcActivityState state = getActivityState(activity);
Alisher Alikhodjaev8802f872023-11-28 14:52:44 -0800397 state.mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
398 state.mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
399 token = state.token;
400 isResumed = state.resumed;
401 }
Alisher Alikhodjaev280c0872024-05-21 20:24:19 +0000402 if (isResumed) {
Alisher Alikhodjaev8802f872023-11-28 14:52:44 -0800403 changeDiscoveryTech(token, NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH);
404 }
405
406 }
407
408 private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) {
409 try {
410 NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech);
411 } catch (RemoteException e) {
412 mAdapter.attemptDeadServiceRecovery(e);
413 }
414 }
415
Nick Pellyc84c89a2011-08-22 22:27:11 -0700416}