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 17 package android.os; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.TestApi; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.ravenwood.annotation.RavenwoodKeepWholeClass; 24 import android.ravenwood.annotation.RavenwoodRedirect; 25 import android.ravenwood.annotation.RavenwoodRedirectionClass; 26 import android.util.Log; 27 import android.util.Printer; 28 import android.util.SparseArray; 29 import android.util.proto.ProtoOutputStream; 30 31 import com.android.internal.annotations.GuardedBy; 32 33 import dalvik.annotation.optimization.NeverCompile; 34 35 import java.io.FileDescriptor; 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.concurrent.atomic.AtomicLong; 41 42 /** 43 * Low-level class holding the list of messages to be dispatched by a 44 * {@link Looper}. Messages are not added directly to a MessageQueue, 45 * but rather through {@link Handler} objects associated with the Looper. 46 * 47 * <p>You can retrieve the MessageQueue for the current thread with 48 * {@link Looper#myQueue() Looper.myQueue()}. 49 */ 50 @RavenwoodKeepWholeClass 51 @RavenwoodRedirectionClass("MessageQueue_ravenwood") 52 public final class MessageQueue { 53 private static final String TAG = "LockedMessageQueue"; 54 private static final boolean DEBUG = false; 55 private static final boolean TRACE = false; 56 57 static final class MessageHeap { 58 static final int MESSAGE_HEAP_INITIAL_SIZE = 16; 59 60 Message[] mHeap = new Message[MESSAGE_HEAP_INITIAL_SIZE]; 61 int mNumElements = 0; 62 parentNodeIdx(int i)63 static int parentNodeIdx(int i) { 64 return (i - 1) >>> 1; 65 } 66 getParentNode(int i)67 Message getParentNode(int i) { 68 return mHeap[(i - 1) >>> 1]; 69 } 70 rightNodeIdx(int i)71 static int rightNodeIdx(int i) { 72 return 2 * i + 2; 73 } 74 getRightNode(int i)75 Message getRightNode(int i) { 76 return mHeap[2 * i + 2]; 77 } 78 leftNodeIdx(int i)79 static int leftNodeIdx(int i) { 80 return 2 * i + 1; 81 } 82 getLeftNode(int i)83 Message getLeftNode(int i) { 84 return mHeap[2 * i + 1]; 85 } 86 size()87 int size() { 88 return mHeap.length; 89 } 90 numElements()91 int numElements() { 92 return mNumElements; 93 } 94 isEmpty()95 boolean isEmpty() { 96 return mNumElements == 0; 97 } 98 getMessageAt(int index)99 Message getMessageAt(int index) { 100 return mHeap[index]; 101 } 102 103 /* 104 * Returns: 105 * 0 if x==y. 106 * A value less than 0 if x<y. 107 * A value greater than 0 if x>y. 108 */ compareMessage(Message x, Message y)109 int compareMessage(Message x, Message y) { 110 int compared = Long.compare(x.when, y.when); 111 if (compared == 0) { 112 compared = Long.compare(x.mInsertSeq, y.mInsertSeq); 113 } 114 return compared; 115 } 116 compareMessageByIdx(int x, int y)117 int compareMessageByIdx(int x, int y) { 118 return compareMessage(mHeap[x], mHeap[y]); 119 } 120 swap(int x, int y)121 void swap(int x, int y) { 122 Message tmp = mHeap[x]; 123 mHeap[x] = mHeap[y]; 124 mHeap[y] = tmp; 125 } 126 siftDown(int i)127 void siftDown(int i) { 128 int smallest = i; 129 int r, l; 130 131 while (true) { 132 r = rightNodeIdx(i); 133 l = leftNodeIdx(i); 134 135 if (r < mNumElements && compareMessageByIdx(r, smallest) < 0) { 136 smallest = r; 137 } 138 139 if (l < mNumElements && compareMessageByIdx(l, smallest) < 0) { 140 smallest = l; 141 } 142 143 if (smallest != i) { 144 swap(i, smallest); 145 i = smallest; 146 continue; 147 } 148 break; 149 } 150 } 151 siftUp(int i)152 boolean siftUp(int i) { 153 boolean swapped = false; 154 while (i != 0 && compareMessage(mHeap[i], getParentNode(i)) < 0) { 155 int p = parentNodeIdx(i); 156 157 swap(i, p); 158 swapped = true; 159 i = p; 160 } 161 162 return swapped; 163 } 164 maybeGrowHeap()165 void maybeGrowHeap() { 166 if (mNumElements == mHeap.length) { 167 /* Grow by 1.5x */ 168 int newSize = mHeap.length + (mHeap.length >>> 1); 169 Message[] newHeap; 170 if (DEBUG) { 171 Log.v(TAG, "maybeGrowHeap mNumElements " + mNumElements + " mHeap.length " 172 + mHeap.length + " newSize " + newSize); 173 } 174 175 newHeap = Arrays.copyOf(mHeap, newSize); 176 mHeap = newHeap; 177 } 178 } 179 add(Message m)180 void add(Message m) { 181 int i; 182 183 maybeGrowHeap(); 184 185 i = mNumElements; 186 mNumElements++; 187 mHeap[i] = m; 188 189 siftUp(i); 190 } 191 maybeShrinkHeap()192 void maybeShrinkHeap() { 193 /* Shrink by 2x */ 194 int newSize = mHeap.length >>> 1; 195 196 if (newSize >= MESSAGE_HEAP_INITIAL_SIZE 197 && mNumElements <= newSize) { 198 Message[] newHeap; 199 200 if (DEBUG) { 201 Log.v(TAG, "maybeShrinkHeap mNumElements " + mNumElements + " mHeap.length " 202 + mHeap.length + " newSize " + newSize); 203 } 204 205 newHeap = Arrays.copyOf(mHeap, newSize); 206 mHeap = newHeap; 207 } 208 } 209 poll()210 Message poll() { 211 if (mNumElements > 0) { 212 Message ret = mHeap[0]; 213 mNumElements--; 214 mHeap[0] = mHeap[mNumElements]; 215 mHeap[mNumElements] = null; 216 217 siftDown(0); 218 219 maybeShrinkHeap(); 220 return ret; 221 } 222 return null; 223 } 224 peek()225 Message peek() { 226 if (mNumElements > 0) { 227 return mHeap[0]; 228 } 229 return null; 230 } 231 remove(int i)232 private void remove(int i) throws IllegalArgumentException { 233 if (i > mNumElements || mNumElements == 0) { 234 throw new IllegalArgumentException("Index " + i + " out of bounds: " 235 + mNumElements); 236 } else if (i == (mNumElements - 1)) { 237 mHeap[i] = null; 238 mNumElements--; 239 } else { 240 mNumElements--; 241 mHeap[i] = mHeap[mNumElements]; 242 mHeap[mNumElements] = null; 243 if (!siftUp(i)) { 244 siftDown(i); 245 } 246 } 247 /* Don't shink here, let the caller do this once it has removed all matching items. */ 248 } 249 removeAll()250 void removeAll() { 251 Message m; 252 for (int i = 0; i < mNumElements; i++) { 253 m = mHeap[i]; 254 mHeap[i] = null; 255 m.recycleUnchecked(); 256 } 257 mNumElements = 0; 258 maybeShrinkHeap(); 259 } 260 261 abstract static class MessageHeapCompare { compareMessage(Message m, Handler h, int what, Object object, Runnable r, long when)262 public abstract boolean compareMessage(Message m, Handler h, int what, Object object, 263 Runnable r, long when); 264 } 265 findOrRemoveMessages(Handler h, int what, Object object, Runnable r, long when, MessageHeapCompare compare, boolean removeMatches)266 boolean findOrRemoveMessages(Handler h, int what, Object object, Runnable r, long when, 267 MessageHeapCompare compare, boolean removeMatches) { 268 boolean found = false; 269 /* 270 * Walk the heap backwards so we don't have to re-visit an array element due to 271 * sifting 272 */ 273 for (int i = mNumElements - 1; i >= 0; i--) { 274 if (compare.compareMessage(mHeap[i], h, what, object, r, when)) { 275 found = true; 276 if (removeMatches) { 277 Message m = mHeap[i]; 278 try { 279 remove(i); 280 } catch (IllegalArgumentException e) { 281 Log.wtf(TAG, "Index out of bounds during remove " + e); 282 } 283 m.recycleUnchecked(); 284 continue; 285 } 286 break; 287 } 288 } 289 if (found && removeMatches) { 290 maybeShrinkHeap(); 291 } 292 return found; 293 } 294 295 /* 296 * Keep this for manual debugging. It's easier to pepper the code with this function 297 * than MessageQueue.dump() 298 */ 299 @NeverCompile print()300 void print() { 301 Log.v(TAG, "heap num elem: " + mNumElements + " mHeap.length " + mHeap.length); 302 for (int i = 0; i < mNumElements; i++) { 303 Log.v(TAG, "[" + i + "]\t" + mHeap[i] + " seq: " + mHeap[i].mInsertSeq + " async: " 304 + mHeap[i].isAsynchronous()); 305 } 306 } 307 verify(int root)308 boolean verify(int root) { 309 int r = rightNodeIdx(root); 310 int l = leftNodeIdx(root); 311 312 if (l >= mNumElements && r >= mNumElements) { 313 return true; 314 } 315 316 if (l < mNumElements && compareMessageByIdx(l, root) < 0) { 317 Log.wtf(TAG, "Verify failure: root idx/when: " + root + "/" + mHeap[root].when 318 + " left node idx/when: " + l + "/" + mHeap[l].when); 319 return false; 320 } 321 322 if (r < mNumElements && compareMessageByIdx(r, root) < 0) { 323 Log.wtf(TAG, "Verify failure: root idx/when: " + root + "/" + mHeap[root].when 324 + " right node idx/when: " + r + "/" + mHeap[r].when); 325 return false; 326 } 327 328 if (!verify(r) || !verify(l)) { 329 return false; 330 } 331 return true; 332 } 333 checkDanglingReferences(String where)334 boolean checkDanglingReferences(String where) { 335 /* First, let's make sure we didn't leave any dangling references */ 336 for (int i = mNumElements; i < mHeap.length; i++) { 337 if (mHeap[i] != null) { 338 Log.wtf(TAG, "[" + where 339 + "] Verify failure: dangling reference found at index " 340 + i + ": " + mHeap[i] + " Async " + mHeap[i].isAsynchronous() 341 + " mNumElements " + mNumElements + " mHeap.length " + mHeap.length); 342 return false; 343 } 344 } 345 return true; 346 } 347 verify()348 boolean verify() { 349 if (!checkDanglingReferences(TAG)) { 350 return false; 351 } 352 return verify(0); 353 } 354 } 355 356 // True if the message queue can be quit. 357 @UnsupportedAppUsage 358 private final boolean mQuitAllowed; 359 360 @UnsupportedAppUsage 361 @SuppressWarnings("unused") 362 private long mPtr; // used by native code 363 364 private final MessageHeap mPriorityQueue = new MessageHeap(); 365 private final MessageHeap mAsyncPriorityQueue = new MessageHeap(); 366 367 /* 368 * This helps us ensure that messages with the same timestamp are inserted in FIFO order. 369 * Increments on each insert, starting at 0. MessaeHeap.compareMessage() will compare sequences 370 * when delivery timestamps are identical. 371 */ 372 private long mNextInsertSeq; 373 374 /* 375 * The exception to the FIFO order rule is sendMessageAtFrontOfQueue(). 376 * Those messages must be in LIFO order. 377 * Decrements on each front of queue insert. 378 */ 379 private long mNextFrontInsertSeq = -1; 380 381 @UnsupportedAppUsage 382 private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); 383 private SparseArray<FileDescriptorRecord> mFileDescriptorRecords; 384 private IdleHandler[] mPendingIdleHandlers; 385 private boolean mQuitting; 386 387 // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout. 388 private boolean mBlocked; 389 390 // The next barrier token. 391 // Barriers are indicated by messages with a null target whose arg1 field carries the token. 392 @UnsupportedAppUsage 393 private int mNextBarrierToken; 394 395 @RavenwoodRedirect nativeInit()396 private native static long nativeInit(); 397 @RavenwoodRedirect nativeDestroy(long ptr)398 private native static void nativeDestroy(long ptr); 399 @UnsupportedAppUsage 400 @RavenwoodRedirect nativePollOnce(long ptr, int timeoutMillis)401 private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/ 402 @RavenwoodRedirect nativeWake(long ptr)403 private native static void nativeWake(long ptr); 404 @RavenwoodRedirect nativeIsPolling(long ptr)405 private native static boolean nativeIsPolling(long ptr); 406 @RavenwoodRedirect nativeSetFileDescriptorEvents(long ptr, int fd, int events)407 private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); 408 MessageQueue(boolean quitAllowed)409 MessageQueue(boolean quitAllowed) { 410 mQuitAllowed = quitAllowed; 411 mPtr = nativeInit(); 412 } 413 414 @GuardedBy("this") removeRootFromPriorityQueue(Message msg)415 private void removeRootFromPriorityQueue(Message msg) { 416 Message tmp; 417 if (msg.isAsynchronous()) { 418 tmp = mAsyncPriorityQueue.poll(); 419 } else { 420 tmp = mPriorityQueue.poll(); 421 } 422 if (DEBUG && tmp != msg) { 423 Log.wtf(TAG, "Unexpected message at head of heap. Wanted: " + msg + " msg.isAsync " 424 + msg.isAsynchronous() + " Found: " + tmp); 425 426 mPriorityQueue.print(); 427 mAsyncPriorityQueue.print(); 428 } 429 } 430 431 @GuardedBy("this") pickEarliestMessage(Message x, Message y)432 private Message pickEarliestMessage(Message x, Message y) { 433 if (x != null && y != null) { 434 if (mPriorityQueue.compareMessage(x, y) < 0) { 435 return x; 436 } 437 return y; 438 } 439 440 return x != null ? x : y; 441 } 442 443 @GuardedBy("this") peekEarliestMessage()444 private Message peekEarliestMessage() { 445 Message x = mPriorityQueue.peek(); 446 Message y = mAsyncPriorityQueue.peek(); 447 448 return pickEarliestMessage(x, y); 449 } 450 451 @GuardedBy("this") priorityQueuesAreEmpty()452 private boolean priorityQueuesAreEmpty() { 453 return mPriorityQueue.isEmpty() && mAsyncPriorityQueue.isEmpty(); 454 } 455 456 @GuardedBy("this") priorityQueueHasBarrier()457 private boolean priorityQueueHasBarrier() { 458 Message m = mPriorityQueue.peek(); 459 460 if (m != null && m.target == null) { 461 return true; 462 } 463 return false; 464 } 465 466 @Override finalize()467 protected void finalize() throws Throwable { 468 try { 469 dispose(); 470 } finally { 471 super.finalize(); 472 } 473 } 474 475 // Disposes of the underlying message queue. 476 // Must only be called on the looper thread or the finalizer. dispose()477 private void dispose() { 478 if (mPtr != 0) { 479 nativeDestroy(mPtr); 480 mPtr = 0; 481 } 482 } 483 484 /** 485 * Returns true if the looper has no pending messages which are due to be processed. 486 * 487 * <p>This method is safe to call from any thread. 488 * 489 * @return True if the looper is idle. 490 */ isIdle()491 public boolean isIdle() { 492 synchronized (this) { 493 Message m = peekEarliestMessage(); 494 final long now = SystemClock.uptimeMillis(); 495 496 return (priorityQueuesAreEmpty() || now < m.when); 497 } 498 } 499 500 /** 501 * Add a new {@link IdleHandler} to this message queue. This may be 502 * removed automatically for you by returning false from 503 * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is 504 * invoked, or explicitly removing it with {@link #removeIdleHandler}. 505 * 506 * <p>This method is safe to call from any thread. 507 * 508 * @param handler The IdleHandler to be added. 509 */ addIdleHandler(@onNull IdleHandler handler)510 public void addIdleHandler(@NonNull IdleHandler handler) { 511 if (handler == null) { 512 throw new NullPointerException("Can't add a null IdleHandler"); 513 } 514 synchronized (this) { 515 mIdleHandlers.add(handler); 516 } 517 } 518 519 /** 520 * Remove an {@link IdleHandler} from the queue that was previously added 521 * with {@link #addIdleHandler}. If the given object is not currently 522 * in the idle list, nothing is done. 523 * 524 * <p>This method is safe to call from any thread. 525 * 526 * @param handler The IdleHandler to be removed. 527 */ removeIdleHandler(@onNull IdleHandler handler)528 public void removeIdleHandler(@NonNull IdleHandler handler) { 529 synchronized (this) { 530 mIdleHandlers.remove(handler); 531 } 532 } 533 534 /** 535 * Returns whether this looper's thread is currently polling for more work to do. 536 * This is a good signal that the loop is still alive rather than being stuck 537 * handling a callback. Note that this method is intrinsically racy, since the 538 * state of the loop can change before you get the result back. 539 * 540 * <p>This method is safe to call from any thread. 541 * 542 * @return True if the looper is currently polling for events. 543 * @hide 544 */ isPolling()545 public boolean isPolling() { 546 synchronized (this) { 547 return isPollingLocked(); 548 } 549 } 550 isPollingLocked()551 private boolean isPollingLocked() { 552 // If the loop is quitting then it must not be idling. 553 // We can assume mPtr != 0 when mQuitting is false. 554 return !mQuitting && nativeIsPolling(mPtr); 555 } 556 557 /** 558 * Adds a file descriptor listener to receive notification when file descriptor 559 * related events occur. 560 * <p> 561 * If the file descriptor has already been registered, the specified events 562 * and listener will replace any that were previously associated with it. 563 * It is not possible to set more than one listener per file descriptor. 564 * </p><p> 565 * It is important to always unregister the listener when the file descriptor 566 * is no longer of use. 567 * </p> 568 * 569 * @param fd The file descriptor for which a listener will be registered. 570 * @param events The set of events to receive: a combination of the 571 * {@link OnFileDescriptorEventListener#EVENT_INPUT}, 572 * {@link OnFileDescriptorEventListener#EVENT_OUTPUT}, and 573 * {@link OnFileDescriptorEventListener#EVENT_ERROR} event masks. If the requested 574 * set of events is zero, then the listener is unregistered. 575 * @param listener The listener to invoke when file descriptor events occur. 576 * 577 * @see OnFileDescriptorEventListener 578 * @see #removeOnFileDescriptorEventListener 579 */ 580 @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) addOnFileDescriptorEventListener(@onNull FileDescriptor fd, @OnFileDescriptorEventListener.Events int events, @NonNull OnFileDescriptorEventListener listener)581 public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd, 582 @OnFileDescriptorEventListener.Events int events, 583 @NonNull OnFileDescriptorEventListener listener) { 584 if (fd == null) { 585 throw new IllegalArgumentException("fd must not be null"); 586 } 587 if (listener == null) { 588 throw new IllegalArgumentException("listener must not be null"); 589 } 590 591 synchronized (this) { 592 updateOnFileDescriptorEventListenerLocked(fd, events, listener); 593 } 594 } 595 596 /** 597 * Removes a file descriptor listener. 598 * <p> 599 * This method does nothing if no listener has been registered for the 600 * specified file descriptor. 601 * </p> 602 * 603 * @param fd The file descriptor whose listener will be unregistered. 604 * 605 * @see OnFileDescriptorEventListener 606 * @see #addOnFileDescriptorEventListener 607 */ 608 @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) removeOnFileDescriptorEventListener(@onNull FileDescriptor fd)609 public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) { 610 if (fd == null) { 611 throw new IllegalArgumentException("fd must not be null"); 612 } 613 614 synchronized (this) { 615 updateOnFileDescriptorEventListenerLocked(fd, 0, null); 616 } 617 } 618 619 @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events, OnFileDescriptorEventListener listener)620 private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events, 621 OnFileDescriptorEventListener listener) { 622 final int fdNum = fd.getInt$(); 623 624 int index = -1; 625 FileDescriptorRecord record = null; 626 if (mFileDescriptorRecords != null) { 627 index = mFileDescriptorRecords.indexOfKey(fdNum); 628 if (index >= 0) { 629 record = mFileDescriptorRecords.valueAt(index); 630 if (record != null && record.mEvents == events) { 631 return; 632 } 633 } 634 } 635 636 if (events != 0) { 637 events |= OnFileDescriptorEventListener.EVENT_ERROR; 638 if (record == null) { 639 if (mFileDescriptorRecords == null) { 640 mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>(); 641 } 642 record = new FileDescriptorRecord(fd, events, listener); 643 mFileDescriptorRecords.put(fdNum, record); 644 } else { 645 record.mListener = listener; 646 record.mEvents = events; 647 record.mSeq += 1; 648 } 649 nativeSetFileDescriptorEvents(mPtr, fdNum, events); 650 } else if (record != null) { 651 record.mEvents = 0; 652 mFileDescriptorRecords.removeAt(index); 653 nativeSetFileDescriptorEvents(mPtr, fdNum, 0); 654 } 655 } 656 657 // Called from native code. 658 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) dispatchEvents(int fd, int events)659 private int dispatchEvents(int fd, int events) { 660 // Get the file descriptor record and any state that might change. 661 final FileDescriptorRecord record; 662 final int oldWatchedEvents; 663 final OnFileDescriptorEventListener listener; 664 final int seq; 665 synchronized (this) { 666 record = mFileDescriptorRecords.get(fd); 667 if (record == null) { 668 return 0; // spurious, no listener registered 669 } 670 671 oldWatchedEvents = record.mEvents; 672 events &= oldWatchedEvents; // filter events based on current watched set 673 if (events == 0) { 674 return oldWatchedEvents; // spurious, watched events changed 675 } 676 677 listener = record.mListener; 678 seq = record.mSeq; 679 } 680 681 // Invoke the listener outside of the lock. 682 int newWatchedEvents = listener.onFileDescriptorEvents( 683 record.mDescriptor, events); 684 if (newWatchedEvents != 0) { 685 newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR; 686 } 687 688 // Update the file descriptor record if the listener changed the set of 689 // events to watch and the listener itself hasn't been updated since. 690 if (newWatchedEvents != oldWatchedEvents) { 691 synchronized (this) { 692 int index = mFileDescriptorRecords.indexOfKey(fd); 693 if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record 694 && record.mSeq == seq) { 695 record.mEvents = newWatchedEvents; 696 if (newWatchedEvents == 0) { 697 mFileDescriptorRecords.removeAt(index); 698 } 699 } 700 } 701 } 702 703 // Return the new set of events to watch for native code to take care of. 704 return newWatchedEvents; 705 } 706 707 private static final AtomicLong mMessagesDelivered = new AtomicLong(); 708 709 @UnsupportedAppUsage next()710 Message next() { 711 // Return here if the message loop has already quit and been disposed. 712 // This can happen if the application tries to restart a looper after quit 713 // which is not supported. 714 final long ptr = mPtr; 715 if (ptr == 0) { 716 return null; 717 } 718 719 int pendingIdleHandlerCount = -1; // -1 only during first iteration 720 int nextPollTimeoutMillis = 0; 721 for (;;) { 722 if (nextPollTimeoutMillis != 0) { 723 Binder.flushPendingCommands(); 724 } 725 726 nativePollOnce(ptr, nextPollTimeoutMillis); 727 728 synchronized (this) { 729 // Try to retrieve the next message. Return if found. 730 final long now = SystemClock.uptimeMillis(); 731 Message prevMsg = null; 732 Message msg = peekEarliestMessage(); 733 734 if (DEBUG && msg != null) { 735 Log.v(TAG, "Next found message " + msg + " isAsynchronous: " 736 + msg.isAsynchronous() + " target " + msg.target); 737 } 738 739 if (msg != null && !msg.isAsynchronous() && msg.target == null) { 740 // Stalled by a barrier. Find the next asynchronous message in the queue. 741 msg = mAsyncPriorityQueue.peek(); 742 if (DEBUG) { 743 Log.v(TAG, "Next message was barrier async msg: " + msg); 744 } 745 } 746 747 if (msg != null) { 748 if (now < msg.when) { 749 // Next message is not ready. Set a timeout to wake up when it is ready. 750 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); 751 } else { 752 mBlocked = false; 753 removeRootFromPriorityQueue(msg); 754 if (DEBUG) Log.v(TAG, "Returning message: " + msg); 755 msg.markInUse(); 756 if (TRACE) { 757 Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet()); 758 } 759 return msg; 760 } 761 } else { 762 // No more messages. 763 nextPollTimeoutMillis = -1; 764 } 765 766 // Process the quit message now that all pending messages have been handled. 767 if (mQuitting) { 768 dispose(); 769 return null; 770 } 771 772 // If first time idle, then get the number of idlers to run. 773 // Idle handles only run if the queue is empty or if the first message 774 // in the queue (possibly a barrier) is due to be handled in the future. 775 Message next = peekEarliestMessage(); 776 if (pendingIdleHandlerCount < 0 777 && (next == null || now < next.when)) { 778 pendingIdleHandlerCount = mIdleHandlers.size(); 779 } 780 if (pendingIdleHandlerCount <= 0) { 781 // No idle handlers to run. Loop and wait some more. 782 mBlocked = true; 783 continue; 784 } 785 786 if (mPendingIdleHandlers == null) { 787 mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; 788 } 789 mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); 790 } 791 792 // Run the idle handlers. 793 // We only ever reach this code block during the first iteration. 794 for (int i = 0; i < pendingIdleHandlerCount; i++) { 795 final IdleHandler idler = mPendingIdleHandlers[i]; 796 mPendingIdleHandlers[i] = null; // release the reference to the handler 797 798 boolean keep = false; 799 try { 800 keep = idler.queueIdle(); 801 } catch (Throwable t) { 802 Log.wtf(TAG, "IdleHandler threw exception", t); 803 } 804 805 if (!keep) { 806 synchronized (this) { 807 mIdleHandlers.remove(idler); 808 } 809 } 810 } 811 812 // Reset the idle handler count to 0 so we do not run them again. 813 pendingIdleHandlerCount = 0; 814 815 // While calling an idle handler, a new message could have been delivered 816 // so go back and look again for a pending message without waiting. 817 nextPollTimeoutMillis = 0; 818 } 819 } 820 quit(boolean safe)821 void quit(boolean safe) { 822 if (!mQuitAllowed) { 823 throw new IllegalStateException("Main thread not allowed to quit."); 824 } 825 826 synchronized (this) { 827 if (mQuitting) { 828 return; 829 } 830 mQuitting = true; 831 832 if (safe) { 833 removeAllFutureMessagesLocked(); 834 } else { 835 removeAllMessagesLocked(); 836 } 837 838 // We can assume mPtr != 0 because mQuitting was previously false. 839 nativeWake(mPtr); 840 } 841 } 842 843 /** 844 * Posts a synchronization barrier to the Looper's message queue. 845 * 846 * Message processing occurs as usual until the message queue encounters the 847 * synchronization barrier that has been posted. When the barrier is encountered, 848 * later synchronous messages in the queue are stalled (prevented from being executed) 849 * until the barrier is released by calling {@link #removeSyncBarrier} and specifying 850 * the token that identifies the synchronization barrier. 851 * 852 * This method is used to immediately postpone execution of all subsequently posted 853 * synchronous messages until a condition is met that releases the barrier. 854 * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier 855 * and continue to be processed as usual. 856 * 857 * This call must be always matched by a call to {@link #removeSyncBarrier} with 858 * the same token to ensure that the message queue resumes normal operation. 859 * Otherwise the application will probably hang! 860 * 861 * @return A token that uniquely identifies the barrier. This token must be 862 * passed to {@link #removeSyncBarrier} to release the barrier. 863 * 864 * @hide 865 */ 866 @UnsupportedAppUsage 867 @TestApi postSyncBarrier()868 public int postSyncBarrier() { 869 return postSyncBarrier(SystemClock.uptimeMillis()); 870 } 871 postSyncBarrier(long when)872 private int postSyncBarrier(long when) { 873 // Enqueue a new sync barrier token. 874 // We don't need to wake the queue because the purpose of a barrier is to stall it. 875 synchronized (this) { 876 final int token = mNextBarrierToken++; 877 final Message msg = Message.obtain(); 878 msg.arg1 = token; 879 880 enqueueMessageUnchecked(msg, when); 881 return token; 882 } 883 } 884 885 private class MatchBarrierToken extends MessageHeap.MessageHeapCompare { 886 int mBarrierToken; 887 MatchBarrierToken(int token)888 MatchBarrierToken(int token) { 889 super(); 890 mBarrierToken = token; 891 } 892 893 @Override compareMessage(Message m, Handler h, int what, Object object, Runnable r, long when)894 public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, 895 long when) { 896 if (m.target == null && m.arg1 == mBarrierToken) { 897 return true; 898 } 899 return false; 900 } 901 } 902 903 /** 904 * Removes a synchronization barrier. 905 * 906 * @param token The synchronization barrier token that was returned by 907 * {@link #postSyncBarrier}. 908 * 909 * @throws IllegalStateException if the barrier was not found. 910 * 911 * @hide 912 */ 913 @UnsupportedAppUsage 914 @TestApi removeSyncBarrier(int token)915 public void removeSyncBarrier(int token) { 916 final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token); 917 918 // Remove a sync barrier token from the queue. 919 // If the queue is no longer stalled by a barrier then wake it. 920 synchronized (this) { 921 boolean removed; 922 Message first = mPriorityQueue.peek(); 923 924 removed = mPriorityQueue.findOrRemoveMessages(null, 0, null, null, 0, 925 matchBarrierToken, true); 926 if (removed && first != null) { 927 // If the loop is quitting then it is already awake. 928 // We can assume mPtr != 0 when mQuitting is false. 929 if (first.target == null && first.arg1 == token && !mQuitting) { 930 nativeWake(mPtr); 931 } 932 } else if (!removed) { 933 throw new IllegalStateException("The specified message queue synchronization " 934 + " barrier token has not been posted or has already been removed."); 935 } 936 } 937 } 938 enqueueMessage(Message msg, long when)939 boolean enqueueMessage(Message msg, long when) { 940 if (msg.target == null) { 941 throw new IllegalArgumentException("Message must have a target."); 942 } 943 944 return enqueueMessageUnchecked(msg, when); 945 } 946 enqueueMessageUnchecked(Message msg, long when)947 boolean enqueueMessageUnchecked(Message msg, long when) { 948 synchronized (this) { 949 if (mQuitting) { 950 IllegalStateException e = new IllegalStateException( 951 msg.target + " sending message to a Handler on a dead thread"); 952 Log.w(TAG, e.getMessage(), e); 953 msg.recycle(); 954 return false; 955 } 956 957 if (msg.isInUse()) { 958 throw new IllegalStateException(msg + " This message is already in use."); 959 } 960 961 msg.markInUse(); 962 msg.when = when; 963 msg.mInsertSeq = when != 0 ? mNextInsertSeq++ : mNextFrontInsertSeq--; 964 if (DEBUG) Log.v(TAG, "Enqueue message: " + msg); 965 boolean needWake; 966 boolean isBarrier = msg.target == null; 967 Message first = peekEarliestMessage(); 968 969 if (priorityQueuesAreEmpty() || when == 0 || when < first.when) { 970 needWake = mBlocked && !isBarrier; 971 } else { 972 Message firstNonAsyncMessage = 973 first.isAsynchronous() ? mPriorityQueue.peek() : first; 974 975 needWake = mBlocked && firstNonAsyncMessage != null 976 && firstNonAsyncMessage.target == null && msg.isAsynchronous(); 977 } 978 979 if (msg.isAsynchronous()) { 980 mAsyncPriorityQueue.add(msg); 981 } else { 982 mPriorityQueue.add(msg); 983 } 984 985 // We can assume mPtr != 0 because mQuitting is false. 986 if (needWake) { 987 nativeWake(mPtr); 988 } 989 } 990 return true; 991 } 992 993 @GuardedBy("this") findOrRemoveMessages(Handler h, int what, Object object, Runnable r, long when, MessageHeap.MessageHeapCompare compare, boolean removeMatches)994 boolean findOrRemoveMessages(Handler h, int what, Object object, Runnable r, long when, 995 MessageHeap.MessageHeapCompare compare, boolean removeMatches) { 996 boolean found = mPriorityQueue.findOrRemoveMessages(h, what, object, r, when, compare, 997 removeMatches); 998 boolean foundAsync = mAsyncPriorityQueue.findOrRemoveMessages(h, what, object, r, when, 999 compare, removeMatches); 1000 return found || foundAsync; 1001 } 1002 1003 private static class MatchHandlerWhatAndObject extends MessageHeap.MessageHeapCompare { 1004 @Override compareMessage(Message m, Handler h, int what, Object object, Runnable r, long when)1005 public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, 1006 long when) { 1007 if (m.target == h && m.what == what && (object == null || m.obj == object)) { 1008 return true; 1009 } 1010 return false; 1011 } 1012 } 1013 private static final MatchHandlerWhatAndObject sMatchHandlerWhatAndObject = 1014 new MatchHandlerWhatAndObject(); 1015 hasMessages(Handler h, int what, Object object)1016 boolean hasMessages(Handler h, int what, Object object) { 1017 if (h == null) { 1018 return false; 1019 } 1020 1021 synchronized (this) { 1022 return findOrRemoveMessages(h, what, object, null, 0, sMatchHandlerWhatAndObject, 1023 false); 1024 } 1025 } 1026 1027 private static class MatchHandlerWhatAndObjectEquals extends MessageHeap.MessageHeapCompare { 1028 @Override compareMessage(Message m, Handler h, int what, Object object, Runnable r, long when)1029 public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, 1030 long when) { 1031 if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) { 1032 return true; 1033 } 1034 return false; 1035 } 1036 } 1037 private static final MatchHandlerWhatAndObjectEquals sMatchHandlerWhatAndObjectEquals = 1038 new MatchHandlerWhatAndObjectEquals(); hasEqualMessages(Handler h, int what, Object object)1039 boolean hasEqualMessages(Handler h, int what, Object object) { 1040 if (h == null) { 1041 return false; 1042 } 1043 1044 synchronized (this) { 1045 return findOrRemoveMessages(h, what, object, null, 0, 1046 sMatchHandlerWhatAndObjectEquals, false); 1047 } 1048 } 1049 1050 private static class MatchHandlerRunnableAndObject extends MessageHeap.MessageHeapCompare { 1051 @Override compareMessage(Message m, Handler h, int what, Object object, Runnable r, long when)1052 public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, 1053 long when) { 1054 if (m.target == h && m.callback == r && (object == null || m.obj == object)) { 1055 return true; 1056 } 1057 return false; 1058 } 1059 } 1060 private static final MatchHandlerRunnableAndObject sMatchHandlerRunnableAndObject = 1061 new MatchHandlerRunnableAndObject(); 1062 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) hasMessages(Handler h, Runnable r, Object object)1063 boolean hasMessages(Handler h, Runnable r, Object object) { 1064 if (h == null) { 1065 return false; 1066 } 1067 1068 synchronized (this) { 1069 return findOrRemoveMessages(h, -1, object, r, 0, sMatchHandlerRunnableAndObject, 1070 false); 1071 } 1072 } 1073 1074 private static class MatchHandler extends MessageHeap.MessageHeapCompare { 1075 @Override compareMessage(Message m, Handler h, int what, Object object, Runnable r, long when)1076 public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, 1077 long when) { 1078 if (m.target == h) { 1079 return true; 1080 } 1081 return false; 1082 } 1083 } 1084 private static final MatchHandler sMatchHandler = new MatchHandler(); hasMessages(Handler h)1085 boolean hasMessages(Handler h) { 1086 if (h == null) { 1087 return false; 1088 } 1089 1090 synchronized (this) { 1091 return findOrRemoveMessages(h, -1, null, null, 0, sMatchHandler, false); 1092 } 1093 } 1094 removeMessages(Handler h, int what, Object object)1095 void removeMessages(Handler h, int what, Object object) { 1096 if (h == null) { 1097 return; 1098 } 1099 1100 synchronized (this) { 1101 findOrRemoveMessages(h, what, object, null, 0, sMatchHandlerWhatAndObject, true); 1102 } 1103 } 1104 removeEqualMessages(Handler h, int what, Object object)1105 void removeEqualMessages(Handler h, int what, Object object) { 1106 if (h == null) { 1107 return; 1108 } 1109 1110 synchronized (this) { 1111 findOrRemoveMessages(h, what, object, null, 0, sMatchHandlerWhatAndObjectEquals, true); 1112 } 1113 } 1114 removeMessages(Handler h, Runnable r, Object object)1115 void removeMessages(Handler h, Runnable r, Object object) { 1116 if (h == null || r == null) { 1117 return; 1118 } 1119 1120 synchronized (this) { 1121 findOrRemoveMessages(h, -1, object, r, 0, sMatchHandlerRunnableAndObject, true); 1122 } 1123 } 1124 1125 private static class MatchHandlerRunnableAndObjectEquals 1126 extends MessageHeap.MessageHeapCompare { 1127 @Override compareMessage(Message m, Handler h, int what, Object object, Runnable r, long when)1128 public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, 1129 long when) { 1130 if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) { 1131 return true; 1132 } 1133 return false; 1134 } 1135 } 1136 private static final MatchHandlerRunnableAndObjectEquals sMatchHandlerRunnableAndObjectEquals = 1137 new MatchHandlerRunnableAndObjectEquals(); removeEqualMessages(Handler h, Runnable r, Object object)1138 void removeEqualMessages(Handler h, Runnable r, Object object) { 1139 if (h == null || r == null) { 1140 return; 1141 } 1142 1143 synchronized (this) { 1144 findOrRemoveMessages(h, -1, object, r, 0, sMatchHandlerRunnableAndObjectEquals, true); 1145 } 1146 } 1147 1148 private static class MatchHandlerAndObject extends MessageHeap.MessageHeapCompare { 1149 @Override compareMessage(Message m, Handler h, int what, Object object, Runnable r, long when)1150 public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, 1151 long when) { 1152 if (m.target == h && (object == null || m.obj == object)) { 1153 return true; 1154 } 1155 return false; 1156 } 1157 } 1158 private static final MatchHandlerAndObject sMatchHandlerAndObject = new MatchHandlerAndObject(); removeCallbacksAndMessages(Handler h, Object object)1159 void removeCallbacksAndMessages(Handler h, Object object) { 1160 if (h == null) { 1161 return; 1162 } 1163 1164 synchronized (this) { 1165 findOrRemoveMessages(h, -1, object, null, 0, sMatchHandlerAndObject, true); 1166 } 1167 } 1168 1169 private static class MatchHandlerAndObjectEquals extends MessageHeap.MessageHeapCompare { 1170 @Override compareMessage(Message m, Handler h, int what, Object object, Runnable r, long when)1171 public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, 1172 long when) { 1173 if (m.target == h && (object == null || object.equals(m.obj))) { 1174 return true; 1175 } 1176 return false; 1177 } 1178 } 1179 private static final MatchHandlerAndObjectEquals sMatchHandlerAndObjectEquals = 1180 new MatchHandlerAndObjectEquals(); removeCallbacksAndEqualMessages(Handler h, Object object)1181 void removeCallbacksAndEqualMessages(Handler h, Object object) { 1182 if (h == null) { 1183 return; 1184 } 1185 1186 synchronized (this) { 1187 findOrRemoveMessages(h, -1, object, null, 0, sMatchHandlerAndObjectEquals, true); 1188 } 1189 } 1190 1191 @GuardedBy("this") removeAllMessagesLocked()1192 private void removeAllMessagesLocked() { 1193 mPriorityQueue.removeAll(); 1194 mAsyncPriorityQueue.removeAll(); 1195 } 1196 1197 private static class MatchAllFutureMessages extends MessageHeap.MessageHeapCompare { 1198 @Override compareMessage(Message m, Handler h, int what, Object object, Runnable r, long when)1199 public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, 1200 long when) { 1201 if (m.when > when) { 1202 return true; 1203 } 1204 return false; 1205 } 1206 } 1207 private static final MatchAllFutureMessages sMatchAllFutureMessages = 1208 new MatchAllFutureMessages(); 1209 @GuardedBy("this") removeAllFutureMessagesLocked()1210 private void removeAllFutureMessagesLocked() { 1211 findOrRemoveMessages(null, -1, null, null, SystemClock.uptimeMillis(), 1212 sMatchAllFutureMessages, true); 1213 } 1214 1215 @NeverCompile dumpPriorityQueue(Printer pw, String prefix, Handler h, MessageHeap priorityQueue)1216 int dumpPriorityQueue(Printer pw, String prefix, Handler h, MessageHeap priorityQueue) { 1217 int n = 0; 1218 long now = SystemClock.uptimeMillis(); 1219 for (int i = 0; i < priorityQueue.numElements(); i++) { 1220 Message m = priorityQueue.getMessageAt(i); 1221 if (h == null && h == m.target) { 1222 pw.println(prefix + "Message " + n + ": " + m.toString(now)); 1223 n++; 1224 } 1225 } 1226 return n; 1227 } 1228 1229 @NeverCompile dumpPriorityQueue(ProtoOutputStream proto, MessageHeap priorityQueue)1230 void dumpPriorityQueue(ProtoOutputStream proto, MessageHeap priorityQueue) { 1231 for (int i = 0; i < priorityQueue.numElements(); i++) { 1232 Message m = priorityQueue.getMessageAt(i); 1233 m.dumpDebug(proto, MessageQueueProto.MESSAGES); 1234 } 1235 } 1236 1237 @NeverCompile dump(Printer pw, String prefix, Handler h)1238 void dump(Printer pw, String prefix, Handler h) { 1239 synchronized (this) { 1240 pw.println(prefix + "(MessageQueue is using Locked implementation)"); 1241 long now = SystemClock.uptimeMillis(); 1242 int n = dumpPriorityQueue(pw, prefix, h, mPriorityQueue); 1243 n += dumpPriorityQueue(pw, prefix, h, mAsyncPriorityQueue); 1244 pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked() 1245 + ", quitting=" + mQuitting + ")"); 1246 } 1247 } 1248 1249 @NeverCompile dumpDebug(ProtoOutputStream proto, long fieldId)1250 void dumpDebug(ProtoOutputStream proto, long fieldId) { 1251 final long messageQueueToken = proto.start(fieldId); 1252 synchronized (this) { 1253 dumpPriorityQueue(proto, mPriorityQueue); 1254 dumpPriorityQueue(proto, mAsyncPriorityQueue); 1255 proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPollingLocked()); 1256 proto.write(MessageQueueProto.IS_QUITTING, mQuitting); 1257 } 1258 proto.end(messageQueueToken); 1259 } 1260 1261 /** 1262 * Callback interface for discovering when a thread is going to block 1263 * waiting for more messages. 1264 */ 1265 public static interface IdleHandler { 1266 /** 1267 * Called when the message queue has run out of messages and will now 1268 * wait for more. Return true to keep your idle handler active, false 1269 * to have it removed. This may be called if there are still messages 1270 * pending in the queue, but they are all scheduled to be dispatched 1271 * after the current time. 1272 */ queueIdle()1273 boolean queueIdle(); 1274 } 1275 1276 /** 1277 * A listener which is invoked when file descriptor related events occur. 1278 */ 1279 public interface OnFileDescriptorEventListener { 1280 /** 1281 * File descriptor event: Indicates that the file descriptor is ready for input 1282 * operations, such as reading. 1283 * <p> 1284 * The listener should read all available data from the file descriptor 1285 * then return <code>true</code> to keep the listener active or <code>false</code> 1286 * to remove the listener. 1287 * </p><p> 1288 * In the case of a socket, this event may be generated to indicate 1289 * that there is at least one incoming connection that the listener 1290 * should accept. 1291 * </p><p> 1292 * This event will only be generated if the {@link #EVENT_INPUT} event mask was 1293 * specified when the listener was added. 1294 * </p> 1295 */ 1296 public static final int EVENT_INPUT = 1 << 0; 1297 1298 /** 1299 * File descriptor event: Indicates that the file descriptor is ready for output 1300 * operations, such as writing. 1301 * <p> 1302 * The listener should write as much data as it needs. If it could not 1303 * write everything at once, then it should return <code>true</code> to 1304 * keep the listener active. Otherwise, it should return <code>false</code> 1305 * to remove the listener then re-register it later when it needs to write 1306 * something else. 1307 * </p><p> 1308 * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was 1309 * specified when the listener was added. 1310 * </p> 1311 */ 1312 public static final int EVENT_OUTPUT = 1 << 1; 1313 1314 /** 1315 * File descriptor event: Indicates that the file descriptor encountered a 1316 * fatal error. 1317 * <p> 1318 * File descriptor errors can occur for various reasons. One common error 1319 * is when the remote peer of a socket or pipe closes its end of the connection. 1320 * </p><p> 1321 * This event may be generated at any time regardless of whether the 1322 * {@link #EVENT_ERROR} event mask was specified when the listener was added. 1323 * </p> 1324 */ 1325 public static final int EVENT_ERROR = 1 << 2; 1326 1327 /** @hide */ 1328 @Retention(RetentionPolicy.SOURCE) 1329 @IntDef(flag = true, prefix = { "EVENT_" }, value = { 1330 EVENT_INPUT, 1331 EVENT_OUTPUT, 1332 EVENT_ERROR 1333 }) 1334 public @interface Events {} 1335 1336 /** 1337 * Called when a file descriptor receives events. 1338 * 1339 * @param fd The file descriptor. 1340 * @param events The set of events that occurred: a combination of the 1341 * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks. 1342 * @return The new set of events to watch, or 0 to unregister the listener. 1343 * 1344 * @see #EVENT_INPUT 1345 * @see #EVENT_OUTPUT 1346 * @see #EVENT_ERROR 1347 */ onFileDescriptorEvents(@onNull FileDescriptor fd, @Events int events)1348 @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events); 1349 } 1350 1351 private static final class FileDescriptorRecord { 1352 public final FileDescriptor mDescriptor; 1353 public int mEvents; 1354 public OnFileDescriptorEventListener mListener; 1355 public int mSeq; 1356 FileDescriptorRecord(FileDescriptor descriptor, int events, OnFileDescriptorEventListener listener)1357 public FileDescriptorRecord(FileDescriptor descriptor, 1358 int events, OnFileDescriptorEventListener listener) { 1359 mDescriptor = descriptor; 1360 mEvents = events; 1361 mListener = listener; 1362 } 1363 } 1364 } 1365