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 java.io.PrintWriter;
20import java.util.ArrayList;
21import java.util.WeakHashMap;
22import java.util.Set;
23import android.util.Log;
24
25/**
26 * Helper class that helps you use IBinder objects as reference counted
27 * tokens.  IBinders make good tokens because we find out when they are
28 * removed
29 *
30 */
31public abstract class TokenWatcher
32{
33    /**
34     * Construct the TokenWatcher
35     *
36     * @param h A handler to call {@link #acquired} and {@link #released}
37     * on.  If you don't care, just call it like this, although your thread
38     * will have to be a Looper thread.
39     * <code>new TokenWatcher(new Handler())</code>
40     * @param tag A debugging tag for this TokenWatcher
41     */
42    public TokenWatcher(Handler h, String tag)
43    {
44        mHandler = h;
45        mTag = tag != null ? tag : "TokenWatcher";
46    }
47
48    /**
49     * Called when the number of active tokens goes from 0 to 1.
50     */
51    public abstract void acquired();
52
53    /**
54     * Called when the number of active tokens goes from 1 to 0.
55     */
56    public abstract void released();
57
58    /**
59     * Record that this token has been acquired.  When acquire is called, and
60     * the current count is 0, the acquired method is called on the given
61     * handler.
62     *
63     * @param token An IBinder object.  If this token has already been acquired,
64     *              no action is taken.
65     * @param tag   A string used by the {@link #dump} method for debugging,
66     *              to see who has references.
67     */
68    public void acquire(IBinder token, String tag)
69    {
70        synchronized (mTokens) {
71            // explicitly checked to avoid bogus sendNotification calls because
72            // of the WeakHashMap and the GC
73            int oldSize = mTokens.size();
74
75            Death d = new Death(token, tag);
76            try {
77                token.linkToDeath(d, 0);
78            } catch (RemoteException e) {
79                return;
80            }
81            mTokens.put(token, d);
82
83            if (oldSize == 0 && !mAcquired) {
84                sendNotificationLocked(true);
85                mAcquired = true;
86            }
87        }
88    }
89
90    public void cleanup(IBinder token, boolean unlink)
91    {
92        synchronized (mTokens) {
93            Death d = mTokens.remove(token);
94            if (unlink && d != null) {
95                d.token.unlinkToDeath(d, 0);
96                d.token = null;
97            }
98
99            if (mTokens.size() == 0 && mAcquired) {
100                sendNotificationLocked(false);
101                mAcquired = false;
102            }
103        }
104    }
105
106    public void release(IBinder token)
107    {
108        cleanup(token, true);
109    }
110
111    public boolean isAcquired()
112    {
113        synchronized (mTokens) {
114            return mAcquired;
115        }
116    }
117
118    public void dump()
119    {
120        ArrayList<String> a = dumpInternal();
121        for (String s : a) {
122            Log.i(mTag, s);
123        }
124    }
125
126    public void dump(PrintWriter pw) {
127        ArrayList<String> a = dumpInternal();
128        for (String s : a) {
129            pw.println(s);
130        }
131    }
132
133    private ArrayList<String> dumpInternal() {
134        ArrayList<String> a = new ArrayList<String>();
135        synchronized (mTokens) {
136            Set<IBinder> keys = mTokens.keySet();
137            a.add("Token count: " + mTokens.size());
138            int i = 0;
139            for (IBinder b: keys) {
140                a.add("[" + i + "] " + mTokens.get(b).tag + " - " + b);
141                i++;
142            }
143        }
144        return a;
145    }
146
147    private Runnable mNotificationTask = new Runnable() {
148        public void run()
149        {
150            int value;
151            synchronized (mTokens) {
152                value = mNotificationQueue;
153                mNotificationQueue = -1;
154            }
155            if (value == 1) {
156                acquired();
157            }
158            else if (value == 0) {
159                released();
160            }
161        }
162    };
163
164    private void sendNotificationLocked(boolean on)
165    {
166        int value = on ? 1 : 0;
167        if (mNotificationQueue == -1) {
168            // empty
169            mNotificationQueue = value;
170            mHandler.post(mNotificationTask);
171        }
172        else if (mNotificationQueue != value) {
173            // it's a pair, so cancel it
174            mNotificationQueue = -1;
175            mHandler.removeCallbacks(mNotificationTask);
176        }
177        // else, same so do nothing -- maybe we should warn?
178    }
179
180    private class Death implements IBinder.DeathRecipient
181    {
182        IBinder token;
183        String tag;
184
185        Death(IBinder token, String tag)
186        {
187            this.token = token;
188            this.tag = tag;
189        }
190
191        public void binderDied()
192        {
193            cleanup(token, false);
194        }
195
196        protected void finalize() throws Throwable
197        {
198            try {
199                if (token != null) {
200                    Log.w(mTag, "cleaning up leaked reference: " + tag);
201                    release(token);
202                }
203            }
204            finally {
205                super.finalize();
206            }
207        }
208    }
209
210    private WeakHashMap<IBinder,Death> mTokens = new WeakHashMap<IBinder,Death>();
211    private Handler mHandler;
212    private String mTag;
213    private int mNotificationQueue = -1;
214    private volatile boolean mAcquired = false;
215}
216