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.net.Uri;
22import android.nfc.NfcAdapter.ReaderCallback;
23import android.os.Binder;
24import android.os.Bundle;
25import android.os.RemoteException;
26import android.util.Log;
27
28import java.util.ArrayList;
29import java.util.LinkedList;
30import 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 */
40public 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;
58        public NfcApplicationState(Application app) {
59            this.app = app;
60        }
61        public void register() {
62            refCount++;
63            if (refCount == 1) {
64                this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
65            }
66        }
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
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
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
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
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        }
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
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 */
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 */
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
179    synchronized NfcActivityState findResumedActivityState() {
180        for (NfcActivityState state : mActivities) {
181            if (state.resumed) {
182                return state;
183            }
184        }
185        return null;
186    }
187
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
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
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
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
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
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
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
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
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
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     */
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
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
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
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
404    public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
405
406    /** Callback from Activity life-cycle, on main thread */
407    @Override
408    public void onActivityStarted(Activity activity) { /* NO-OP */ }
409
410    /** Callback from Activity life-cycle, on main thread */
411    @Override
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
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
452    public void onActivityStopped(Activity activity) { /* NO-OP */ }
453
454    /** Callback from Activity life-cycle, on main thread */
455    @Override
456    public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
457
458    /** Callback from Activity life-cycle, on main thread */
459    @Override
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