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