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