• 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.util.ArrayMap;
20 import android.util.Slog;
21 
22 import java.io.PrintWriter;
23 import 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  */
53 public 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 
Callback(E callback, Object cookie)67         Callback(E callback, Object cookie) {
68             mCallback = callback;
69             mCookie = cookie;
70         }
71 
binderDied()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      */
register(E callback)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      */
register(E callback, Object cookie)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      */
unregister(E callback)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      */
kill()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      */
onCallbackDied(E callback)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      */
onCallbackDied(E callback, Object cookie)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      */
beginBroadcast()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      */
getBroadcastItem(int index)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      */
getBroadcastCookie(int index)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      */
finishBroadcast()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      */
broadcast(Consumer<E> action)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      */
broadcastForEachCookie(Consumer<C> action)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      */
getRegisteredCallbackCount()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      */
getRegisteredCallbackItem(int index)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      */
getRegisteredCallbackCookie(int index)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 */
dump(PrintWriter pw, String prefix)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 
logExcessiveCallbacks()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