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.content.ContentProvider;
22 import android.content.Intent;
23 import android.net.Uri;
24 import android.nfc.NfcAdapter.ReaderCallback;
25 import android.os.Binder;
26 import android.os.Bundle;
27 import android.os.RemoteException;
28 import android.os.UserHandle;
29 import android.util.Log;
30 
31 import java.util.ArrayList;
32 import java.util.LinkedList;
33 import java.util.List;
34 
35 /**
36  * Manages NFC API's that are coupled to the life-cycle of an Activity.
37  *
38  * <p>Uses {@link Application#registerActivityLifecycleCallbacks} to hook
39  * into activity life-cycle events such as onPause() and onResume().
40  *
41  * @hide
42  */
43 public final class NfcActivityManager extends IAppCallback.Stub
44         implements Application.ActivityLifecycleCallbacks {
45     static final String TAG = NfcAdapter.TAG;
46     static final Boolean DBG = false;
47 
48     final NfcAdapter mAdapter;
49 
50     // All objects in the lists are protected by this
51     final List<NfcApplicationState> mApps;  // Application(s) that have NFC state. Usually one
52     final List<NfcActivityState> mActivities;  // Activities that have NFC state
53 
54     /**
55      * NFC State associated with an {@link Application}.
56      */
57     class NfcApplicationState {
58         int refCount = 0;
59         final Application app;
NfcApplicationState(Application app)60         public NfcApplicationState(Application app) {
61             this.app = app;
62         }
register()63         public void register() {
64             refCount++;
65             if (refCount == 1) {
66                 this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
67             }
68         }
unregister()69         public void unregister() {
70             refCount--;
71             if (refCount == 0) {
72                 this.app.unregisterActivityLifecycleCallbacks(NfcActivityManager.this);
73             } else if (refCount < 0) {
74                 Log.e(TAG, "-ve refcount for " + app);
75             }
76         }
77     }
78 
findAppState(Application app)79     NfcApplicationState findAppState(Application app) {
80         for (NfcApplicationState appState : mApps) {
81             if (appState.app == app) {
82                 return appState;
83             }
84         }
85         return null;
86     }
87 
registerApplication(Application app)88     void registerApplication(Application app) {
89         NfcApplicationState appState = findAppState(app);
90         if (appState == null) {
91             appState = new NfcApplicationState(app);
92             mApps.add(appState);
93         }
94         appState.register();
95     }
96 
unregisterApplication(Application app)97     void unregisterApplication(Application app) {
98         NfcApplicationState appState = findAppState(app);
99         if (appState == null) {
100             Log.e(TAG, "app was not registered " + app);
101             return;
102         }
103         appState.unregister();
104     }
105 
106     /**
107      * NFC state associated with an {@link Activity}
108      */
109     class NfcActivityState {
110         boolean resumed = false;
111         Activity activity;
112         NdefMessage ndefMessage = null;  // static NDEF message
113         NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
114         NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
115         NfcAdapter.CreateBeamUrisCallback uriCallback = null;
116         Uri[] uris = null;
117         int flags = 0;
118         int readerModeFlags = 0;
119         NfcAdapter.ReaderCallback readerCallback = null;
120         Bundle readerModeExtras = null;
121         Binder token;
122 
NfcActivityState(Activity activity)123         public NfcActivityState(Activity activity) {
124             if (activity.getWindow().isDestroyed()) {
125                 throw new IllegalStateException("activity is already destroyed");
126             }
127             // Check if activity is resumed right now, as we will not
128             // immediately get a callback for that.
129             resumed = activity.isResumed();
130 
131             this.activity = activity;
132             this.token = new Binder();
133             registerApplication(activity.getApplication());
134         }
destroy()135         public void destroy() {
136             unregisterApplication(activity.getApplication());
137             resumed = false;
138             activity = null;
139             ndefMessage = null;
140             ndefMessageCallback = null;
141             onNdefPushCompleteCallback = null;
142             uriCallback = null;
143             uris = null;
144             readerModeFlags = 0;
145             token = null;
146         }
147         @Override
toString()148         public String toString() {
149             StringBuilder s = new StringBuilder("[").append(" ");
150             s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
151             s.append(uriCallback).append(" ");
152             if (uris != null) {
153                 for (Uri uri : uris) {
154                     s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
155                 }
156             }
157             return s.toString();
158         }
159     }
160 
161     /** find activity state from mActivities */
findActivityState(Activity activity)162     synchronized NfcActivityState findActivityState(Activity activity) {
163         for (NfcActivityState state : mActivities) {
164             if (state.activity == activity) {
165                 return state;
166             }
167         }
168         return null;
169     }
170 
171     /** find or create activity state from mActivities */
getActivityState(Activity activity)172     synchronized NfcActivityState getActivityState(Activity activity) {
173         NfcActivityState state = findActivityState(activity);
174         if (state == null) {
175             state = new NfcActivityState(activity);
176             mActivities.add(state);
177         }
178         return state;
179     }
180 
findResumedActivityState()181     synchronized NfcActivityState findResumedActivityState() {
182         for (NfcActivityState state : mActivities) {
183             if (state.resumed) {
184                 return state;
185             }
186         }
187         return null;
188     }
189 
destroyActivityState(Activity activity)190     synchronized void destroyActivityState(Activity activity) {
191         NfcActivityState activityState = findActivityState(activity);
192         if (activityState != null) {
193             activityState.destroy();
194             mActivities.remove(activityState);
195         }
196     }
197 
NfcActivityManager(NfcAdapter adapter)198     public NfcActivityManager(NfcAdapter adapter) {
199         mAdapter = adapter;
200         mActivities = new LinkedList<NfcActivityState>();
201         mApps = new ArrayList<NfcApplicationState>(1);  // Android VM usually has 1 app
202     }
203 
enableReaderMode(Activity activity, ReaderCallback callback, int flags, Bundle extras)204     public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
205             Bundle extras) {
206         boolean isResumed;
207         Binder token;
208         synchronized (NfcActivityManager.this) {
209             NfcActivityState state = getActivityState(activity);
210             state.readerCallback = callback;
211             state.readerModeFlags = flags;
212             state.readerModeExtras = extras;
213             token = state.token;
214             isResumed = state.resumed;
215         }
216         if (isResumed) {
217             setReaderMode(token, flags, extras);
218         }
219     }
220 
disableReaderMode(Activity activity)221     public void disableReaderMode(Activity activity) {
222         boolean isResumed;
223         Binder token;
224         synchronized (NfcActivityManager.this) {
225             NfcActivityState state = getActivityState(activity);
226             state.readerCallback = null;
227             state.readerModeFlags = 0;
228             state.readerModeExtras = null;
229             token = state.token;
230             isResumed = state.resumed;
231         }
232         if (isResumed) {
233             setReaderMode(token, 0, null);
234         }
235 
236     }
237 
setReaderMode(Binder token, int flags, Bundle extras)238     public void setReaderMode(Binder token, int flags, Bundle extras) {
239         if (DBG) Log.d(TAG, "Setting reader mode");
240         try {
241             NfcAdapter.sService.setReaderMode(token, this, flags, extras);
242         } catch (RemoteException e) {
243             mAdapter.attemptDeadServiceRecovery(e);
244         }
245     }
246 
setNdefPushContentUri(Activity activity, Uri[] uris)247     public void setNdefPushContentUri(Activity activity, Uri[] uris) {
248         boolean isResumed;
249         synchronized (NfcActivityManager.this) {
250             NfcActivityState state = getActivityState(activity);
251             state.uris = uris;
252             isResumed = state.resumed;
253         }
254         if (isResumed) {
255             // requestNfcServiceCallback() verifies permission also
256             requestNfcServiceCallback();
257         } else {
258             // Crash API calls early in case NFC permission is missing
259             verifyNfcPermission();
260         }
261     }
262 
263 
setNdefPushContentUriCallback(Activity activity, NfcAdapter.CreateBeamUrisCallback callback)264     public void setNdefPushContentUriCallback(Activity activity,
265             NfcAdapter.CreateBeamUrisCallback callback) {
266         boolean isResumed;
267         synchronized (NfcActivityManager.this) {
268             NfcActivityState state = getActivityState(activity);
269             state.uriCallback = callback;
270             isResumed = state.resumed;
271         }
272         if (isResumed) {
273             // requestNfcServiceCallback() verifies permission also
274             requestNfcServiceCallback();
275         } else {
276             // Crash API calls early in case NFC permission is missing
277             verifyNfcPermission();
278         }
279     }
280 
setNdefPushMessage(Activity activity, NdefMessage message, int flags)281     public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) {
282         boolean isResumed;
283         synchronized (NfcActivityManager.this) {
284             NfcActivityState state = getActivityState(activity);
285             state.ndefMessage = message;
286             state.flags = flags;
287             isResumed = state.resumed;
288         }
289         if (isResumed) {
290             // requestNfcServiceCallback() verifies permission also
291             requestNfcServiceCallback();
292         } else {
293             // Crash API calls early in case NFC permission is missing
294             verifyNfcPermission();
295         }
296     }
297 
setNdefPushMessageCallback(Activity activity, NfcAdapter.CreateNdefMessageCallback callback, int flags)298     public void setNdefPushMessageCallback(Activity activity,
299             NfcAdapter.CreateNdefMessageCallback callback, int flags) {
300         boolean isResumed;
301         synchronized (NfcActivityManager.this) {
302             NfcActivityState state = getActivityState(activity);
303             state.ndefMessageCallback = callback;
304             state.flags = flags;
305             isResumed = state.resumed;
306         }
307         if (isResumed) {
308             // requestNfcServiceCallback() verifies permission also
309             requestNfcServiceCallback();
310         } else {
311             // Crash API calls early in case NFC permission is missing
312             verifyNfcPermission();
313         }
314     }
315 
setOnNdefPushCompleteCallback(Activity activity, NfcAdapter.OnNdefPushCompleteCallback callback)316     public void setOnNdefPushCompleteCallback(Activity activity,
317             NfcAdapter.OnNdefPushCompleteCallback callback) {
318         boolean isResumed;
319         synchronized (NfcActivityManager.this) {
320             NfcActivityState state = getActivityState(activity);
321             state.onNdefPushCompleteCallback = callback;
322             isResumed = state.resumed;
323         }
324         if (isResumed) {
325             // requestNfcServiceCallback() verifies permission also
326             requestNfcServiceCallback();
327         } else {
328             // Crash API calls early in case NFC permission is missing
329             verifyNfcPermission();
330         }
331     }
332 
333     /**
334      * Request or unrequest NFC service callbacks.
335      * Makes IPC call - do not hold lock.
336      */
requestNfcServiceCallback()337     void requestNfcServiceCallback() {
338         try {
339             NfcAdapter.sService.setAppCallback(this);
340         } catch (RemoteException e) {
341             mAdapter.attemptDeadServiceRecovery(e);
342         }
343     }
344 
verifyNfcPermission()345     void verifyNfcPermission() {
346         try {
347             NfcAdapter.sService.verifyNfcPermission();
348         } catch (RemoteException e) {
349             mAdapter.attemptDeadServiceRecovery(e);
350         }
351     }
352 
353     /** Callback from NFC service, usually on binder thread */
354     @Override
createBeamShareData(byte peerLlcpVersion)355     public BeamShareData createBeamShareData(byte peerLlcpVersion) {
356         NfcAdapter.CreateNdefMessageCallback ndefCallback;
357         NfcAdapter.CreateBeamUrisCallback urisCallback;
358         NdefMessage message;
359         Activity activity;
360         Uri[] uris;
361         int flags;
362         NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
363         synchronized (NfcActivityManager.this) {
364             NfcActivityState state = findResumedActivityState();
365             if (state == null) return null;
366 
367             ndefCallback = state.ndefMessageCallback;
368             urisCallback = state.uriCallback;
369             message = state.ndefMessage;
370             uris = state.uris;
371             flags = state.flags;
372             activity = state.activity;
373         }
374         final long ident = Binder.clearCallingIdentity();
375         try {
376             // Make callbacks without lock
377             if (ndefCallback != null) {
378                 message = ndefCallback.createNdefMessage(event);
379             }
380             if (urisCallback != null) {
381                 uris = urisCallback.createBeamUris(event);
382                 if (uris != null) {
383                     ArrayList<Uri> validUris = new ArrayList<Uri>();
384                     for (Uri uri : uris) {
385                         if (uri == null) {
386                             Log.e(TAG, "Uri not allowed to be null.");
387                             continue;
388                         }
389                         String scheme = uri.getScheme();
390                         if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
391                                 !scheme.equalsIgnoreCase("content"))) {
392                             Log.e(TAG, "Uri needs to have " +
393                                     "either scheme file or scheme content");
394                             continue;
395                         }
396                         uri = ContentProvider.maybeAddUserId(uri, UserHandle.myUserId());
397                         validUris.add(uri);
398                     }
399 
400                     uris = validUris.toArray(new Uri[validUris.size()]);
401                 }
402             }
403             if (uris != null && uris.length > 0) {
404                 for (Uri uri : uris) {
405                     // Grant the NFC process permission to read these URIs
406                     activity.grantUriPermission("com.android.nfc", uri,
407                             Intent.FLAG_GRANT_READ_URI_PERMISSION);
408                 }
409             }
410         } finally {
411             Binder.restoreCallingIdentity(ident);
412         }
413         return new BeamShareData(message, uris, new UserHandle(UserHandle.myUserId()), flags);
414     }
415 
416     /** Callback from NFC service, usually on binder thread */
417     @Override
onNdefPushComplete(byte peerLlcpVersion)418     public void onNdefPushComplete(byte peerLlcpVersion) {
419         NfcAdapter.OnNdefPushCompleteCallback callback;
420         synchronized (NfcActivityManager.this) {
421             NfcActivityState state = findResumedActivityState();
422             if (state == null) return;
423 
424             callback = state.onNdefPushCompleteCallback;
425         }
426         NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
427         // Make callback without lock
428         if (callback != null) {
429             callback.onNdefPushComplete(event);
430         }
431     }
432 
433     @Override
onTagDiscovered(Tag tag)434     public void onTagDiscovered(Tag tag) throws RemoteException {
435         NfcAdapter.ReaderCallback callback;
436         synchronized (NfcActivityManager.this) {
437             NfcActivityState state = findResumedActivityState();
438             if (state == null) return;
439 
440             callback = state.readerCallback;
441         }
442 
443         // Make callback without lock
444         if (callback != null) {
445             callback.onTagDiscovered(tag);
446         }
447 
448     }
449     /** Callback from Activity life-cycle, on main thread */
450     @Override
onActivityCreated(Activity activity, Bundle savedInstanceState)451     public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
452 
453     /** Callback from Activity life-cycle, on main thread */
454     @Override
onActivityStarted(Activity activity)455     public void onActivityStarted(Activity activity) { /* NO-OP */ }
456 
457     /** Callback from Activity life-cycle, on main thread */
458     @Override
onActivityResumed(Activity activity)459     public void onActivityResumed(Activity activity) {
460         int readerModeFlags = 0;
461         Bundle readerModeExtras = null;
462         Binder token;
463         synchronized (NfcActivityManager.this) {
464             NfcActivityState state = findActivityState(activity);
465             if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
466             if (state == null) return;
467             state.resumed = true;
468             token = state.token;
469             readerModeFlags = state.readerModeFlags;
470             readerModeExtras = state.readerModeExtras;
471         }
472         if (readerModeFlags != 0) {
473             setReaderMode(token, readerModeFlags, readerModeExtras);
474         }
475         requestNfcServiceCallback();
476     }
477 
478     /** Callback from Activity life-cycle, on main thread */
479     @Override
onActivityPaused(Activity activity)480     public void onActivityPaused(Activity activity) {
481         boolean readerModeFlagsSet;
482         Binder token;
483         synchronized (NfcActivityManager.this) {
484             NfcActivityState state = findActivityState(activity);
485             if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
486             if (state == null) return;
487             state.resumed = false;
488             token = state.token;
489             readerModeFlagsSet = state.readerModeFlags != 0;
490         }
491         if (readerModeFlagsSet) {
492             // Restore default p2p modes
493             setReaderMode(token, 0, null);
494         }
495     }
496 
497     /** Callback from Activity life-cycle, on main thread */
498     @Override
onActivityStopped(Activity activity)499     public void onActivityStopped(Activity activity) { /* NO-OP */ }
500 
501     /** Callback from Activity life-cycle, on main thread */
502     @Override
onActivitySaveInstanceState(Activity activity, Bundle outState)503     public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
504 
505     /** Callback from Activity life-cycle, on main thread */
506     @Override
onActivityDestroyed(Activity activity)507     public void onActivityDestroyed(Activity activity) {
508         synchronized (NfcActivityManager.this) {
509             NfcActivityState state = findActivityState(activity);
510             if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
511             if (state != null) {
512                 // release all associated references
513                 destroyActivityState(activity);
514             }
515         }
516     }
517 
518 }
519