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