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.compat.annotation.UnsupportedAppUsage;
22 import android.nfc.NfcAdapter.ReaderCallback;
23 import android.os.Binder;
24 import android.os.Bundle;
25 import android.os.RemoteException;
26 import android.util.Log;
27 
28 import java.util.ArrayList;
29 import java.util.LinkedList;
30 import java.util.List;
31 
32 /**
33  * Manages NFC API's that are coupled to the life-cycle of an Activity.
34  *
35  * <p>Uses {@link Application#registerActivityLifecycleCallbacks} to hook
36  * into activity life-cycle events such as onPause() and onResume().
37  *
38  * @hide
39  */
40 public final class NfcActivityManager extends IAppCallback.Stub
41         implements Application.ActivityLifecycleCallbacks {
42     static final String TAG = NfcAdapter.TAG;
43     static final Boolean DBG = false;
44 
45     @UnsupportedAppUsage
46     final NfcAdapter mAdapter;
47 
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;
NfcApplicationState(Application app)58         public NfcApplicationState(Application app) {
59             this.app = app;
60         }
register()61         public void register() {
62             refCount++;
63             if (refCount == 1) {
64                 this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
65             }
66         }
unregister()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 
findAppState(Application app)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 
registerApplication(Application app)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 
unregisterApplication(Application app)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     }
103 
104     /**
105      * NFC state associated with an {@link Activity}
106      */
107     class NfcActivityState {
108         boolean resumed = false;
109         Activity activity;
110         NfcAdapter.ReaderCallback readerCallback = null;
111         int readerModeFlags = 0;
112         Bundle readerModeExtras = null;
113         Binder token;
114 
115         int mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
116         int mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
117 
NfcActivityState(Activity activity)118         public NfcActivityState(Activity activity) {
119             if (activity.isDestroyed()) {
120                 throw new IllegalStateException("activity is already destroyed");
121             }
122             // Check if activity is resumed right now, as we will not
123             // immediately get a callback for that.
124             resumed = activity.isResumed();
125 
126             this.activity = activity;
127             this.token = new Binder();
128             registerApplication(activity.getApplication());
129         }
destroy()130         public void destroy() {
131             unregisterApplication(activity.getApplication());
132             resumed = false;
133             activity = null;
134             readerCallback = null;
135             readerModeFlags = 0;
136             readerModeExtras = null;
137             token = null;
138 
139             mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
140             mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
141         }
142         @Override
toString()143         public String toString() {
144             StringBuilder s = new StringBuilder("[");
145             s.append(readerCallback);
146             s.append("]");
147             return s.toString();
148         }
149     }
150 
151     /** find activity state from mActivities */
findActivityState(Activity activity)152     synchronized NfcActivityState findActivityState(Activity activity) {
153         for (NfcActivityState state : mActivities) {
154             if (state.activity == activity) {
155                 return state;
156             }
157         }
158         return null;
159     }
160 
161     /** find or create activity state from mActivities */
getActivityState(Activity activity)162     synchronized NfcActivityState getActivityState(Activity activity) {
163         NfcActivityState state = findActivityState(activity);
164         if (state == null) {
165             state = new NfcActivityState(activity);
166             mActivities.add(state);
167         }
168         return state;
169     }
170 
findResumedActivityState()171     synchronized NfcActivityState findResumedActivityState() {
172         for (NfcActivityState state : mActivities) {
173             if (state.resumed) {
174                 return state;
175             }
176         }
177         return null;
178     }
179 
destroyActivityState(Activity activity)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 
NfcActivityManager(NfcAdapter adapter)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
192     }
193 
enableReaderMode(Activity activity, ReaderCallback callback, int flags, Bundle extras)194     public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
195             Bundle extras) {
196         boolean isResumed;
197         Binder token;
198         int pollTech, listenTech;
199         synchronized (NfcActivityManager.this) {
200             NfcActivityState state = getActivityState(activity);
201             state.readerCallback = callback;
202             state.readerModeFlags = flags;
203             state.readerModeExtras = extras;
204             pollTech = state.mPollTech;
205             listenTech = state.mListenTech;
206             token = state.token;
207             isResumed = state.resumed;
208         }
209         if (isResumed) {
210             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             }
217         }
218     }
219 
disableReaderMode(Activity activity)220     public void disableReaderMode(Activity activity) {
221         boolean isResumed;
222         Binder token;
223         synchronized (NfcActivityManager.this) {
224             NfcActivityState state = getActivityState(activity);
225             state.readerCallback = null;
226             state.readerModeFlags = 0;
227             state.readerModeExtras = null;
228             token = state.token;
229             isResumed = state.resumed;
230         }
231         if (isResumed) {
232             setReaderMode(token, 0, null);
233         }
234 
235     }
236 
setReaderMode(Binder token, int flags, Bundle extras)237     public void setReaderMode(Binder token, int flags, Bundle extras) {
238         if (DBG) Log.d(TAG, "Setting reader mode");
239         try {
240             NfcAdapter.sService.setReaderMode(token, this, flags, extras);
241         } catch (RemoteException e) {
242             mAdapter.attemptDeadServiceRecovery(e);
243         }
244     }
245 
246     /**
247      * Request or unrequest NFC service callbacks.
248      * Makes IPC call - do not hold lock.
249      */
requestNfcServiceCallback()250     void requestNfcServiceCallback() {
251         try {
252             NfcAdapter.sService.setAppCallback(this);
253         } catch (RemoteException e) {
254             mAdapter.attemptDeadServiceRecovery(e);
255         }
256     }
257 
verifyNfcPermission()258     void verifyNfcPermission() {
259         try {
260             NfcAdapter.sService.verifyNfcPermission();
261         } catch (RemoteException e) {
262             mAdapter.attemptDeadServiceRecovery(e);
263         }
264     }
265 
266     @Override
onTagDiscovered(Tag tag)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     }
282     /** Callback from Activity life-cycle, on main thread */
283     @Override
onActivityCreated(Activity activity, Bundle savedInstanceState)284     public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
285 
286     /** Callback from Activity life-cycle, on main thread */
287     @Override
onActivityStarted(Activity activity)288     public void onActivityStarted(Activity activity) { /* NO-OP */ }
289 
290     /** Callback from Activity life-cycle, on main thread */
291     @Override
onActivityResumed(Activity activity)292     public void onActivityResumed(Activity activity) {
293         int readerModeFlags = 0;
294         Bundle readerModeExtras = null;
295         Binder token;
296         int pollTech;
297         int listenTech;
298 
299         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;
304             token = state.token;
305             readerModeFlags = state.readerModeFlags;
306             readerModeExtras = state.readerModeExtras;
307 
308             pollTech = state.mPollTech;
309             listenTech = state.mListenTech;
310         }
311         if (readerModeFlags != 0) {
312             setReaderMode(token, readerModeFlags, readerModeExtras);
313         } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
314                 || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
315             changeDiscoveryTech(token, pollTech, listenTech);
316         }
317         requestNfcServiceCallback();
318     }
319 
320     /** Callback from Activity life-cycle, on main thread */
321     @Override
onActivityPaused(Activity activity)322     public void onActivityPaused(Activity activity) {
323         boolean readerModeFlagsSet;
324         Binder token;
325         int pollTech;
326         int listenTech;
327 
328         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;
333             token = state.token;
334             readerModeFlagsSet = state.readerModeFlags != 0;
335 
336             pollTech = state.mPollTech;
337             listenTech = state.mListenTech;
338         }
339         if (readerModeFlagsSet) {
340             // Restore default p2p modes
341             setReaderMode(token, 0, null);
342         } 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);
346         }
347     }
348 
349     /** Callback from Activity life-cycle, on main thread */
350     @Override
onActivityStopped(Activity activity)351     public void onActivityStopped(Activity activity) { /* NO-OP */ }
352 
353     /** Callback from Activity life-cycle, on main thread */
354     @Override
onActivitySaveInstanceState(Activity activity, Bundle outState)355     public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
356 
357     /** Callback from Activity life-cycle, on main thread */
358     @Override
onActivityDestroyed(Activity activity)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     }
369 
370     /** setDiscoveryTechnology() implementation */
setDiscoveryTech(Activity activity, int pollTech, int listenTech)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 */
resetDiscoveryTech(Activity activity)391     public void resetDiscoveryTech(Activity activity) {
392         boolean isResumed;
393         Binder token;
394         boolean readerModeFlagsSet;
395         synchronized (NfcActivityManager.this) {
396             NfcActivityState state = getActivityState(activity);
397             state.mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
398             state.mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
399             token = state.token;
400             isResumed = state.resumed;
401         }
402         if (isResumed) {
403             changeDiscoveryTech(token, NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH);
404         }
405 
406     }
407 
changeDiscoveryTech(Binder token, int pollTech, int listenTech)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 
416 }
417