| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.os; |
| |
| import java.util.WeakHashMap; |
| import java.util.Set; |
| import android.util.Log; |
| |
| /** |
| * Helper class that helps you use IBinder objects as reference counted |
| * tokens. IBinders make good tokens because we find out when they are |
| * removed |
| * |
| */ |
| public abstract class TokenWatcher |
| { |
| /** |
| * Construct the TokenWatcher |
| * |
| * @param h A handler to call {@link #acquired} and {@link #released} |
| * on. If you don't care, just call it like this, although your thread |
| * will have to be a Looper thread. |
| * <code>new TokenWatcher(new Handler())</code> |
| * @param tag A debugging tag for this TokenWatcher |
| */ |
| public TokenWatcher(Handler h, String tag) |
| { |
| mHandler = h; |
| mTag = tag != null ? tag : "TokenWatcher"; |
| } |
| |
| /** |
| * Called when the number of active tokens goes from 0 to 1. |
| */ |
| public abstract void acquired(); |
| |
| /** |
| * Called when the number of active tokens goes from 1 to 0. |
| */ |
| public abstract void released(); |
| |
| /** |
| * Record that this token has been acquired. When acquire is called, and |
| * the current count is 0, the acquired method is called on the given |
| * handler. |
| * |
| * @param token An IBinder object. If this token has already been acquired, |
| * no action is taken. |
| * @param tag A string used by the {@link #dump} method for debugging, |
| * to see who has references. |
| */ |
| public void acquire(IBinder token, String tag) |
| { |
| synchronized (mTokens) { |
| // explicitly checked to avoid bogus sendNotification calls because |
| // of the WeakHashMap and the GC |
| int oldSize = mTokens.size(); |
| |
| Death d = new Death(token, tag); |
| try { |
| token.linkToDeath(d, 0); |
| } catch (RemoteException e) { |
| return; |
| } |
| mTokens.put(token, d); |
| |
| if (oldSize == 0 && !mAcquired) { |
| sendNotificationLocked(true); |
| mAcquired = true; |
| } |
| } |
| } |
| |
| public void cleanup(IBinder token, boolean unlink) |
| { |
| synchronized (mTokens) { |
| Death d = mTokens.remove(token); |
| if (unlink && d != null) { |
| d.token.unlinkToDeath(d, 0); |
| d.token = null; |
| } |
| |
| if (mTokens.size() == 0 && mAcquired) { |
| sendNotificationLocked(false); |
| mAcquired = false; |
| } |
| } |
| } |
| |
| public void release(IBinder token) |
| { |
| cleanup(token, true); |
| } |
| |
| public boolean isAcquired() |
| { |
| synchronized (mTokens) { |
| return mAcquired; |
| } |
| } |
| |
| public void dump() |
| { |
| synchronized (mTokens) { |
| Set<IBinder> keys = mTokens.keySet(); |
| Log.i(mTag, "Token count: " + mTokens.size()); |
| int i = 0; |
| for (IBinder b: keys) { |
| Log.i(mTag, "[" + i + "] " + mTokens.get(b).tag + " - " + b); |
| i++; |
| } |
| } |
| } |
| |
| private Runnable mNotificationTask = new Runnable() { |
| public void run() |
| { |
| int value; |
| synchronized (mTokens) { |
| value = mNotificationQueue; |
| mNotificationQueue = -1; |
| } |
| if (value == 1) { |
| acquired(); |
| } |
| else if (value == 0) { |
| released(); |
| } |
| } |
| }; |
| |
| private void sendNotificationLocked(boolean on) |
| { |
| int value = on ? 1 : 0; |
| if (mNotificationQueue == -1) { |
| // empty |
| mNotificationQueue = value; |
| mHandler.post(mNotificationTask); |
| } |
| else if (mNotificationQueue != value) { |
| // it's a pair, so cancel it |
| mNotificationQueue = -1; |
| mHandler.removeCallbacks(mNotificationTask); |
| } |
| // else, same so do nothing -- maybe we should warn? |
| } |
| |
| private class Death implements IBinder.DeathRecipient |
| { |
| IBinder token; |
| String tag; |
| |
| Death(IBinder token, String tag) |
| { |
| this.token = token; |
| this.tag = tag; |
| } |
| |
| public void binderDied() |
| { |
| cleanup(token, false); |
| } |
| |
| protected void finalize() throws Throwable |
| { |
| try { |
| if (token != null) { |
| Log.w(mTag, "cleaning up leaked reference: " + tag); |
| release(token); |
| } |
| } |
| finally { |
| super.finalize(); |
| } |
| } |
| } |
| |
| private WeakHashMap<IBinder,Death> mTokens = new WeakHashMap<IBinder,Death>(); |
| private Handler mHandler; |
| private String mTag; |
| private int mNotificationQueue = -1; |
| private volatile boolean mAcquired = false; |
| } |