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