RemoteCallbackList.java revision 390517be2d60dd6e6264150c190c372d89bb331a
1/*
2 * Copyright (C) 2008 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.os;
18
19import android.util.ArrayMap;
20
21import java.util.HashMap;
22
23/**
24 * Takes care of the grunt work of maintaining a list of remote interfaces,
25 * typically for the use of performing callbacks from a
26 * {@link android.app.Service} to its clients.  In particular, this:
27 *
28 * <ul>
29 * <li> Keeps track of a set of registered {@link IInterface} callbacks,
30 * taking care to identify them through their underlying unique {@link IBinder}
31 * (by calling {@link IInterface#asBinder IInterface.asBinder()}.
32 * <li> Attaches a {@link IBinder.DeathRecipient IBinder.DeathRecipient} to
33 * each registered interface, so that it can be cleaned out of the list if its
34 * process goes away.
35 * <li> Performs locking of the underlying list of interfaces to deal with
36 * multithreaded incoming calls, and a thread-safe way to iterate over a
37 * snapshot of the list without holding its lock.
38 * </ul>
39 *
40 * <p>To use this class, simply create a single instance along with your
41 * service, and call its {@link #register} and {@link #unregister} methods
42 * as client register and unregister with your service.  To call back on to
43 * the registered clients, use {@link #beginBroadcast},
44 * {@link #getBroadcastItem}, and {@link #finishBroadcast}.
45 *
46 * <p>If a registered callback's process goes away, this class will take
47 * care of automatically removing it from the list.  If you want to do
48 * additional work in this situation, you can create a subclass that
49 * implements the {@link #onCallbackDied} method.
50 */
51public class RemoteCallbackList<E extends IInterface> {
52    /*package*/ ArrayMap<IBinder, Callback> mCallbacks
53            = new ArrayMap<IBinder, Callback>();
54    private Object[] mActiveBroadcast;
55    private int mBroadcastCount = -1;
56    private boolean mKilled = false;
57
58    private final class Callback implements IBinder.DeathRecipient {
59        final E mCallback;
60        final Object mCookie;
61
62        Callback(E callback, Object cookie) {
63            mCallback = callback;
64            mCookie = cookie;
65        }
66
67        public void binderDied() {
68            synchronized (mCallbacks) {
69                mCallbacks.remove(mCallback.asBinder());
70            }
71            onCallbackDied(mCallback, mCookie);
72        }
73    }
74
75    /**
76     * Simple version of {@link RemoteCallbackList#register(E, Object)}
77     * that does not take a cookie object.
78     */
79    public boolean register(E callback) {
80        return register(callback, null);
81    }
82
83    /**
84     * Add a new callback to the list.  This callback will remain in the list
85     * until a corresponding call to {@link #unregister} or its hosting process
86     * goes away.  If the callback was already registered (determined by
87     * checking to see if the {@link IInterface#asBinder callback.asBinder()}
88     * object is already in the list), then it will be left as-is.
89     * Registrations are not counted; a single call to {@link #unregister}
90     * will remove a callback after any number calls to register it.
91     *
92     * @param callback The callback interface to be added to the list.  Must
93     * not be null -- passing null here will cause a NullPointerException.
94     * Most services will want to check for null before calling this with
95     * an object given from a client, so that clients can't crash the
96     * service with bad data.
97     *
98     * @param cookie Optional additional data to be associated with this
99     * callback.
100     *
101     * @return Returns true if the callback was successfully added to the list.
102     * Returns false if it was not added, either because {@link #kill} had
103     * previously been called or the callback's process has gone away.
104     *
105     * @see #unregister
106     * @see #kill
107     * @see #onCallbackDied
108     */
109    public boolean register(E callback, Object cookie) {
110        synchronized (mCallbacks) {
111            if (mKilled) {
112                return false;
113            }
114            IBinder binder = callback.asBinder();
115            try {
116                Callback cb = new Callback(callback, cookie);
117                binder.linkToDeath(cb, 0);
118                mCallbacks.put(binder, cb);
119                return true;
120            } catch (RemoteException e) {
121                return false;
122            }
123        }
124    }
125
126    /**
127     * Remove from the list a callback that was previously added with
128     * {@link #register}.  This uses the
129     * {@link IInterface#asBinder callback.asBinder()} object to correctly
130     * find the previous registration.
131     * Registrations are not counted; a single unregister call will remove
132     * a callback after any number calls to {@link #register} for it.
133     *
134     * @param callback The callback to be removed from the list.  Passing
135     * null here will cause a NullPointerException, so you will generally want
136     * to check for null before calling.
137     *
138     * @return Returns true if the callback was found and unregistered.  Returns
139     * false if the given callback was not found on the list.
140     *
141     * @see #register
142     */
143    public boolean unregister(E callback) {
144        synchronized (mCallbacks) {
145            Callback cb = mCallbacks.remove(callback.asBinder());
146            if (cb != null) {
147                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
148                return true;
149            }
150            return false;
151        }
152    }
153
154    /**
155     * Disable this callback list.  All registered callbacks are unregistered,
156     * and the list is disabled so that future calls to {@link #register} will
157     * fail.  This should be used when a Service is stopping, to prevent clients
158     * from registering callbacks after it is stopped.
159     *
160     * @see #register
161     */
162    public void kill() {
163        synchronized (mCallbacks) {
164            for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) {
165                Callback cb = mCallbacks.valueAt(cbi);
166                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
167            }
168            mCallbacks.clear();
169            mKilled = true;
170        }
171    }
172
173    /**
174     * Old version of {@link #onCallbackDied(E, Object)} that
175     * does not provide a cookie.
176     */
177    public void onCallbackDied(E callback) {
178    }
179
180    /**
181     * Called when the process hosting a callback in the list has gone away.
182     * The default implementation calls {@link #onCallbackDied(E)}
183     * for backwards compatibility.
184     *
185     * @param callback The callback whose process has died.  Note that, since
186     * its process has died, you can not make any calls on to this interface.
187     * You can, however, retrieve its IBinder and compare it with another
188     * IBinder to see if it is the same object.
189     * @param cookie The cookie object original provided to
190     * {@link #register(E, Object)}.
191     *
192     * @see #register
193     */
194    public void onCallbackDied(E callback, Object cookie) {
195        onCallbackDied(callback);
196    }
197
198    /**
199     * Prepare to start making calls to the currently registered callbacks.
200     * This creates a copy of the callback list, which you can retrieve items
201     * from using {@link #getBroadcastItem}.  Note that only one broadcast can
202     * be active at a time, so you must be sure to always call this from the
203     * same thread (usually by scheduling with {@link Handler}) or
204     * do your own synchronization.  You must call {@link #finishBroadcast}
205     * when done.
206     *
207     * <p>A typical loop delivering a broadcast looks like this:
208     *
209     * <pre>
210     * int i = callbacks.beginBroadcast();
211     * while (i &gt; 0) {
212     *     i--;
213     *     try {
214     *         callbacks.getBroadcastItem(i).somethingHappened();
215     *     } catch (RemoteException e) {
216     *         // The RemoteCallbackList will take care of removing
217     *         // the dead object for us.
218     *     }
219     * }
220     * callbacks.finishBroadcast();</pre>
221     *
222     * @return Returns the number of callbacks in the broadcast, to be used
223     * with {@link #getBroadcastItem} to determine the range of indices you
224     * can supply.
225     *
226     * @see #getBroadcastItem
227     * @see #finishBroadcast
228     */
229    public int beginBroadcast() {
230        synchronized (mCallbacks) {
231            if (mBroadcastCount > 0) {
232                throw new IllegalStateException(
233                        "beginBroadcast() called while already in a broadcast");
234            }
235
236            final int N = mBroadcastCount = mCallbacks.size();
237            if (N <= 0) {
238                return 0;
239            }
240            Object[] active = mActiveBroadcast;
241            if (active == null || active.length < N) {
242                mActiveBroadcast = active = new Object[N];
243            }
244            for (int i=0; i<N; i++) {
245                active[i] = mCallbacks.valueAt(i);
246            }
247            return N;
248        }
249    }
250
251    /**
252     * Retrieve an item in the active broadcast that was previously started
253     * with {@link #beginBroadcast}.  This can <em>only</em> be called after
254     * the broadcast is started, and its data is no longer valid after
255     * calling {@link #finishBroadcast}.
256     *
257     * <p>Note that it is possible for the process of one of the returned
258     * callbacks to go away before you call it, so you will need to catch
259     * {@link RemoteException} when calling on to the returned object.
260     * The callback list itself, however, will take care of unregistering
261     * these objects once it detects that it is no longer valid, so you can
262     * handle such an exception by simply ignoring it.
263     *
264     * @param index Which of the registered callbacks you would like to
265     * retrieve.  Ranges from 0 to 1-{@link #beginBroadcast}.
266     *
267     * @return Returns the callback interface that you can call.  This will
268     * always be non-null.
269     *
270     * @see #beginBroadcast
271     */
272    public E getBroadcastItem(int index) {
273        return ((Callback)mActiveBroadcast[index]).mCallback;
274    }
275
276    /**
277     * Retrieve the cookie associated with the item
278     * returned by {@link #getBroadcastItem(int)}.
279     *
280     * @see #getBroadcastItem
281     */
282    public Object getBroadcastCookie(int index) {
283        return ((Callback)mActiveBroadcast[index]).mCookie;
284    }
285
286    /**
287     * Clean up the state of a broadcast previously initiated by calling
288     * {@link #beginBroadcast}.  This must always be called when you are done
289     * with a broadcast.
290     *
291     * @see #beginBroadcast
292     */
293    public void finishBroadcast() {
294        if (mBroadcastCount < 0) {
295            throw new IllegalStateException(
296                    "finishBroadcast() called outside of a broadcast");
297        }
298
299        Object[] active = mActiveBroadcast;
300        if (active != null) {
301            final int N = mBroadcastCount;
302            for (int i=0; i<N; i++) {
303                active[i] = null;
304            }
305        }
306
307        mBroadcastCount = -1;
308    }
309
310    /**
311     * Returns the number of registered callbacks. Note that the number of registered
312     * callbacks may differ from the value returned by {@link #beginBroadcast()} since
313     * the former returns the number of callbacks registered at the time of the call
314     * and the second the number of callback to which the broadcast will be delivered.
315     * <p>
316     * This function is useful to decide whether to schedule a broadcast if this
317     * requires doing some work which otherwise would not be performed.
318     * </p>
319     *
320     * @return The size.
321     */
322    public int getRegisteredCallbackCount() {
323        synchronized (mCallbacks) {
324            if (mKilled) {
325                return 0;
326            }
327            return mCallbacks.size();
328        }
329    }
330}
331