| /* |
| * 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 android.util.Log; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Set; |
| import java.util.WeakHashMap; |
| |
| /** |
| * A TokenWatcher watches a collection of {@link IBinder}s. IBinders are added |
| * to the collection by calling {@link #acquire}, and removed by calling {@link |
| * #release}. IBinders are also implicitly removed when they become weakly |
| * reachable. Each IBinder may be added at most once. |
| * |
| * The {@link #acquired} method is invoked by posting to the specified handler |
| * whenever the size of the watched collection becomes nonzero. The {@link |
| * #released} method is invoked on the specified handler whenever the size of |
| * the watched collection becomes zero. |
| */ |
| 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. |
| * |
| * Note that the same {@code token} can only be acquired once. If this |
| * {@code token} has already been acquired, no action is taken. The first |
| * subsequent call to {@link #release} will release this {@code token} |
| * immediately. |
| * |
| * @param token An IBinder object. |
| * @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) { |
| if (mTokens.containsKey(token)) { |
| return; |
| } |
| |
| // 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() |
| { |
| ArrayList<String> a = dumpInternal(); |
| for (String s : a) { |
| Log.i(mTag, s); |
| } |
| } |
| |
| public void dump(PrintWriter pw) { |
| ArrayList<String> a = dumpInternal(); |
| for (String s : a) { |
| pw.println(s); |
| } |
| } |
| |
| private ArrayList<String> dumpInternal() { |
| ArrayList<String> a = new ArrayList<String>(); |
| synchronized (mTokens) { |
| Set<IBinder> keys = mTokens.keySet(); |
| a.add("Token count: " + mTokens.size()); |
| int i = 0; |
| for (IBinder b: keys) { |
| a.add("[" + i + "] " + mTokens.get(b).tag + " - " + b); |
| i++; |
| } |
| } |
| return a; |
| } |
| |
| 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; |
| } |