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