NfcActivityManager.java revision d8bcfbadf7037dd2574312a3bfa04dd70d967166
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.Intent;
22import android.net.Uri;
23import android.nfc.NfcAdapter.ReaderCallback;
24import android.os.Binder;
25import android.os.Bundle;
26import android.os.RemoteException;
27import android.util.Log;
28
29import java.util.ArrayList;
30import java.util.LinkedList;
31import 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 */
41public 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;
59        public NfcApplicationState(Application app) {
60            this.app = app;
61        }
62        public void register() {
63            refCount++;
64            if (refCount == 1) {
65                this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
66            }
67        }
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
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
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
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
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        }
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
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 */
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 */
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
180    synchronized NfcActivityState findResumedActivityState() {
181        for (NfcActivityState state : mActivities) {
182            if (state.resumed) {
183                return state;
184            }
185        }
186        return null;
187    }
188
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
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
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
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
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
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
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
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
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
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     */
337    void requestNfcServiceCallback() {
338        try {
339            NfcAdapter.sService.setAppCallback(this);
340        } catch (RemoteException e) {
341            mAdapter.attemptDeadServiceRecovery(e);
342        }
343    }
344
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
355    public BeamShareData createBeamShareData() {
356        NfcAdapter.CreateNdefMessageCallback ndefCallback;
357        NfcAdapter.CreateBeamUrisCallback urisCallback;
358        NdefMessage message;
359        Activity activity;
360        Uri[] uris;
361        int flags;
362        synchronized (NfcActivityManager.this) {
363            NfcActivityState state = findResumedActivityState();
364            if (state == null) return null;
365
366            ndefCallback = state.ndefMessageCallback;
367            urisCallback = state.uriCallback;
368            message = state.ndefMessage;
369            uris = state.uris;
370            flags = state.flags;
371            activity = state.activity;
372        }
373
374        // Make callbacks without lock
375        if (ndefCallback != null) {
376            message  = ndefCallback.createNdefMessage(mDefaultEvent);
377        }
378        if (urisCallback != null) {
379            uris = urisCallback.createBeamUris(mDefaultEvent);
380            if (uris != null) {
381                for (Uri uri : uris) {
382                    if (uri == null) {
383                        Log.e(TAG, "Uri not allowed to be null.");
384                        return null;
385                    }
386                    String scheme = uri.getScheme();
387                    if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
388                            !scheme.equalsIgnoreCase("content"))) {
389                        Log.e(TAG, "Uri needs to have " +
390                                "either scheme file or scheme content");
391                        return null;
392                    }
393                }
394            }
395        }
396        if (uris != null && uris.length > 0) {
397            for (Uri uri : uris) {
398                // Grant the NFC process permission to read these URIs
399                activity.grantUriPermission("com.android.nfc", uri,
400                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
401            }
402        }
403        return new BeamShareData(message, uris, flags);
404    }
405
406    /** Callback from NFC service, usually on binder thread */
407    @Override
408    public void onNdefPushComplete() {
409        NfcAdapter.OnNdefPushCompleteCallback callback;
410        synchronized (NfcActivityManager.this) {
411            NfcActivityState state = findResumedActivityState();
412            if (state == null) return;
413
414            callback = state.onNdefPushCompleteCallback;
415        }
416
417        // Make callback without lock
418        if (callback != null) {
419            callback.onNdefPushComplete(mDefaultEvent);
420        }
421    }
422
423    @Override
424    public void onTagDiscovered(Tag tag) throws RemoteException {
425        NfcAdapter.ReaderCallback callback;
426        synchronized (NfcActivityManager.this) {
427            NfcActivityState state = findResumedActivityState();
428            if (state == null) return;
429
430            callback = state.readerCallback;
431        }
432
433        // Make callback without lock
434        if (callback != null) {
435            callback.onTagDiscovered(tag);
436        }
437
438    }
439    /** Callback from Activity life-cycle, on main thread */
440    @Override
441    public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
442
443    /** Callback from Activity life-cycle, on main thread */
444    @Override
445    public void onActivityStarted(Activity activity) { /* NO-OP */ }
446
447    /** Callback from Activity life-cycle, on main thread */
448    @Override
449    public void onActivityResumed(Activity activity) {
450        int readerModeFlags = 0;
451        Bundle readerModeExtras = null;
452        Binder token;
453        synchronized (NfcActivityManager.this) {
454            NfcActivityState state = findActivityState(activity);
455            if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
456            if (state == null) return;
457            state.resumed = true;
458            token = state.token;
459            readerModeFlags = state.readerModeFlags;
460            readerModeExtras = state.readerModeExtras;
461        }
462        if (readerModeFlags != 0) {
463            setReaderMode(token, readerModeFlags, readerModeExtras);
464        }
465        requestNfcServiceCallback();
466    }
467
468    /** Callback from Activity life-cycle, on main thread */
469    @Override
470    public void onActivityPaused(Activity activity) {
471        boolean readerModeFlagsSet;
472        Binder token;
473        synchronized (NfcActivityManager.this) {
474            NfcActivityState state = findActivityState(activity);
475            if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
476            if (state == null) return;
477            state.resumed = false;
478            token = state.token;
479            readerModeFlagsSet = state.readerModeFlags != 0;
480        }
481        if (readerModeFlagsSet) {
482            // Restore default p2p modes
483            setReaderMode(token, 0, null);
484        }
485    }
486
487    /** Callback from Activity life-cycle, on main thread */
488    @Override
489    public void onActivityStopped(Activity activity) { /* NO-OP */ }
490
491    /** Callback from Activity life-cycle, on main thread */
492    @Override
493    public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
494
495    /** Callback from Activity life-cycle, on main thread */
496    @Override
497    public void onActivityDestroyed(Activity activity) {
498        synchronized (NfcActivityManager.this) {
499            NfcActivityState state = findActivityState(activity);
500            if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
501            if (state != null) {
502                // release all associated references
503                destroyActivityState(activity);
504            }
505        }
506    }
507
508}
509