|  | /* | 
|  | * 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; | 
|  | } |