Home | History | Annotate | Download | only in os
      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 
     66         Callback(E callback, Object cookie) {
     67             mCallback = callback;
     68             mCookie = cookie;
     69         }
     70 
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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