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