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