NfcActivityManager.java revision 20e8dd9f9e7cff63c83e36a1761538a04c224cc1
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.os.Bundle;
23import android.os.RemoteException;
24import android.util.Log;
25
26import java.util.ArrayList;
27import java.util.LinkedList;
28import java.util.List;
29
30/**
31 * Manages NFC API's that are coupled to the life-cycle of an Activity.
32 *
33 * <p>Uses {@link Application#registerActivityLifecycleCallbacks} to hook
34 * into activity life-cycle events such as onPause() and onResume().
35 *
36 * @hide
37 */
38public final class NfcActivityManager extends INdefPushCallback.Stub
39        implements Application.ActivityLifecycleCallbacks {
40    static final String TAG = NfcAdapter.TAG;
41    static final Boolean DBG = false;
42
43    final NfcAdapter mAdapter;
44    final NfcEvent mDefaultEvent;  // cached NfcEvent (its currently always the same)
45
46    // All objects in the lists are protected by this
47    final List<NfcApplicationState> mApps;  // Application(s) that have NFC state. Usually one
48    final List<NfcActivityState> mActivities;  // Activities that have NFC state
49
50    /**
51     * NFC State associated with an {@link Application}.
52     */
53    class NfcApplicationState {
54        int refCount = 0;
55        final Application app;
56        public NfcApplicationState(Application app) {
57            this.app = app;
58        }
59        public void register() {
60            refCount++;
61            if (refCount == 1) {
62                this.app.registerActivityLifecycleCallbacks(NfcActivityManager.this);
63            }
64        }
65        public void unregister() {
66            refCount--;
67            if (refCount == 0) {
68                this.app.unregisterActivityLifecycleCallbacks(NfcActivityManager.this);
69            } else if (refCount < 0) {
70                Log.e(TAG, "-ve refcount for " + app);
71            }
72        }
73    }
74
75    NfcApplicationState findAppState(Application app) {
76        for (NfcApplicationState appState : mApps) {
77            if (appState.app == app) {
78                return appState;
79            }
80        }
81        return null;
82    }
83
84    void registerApplication(Application app) {
85        NfcApplicationState appState = findAppState(app);
86        if (appState == null) {
87            appState = new NfcApplicationState(app);
88            mApps.add(appState);
89        }
90        appState.register();
91    }
92
93    void unregisterApplication(Application app) {
94        NfcApplicationState appState = findAppState(app);
95        if (appState == null) {
96            Log.e(TAG, "app was not registered " + app);
97            return;
98        }
99        appState.unregister();
100    }
101
102    /**
103     * NFC state associated with an {@link Activity}
104     */
105    class NfcActivityState {
106        boolean resumed = false;
107        Activity activity;
108        NdefMessage ndefMessage = null;  // static NDEF message
109        NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
110        NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
111        NfcAdapter.CreateBeamUrisCallback uriCallback = null;
112        Uri[] uris = null;
113        public NfcActivityState(Activity activity) {
114            if (activity.getWindow().isDestroyed()) {
115                throw new IllegalStateException("activity is already destroyed");
116            }
117            // Check if activity is resumed right now, as we will not
118            // immediately get a callback for that.
119            resumed = activity.isResumed();
120
121            this.activity = activity;
122            registerApplication(activity.getApplication());
123        }
124        public void destroy() {
125            unregisterApplication(activity.getApplication());
126            resumed = false;
127            activity = null;
128            ndefMessage = null;
129            ndefMessageCallback = null;
130            onNdefPushCompleteCallback = null;
131            uriCallback = null;
132            uris = null;
133        }
134        @Override
135        public String toString() {
136            StringBuilder s = new StringBuilder("[").append(" ");
137            s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
138            s.append(uriCallback).append(" ");
139            if (uris != null) {
140                for (Uri uri : uris) {
141                    s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
142                }
143            }
144            return s.toString();
145        }
146    }
147
148    /** find activity state from mActivities */
149    synchronized NfcActivityState findActivityState(Activity activity) {
150        for (NfcActivityState state : mActivities) {
151            if (state.activity == activity) {
152                return state;
153            }
154        }
155        return null;
156    }
157
158    /** find or create activity state from mActivities */
159    synchronized NfcActivityState getActivityState(Activity activity) {
160        NfcActivityState state = findActivityState(activity);
161        if (state == null) {
162            state = new NfcActivityState(activity);
163            mActivities.add(state);
164        }
165        return state;
166    }
167
168    synchronized NfcActivityState findResumedActivityState() {
169        for (NfcActivityState state : mActivities) {
170            if (state.resumed) {
171                return state;
172            }
173        }
174        return null;
175    }
176
177    synchronized void destroyActivityState(Activity activity) {
178        NfcActivityState activityState = findActivityState(activity);
179        if (activityState != null) {
180            activityState.destroy();
181            mActivities.remove(activityState);
182        }
183    }
184
185    public NfcActivityManager(NfcAdapter adapter) {
186        mAdapter = adapter;
187        mActivities = new LinkedList<NfcActivityState>();
188        mApps = new ArrayList<NfcApplicationState>(1);  // Android VM usually has 1 app
189        mDefaultEvent = new NfcEvent(mAdapter);
190    }
191
192    public void setNdefPushContentUri(Activity activity, Uri[] uris) {
193        boolean isResumed;
194        synchronized (NfcActivityManager.this) {
195            NfcActivityState state = getActivityState(activity);
196            state.uris = uris;
197            isResumed = state.resumed;
198        }
199        if (isResumed) {
200            requestNfcServiceCallback(true);
201        }
202    }
203
204
205    public void setNdefPushContentUriCallback(Activity activity,
206            NfcAdapter.CreateBeamUrisCallback callback) {
207        boolean isResumed;
208        synchronized (NfcActivityManager.this) {
209            NfcActivityState state = getActivityState(activity);
210            state.uriCallback = callback;
211            isResumed = state.resumed;
212        }
213        if (isResumed) {
214            requestNfcServiceCallback(true);
215        }
216    }
217
218    public void setNdefPushMessage(Activity activity, NdefMessage message) {
219        boolean isResumed;
220        synchronized (NfcActivityManager.this) {
221            NfcActivityState state = getActivityState(activity);
222            state.ndefMessage = message;
223            isResumed = state.resumed;
224        }
225        if (isResumed) {
226            requestNfcServiceCallback(true);
227        }
228    }
229
230    public void setNdefPushMessageCallback(Activity activity,
231            NfcAdapter.CreateNdefMessageCallback callback) {
232        boolean isResumed;
233        synchronized (NfcActivityManager.this) {
234            NfcActivityState state = getActivityState(activity);
235            state.ndefMessageCallback = callback;
236            isResumed = state.resumed;
237        }
238        if (isResumed) {
239            requestNfcServiceCallback(true);
240        }
241    }
242
243    public void setOnNdefPushCompleteCallback(Activity activity,
244            NfcAdapter.OnNdefPushCompleteCallback callback) {
245        boolean isResumed;
246        synchronized (NfcActivityManager.this) {
247            NfcActivityState state = getActivityState(activity);
248            state.onNdefPushCompleteCallback = callback;
249            isResumed = state.resumed;
250        }
251        if (isResumed) {
252            requestNfcServiceCallback(true);
253        }
254    }
255
256    /**
257     * Request or unrequest NFC service callbacks for NDEF push.
258     * Makes IPC call - do not hold lock.
259     * TODO: Do not do IPC on every onPause/onResume
260     */
261    void requestNfcServiceCallback(boolean request) {
262        try {
263            NfcAdapter.sService.setNdefPushCallback(request ? this : null);
264        } catch (RemoteException e) {
265            mAdapter.attemptDeadServiceRecovery(e);
266        }
267    }
268
269    /** Callback from NFC service, usually on binder thread */
270    @Override
271    public NdefMessage createMessage() {
272        NfcAdapter.CreateNdefMessageCallback callback;
273        NdefMessage message;
274        synchronized (NfcActivityManager.this) {
275            NfcActivityState state = findResumedActivityState();
276            if (state == null) return null;
277
278            callback = state.ndefMessageCallback;
279            message = state.ndefMessage;
280        }
281
282        // Make callback without lock
283        if (callback != null) {
284            return callback.createNdefMessage(mDefaultEvent);
285        } else {
286            return message;
287        }
288    }
289
290    /** Callback from NFC service, usually on binder thread */
291    @Override
292    public Uri[] getUris() {
293        Uri[] uris;
294        NfcAdapter.CreateBeamUrisCallback callback;
295        synchronized (NfcActivityManager.this) {
296            NfcActivityState state = findResumedActivityState();
297            if (state == null) return null;
298            uris = state.uris;
299            callback = state.uriCallback;
300        }
301        if (callback != null) {
302            return callback.createBeamUris(mDefaultEvent);
303        } else {
304            return uris;
305        }
306    }
307
308    /** Callback from NFC service, usually on binder thread */
309    @Override
310    public void onNdefPushComplete() {
311        NfcAdapter.OnNdefPushCompleteCallback callback;
312        synchronized (NfcActivityManager.this) {
313            NfcActivityState state = findResumedActivityState();
314            if (state == null) return;
315
316            callback = state.onNdefPushCompleteCallback;
317        }
318
319        // Make callback without lock
320        if (callback != null) {
321            callback.onNdefPushComplete(mDefaultEvent);
322        }
323    }
324
325    /** Callback from Activity life-cycle, on main thread */
326    @Override
327    public void onActivityCreated(Activity activity, Bundle savedInstanceState) { /* NO-OP */ }
328
329    /** Callback from Activity life-cycle, on main thread */
330    @Override
331    public void onActivityStarted(Activity activity) { /* NO-OP */ }
332
333    /** Callback from Activity life-cycle, on main thread */
334    @Override
335    public void onActivityResumed(Activity activity) {
336        synchronized (NfcActivityManager.this) {
337            NfcActivityState state = findActivityState(activity);
338            if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
339            if (state == null) return;
340            state.resumed = true;
341        }
342        requestNfcServiceCallback(true);
343    }
344
345    /** Callback from Activity life-cycle, on main thread */
346    @Override
347    public void onActivityPaused(Activity activity) {
348        synchronized (NfcActivityManager.this) {
349            NfcActivityState state = findActivityState(activity);
350            if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
351            if (state == null) return;
352            state.resumed = false;
353        }
354        requestNfcServiceCallback(false);
355    }
356
357    /** Callback from Activity life-cycle, on main thread */
358    @Override
359    public void onActivityStopped(Activity activity) { /* NO-OP */ }
360
361    /** Callback from Activity life-cycle, on main thread */
362    @Override
363    public void onActivitySaveInstanceState(Activity activity, Bundle outState) { /* NO-OP */ }
364
365    /** Callback from Activity life-cycle, on main thread */
366    @Override
367    public void onActivityDestroyed(Activity activity) {
368        synchronized (NfcActivityManager.this) {
369            NfcActivityState state = findActivityState(activity);
370            if (DBG) Log.d(TAG, "onDestroy() for " + activity + " " + state);
371            if (state != null) {
372                // release all associated references
373                destroyActivityState(activity);
374            }
375        }
376    }
377
378}
379