• 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.net.Uri;
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     final NfcAdapter mAdapter;
46     final NfcEvent mDefaultEvent;  // cached NfcEvent (its currently always the same)
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         NdefMessage ndefMessage = null;  // static NDEF message
111         NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
112         NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
113         NfcAdapter.CreateBeamUrisCallback uriCallback = null;
114         Uri[] uris = null;
115         int flags = 0;
116         int readerModeFlags = 0;
117         NfcAdapter.ReaderCallback readerCallback = null;
118         Bundle readerModeExtras = null;
119         Binder token;
120 
NfcActivityState(Activity activity)121         public NfcActivityState(Activity activity) {
122             if (activity.getWindow().isDestroyed()) {
123                 throw new IllegalStateException("activity is already destroyed");
124             }
125             // Check if activity is resumed right now, as we will not
126             // immediately get a callback for that.
127             resumed = activity.isResumed();
128 
129             this.activity = activity;
130             this.token = new Binder();
131             registerApplication(activity.getApplication());
132         }
destroy()133         public void destroy() {
134             unregisterApplication(activity.getApplication());
135             resumed = false;
136             activity = null;
137             ndefMessage = null;
138             ndefMessageCallback = null;
139             onNdefPushCompleteCallback = null;
140             uriCallback = null;
141             uris = null;
142             readerModeFlags = 0;
143             token = null;
144         }
145         @Override
toString()146         public String toString() {
147             StringBuilder s = new StringBuilder("[").append(" ");
148             s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
149             s.append(uriCallback).append(" ");
150             if (uris != null) {
151                 for (Uri uri : uris) {
152                     s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
153                 }
154             }
155             return s.toString();
156         }
157     }
158 
159     /** find activity state from mActivities */
findActivityState(Activity activity)160     synchronized NfcActivityState findActivityState(Activity activity) {
161         for (NfcActivityState state : mActivities) {
162             if (state.activity == activity) {
163                 return state;
164             }
165         }
166         return null;
167     }
168 
169     /** find or create activity state from mActivities */
getActivityState(Activity activity)170     synchronized NfcActivityState getActivityState(Activity activity) {
171         NfcActivityState state = findActivityState(activity);
172         if (state == null) {
173             state = new NfcActivityState(activity);
174             mActivities.add(state);
175         }
176         return state;
177     }
178 
findResumedActivityState()179     synchronized NfcActivityState findResumedActivityState() {
180         for (NfcActivityState state : mActivities) {
181             if (state.resumed) {
182                 return state;
183             }
184         }
185         return null;
186     }
187 
destroyActivityState(Activity activity)188     synchronized void destroyActivityState(Activity activity) {
189         NfcActivityState activityState = findActivityState(activity);
190         if (activityState != null) {
191             activityState.destroy();
192             mActivities.remove(activityState);
193         }
194     }
195 
NfcActivityManager(NfcAdapter adapter)196     public NfcActivityManager(NfcAdapter adapter) {
197         mAdapter = adapter;
198         mActivities = new LinkedList<NfcActivityState>();
199         mApps = new ArrayList<NfcApplicationState>(1);  // Android VM usually has 1 app
200         mDefaultEvent = new NfcEvent(mAdapter);
201     }
202 
enableReaderMode(Activity activity, ReaderCallback callback, int flags, Bundle extras)203     public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
204             Bundle extras) {
205         boolean isResumed;
206         Binder token;
207         synchronized (NfcActivityManager.this) {
208             NfcActivityState state = getActivityState(activity);
209             state.readerCallback = callback;
210             state.readerModeFlags = flags;
211             state.readerModeExtras = extras;
212             token = state.token;
213             isResumed = state.resumed;
214         }
215         if (isResumed) {
216             setReaderMode(token, flags, extras);
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 
setNdefPushContentUri(Activity activity, Uri[] uris)246     public void setNdefPushContentUri(Activity activity, Uri[] uris) {
247         boolean isResumed;
248         synchronized (NfcActivityManager.this) {
249             NfcActivityState state = getActivityState(activity);
250             state.uris = uris;
251             isResumed = state.resumed;
252         }
253         if (isResumed) {
254             requestNfcServiceCallback();
255         }
256     }
257 
258 
setNdefPushContentUriCallback(Activity activity, NfcAdapter.CreateBeamUrisCallback callback)259     public void setNdefPushContentUriCallback(Activity activity,
260             NfcAdapter.CreateBeamUrisCallback callback) {
261         boolean isResumed;
262         synchronized (NfcActivityManager.this) {
263             NfcActivityState state = getActivityState(activity);
264             state.uriCallback = callback;
265             isResumed = state.resumed;
266         }
267         if (isResumed) {
268             requestNfcServiceCallback();
269         }
270     }
271 
setNdefPushMessage(Activity activity, NdefMessage message, int flags)272     public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) {
273         boolean isResumed;
274         synchronized (NfcActivityManager.this) {
275             NfcActivityState state = getActivityState(activity);
276             state.ndefMessage = message;
277             state.flags = flags;
278             isResumed = state.resumed;
279         }
280         if (isResumed) {
281             requestNfcServiceCallback();
282         }
283     }
284 
setNdefPushMessageCallback(Activity activity, NfcAdapter.CreateNdefMessageCallback callback, int flags)285     public void setNdefPushMessageCallback(Activity activity,
286             NfcAdapter.CreateNdefMessageCallback callback, int flags) {
287         boolean isResumed;
288         synchronized (NfcActivityManager.this) {
289             NfcActivityState state = getActivityState(activity);
290             state.ndefMessageCallback = callback;
291             state.flags = flags;
292             isResumed = state.resumed;
293         }
294         if (isResumed) {
295             requestNfcServiceCallback();
296         }
297     }
298 
setOnNdefPushCompleteCallback(Activity activity, NfcAdapter.OnNdefPushCompleteCallback callback)299     public void setOnNdefPushCompleteCallback(Activity activity,
300             NfcAdapter.OnNdefPushCompleteCallback callback) {
301         boolean isResumed;
302         synchronized (NfcActivityManager.this) {
303             NfcActivityState state = getActivityState(activity);
304             state.onNdefPushCompleteCallback = callback;
305             isResumed = state.resumed;
306         }
307         if (isResumed) {
308             requestNfcServiceCallback();
309         }
310     }
311 
312     /**
313      * Request or unrequest NFC service callbacks.
314      * Makes IPC call - do not hold lock.
315      */
requestNfcServiceCallback()316     void requestNfcServiceCallback() {
317         try {
318             NfcAdapter.sService.setAppCallback(this);
319         } catch (RemoteException e) {
320             mAdapter.attemptDeadServiceRecovery(e);
321         }
322     }
323 
324     /** Callback from NFC service, usually on binder thread */
325     @Override
createBeamShareData()326     public BeamShareData createBeamShareData() {
327         NfcAdapter.CreateNdefMessageCallback ndefCallback;
328         NfcAdapter.CreateBeamUrisCallback urisCallback;
329         NdefMessage message;
330         Uri[] uris;
331         int flags;
332         synchronized (NfcActivityManager.this) {
333             NfcActivityState state = findResumedActivityState();
334             if (state == null) return null;
335 
336             ndefCallback = state.ndefMessageCallback;
337             urisCallback = state.uriCallback;
338             message = state.ndefMessage;
339             uris = state.uris;
340             flags = state.flags;
341         }
342 
343         // Make callbacks without lock
344         if (ndefCallback != null) {
345             message  = ndefCallback.createNdefMessage(mDefaultEvent);
346         }
347         if (urisCallback != null) {
348             uris = urisCallback.createBeamUris(mDefaultEvent);
349             if (uris != null) {
350                 for (Uri uri : uris) {
351                     if (uri == null) {
352                         Log.e(TAG, "Uri not allowed to be null.");
353                         return null;
354                     }
355                     String scheme = uri.getScheme();
356                     if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
357                             !scheme.equalsIgnoreCase("content"))) {
358                         Log.e(TAG, "Uri needs to have " +
359                                 "either scheme file or scheme content");
360                         return null;
361                     }
362                 }
363             }
364         }
365 
366         return new BeamShareData(message, uris, flags);
367     }
368 
369     /** Callback from NFC service, usually on binder thread */
370     @Override
onNdefPushComplete()371     public void onNdefPushComplete() {
372         NfcAdapter.OnNdefPushCompleteCallback callback;
373         synchronized (NfcActivityManager.this) {
374             NfcActivityState state = findResumedActivityState();
375             if (state == null) return;
376 
377             callback = state.onNdefPushCompleteCallback;
378         }
379 
380         // Make callback without lock
381         if (callback != null) {
382             callback.onNdefPushComplete(mDefaultEvent);
383         }
384     }
385 
386     @Override
onTagDiscovered(Tag tag)387     public void onTagDiscovered(Tag tag) throws RemoteException {
388         NfcAdapter.ReaderCallback callback;
389         synchronized (NfcActivityManager.this) {
390             NfcActivityState state = findResumedActivityState();
391             if (state == null) return;
392 
393             callback = state.readerCallback;
394         }
395 
396         // Make callback without lock
397         if (callback != null) {
398             callback.onTagDiscovered(tag);
399         }
400 
401     }
402     /** Callback from Activity life-cycle, on main thread */
403     @Override
onActivityCreated(Activity activity, Bundle savedInstanceState)404     public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
405 
406     /** Callback from Activity life-cycle, on main thread */
407     @Override
onActivityStarted(Activity activity)408     public void onActivityStarted(Activity activity) { /* NO-OP */ }
409 
410     /** Callback from Activity life-cycle, on main thread */
411     @Override
onActivityResumed(Activity activity)412     public void onActivityResumed(Activity activity) {
413         int readerModeFlags = 0;
414         Bundle readerModeExtras = null;
415         Binder token;
416         synchronized (NfcActivityManager.this) {
417             NfcActivityState state = findActivityState(activity);
418             if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
419             if (state == null) return;
420             state.resumed = true;
421             token = state.token;
422             readerModeFlags = state.readerModeFlags;
423             readerModeExtras = state.readerModeExtras;
424         }
425         if (readerModeFlags != 0) {
426             setReaderMode(token, readerModeFlags, readerModeExtras);
427         }
428         requestNfcServiceCallback();
429     }
430 
431     /** Callback from Activity life-cycle, on main thread */
432     @Override
onActivityPaused(Activity activity)433     public void onActivityPaused(Activity activity) {
434         boolean readerModeFlagsSet;
435         Binder token;
436         synchronized (NfcActivityManager.this) {
437             NfcActivityState state = findActivityState(activity);
438             if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
439             if (state == null) return;
440             state.resumed = false;
441             token = state.token;
442             readerModeFlagsSet = state.readerModeFlags != 0;
443         }
444         if (readerModeFlagsSet) {
445             // Restore default p2p modes
446             setReaderMode(token, 0, null);
447         }
448     }
449 
450     /** Callback from Activity life-cycle, on main thread */
451     @Override
onActivityStopped(Activity activity)452     public void onActivityStopped(Activity activity) { /* NO-OP */ }
453 
454     /** Callback from Activity life-cycle, on main thread */
455     @Override
onActivitySaveInstanceState(Activity activity, Bundle outState)456     public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
457 
458     /** Callback from Activity life-cycle, on main thread */
459     @Override
onActivityDestroyed(Activity activity)460     public void onActivityDestroyed(Activity activity) {
461         synchronized (NfcActivityManager.this) {
462             NfcActivityState state = findActivityState(activity);
463             if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
464             if (state != null) {
465                 // release all associated references
466                 destroyActivityState(activity);
467             }
468         }
469     }
470 
471 }
472