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