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