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