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 
NfcActivityState(Activity activity)115         public NfcActivityState(Activity activity) {
116             if (activity.isDestroyed()) {
117                 throw new IllegalStateException("activity is already destroyed");
118             }
119             // Check if activity is resumed right now, as we will not
120             // immediately get a callback for that.
121             resumed = activity.isResumed();
122 
123             this.activity = activity;
124             this.token = new Binder();
125             registerApplication(activity.getApplication());
126         }
destroy()127         public void destroy() {
128             unregisterApplication(activity.getApplication());
129             resumed = false;
130             activity = null;
131             readerCallback = null;
132             readerModeFlags = 0;
133             readerModeExtras = null;
134             token = null;
135         }
136         @Override
toString()137         public String toString() {
138             StringBuilder s = new StringBuilder("[");
139             s.append(readerCallback);
140             s.append("]");
141             return s.toString();
142         }
143     }
144 
145     /** find activity state from mActivities */
findActivityState(Activity activity)146     synchronized NfcActivityState findActivityState(Activity activity) {
147         for (NfcActivityState state : mActivities) {
148             if (state.activity == activity) {
149                 return state;
150             }
151         }
152         return null;
153     }
154 
155     /** find or create activity state from mActivities */
getActivityState(Activity activity)156     synchronized NfcActivityState getActivityState(Activity activity) {
157         NfcActivityState state = findActivityState(activity);
158         if (state == null) {
159             state = new NfcActivityState(activity);
160             mActivities.add(state);
161         }
162         return state;
163     }
164 
findResumedActivityState()165     synchronized NfcActivityState findResumedActivityState() {
166         for (NfcActivityState state : mActivities) {
167             if (state.resumed) {
168                 return state;
169             }
170         }
171         return null;
172     }
173 
destroyActivityState(Activity activity)174     synchronized void destroyActivityState(Activity activity) {
175         NfcActivityState activityState = findActivityState(activity);
176         if (activityState != null) {
177             activityState.destroy();
178             mActivities.remove(activityState);
179         }
180     }
181 
NfcActivityManager(NfcAdapter adapter)182     public NfcActivityManager(NfcAdapter adapter) {
183         mAdapter = adapter;
184         mActivities = new LinkedList<NfcActivityState>();
185         mApps = new ArrayList<NfcApplicationState>(1);  // Android VM usually has 1 app
186     }
187 
enableReaderMode(Activity activity, ReaderCallback callback, int flags, Bundle extras)188     public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
189             Bundle extras) {
190         boolean isResumed;
191         Binder token;
192         synchronized (NfcActivityManager.this) {
193             NfcActivityState state = getActivityState(activity);
194             state.readerCallback = callback;
195             state.readerModeFlags = flags;
196             state.readerModeExtras = extras;
197             token = state.token;
198             isResumed = state.resumed;
199         }
200         if (isResumed) {
201             setReaderMode(token, flags, extras);
202         }
203     }
204 
disableReaderMode(Activity activity)205     public void disableReaderMode(Activity activity) {
206         boolean isResumed;
207         Binder token;
208         synchronized (NfcActivityManager.this) {
209             NfcActivityState state = getActivityState(activity);
210             state.readerCallback = null;
211             state.readerModeFlags = 0;
212             state.readerModeExtras = null;
213             token = state.token;
214             isResumed = state.resumed;
215         }
216         if (isResumed) {
217             setReaderMode(token, 0, null);
218         }
219 
220     }
221 
setReaderMode(Binder token, int flags, Bundle extras)222     public void setReaderMode(Binder token, int flags, Bundle extras) {
223         if (DBG) Log.d(TAG, "Setting reader mode");
224         try {
225             NfcAdapter.sService.setReaderMode(token, this, flags, extras);
226         } catch (RemoteException e) {
227             mAdapter.attemptDeadServiceRecovery(e);
228         }
229     }
230 
231     /**
232      * Request or unrequest NFC service callbacks.
233      * Makes IPC call - do not hold lock.
234      */
requestNfcServiceCallback()235     void requestNfcServiceCallback() {
236         try {
237             NfcAdapter.sService.setAppCallback(this);
238         } catch (RemoteException e) {
239             mAdapter.attemptDeadServiceRecovery(e);
240         }
241     }
242 
verifyNfcPermission()243     void verifyNfcPermission() {
244         try {
245             NfcAdapter.sService.verifyNfcPermission();
246         } catch (RemoteException e) {
247             mAdapter.attemptDeadServiceRecovery(e);
248         }
249     }
250 
251     @Override
onTagDiscovered(Tag tag)252     public void onTagDiscovered(Tag tag) throws RemoteException {
253         NfcAdapter.ReaderCallback callback;
254         synchronized (NfcActivityManager.this) {
255             NfcActivityState state = findResumedActivityState();
256             if (state == null) return;
257 
258             callback = state.readerCallback;
259         }
260 
261         // Make callback without lock
262         if (callback != null) {
263             callback.onTagDiscovered(tag);
264         }
265 
266     }
267     /** Callback from Activity life-cycle, on main thread */
268     @Override
onActivityCreated(Activity activity, Bundle savedInstanceState)269     public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
270 
271     /** Callback from Activity life-cycle, on main thread */
272     @Override
onActivityStarted(Activity activity)273     public void onActivityStarted(Activity activity) { /* NO-OP */ }
274 
275     /** Callback from Activity life-cycle, on main thread */
276     @Override
onActivityResumed(Activity activity)277     public void onActivityResumed(Activity activity) {
278         int readerModeFlags = 0;
279         Bundle readerModeExtras = null;
280         Binder token;
281         synchronized (NfcActivityManager.this) {
282             NfcActivityState state = findActivityState(activity);
283             if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
284             if (state == null) return;
285             state.resumed = true;
286             token = state.token;
287             readerModeFlags = state.readerModeFlags;
288             readerModeExtras = state.readerModeExtras;
289         }
290         if (readerModeFlags != 0) {
291             setReaderMode(token, readerModeFlags, readerModeExtras);
292         }
293         requestNfcServiceCallback();
294     }
295 
296     /** Callback from Activity life-cycle, on main thread */
297     @Override
onActivityPaused(Activity activity)298     public void onActivityPaused(Activity activity) {
299         boolean readerModeFlagsSet;
300         Binder token;
301         synchronized (NfcActivityManager.this) {
302             NfcActivityState state = findActivityState(activity);
303             if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
304             if (state == null) return;
305             state.resumed = false;
306             token = state.token;
307             readerModeFlagsSet = state.readerModeFlags != 0;
308         }
309         if (readerModeFlagsSet) {
310             // Restore default p2p modes
311             setReaderMode(token, 0, null);
312         }
313     }
314 
315     /** Callback from Activity life-cycle, on main thread */
316     @Override
onActivityStopped(Activity activity)317     public void onActivityStopped(Activity activity) { /* NO-OP */ }
318 
319     /** Callback from Activity life-cycle, on main thread */
320     @Override
onActivitySaveInstanceState(Activity activity, Bundle outState)321     public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
322 
323     /** Callback from Activity life-cycle, on main thread */
324     @Override
onActivityDestroyed(Activity activity)325     public void onActivityDestroyed(Activity activity) {
326         synchronized (NfcActivityManager.this) {
327             NfcActivityState state = findActivityState(activity);
328             if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
329             if (state != null) {
330                 // release all associated references
331                 destroyActivityState(activity);
332             }
333         }
334     }
335 
336 }
337