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