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