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