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
17package android.os;
18
19import android.util.Log;
20
21import java.io.PrintWriter;
22import java.util.ArrayList;
23import java.util.Set;
24import 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 */
37public 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     */
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     */
57    public abstract void acquired();
58
59    /**
60     * Called when the number of active tokens goes from 1 to 0.
61     */
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     */
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
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
120    public void release(IBinder token)
121    {
122        cleanup(token, true);
123    }
124
125    public boolean isAcquired()
126    {
127        synchronized (mTokens) {
128            return mAcquired;
129        }
130    }
131
132    public void dump()
133    {
134        ArrayList<String> a = dumpInternal();
135        for (String s : a) {
136            Log.i(mTag, s);
137        }
138    }
139
140    public void dump(PrintWriter pw) {
141        ArrayList<String> a = dumpInternal();
142        for (String s : a) {
143            pw.println(s);
144        }
145    }
146
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
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
199        Death(IBinder token, String tag)
200        {
201            this.token = token;
202            this.tag = tag;
203        }
204
205        public void binderDied()
206        {
207            cleanup(token, false);
208        }
209
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