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