Home | History | Annotate | Download | only in os
      1 /*
      2  * Copyright (C) 2007 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 java.util.WeakHashMap;
     20 import java.util.Set;
     21 import android.util.Log;
     22 
     23 /**
     24  * Helper class that helps you use IBinder objects as reference counted
     25  * tokens.  IBinders make good tokens because we find out when they are
     26  * removed
     27  *
     28  */
     29 public abstract class TokenWatcher
     30 {
     31     /**
     32      * Construct the TokenWatcher
     33      *
     34      * @param h A handler to call {@link #acquired} and {@link #released}
     35      * on.  If you don't care, just call it like this, although your thread
     36      * will have to be a Looper thread.
     37      * <code>new TokenWatcher(new Handler())</code>
     38      * @param tag A debugging tag for this TokenWatcher
     39      */
     40     public TokenWatcher(Handler h, String tag)
     41     {
     42         mHandler = h;
     43         mTag = tag != null ? tag : "TokenWatcher";
     44     }
     45 
     46     /**
     47      * Called when the number of active tokens goes from 0 to 1.
     48      */
     49     public abstract void acquired();
     50 
     51     /**
     52      * Called when the number of active tokens goes from 1 to 0.
     53      */
     54     public abstract void released();
     55 
     56     /**
     57      * Record that this token has been acquired.  When acquire is called, and
     58      * the current count is 0, the acquired method is called on the given
     59      * handler.
     60      *
     61      * @param token An IBinder object.  If this token has already been acquired,
     62      *              no action is taken.
     63      * @param tag   A string used by the {@link #dump} method for debugging,
     64      *              to see who has references.
     65      */
     66     public void acquire(IBinder token, String tag)
     67     {
     68         synchronized (mTokens) {
     69             // explicitly checked to avoid bogus sendNotification calls because
     70             // of the WeakHashMap and the GC
     71             int oldSize = mTokens.size();
     72 
     73             Death d = new Death(token, tag);
     74             try {
     75                 token.linkToDeath(d, 0);
     76             } catch (RemoteException e) {
     77                 return;
     78             }
     79             mTokens.put(token, d);
     80 
     81             if (oldSize == 0 && !mAcquired) {
     82                 sendNotificationLocked(true);
     83                 mAcquired = true;
     84             }
     85         }
     86     }
     87 
     88     public void cleanup(IBinder token, boolean unlink)
     89     {
     90         synchronized (mTokens) {
     91             Death d = mTokens.remove(token);
     92             if (unlink && d != null) {
     93                 d.token.unlinkToDeath(d, 0);
     94                 d.token = null;
     95             }
     96 
     97             if (mTokens.size() == 0 && mAcquired) {
     98                 sendNotificationLocked(false);
     99                 mAcquired = false;
    100             }
    101         }
    102     }
    103 
    104     public void release(IBinder token)
    105     {
    106         cleanup(token, true);
    107     }
    108 
    109     public boolean isAcquired()
    110     {
    111         synchronized (mTokens) {
    112             return mAcquired;
    113         }
    114     }
    115 
    116     public void dump()
    117     {
    118         synchronized (mTokens) {
    119             Set<IBinder> keys = mTokens.keySet();
    120             Log.i(mTag, "Token count: " + mTokens.size());
    121             int i = 0;
    122             for (IBinder b: keys) {
    123                 Log.i(mTag, "[" + i + "] " + mTokens.get(b).tag + " - " + b);
    124                 i++;
    125             }
    126         }
    127     }
    128 
    129     private Runnable mNotificationTask = new Runnable() {
    130         public void run()
    131         {
    132             int value;
    133             synchronized (mTokens) {
    134                 value = mNotificationQueue;
    135                 mNotificationQueue = -1;
    136             }
    137             if (value == 1) {
    138                 acquired();
    139             }
    140             else if (value == 0) {
    141                 released();
    142             }
    143         }
    144     };
    145 
    146     private void sendNotificationLocked(boolean on)
    147     {
    148         int value = on ? 1 : 0;
    149         if (mNotificationQueue == -1) {
    150             // empty
    151             mNotificationQueue = value;
    152             mHandler.post(mNotificationTask);
    153         }
    154         else if (mNotificationQueue != value) {
    155             // it's a pair, so cancel it
    156             mNotificationQueue = -1;
    157             mHandler.removeCallbacks(mNotificationTask);
    158         }
    159         // else, same so do nothing -- maybe we should warn?
    160     }
    161 
    162     private class Death implements IBinder.DeathRecipient
    163     {
    164         IBinder token;
    165         String tag;
    166 
    167         Death(IBinder token, String tag)
    168         {
    169             this.token = token;
    170             this.tag = tag;
    171         }
    172 
    173         public void binderDied()
    174         {
    175             cleanup(token, false);
    176         }
    177 
    178         protected void finalize() throws Throwable
    179         {
    180             try {
    181                 if (token != null) {
    182                     Log.w(mTag, "cleaning up leaked reference: " + tag);
    183                     release(token);
    184                 }
    185             }
    186             finally {
    187                 super.finalize();
    188             }
    189         }
    190     }
    191 
    192     private WeakHashMap<IBinder,Death> mTokens = new WeakHashMap<IBinder,Death>();
    193     private Handler mHandler;
    194     private String mTag;
    195     private int mNotificationQueue = -1;
    196     private volatile boolean mAcquired = false;
    197 }
    198