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
50    // All objects in the lists are protected by this
51    final List<NfcApplicationState> mApps;  // Application(s) that have NFC state. Usually one
52    final List<NfcActivityState> mActivities;  // Activities that have NFC state
53
54    /**
55     * NFC State associated with an {@link Application}.
56     */
57    class NfcApplicationState {
58        int refCount = 0;
59        final Application app;
60        public NfcApplicationState(Application app) {
61            this.app = app;
62        }
63        public void register() {
64            refCount++;
65            if (refCount == 1) {
66                this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
67            }
68        }
69        public void unregister() {
70            refCount--;
71            if (refCount == 0) {
72                this.app.unregisterActivityLifecycleCallbacks(NfcActivityManager.this);
73            } else if (refCount < 0) {
74                Log.e(TAG, "-ve refcount for " + app);
75            }
76        }
77    }
78
79    NfcApplicationState findAppState(Application app) {
80        for (NfcApplicationState appState : mApps) {
81            if (appState.app == app) {
82                return appState;
83            }
84        }
85        return null;
86    }
87
88    void registerApplication(Application app) {
89        NfcApplicationState appState = findAppState(app);
90        if (appState == null) {
91            appState = new NfcApplicationState(app);
92            mApps.add(appState);
93        }
94        appState.register();
95    }
96
97    void unregisterApplication(Application app) {
98        NfcApplicationState appState = findAppState(app);
99        if (appState == null) {
100            Log.e(TAG, "app was not registered " + app);
101            return;
102        }
103        appState.unregister();
104    }
105
106    /**
107     * NFC state associated with an {@link Activity}
108     */
109    class NfcActivityState {
110        boolean resumed = false;
111        Activity activity;
112        NdefMessage ndefMessage = null;  // static NDEF message
113        NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
114        NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
115        NfcAdapter.CreateBeamUrisCallback uriCallback = null;
116        Uri[] uris = null;
117        int flags = 0;
118        int readerModeFlags = 0;
119        NfcAdapter.ReaderCallback readerCallback = null;
120        Bundle readerModeExtras = null;
121        Binder token;
122
123        public NfcActivityState(Activity activity) {
124            if (activity.getWindow().isDestroyed()) {
125                throw new IllegalStateException("activity is already destroyed");
126            }
127            // Check if activity is resumed right now, as we will not
128            // immediately get a callback for that.
129            resumed = activity.isResumed();
130
131            this.activity = activity;
132            this.token = new Binder();
133            registerApplication(activity.getApplication());
134        }
135        public void destroy() {
136            unregisterApplication(activity.getApplication());
137            resumed = false;
138            activity = null;
139            ndefMessage = null;
140            ndefMessageCallback = null;
141            onNdefPushCompleteCallback = null;
142            uriCallback = null;
143            uris = null;
144            readerModeFlags = 0;
145            token = null;
146        }
147        @Override
148        public String toString() {
149            StringBuilder s = new StringBuilder("[").append(" ");
150            s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
151            s.append(uriCallback).append(" ");
152            if (uris != null) {
153                for (Uri uri : uris) {
154                    s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
155                }
156            }
157            return s.toString();
158        }
159    }
160
161    /** find activity state from mActivities */
162    synchronized NfcActivityState findActivityState(Activity activity) {
163        for (NfcActivityState state : mActivities) {
164            if (state.activity == activity) {
165                return state;
166            }
167        }
168        return null;
169    }
170
171    /** find or create activity state from mActivities */
172    synchronized NfcActivityState getActivityState(Activity activity) {
173        NfcActivityState state = findActivityState(activity);
174        if (state == null) {
175            state = new NfcActivityState(activity);
176            mActivities.add(state);
177        }
178        return state;
179    }
180
181    synchronized NfcActivityState findResumedActivityState() {
182        for (NfcActivityState state : mActivities) {
183            if (state.resumed) {
184                return state;
185            }
186        }
187        return null;
188    }
189
190    synchronized void destroyActivityState(Activity activity) {
191        NfcActivityState activityState = findActivityState(activity);
192        if (activityState != null) {
193            activityState.destroy();
194            mActivities.remove(activityState);
195        }
196    }
197
198    public NfcActivityManager(NfcAdapter adapter) {
199        mAdapter = adapter;
200        mActivities = new LinkedList<NfcActivityState>();
201        mApps = new ArrayList<NfcApplicationState>(1);  // Android VM usually has 1 app
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(byte peerLlcpVersion) {
356        NfcAdapter.CreateNdefMessageCallback ndefCallback;
357        NfcAdapter.CreateBeamUrisCallback urisCallback;
358        NdefMessage message;
359        Activity activity;
360        Uri[] uris;
361        int flags;
362        NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
363        synchronized (NfcActivityManager.this) {
364            NfcActivityState state = findResumedActivityState();
365            if (state == null) return null;
366
367            ndefCallback = state.ndefMessageCallback;
368            urisCallback = state.uriCallback;
369            message = state.ndefMessage;
370            uris = state.uris;
371            flags = state.flags;
372            activity = state.activity;
373        }
374        final long ident = Binder.clearCallingIdentity();
375        try {
376            // Make callbacks without lock
377            if (ndefCallback != null) {
378                message = ndefCallback.createNdefMessage(event);
379            }
380            if (urisCallback != null) {
381                uris = urisCallback.createBeamUris(event);
382                if (uris != null) {
383                    ArrayList<Uri> validUris = new ArrayList<Uri>();
384                    for (Uri uri : uris) {
385                        if (uri == null) {
386                            Log.e(TAG, "Uri not allowed to be null.");
387                            continue;
388                        }
389                        String scheme = uri.getScheme();
390                        if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
391                                !scheme.equalsIgnoreCase("content"))) {
392                            Log.e(TAG, "Uri needs to have " +
393                                    "either scheme file or scheme content");
394                            continue;
395                        }
396                        uri = ContentProvider.maybeAddUserId(uri, activity.getUserId());
397                        validUris.add(uri);
398                    }
399
400                    uris = validUris.toArray(new Uri[validUris.size()]);
401                }
402            }
403            if (uris != null && uris.length > 0) {
404                for (Uri uri : uris) {
405                    // Grant the NFC process permission to read these URIs
406                    activity.grantUriPermission("com.android.nfc", uri,
407                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
408                }
409            }
410        } finally {
411            Binder.restoreCallingIdentity(ident);
412        }
413        return new BeamShareData(message, uris, activity.getUser(), flags);
414    }
415
416    /** Callback from NFC service, usually on binder thread */
417    @Override
418    public void onNdefPushComplete(byte peerLlcpVersion) {
419        NfcAdapter.OnNdefPushCompleteCallback callback;
420        synchronized (NfcActivityManager.this) {
421            NfcActivityState state = findResumedActivityState();
422            if (state == null) return;
423
424            callback = state.onNdefPushCompleteCallback;
425        }
426        NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion);
427        // Make callback without lock
428        if (callback != null) {
429            callback.onNdefPushComplete(event);
430        }
431    }
432
433    @Override
434    public void onTagDiscovered(Tag tag) throws RemoteException {
435        NfcAdapter.ReaderCallback callback;
436        synchronized (NfcActivityManager.this) {
437            NfcActivityState state = findResumedActivityState();
438            if (state == null) return;
439
440            callback = state.readerCallback;
441        }
442
443        // Make callback without lock
444        if (callback != null) {
445            callback.onTagDiscovered(tag);
446        }
447
448    }
449    /** Callback from Activity life-cycle, on main thread */
450    @Override
451    public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
452
453    /** Callback from Activity life-cycle, on main thread */
454    @Override
455    public void onActivityStarted(Activity activity) { /* NO-OP */ }
456
457    /** Callback from Activity life-cycle, on main thread */
458    @Override
459    public void onActivityResumed(Activity activity) {
460        int readerModeFlags = 0;
461        Bundle readerModeExtras = null;
462        Binder token;
463        synchronized (NfcActivityManager.this) {
464            NfcActivityState state = findActivityState(activity);
465            if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
466            if (state == null) return;
467            state.resumed = true;
468            token = state.token;
469            readerModeFlags = state.readerModeFlags;
470            readerModeExtras = state.readerModeExtras;
471        }
472        if (readerModeFlags != 0) {
473            setReaderMode(token, readerModeFlags, readerModeExtras);
474        }
475        requestNfcServiceCallback();
476    }
477
478    /** Callback from Activity life-cycle, on main thread */
479    @Override
480    public void onActivityPaused(Activity activity) {
481        boolean readerModeFlagsSet;
482        Binder token;
483        synchronized (NfcActivityManager.this) {
484            NfcActivityState state = findActivityState(activity);
485            if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
486            if (state == null) return;
487            state.resumed = false;
488            token = state.token;
489            readerModeFlagsSet = state.readerModeFlags != 0;
490        }
491        if (readerModeFlagsSet) {
492            // Restore default p2p modes
493            setReaderMode(token, 0, null);
494        }
495    }
496
497    /** Callback from Activity life-cycle, on main thread */
498    @Override
499    public void onActivityStopped(Activity activity) { /* NO-OP */ }
500
501    /** Callback from Activity life-cycle, on main thread */
502    @Override
503    public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
504
505    /** Callback from Activity life-cycle, on main thread */
506    @Override
507    public void onActivityDestroyed(Activity activity) {
508        synchronized (NfcActivityManager.this) {
509            NfcActivityState state = findActivityState(activity);
510            if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
511            if (state != null) {
512                // release all associated references
513                destroyActivityState(activity);
514            }
515        }
516    }
517
518}
519