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;
20import android.util.Slog;
21
22import java.io.PrintWriter;
23import java.util.function.Consumer;
24
25/**
26 * Takes care of the grunt work of maintaining a list of remote interfaces,
27 * typically for the use of performing callbacks from a
28 * {@link android.app.Service} to its clients.  In particular, this:
29 *
30 * <ul>
31 * <li> Keeps track of a set of registered {@link IInterface} callbacks,
32 * taking care to identify them through their underlying unique {@link IBinder}
33 * (by calling {@link IInterface#asBinder IInterface.asBinder()}.
34 * <li> Attaches a {@link IBinder.DeathRecipient IBinder.DeathRecipient} to
35 * each registered interface, so that it can be cleaned out of the list if its
36 * process goes away.
37 * <li> Performs locking of the underlying list of interfaces to deal with
38 * multithreaded incoming calls, and a thread-safe way to iterate over a
39 * snapshot of the list without holding its lock.
40 * </ul>
41 *
42 * <p>To use this class, simply create a single instance along with your
43 * service, and call its {@link #register} and {@link #unregister} methods
44 * as client register and unregister with your service.  To call back on to
45 * the registered clients, use {@link #beginBroadcast},
46 * {@link #getBroadcastItem}, and {@link #finishBroadcast}.
47 *
48 * <p>If a registered callback's process goes away, this class will take
49 * care of automatically removing it from the list.  If you want to do
50 * additional work in this situation, you can create a subclass that
51 * implements the {@link #onCallbackDied} method.
52 */
53public class RemoteCallbackList<E extends IInterface> {
54    private static final String TAG = "RemoteCallbackList";
55
56    /*package*/ ArrayMap<IBinder, Callback> mCallbacks
57            = new ArrayMap<IBinder, Callback>();
58    private Object[] mActiveBroadcast;
59    private int mBroadcastCount = -1;
60    private boolean mKilled = false;
61    private StringBuilder mRecentCallers;
62
63    private final class Callback implements IBinder.DeathRecipient {
64        final E mCallback;
65        final Object mCookie;
66
67        Callback(E callback, Object cookie) {
68            mCallback = callback;
69            mCookie = cookie;
70        }
71
72        public void binderDied() {
73            synchronized (mCallbacks) {
74                mCallbacks.remove(mCallback.asBinder());
75            }
76            onCallbackDied(mCallback, mCookie);
77        }
78    }
79
80    /**
81     * Simple version of {@link RemoteCallbackList#register(E, Object)}
82     * that does not take a cookie object.
83     */
84    public boolean register(E callback) {
85        return register(callback, null);
86    }
87
88    /**
89     * Add a new callback to the list.  This callback will remain in the list
90     * until a corresponding call to {@link #unregister} or its hosting process
91     * goes away.  If the callback was already registered (determined by
92     * checking to see if the {@link IInterface#asBinder callback.asBinder()}
93     * object is already in the list), then it will be left as-is.
94     * Registrations are not counted; a single call to {@link #unregister}
95     * will remove a callback after any number calls to register it.
96     *
97     * @param callback The callback interface to be added to the list.  Must
98     * not be null -- passing null here will cause a NullPointerException.
99     * Most services will want to check for null before calling this with
100     * an object given from a client, so that clients can't crash the
101     * service with bad data.
102     *
103     * @param cookie Optional additional data to be associated with this
104     * callback.
105     *
106     * @return Returns true if the callback was successfully added to the list.
107     * Returns false if it was not added, either because {@link #kill} had
108     * previously been called or the callback's process has gone away.
109     *
110     * @see #unregister
111     * @see #kill
112     * @see #onCallbackDied
113     */
114    public boolean register(E callback, Object cookie) {
115        synchronized (mCallbacks) {
116            if (mKilled) {
117                return false;
118            }
119            // Flag unusual case that could be caused by a leak. b/36778087
120            logExcessiveCallbacks();
121            IBinder binder = callback.asBinder();
122            try {
123                Callback cb = new Callback(callback, cookie);
124                binder.linkToDeath(cb, 0);
125                mCallbacks.put(binder, cb);
126                return true;
127            } catch (RemoteException e) {
128                return false;
129            }
130        }
131    }
132
133    /**
134     * Remove from the list a callback that was previously added with
135     * {@link #register}.  This uses the
136     * {@link IInterface#asBinder callback.asBinder()} object to correctly
137     * find the previous registration.
138     * Registrations are not counted; a single unregister call will remove
139     * a callback after any number calls to {@link #register} for it.
140     *
141     * @param callback The callback to be removed from the list.  Passing
142     * null here will cause a NullPointerException, so you will generally want
143     * to check for null before calling.
144     *
145     * @return Returns true if the callback was found and unregistered.  Returns
146     * false if the given callback was not found on the list.
147     *
148     * @see #register
149     */
150    public boolean unregister(E callback) {
151        synchronized (mCallbacks) {
152            Callback cb = mCallbacks.remove(callback.asBinder());
153            if (cb != null) {
154                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
155                return true;
156            }
157            return false;
158        }
159    }
160
161    /**
162     * Disable this callback list.  All registered callbacks are unregistered,
163     * and the list is disabled so that future calls to {@link #register} will
164     * fail.  This should be used when a Service is stopping, to prevent clients
165     * from registering callbacks after it is stopped.
166     *
167     * @see #register
168     */
169    public void kill() {
170        synchronized (mCallbacks) {
171            for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) {
172                Callback cb = mCallbacks.valueAt(cbi);
173                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
174            }
175            mCallbacks.clear();
176            mKilled = true;
177        }
178    }
179
180    /**
181     * Old version of {@link #onCallbackDied(E, Object)} that
182     * does not provide a cookie.
183     */
184    public void onCallbackDied(E callback) {
185    }
186
187    /**
188     * Called when the process hosting a callback in the list has gone away.
189     * The default implementation calls {@link #onCallbackDied(E)}
190     * for backwards compatibility.
191     *
192     * @param callback The callback whose process has died.  Note that, since
193     * its process has died, you can not make any calls on to this interface.
194     * You can, however, retrieve its IBinder and compare it with another
195     * IBinder to see if it is the same object.
196     * @param cookie The cookie object original provided to
197     * {@link #register(E, Object)}.
198     *
199     * @see #register
200     */
201    public void onCallbackDied(E callback, Object cookie) {
202        onCallbackDied(callback);
203    }
204
205    /**
206     * Prepare to start making calls to the currently registered callbacks.
207     * This creates a copy of the callback list, which you can retrieve items
208     * from using {@link #getBroadcastItem}.  Note that only one broadcast can
209     * be active at a time, so you must be sure to always call this from the
210     * same thread (usually by scheduling with {@link Handler}) or
211     * do your own synchronization.  You must call {@link #finishBroadcast}
212     * when done.
213     *
214     * <p>A typical loop delivering a broadcast looks like this:
215     *
216     * <pre>
217     * int i = callbacks.beginBroadcast();
218     * while (i &gt; 0) {
219     *     i--;
220     *     try {
221     *         callbacks.getBroadcastItem(i).somethingHappened();
222     *     } catch (RemoteException e) {
223     *         // The RemoteCallbackList will take care of removing
224     *         // the dead object for us.
225     *     }
226     * }
227     * callbacks.finishBroadcast();</pre>
228     *
229     * @return Returns the number of callbacks in the broadcast, to be used
230     * with {@link #getBroadcastItem} to determine the range of indices you
231     * can supply.
232     *
233     * @see #getBroadcastItem
234     * @see #finishBroadcast
235     */
236    public int beginBroadcast() {
237        synchronized (mCallbacks) {
238            if (mBroadcastCount > 0) {
239                throw new IllegalStateException(
240                        "beginBroadcast() called while already in a broadcast");
241            }
242
243            final int N = mBroadcastCount = mCallbacks.size();
244            if (N <= 0) {
245                return 0;
246            }
247            Object[] active = mActiveBroadcast;
248            if (active == null || active.length < N) {
249                mActiveBroadcast = active = new Object[N];
250            }
251            for (int i=0; i<N; i++) {
252                active[i] = mCallbacks.valueAt(i);
253            }
254            return N;
255        }
256    }
257
258    /**
259     * Retrieve an item in the active broadcast that was previously started
260     * with {@link #beginBroadcast}.  This can <em>only</em> be called after
261     * the broadcast is started, and its data is no longer valid after
262     * calling {@link #finishBroadcast}.
263     *
264     * <p>Note that it is possible for the process of one of the returned
265     * callbacks to go away before you call it, so you will need to catch
266     * {@link RemoteException} when calling on to the returned object.
267     * The callback list itself, however, will take care of unregistering
268     * these objects once it detects that it is no longer valid, so you can
269     * handle such an exception by simply ignoring it.
270     *
271     * @param index Which of the registered callbacks you would like to
272     * retrieve.  Ranges from 0 to 1-{@link #beginBroadcast}.
273     *
274     * @return Returns the callback interface that you can call.  This will
275     * always be non-null.
276     *
277     * @see #beginBroadcast
278     */
279    public E getBroadcastItem(int index) {
280        return ((Callback)mActiveBroadcast[index]).mCallback;
281    }
282
283    /**
284     * Retrieve the cookie associated with the item
285     * returned by {@link #getBroadcastItem(int)}.
286     *
287     * @see #getBroadcastItem
288     */
289    public Object getBroadcastCookie(int index) {
290        return ((Callback)mActiveBroadcast[index]).mCookie;
291    }
292
293    /**
294     * Clean up the state of a broadcast previously initiated by calling
295     * {@link #beginBroadcast}.  This must always be called when you are done
296     * with a broadcast.
297     *
298     * @see #beginBroadcast
299     */
300    public void finishBroadcast() {
301        synchronized (mCallbacks) {
302            if (mBroadcastCount < 0) {
303                throw new IllegalStateException(
304                        "finishBroadcast() called outside of a broadcast");
305            }
306
307            Object[] active = mActiveBroadcast;
308            if (active != null) {
309                final int N = mBroadcastCount;
310                for (int i=0; i<N; i++) {
311                    active[i] = null;
312                }
313            }
314
315            mBroadcastCount = -1;
316        }
317    }
318
319    /**
320     * Performs {@code action} on each callback, calling
321     * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping
322     *
323     * @hide
324     */
325    public void broadcast(Consumer<E> action) {
326        int itemCount = beginBroadcast();
327        try {
328            for (int i = 0; i < itemCount; i++) {
329                action.accept(getBroadcastItem(i));
330            }
331        } finally {
332            finishBroadcast();
333        }
334    }
335
336    /**
337     * Performs {@code action} for each cookie associated with a callback, calling
338     * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping
339     *
340     * @hide
341     */
342    public <C> void broadcastForEachCookie(Consumer<C> action) {
343        int itemCount = beginBroadcast();
344        try {
345            for (int i = 0; i < itemCount; i++) {
346                action.accept((C) getBroadcastCookie(i));
347            }
348        } finally {
349            finishBroadcast();
350        }
351    }
352
353    /**
354     * Returns the number of registered callbacks. Note that the number of registered
355     * callbacks may differ from the value returned by {@link #beginBroadcast()} since
356     * the former returns the number of callbacks registered at the time of the call
357     * and the second the number of callback to which the broadcast will be delivered.
358     * <p>
359     * This function is useful to decide whether to schedule a broadcast if this
360     * requires doing some work which otherwise would not be performed.
361     * </p>
362     *
363     * @return The size.
364     */
365    public int getRegisteredCallbackCount() {
366        synchronized (mCallbacks) {
367            if (mKilled) {
368                return 0;
369            }
370            return mCallbacks.size();
371        }
372    }
373
374    /**
375     * Return a currently registered callback.  Note that this is
376     * <em>not</em> the same as {@link #getBroadcastItem} and should not be used
377     * interchangeably with it.  This method returns the registered callback at the given
378     * index, not the current broadcast state.  This means that it is not itself thread-safe:
379     * any call to {@link #register} or {@link #unregister} will change these indices, so you
380     * must do your own thread safety between these to protect from such changes.
381     *
382     * @param index Index of which callback registration to return, from 0 to
383     * {@link #getRegisteredCallbackCount()} - 1.
384     *
385     * @return Returns whatever callback is associated with this index, or null if
386     * {@link #kill()} has been called.
387     */
388    public E getRegisteredCallbackItem(int index) {
389        synchronized (mCallbacks) {
390            if (mKilled) {
391                return null;
392            }
393            return mCallbacks.valueAt(index).mCallback;
394        }
395    }
396
397    /**
398     * Return any cookie associated with a currently registered callback.  Note that this is
399     * <em>not</em> the same as {@link #getBroadcastCookie} and should not be used
400     * interchangeably with it.  This method returns the current cookie registered at the given
401     * index, not the current broadcast state.  This means that it is not itself thread-safe:
402     * any call to {@link #register} or {@link #unregister} will change these indices, so you
403     * must do your own thread safety between these to protect from such changes.
404     *
405     * @param index Index of which registration cookie to return, from 0 to
406     * {@link #getRegisteredCallbackCount()} - 1.
407     *
408     * @return Returns whatever cookie object is associated with this index, or null if
409     * {@link #kill()} has been called.
410     */
411    public Object getRegisteredCallbackCookie(int index) {
412        synchronized (mCallbacks) {
413            if (mKilled) {
414                return null;
415            }
416            return mCallbacks.valueAt(index).mCookie;
417        }
418    }
419
420    /** @hide */
421    public void dump(PrintWriter pw, String prefix) {
422        pw.print(prefix); pw.print("callbacks: "); pw.println(mCallbacks.size());
423        pw.print(prefix); pw.print("killed: "); pw.println(mKilled);
424        pw.print(prefix); pw.print("broadcasts count: "); pw.println(mBroadcastCount);
425    }
426
427    private void logExcessiveCallbacks() {
428        final long size = mCallbacks.size();
429        final long TOO_MANY = 3000;
430        final long MAX_CHARS = 1000;
431        if (size >= TOO_MANY) {
432            if (size == TOO_MANY && mRecentCallers == null) {
433                mRecentCallers = new StringBuilder();
434            }
435            if (mRecentCallers != null && mRecentCallers.length() < MAX_CHARS) {
436                mRecentCallers.append(Debug.getCallers(5));
437                mRecentCallers.append('\n');
438                if (mRecentCallers.length() >= MAX_CHARS) {
439                    Slog.wtf(TAG, "More than "
440                            + TOO_MANY + " remote callbacks registered. Recent callers:\n"
441                            + mRecentCallers.toString());
442                    mRecentCallers = null;
443                }
444            }
445        }
446    }
447}
448