| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.os; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.TestApi; |
| import android.annotation.UnsupportedAppUsage; |
| import android.os.MessageQueueProto; |
| import android.util.Log; |
| import android.util.Printer; |
| import android.util.SparseArray; |
| import android.util.proto.ProtoOutputStream; |
| |
| import java.io.FileDescriptor; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| |
| /** |
| * Low-level class holding the list of messages to be dispatched by a |
| * {@link Looper}. Messages are not added directly to a MessageQueue, |
| * but rather through {@link Handler} objects associated with the Looper. |
| * |
| * <p>You can retrieve the MessageQueue for the current thread with |
| * {@link Looper#myQueue() Looper.myQueue()}. |
| */ |
| public final class MessageQueue { |
| private static final String TAG = "MessageQueue"; |
| private static final boolean DEBUG = false; |
| |
| // True if the message queue can be quit. |
| @UnsupportedAppUsage |
| private final boolean mQuitAllowed; |
| |
| @UnsupportedAppUsage |
| @SuppressWarnings("unused") |
| private long mPtr; // used by native code |
| |
| @UnsupportedAppUsage |
| Message mMessages; |
| @UnsupportedAppUsage |
| private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); |
| private SparseArray<FileDescriptorRecord> mFileDescriptorRecords; |
| private IdleHandler[] mPendingIdleHandlers; |
| private boolean mQuitting; |
| |
| // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout. |
| private boolean mBlocked; |
| |
| // The next barrier token. |
| // Barriers are indicated by messages with a null target whose arg1 field carries the token. |
| @UnsupportedAppUsage |
| private int mNextBarrierToken; |
| |
| private native static long nativeInit(); |
| private native static void nativeDestroy(long ptr); |
| @UnsupportedAppUsage |
| private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/ |
| private native static void nativeWake(long ptr); |
| private native static boolean nativeIsPolling(long ptr); |
| private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); |
| |
| MessageQueue(boolean quitAllowed) { |
| mQuitAllowed = quitAllowed; |
| mPtr = nativeInit(); |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| try { |
| dispose(); |
| } finally { |
| super.finalize(); |
| } |
| } |
| |
| // Disposes of the underlying message queue. |
| // Must only be called on the looper thread or the finalizer. |
| private void dispose() { |
| if (mPtr != 0) { |
| nativeDestroy(mPtr); |
| mPtr = 0; |
| } |
| } |
| |
| /** |
| * Returns true if the looper has no pending messages which are due to be processed. |
| * |
| * <p>This method is safe to call from any thread. |
| * |
| * @return True if the looper is idle. |
| */ |
| public boolean isIdle() { |
| synchronized (this) { |
| final long now = SystemClock.uptimeMillis(); |
| return mMessages == null || now < mMessages.when; |
| } |
| } |
| |
| /** |
| * Add a new {@link IdleHandler} to this message queue. This may be |
| * removed automatically for you by returning false from |
| * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is |
| * invoked, or explicitly removing it with {@link #removeIdleHandler}. |
| * |
| * <p>This method is safe to call from any thread. |
| * |
| * @param handler The IdleHandler to be added. |
| */ |
| public void addIdleHandler(@NonNull IdleHandler handler) { |
| if (handler == null) { |
| throw new NullPointerException("Can't add a null IdleHandler"); |
| } |
| synchronized (this) { |
| mIdleHandlers.add(handler); |
| } |
| } |
| |
| /** |
| * Remove an {@link IdleHandler} from the queue that was previously added |
| * with {@link #addIdleHandler}. If the given object is not currently |
| * in the idle list, nothing is done. |
| * |
| * <p>This method is safe to call from any thread. |
| * |
| * @param handler The IdleHandler to be removed. |
| */ |
| public void removeIdleHandler(@NonNull IdleHandler handler) { |
| synchronized (this) { |
| mIdleHandlers.remove(handler); |
| } |
| } |
| |
| /** |
| * Returns whether this looper's thread is currently polling for more work to do. |
| * This is a good signal that the loop is still alive rather than being stuck |
| * handling a callback. Note that this method is intrinsically racy, since the |
| * state of the loop can change before you get the result back. |
| * |
| * <p>This method is safe to call from any thread. |
| * |
| * @return True if the looper is currently polling for events. |
| * @hide |
| */ |
| public boolean isPolling() { |
| synchronized (this) { |
| return isPollingLocked(); |
| } |
| } |
| |
| private boolean isPollingLocked() { |
| // If the loop is quitting then it must not be idling. |
| // We can assume mPtr != 0 when mQuitting is false. |
| return !mQuitting && nativeIsPolling(mPtr); |
| } |
| |
| /** |
| * Adds a file descriptor listener to receive notification when file descriptor |
| * related events occur. |
| * <p> |
| * If the file descriptor has already been registered, the specified events |
| * and listener will replace any that were previously associated with it. |
| * It is not possible to set more than one listener per file descriptor. |
| * </p><p> |
| * It is important to always unregister the listener when the file descriptor |
| * is no longer of use. |
| * </p> |
| * |
| * @param fd The file descriptor for which a listener will be registered. |
| * @param events The set of events to receive: a combination of the |
| * {@link OnFileDescriptorEventListener#EVENT_INPUT}, |
| * {@link OnFileDescriptorEventListener#EVENT_OUTPUT}, and |
| * {@link OnFileDescriptorEventListener#EVENT_ERROR} event masks. If the requested |
| * set of events is zero, then the listener is unregistered. |
| * @param listener The listener to invoke when file descriptor events occur. |
| * |
| * @see OnFileDescriptorEventListener |
| * @see #removeOnFileDescriptorEventListener |
| */ |
| public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd, |
| @OnFileDescriptorEventListener.Events int events, |
| @NonNull OnFileDescriptorEventListener listener) { |
| if (fd == null) { |
| throw new IllegalArgumentException("fd must not be null"); |
| } |
| if (listener == null) { |
| throw new IllegalArgumentException("listener must not be null"); |
| } |
| |
| synchronized (this) { |
| updateOnFileDescriptorEventListenerLocked(fd, events, listener); |
| } |
| } |
| |
| /** |
| * Removes a file descriptor listener. |
| * <p> |
| * This method does nothing if no listener has been registered for the |
| * specified file descriptor. |
| * </p> |
| * |
| * @param fd The file descriptor whose listener will be unregistered. |
| * |
| * @see OnFileDescriptorEventListener |
| * @see #addOnFileDescriptorEventListener |
| */ |
| public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) { |
| if (fd == null) { |
| throw new IllegalArgumentException("fd must not be null"); |
| } |
| |
| synchronized (this) { |
| updateOnFileDescriptorEventListenerLocked(fd, 0, null); |
| } |
| } |
| |
| private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events, |
| OnFileDescriptorEventListener listener) { |
| final int fdNum = fd.getInt$(); |
| |
| int index = -1; |
| FileDescriptorRecord record = null; |
| if (mFileDescriptorRecords != null) { |
| index = mFileDescriptorRecords.indexOfKey(fdNum); |
| if (index >= 0) { |
| record = mFileDescriptorRecords.valueAt(index); |
| if (record != null && record.mEvents == events) { |
| return; |
| } |
| } |
| } |
| |
| if (events != 0) { |
| events |= OnFileDescriptorEventListener.EVENT_ERROR; |
| if (record == null) { |
| if (mFileDescriptorRecords == null) { |
| mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>(); |
| } |
| record = new FileDescriptorRecord(fd, events, listener); |
| mFileDescriptorRecords.put(fdNum, record); |
| } else { |
| record.mListener = listener; |
| record.mEvents = events; |
| record.mSeq += 1; |
| } |
| nativeSetFileDescriptorEvents(mPtr, fdNum, events); |
| } else if (record != null) { |
| record.mEvents = 0; |
| mFileDescriptorRecords.removeAt(index); |
| nativeSetFileDescriptorEvents(mPtr, fdNum, 0); |
| } |
| } |
| |
| // Called from native code. |
| @UnsupportedAppUsage |
| private int dispatchEvents(int fd, int events) { |
| // Get the file descriptor record and any state that might change. |
| final FileDescriptorRecord record; |
| final int oldWatchedEvents; |
| final OnFileDescriptorEventListener listener; |
| final int seq; |
| synchronized (this) { |
| record = mFileDescriptorRecords.get(fd); |
| if (record == null) { |
| return 0; // spurious, no listener registered |
| } |
| |
| oldWatchedEvents = record.mEvents; |
| events &= oldWatchedEvents; // filter events based on current watched set |
| if (events == 0) { |
| return oldWatchedEvents; // spurious, watched events changed |
| } |
| |
| listener = record.mListener; |
| seq = record.mSeq; |
| } |
| |
| // Invoke the listener outside of the lock. |
| int newWatchedEvents = listener.onFileDescriptorEvents( |
| record.mDescriptor, events); |
| if (newWatchedEvents != 0) { |
| newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR; |
| } |
| |
| // Update the file descriptor record if the listener changed the set of |
| // events to watch and the listener itself hasn't been updated since. |
| if (newWatchedEvents != oldWatchedEvents) { |
| synchronized (this) { |
| int index = mFileDescriptorRecords.indexOfKey(fd); |
| if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record |
| && record.mSeq == seq) { |
| record.mEvents = newWatchedEvents; |
| if (newWatchedEvents == 0) { |
| mFileDescriptorRecords.removeAt(index); |
| } |
| } |
| } |
| } |
| |
| // Return the new set of events to watch for native code to take care of. |
| return newWatchedEvents; |
| } |
| |
| @UnsupportedAppUsage |
| Message next() { |
| // Return here if the message loop has already quit and been disposed. |
| // This can happen if the application tries to restart a looper after quit |
| // which is not supported. |
| final long ptr = mPtr; |
| if (ptr == 0) { |
| return null; |
| } |
| |
| int pendingIdleHandlerCount = -1; // -1 only during first iteration |
| int nextPollTimeoutMillis = 0; |
| for (;;) { |
| if (nextPollTimeoutMillis != 0) { |
| Binder.flushPendingCommands(); |
| } |
| |
| nativePollOnce(ptr, nextPollTimeoutMillis); |
| |
| synchronized (this) { |
| // Try to retrieve the next message. Return if found. |
| final long now = SystemClock.uptimeMillis(); |
| Message prevMsg = null; |
| Message msg = mMessages; |
| if (msg != null && msg.target == null) { |
| // Stalled by a barrier. Find the next asynchronous message in the queue. |
| do { |
| prevMsg = msg; |
| msg = msg.next; |
| } while (msg != null && !msg.isAsynchronous()); |
| } |
| if (msg != null) { |
| if (now < msg.when) { |
| // Next message is not ready. Set a timeout to wake up when it is ready. |
| nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); |
| } else { |
| // Got a message. |
| mBlocked = false; |
| if (prevMsg != null) { |
| prevMsg.next = msg.next; |
| } else { |
| mMessages = msg.next; |
| } |
| msg.next = null; |
| if (DEBUG) Log.v(TAG, "Returning message: " + msg); |
| msg.markInUse(); |
| return msg; |
| } |
| } else { |
| // No more messages. |
| nextPollTimeoutMillis = -1; |
| } |
| |
| // Process the quit message now that all pending messages have been handled. |
| if (mQuitting) { |
| dispose(); |
| return null; |
| } |
| |
| // If first time idle, then get the number of idlers to run. |
| // Idle handles only run if the queue is empty or if the first message |
| // in the queue (possibly a barrier) is due to be handled in the future. |
| if (pendingIdleHandlerCount < 0 |
| && (mMessages == null || now < mMessages.when)) { |
| pendingIdleHandlerCount = mIdleHandlers.size(); |
| } |
| if (pendingIdleHandlerCount <= 0) { |
| // No idle handlers to run. Loop and wait some more. |
| mBlocked = true; |
| continue; |
| } |
| |
| if (mPendingIdleHandlers == null) { |
| mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; |
| } |
| mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); |
| } |
| |
| // Run the idle handlers. |
| // We only ever reach this code block during the first iteration. |
| for (int i = 0; i < pendingIdleHandlerCount; i++) { |
| final IdleHandler idler = mPendingIdleHandlers[i]; |
| mPendingIdleHandlers[i] = null; // release the reference to the handler |
| |
| boolean keep = false; |
| try { |
| keep = idler.queueIdle(); |
| } catch (Throwable t) { |
| Log.wtf(TAG, "IdleHandler threw exception", t); |
| } |
| |
| if (!keep) { |
| synchronized (this) { |
| mIdleHandlers.remove(idler); |
| } |
| } |
| } |
| |
| // Reset the idle handler count to 0 so we do not run them again. |
| pendingIdleHandlerCount = 0; |
| |
| // While calling an idle handler, a new message could have been delivered |
| // so go back and look again for a pending message without waiting. |
| nextPollTimeoutMillis = 0; |
| } |
| } |
| |
| void quit(boolean safe) { |
| if (!mQuitAllowed) { |
| throw new IllegalStateException("Main thread not allowed to quit."); |
| } |
| |
| synchronized (this) { |
| if (mQuitting) { |
| return; |
| } |
| mQuitting = true; |
| |
| if (safe) { |
| removeAllFutureMessagesLocked(); |
| } else { |
| removeAllMessagesLocked(); |
| } |
| |
| // We can assume mPtr != 0 because mQuitting was previously false. |
| nativeWake(mPtr); |
| } |
| } |
| |
| /** |
| * Posts a synchronization barrier to the Looper's message queue. |
| * |
| * Message processing occurs as usual until the message queue encounters the |
| * synchronization barrier that has been posted. When the barrier is encountered, |
| * later synchronous messages in the queue are stalled (prevented from being executed) |
| * until the barrier is released by calling {@link #removeSyncBarrier} and specifying |
| * the token that identifies the synchronization barrier. |
| * |
| * This method is used to immediately postpone execution of all subsequently posted |
| * synchronous messages until a condition is met that releases the barrier. |
| * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier |
| * and continue to be processed as usual. |
| * |
| * This call must be always matched by a call to {@link #removeSyncBarrier} with |
| * the same token to ensure that the message queue resumes normal operation. |
| * Otherwise the application will probably hang! |
| * |
| * @return A token that uniquely identifies the barrier. This token must be |
| * passed to {@link #removeSyncBarrier} to release the barrier. |
| * |
| * @hide |
| */ |
| @UnsupportedAppUsage |
| @TestApi |
| public int postSyncBarrier() { |
| return postSyncBarrier(SystemClock.uptimeMillis()); |
| } |
| |
| private int postSyncBarrier(long when) { |
| // Enqueue a new sync barrier token. |
| // We don't need to wake the queue because the purpose of a barrier is to stall it. |
| synchronized (this) { |
| final int token = mNextBarrierToken++; |
| final Message msg = Message.obtain(); |
| msg.markInUse(); |
| msg.when = when; |
| msg.arg1 = token; |
| |
| Message prev = null; |
| Message p = mMessages; |
| if (when != 0) { |
| while (p != null && p.when <= when) { |
| prev = p; |
| p = p.next; |
| } |
| } |
| if (prev != null) { // invariant: p == prev.next |
| msg.next = p; |
| prev.next = msg; |
| } else { |
| msg.next = p; |
| mMessages = msg; |
| } |
| return token; |
| } |
| } |
| |
| /** |
| * Removes a synchronization barrier. |
| * |
| * @param token The synchronization barrier token that was returned by |
| * {@link #postSyncBarrier}. |
| * |
| * @throws IllegalStateException if the barrier was not found. |
| * |
| * @hide |
| */ |
| @UnsupportedAppUsage |
| @TestApi |
| public void removeSyncBarrier(int token) { |
| // Remove a sync barrier token from the queue. |
| // If the queue is no longer stalled by a barrier then wake it. |
| synchronized (this) { |
| Message prev = null; |
| Message p = mMessages; |
| while (p != null && (p.target != null || p.arg1 != token)) { |
| prev = p; |
| p = p.next; |
| } |
| if (p == null) { |
| throw new IllegalStateException("The specified message queue synchronization " |
| + " barrier token has not been posted or has already been removed."); |
| } |
| final boolean needWake; |
| if (prev != null) { |
| prev.next = p.next; |
| needWake = false; |
| } else { |
| mMessages = p.next; |
| needWake = mMessages == null || mMessages.target != null; |
| } |
| p.recycleUnchecked(); |
| |
| // If the loop is quitting then it is already awake. |
| // We can assume mPtr != 0 when mQuitting is false. |
| if (needWake && !mQuitting) { |
| nativeWake(mPtr); |
| } |
| } |
| } |
| |
| boolean enqueueMessage(Message msg, long when) { |
| if (msg.target == null) { |
| throw new IllegalArgumentException("Message must have a target."); |
| } |
| if (msg.isInUse()) { |
| throw new IllegalStateException(msg + " This message is already in use."); |
| } |
| |
| synchronized (this) { |
| if (mQuitting) { |
| IllegalStateException e = new IllegalStateException( |
| msg.target + " sending message to a Handler on a dead thread"); |
| Log.w(TAG, e.getMessage(), e); |
| msg.recycle(); |
| return false; |
| } |
| |
| msg.markInUse(); |
| msg.when = when; |
| Message p = mMessages; |
| boolean needWake; |
| if (p == null || when == 0 || when < p.when) { |
| // New head, wake up the event queue if blocked. |
| msg.next = p; |
| mMessages = msg; |
| needWake = mBlocked; |
| } else { |
| // Inserted within the middle of the queue. Usually we don't have to wake |
| // up the event queue unless there is a barrier at the head of the queue |
| // and the message is the earliest asynchronous message in the queue. |
| needWake = mBlocked && p.target == null && msg.isAsynchronous(); |
| Message prev; |
| for (;;) { |
| prev = p; |
| p = p.next; |
| if (p == null || when < p.when) { |
| break; |
| } |
| if (needWake && p.isAsynchronous()) { |
| needWake = false; |
| } |
| } |
| msg.next = p; // invariant: p == prev.next |
| prev.next = msg; |
| } |
| |
| // We can assume mPtr != 0 because mQuitting is false. |
| if (needWake) { |
| nativeWake(mPtr); |
| } |
| } |
| return true; |
| } |
| |
| boolean hasMessages(Handler h, int what, Object object) { |
| if (h == null) { |
| return false; |
| } |
| |
| synchronized (this) { |
| Message p = mMessages; |
| while (p != null) { |
| if (p.target == h && p.what == what && (object == null || p.obj == object)) { |
| return true; |
| } |
| p = p.next; |
| } |
| return false; |
| } |
| } |
| |
| @UnsupportedAppUsage |
| boolean hasMessages(Handler h, Runnable r, Object object) { |
| if (h == null) { |
| return false; |
| } |
| |
| synchronized (this) { |
| Message p = mMessages; |
| while (p != null) { |
| if (p.target == h && p.callback == r && (object == null || p.obj == object)) { |
| return true; |
| } |
| p = p.next; |
| } |
| return false; |
| } |
| } |
| |
| boolean hasMessages(Handler h) { |
| if (h == null) { |
| return false; |
| } |
| |
| synchronized (this) { |
| Message p = mMessages; |
| while (p != null) { |
| if (p.target == h) { |
| return true; |
| } |
| p = p.next; |
| } |
| return false; |
| } |
| } |
| |
| void removeMessages(Handler h, int what, Object object) { |
| if (h == null) { |
| return; |
| } |
| |
| synchronized (this) { |
| Message p = mMessages; |
| |
| // Remove all messages at front. |
| while (p != null && p.target == h && p.what == what |
| && (object == null || p.obj == object)) { |
| Message n = p.next; |
| mMessages = n; |
| p.recycleUnchecked(); |
| p = n; |
| } |
| |
| // Remove all messages after front. |
| while (p != null) { |
| Message n = p.next; |
| if (n != null) { |
| if (n.target == h && n.what == what |
| && (object == null || n.obj == object)) { |
| Message nn = n.next; |
| n.recycleUnchecked(); |
| p.next = nn; |
| continue; |
| } |
| } |
| p = n; |
| } |
| } |
| } |
| |
| void removeMessages(Handler h, Runnable r, Object object) { |
| if (h == null || r == null) { |
| return; |
| } |
| |
| synchronized (this) { |
| Message p = mMessages; |
| |
| // Remove all messages at front. |
| while (p != null && p.target == h && p.callback == r |
| && (object == null || p.obj == object)) { |
| Message n = p.next; |
| mMessages = n; |
| p.recycleUnchecked(); |
| p = n; |
| } |
| |
| // Remove all messages after front. |
| while (p != null) { |
| Message n = p.next; |
| if (n != null) { |
| if (n.target == h && n.callback == r |
| && (object == null || n.obj == object)) { |
| Message nn = n.next; |
| n.recycleUnchecked(); |
| p.next = nn; |
| continue; |
| } |
| } |
| p = n; |
| } |
| } |
| } |
| |
| void removeCallbacksAndMessages(Handler h, Object object) { |
| if (h == null) { |
| return; |
| } |
| |
| synchronized (this) { |
| Message p = mMessages; |
| |
| // Remove all messages at front. |
| while (p != null && p.target == h |
| && (object == null || p.obj == object)) { |
| Message n = p.next; |
| mMessages = n; |
| p.recycleUnchecked(); |
| p = n; |
| } |
| |
| // Remove all messages after front. |
| while (p != null) { |
| Message n = p.next; |
| if (n != null) { |
| if (n.target == h && (object == null || n.obj == object)) { |
| Message nn = n.next; |
| n.recycleUnchecked(); |
| p.next = nn; |
| continue; |
| } |
| } |
| p = n; |
| } |
| } |
| } |
| |
| private void removeAllMessagesLocked() { |
| Message p = mMessages; |
| while (p != null) { |
| Message n = p.next; |
| p.recycleUnchecked(); |
| p = n; |
| } |
| mMessages = null; |
| } |
| |
| private void removeAllFutureMessagesLocked() { |
| final long now = SystemClock.uptimeMillis(); |
| Message p = mMessages; |
| if (p != null) { |
| if (p.when > now) { |
| removeAllMessagesLocked(); |
| } else { |
| Message n; |
| for (;;) { |
| n = p.next; |
| if (n == null) { |
| return; |
| } |
| if (n.when > now) { |
| break; |
| } |
| p = n; |
| } |
| p.next = null; |
| do { |
| p = n; |
| n = p.next; |
| p.recycleUnchecked(); |
| } while (n != null); |
| } |
| } |
| } |
| |
| void dump(Printer pw, String prefix, Handler h) { |
| synchronized (this) { |
| long now = SystemClock.uptimeMillis(); |
| int n = 0; |
| for (Message msg = mMessages; msg != null; msg = msg.next) { |
| if (h == null || h == msg.target) { |
| pw.println(prefix + "Message " + n + ": " + msg.toString(now)); |
| } |
| n++; |
| } |
| pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked() |
| + ", quitting=" + mQuitting + ")"); |
| } |
| } |
| |
| void dumpDebug(ProtoOutputStream proto, long fieldId) { |
| final long messageQueueToken = proto.start(fieldId); |
| synchronized (this) { |
| for (Message msg = mMessages; msg != null; msg = msg.next) { |
| msg.dumpDebug(proto, MessageQueueProto.MESSAGES); |
| } |
| proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPollingLocked()); |
| proto.write(MessageQueueProto.IS_QUITTING, mQuitting); |
| } |
| proto.end(messageQueueToken); |
| } |
| |
| /** |
| * Callback interface for discovering when a thread is going to block |
| * waiting for more messages. |
| */ |
| public static interface IdleHandler { |
| /** |
| * Called when the message queue has run out of messages and will now |
| * wait for more. Return true to keep your idle handler active, false |
| * to have it removed. This may be called if there are still messages |
| * pending in the queue, but they are all scheduled to be dispatched |
| * after the current time. |
| */ |
| boolean queueIdle(); |
| } |
| |
| /** |
| * A listener which is invoked when file descriptor related events occur. |
| */ |
| public interface OnFileDescriptorEventListener { |
| /** |
| * File descriptor event: Indicates that the file descriptor is ready for input |
| * operations, such as reading. |
| * <p> |
| * The listener should read all available data from the file descriptor |
| * then return <code>true</code> to keep the listener active or <code>false</code> |
| * to remove the listener. |
| * </p><p> |
| * In the case of a socket, this event may be generated to indicate |
| * that there is at least one incoming connection that the listener |
| * should accept. |
| * </p><p> |
| * This event will only be generated if the {@link #EVENT_INPUT} event mask was |
| * specified when the listener was added. |
| * </p> |
| */ |
| public static final int EVENT_INPUT = 1 << 0; |
| |
| /** |
| * File descriptor event: Indicates that the file descriptor is ready for output |
| * operations, such as writing. |
| * <p> |
| * The listener should write as much data as it needs. If it could not |
| * write everything at once, then it should return <code>true</code> to |
| * keep the listener active. Otherwise, it should return <code>false</code> |
| * to remove the listener then re-register it later when it needs to write |
| * something else. |
| * </p><p> |
| * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was |
| * specified when the listener was added. |
| * </p> |
| */ |
| public static final int EVENT_OUTPUT = 1 << 1; |
| |
| /** |
| * File descriptor event: Indicates that the file descriptor encountered a |
| * fatal error. |
| * <p> |
| * File descriptor errors can occur for various reasons. One common error |
| * is when the remote peer of a socket or pipe closes its end of the connection. |
| * </p><p> |
| * This event may be generated at any time regardless of whether the |
| * {@link #EVENT_ERROR} event mask was specified when the listener was added. |
| * </p> |
| */ |
| public static final int EVENT_ERROR = 1 << 2; |
| |
| /** @hide */ |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(flag = true, prefix = { "EVENT_" }, value = { |
| EVENT_INPUT, |
| EVENT_OUTPUT, |
| EVENT_ERROR |
| }) |
| public @interface Events {} |
| |
| /** |
| * Called when a file descriptor receives events. |
| * |
| * @param fd The file descriptor. |
| * @param events The set of events that occurred: a combination of the |
| * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks. |
| * @return The new set of events to watch, or 0 to unregister the listener. |
| * |
| * @see #EVENT_INPUT |
| * @see #EVENT_OUTPUT |
| * @see #EVENT_ERROR |
| */ |
| @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events); |
| } |
| |
| private static final class FileDescriptorRecord { |
| public final FileDescriptor mDescriptor; |
| public int mEvents; |
| public OnFileDescriptorEventListener mListener; |
| public int mSeq; |
| |
| public FileDescriptorRecord(FileDescriptor descriptor, |
| int events, OnFileDescriptorEventListener listener) { |
| mDescriptor = descriptor; |
| mEvents = events; |
| mListener = listener; |
| } |
| } |
| } |