1/*
2 * Copyright (C) 2006 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.ArrayList;
20
21import android.util.AndroidRuntimeException;
22import android.util.Config;
23import android.util.Log;
24
25import com.android.internal.os.RuntimeInit;
26
27/**
28 * Low-level class holding the list of messages to be dispatched by a
29 * {@link Looper}.  Messages are not added directly to a MessageQueue,
30 * but rather through {@link Handler} objects associated with the Looper.
31 *
32 * <p>You can retrieve the MessageQueue for the current thread with
33 * {@link Looper#myQueue() Looper.myQueue()}.
34 */
35public class MessageQueue {
36    Message mMessages;
37    private final ArrayList mIdleHandlers = new ArrayList();
38    private boolean mQuiting = false;
39    boolean mQuitAllowed = true;
40
41    /**
42     * Callback interface for discovering when a thread is going to block
43     * waiting for more messages.
44     */
45    public static interface IdleHandler {
46        /**
47         * Called when the message queue has run out of messages and will now
48         * wait for more.  Return true to keep your idle handler active, false
49         * to have it removed.  This may be called if there are still messages
50         * pending in the queue, but they are all scheduled to be dispatched
51         * after the current time.
52         */
53        boolean queueIdle();
54    }
55
56    /**
57     * Add a new {@link IdleHandler} to this message queue.  This may be
58     * removed automatically for you by returning false from
59     * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
60     * invoked, or explicitly removing it with {@link #removeIdleHandler}.
61     *
62     * <p>This method is safe to call from any thread.
63     *
64     * @param handler The IdleHandler to be added.
65     */
66    public final void addIdleHandler(IdleHandler handler) {
67        if (handler == null) {
68            throw new NullPointerException("Can't add a null IdleHandler");
69        }
70        synchronized (this) {
71            mIdleHandlers.add(handler);
72        }
73    }
74
75    /**
76     * Remove an {@link IdleHandler} from the queue that was previously added
77     * with {@link #addIdleHandler}.  If the given object is not currently
78     * in the idle list, nothing is done.
79     *
80     * @param handler The IdleHandler to be removed.
81     */
82    public final void removeIdleHandler(IdleHandler handler) {
83        synchronized (this) {
84            mIdleHandlers.remove(handler);
85        }
86    }
87
88    MessageQueue() {
89    }
90
91    final Message next() {
92        boolean tryIdle = true;
93
94        while (true) {
95            long now;
96            Object[] idlers = null;
97
98            // Try to retrieve the next message, returning if found.
99            synchronized (this) {
100                now = SystemClock.uptimeMillis();
101                Message msg = pullNextLocked(now);
102                if (msg != null) return msg;
103                if (tryIdle && mIdleHandlers.size() > 0) {
104                    idlers = mIdleHandlers.toArray();
105                }
106            }
107
108            // There was no message so we are going to wait...  but first,
109            // if there are any idle handlers let them know.
110            boolean didIdle = false;
111            if (idlers != null) {
112                for (Object idler : idlers) {
113                    boolean keep = false;
114                    try {
115                        didIdle = true;
116                        keep = ((IdleHandler)idler).queueIdle();
117                    } catch (Throwable t) {
118                        Log.wtf("MessageQueue", "IdleHandler threw exception", t);
119                    }
120
121                    if (!keep) {
122                        synchronized (this) {
123                            mIdleHandlers.remove(idler);
124                        }
125                    }
126                }
127            }
128
129            // While calling an idle handler, a new message could have been
130            // delivered...  so go back and look again for a pending message.
131            if (didIdle) {
132                tryIdle = false;
133                continue;
134            }
135
136            synchronized (this) {
137                // No messages, nobody to tell about it...  time to wait!
138                try {
139                    if (mMessages != null) {
140                        if (mMessages.when-now > 0) {
141                            Binder.flushPendingCommands();
142                            this.wait(mMessages.when-now);
143                        }
144                    } else {
145                        Binder.flushPendingCommands();
146                        this.wait();
147                    }
148                }
149                catch (InterruptedException e) {
150                }
151            }
152        }
153    }
154
155    final Message pullNextLocked(long now) {
156        Message msg = mMessages;
157        if (msg != null) {
158            if (now >= msg.when) {
159                mMessages = msg.next;
160                if (Config.LOGV) Log.v(
161                    "MessageQueue", "Returning message: " + msg);
162                return msg;
163            }
164        }
165
166        return null;
167    }
168
169    final boolean enqueueMessage(Message msg, long when) {
170        if (msg.when != 0) {
171            throw new AndroidRuntimeException(msg
172                    + " This message is already in use.");
173        }
174        if (msg.target == null && !mQuitAllowed) {
175            throw new RuntimeException("Main thread not allowed to quit");
176        }
177        synchronized (this) {
178            if (mQuiting) {
179                RuntimeException e = new RuntimeException(
180                    msg.target + " sending message to a Handler on a dead thread");
181                Log.w("MessageQueue", e.getMessage(), e);
182                return false;
183            } else if (msg.target == null) {
184                mQuiting = true;
185            }
186
187            msg.when = when;
188            //Log.d("MessageQueue", "Enqueing: " + msg);
189            Message p = mMessages;
190            if (p == null || when == 0 || when < p.when) {
191                msg.next = p;
192                mMessages = msg;
193                this.notify();
194            } else {
195                Message prev = null;
196                while (p != null && p.when <= when) {
197                    prev = p;
198                    p = p.next;
199                }
200                msg.next = prev.next;
201                prev.next = msg;
202                this.notify();
203            }
204        }
205        return true;
206    }
207
208    final boolean removeMessages(Handler h, int what, Object object,
209            boolean doRemove) {
210        synchronized (this) {
211            Message p = mMessages;
212            boolean found = false;
213
214            // Remove all messages at front.
215            while (p != null && p.target == h && p.what == what
216                   && (object == null || p.obj == object)) {
217                if (!doRemove) return true;
218                found = true;
219                Message n = p.next;
220                mMessages = n;
221                p.recycle();
222                p = n;
223            }
224
225            // Remove all messages after front.
226            while (p != null) {
227                Message n = p.next;
228                if (n != null) {
229                    if (n.target == h && n.what == what
230                        && (object == null || n.obj == object)) {
231                        if (!doRemove) return true;
232                        found = true;
233                        Message nn = n.next;
234                        n.recycle();
235                        p.next = nn;
236                        continue;
237                    }
238                }
239                p = n;
240            }
241
242            return found;
243        }
244    }
245
246    final void removeMessages(Handler h, Runnable r, Object object) {
247        if (r == null) {
248            return;
249        }
250
251        synchronized (this) {
252            Message p = mMessages;
253
254            // Remove all messages at front.
255            while (p != null && p.target == h && p.callback == r
256                   && (object == null || p.obj == object)) {
257                Message n = p.next;
258                mMessages = n;
259                p.recycle();
260                p = n;
261            }
262
263            // Remove all messages after front.
264            while (p != null) {
265                Message n = p.next;
266                if (n != null) {
267                    if (n.target == h && n.callback == r
268                        && (object == null || n.obj == object)) {
269                        Message nn = n.next;
270                        n.recycle();
271                        p.next = nn;
272                        continue;
273                    }
274                }
275                p = n;
276            }
277        }
278    }
279
280    final void removeCallbacksAndMessages(Handler h, Object object) {
281        synchronized (this) {
282            Message p = mMessages;
283
284            // Remove all messages at front.
285            while (p != null && p.target == h
286                    && (object == null || p.obj == object)) {
287                Message n = p.next;
288                mMessages = n;
289                p.recycle();
290                p = n;
291            }
292
293            // Remove all messages after front.
294            while (p != null) {
295                Message n = p.next;
296                if (n != null) {
297                    if (n.target == h && (object == null || n.obj == object)) {
298                        Message nn = n.next;
299                        n.recycle();
300                        p.next = nn;
301                        continue;
302                    }
303                }
304                p = n;
305            }
306        }
307    }
308
309    /*
310    private void dumpQueue_l()
311    {
312        Message p = mMessages;
313        System.out.println(this + "  queue is:");
314        while (p != null) {
315            System.out.println("            " + p);
316            p = p.next;
317        }
318    }
319    */
320
321    void poke()
322    {
323        synchronized (this) {
324            this.notify();
325        }
326    }
327}
328