1 /* 2 * Copyright (C) 2008 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.view; 18 19 import static android.view.flags.Flags.FLAG_ENABLE_DISPATCH_ON_SCROLL_CHANGED; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.compat.annotation.UnsupportedAppUsage; 25 import android.content.Context; 26 import android.graphics.Rect; 27 import android.graphics.Region; 28 import android.os.Build; 29 import android.util.Log; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.concurrent.CopyOnWriteArrayList; 34 import java.util.function.Consumer; 35 36 /** 37 * A view tree observer is used to register listeners that can be notified of global 38 * changes in the view tree. Such global events include, but are not limited to, 39 * layout of the whole tree, beginning of the drawing pass, touch mode change.... 40 * 41 * A ViewTreeObserver should never be instantiated by applications as it is provided 42 * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()} 43 * for more information. 44 */ 45 public final class ViewTreeObserver { 46 // Recursive listeners use CopyOnWriteArrayList 47 private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners; 48 private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners; 49 private CopyOnWriteArrayList<OnWindowVisibilityChangeListener> mOnWindowVisibilityListeners; 50 private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; 51 @UnsupportedAppUsage 52 private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; 53 private CopyOnWriteArrayList<OnEnterAnimationCompleteListener> 54 mOnEnterAnimationCompleteListeners; 55 56 // Non-recursive listeners use CopyOnWriteArray 57 // Any listener invoked from ViewRootImpl.performTraversals() should not be recursive 58 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 59 private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners; 60 @UnsupportedAppUsage 61 private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; 62 @UnsupportedAppUsage 63 private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners; 64 private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners; 65 private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners; 66 private CopyOnWriteArray<Consumer<List<Rect>>> mGestureExclusionListeners; 67 68 // These listeners cannot be mutated during dispatch 69 private boolean mInDispatchOnDraw; 70 private ArrayList<OnDrawListener> mOnDrawListeners; 71 private static boolean sIllegalOnDrawModificationIsFatal; 72 73 // These listeners are one-shot 74 private ArrayList<Runnable> mOnFrameCommitListeners; 75 76 /** Remains false until #dispatchOnWindowShown() is called. If a listener registers after 77 * that the listener will be immediately called. */ 78 private boolean mWindowShown; 79 80 // The reason that the last call to dispatchOnPreDraw() returned true to cancel and redraw 81 private StringBuilder mLastDispatchOnPreDrawCanceledReason; 82 83 private boolean mAlive = true; 84 85 /** 86 * Interface definition for a callback to be invoked when the view hierarchy is 87 * attached to and detached from its window. 88 */ 89 public interface OnWindowAttachListener { 90 /** 91 * Callback method to be invoked when the view hierarchy is attached to a window 92 */ onWindowAttached()93 public void onWindowAttached(); 94 95 /** 96 * Callback method to be invoked when the view hierarchy is detached from a window 97 */ onWindowDetached()98 public void onWindowDetached(); 99 } 100 101 /** 102 * Interface definition for a callback to be invoked when the view hierarchy's window 103 * focus state changes. 104 */ 105 public interface OnWindowFocusChangeListener { 106 /** 107 * Callback method to be invoked when the window focus changes in the view tree. 108 * 109 * @param hasFocus Set to true if the window is gaining focus, false if it is 110 * losing focus. 111 */ onWindowFocusChanged(boolean hasFocus)112 public void onWindowFocusChanged(boolean hasFocus); 113 } 114 115 /** 116 * Interface definition for a callback to be invoked when the view hierarchy's window 117 * visibility changes. 118 */ 119 public interface OnWindowVisibilityChangeListener { 120 /** 121 * Callback method to be invoked when the window visibility changes in the view tree. 122 * 123 * @param visibility The new visibility of the window. 124 */ onWindowVisibilityChanged(@iew.Visibility int visibility)125 void onWindowVisibilityChanged(@View.Visibility int visibility); 126 } 127 128 /** 129 * Interface definition for a callback to be invoked when the focus state within 130 * the view tree changes. 131 */ 132 public interface OnGlobalFocusChangeListener { 133 /** 134 * Callback method to be invoked when the focus changes in the view tree. When 135 * the view tree transitions from touch mode to non-touch mode, oldFocus is null. 136 * When the view tree transitions from non-touch mode to touch mode, newFocus is 137 * null. When focus changes in non-touch mode (without transition from or to 138 * touch mode) either oldFocus or newFocus can be null. 139 * 140 * @param oldFocus The previously focused view, if any. 141 * @param newFocus The newly focused View, if any. 142 */ onGlobalFocusChanged(View oldFocus, View newFocus)143 public void onGlobalFocusChanged(View oldFocus, View newFocus); 144 } 145 146 /** 147 * Interface definition for a callback to be invoked when the global layout state 148 * or the visibility of views within the view tree changes. 149 */ 150 public interface OnGlobalLayoutListener { 151 /** 152 * Callback method to be invoked when the global layout state or the visibility of views 153 * within the view tree changes 154 */ onGlobalLayout()155 public void onGlobalLayout(); 156 } 157 158 /** 159 * Interface definition for a callback to be invoked when the view tree is about to be drawn. 160 */ 161 public interface OnPreDrawListener { 162 /** 163 * Callback method to be invoked when the view tree is about to be drawn. At this point, all 164 * views in the tree have been measured and given a frame. Clients can use this to adjust 165 * their scroll bounds or even to request a new layout before drawing occurs. 166 * 167 * @return Return true to proceed with the current drawing pass, or false to cancel. 168 * 169 * @see android.view.View#onMeasure 170 * @see android.view.View#onLayout 171 * @see android.view.View#onDraw 172 */ onPreDraw()173 public boolean onPreDraw(); 174 } 175 176 /** 177 * Interface definition for a callback to be invoked when the view tree is about to be drawn. 178 */ 179 public interface OnDrawListener { 180 /** 181 * <p>Callback method to be invoked when the view tree is about to be drawn. At this point, 182 * views cannot be modified in any way.</p> 183 * 184 * <p>Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the 185 * current drawing pass.</p> 186 * 187 * <p>An {@link OnDrawListener} listener <strong>cannot be added or removed</strong> 188 * from this method.</p> 189 * 190 * @see android.view.View#onMeasure 191 * @see android.view.View#onLayout 192 * @see android.view.View#onDraw 193 */ onDraw()194 public void onDraw(); 195 } 196 197 /** 198 * Interface definition for a callback to be invoked when the touch mode changes. 199 */ 200 public interface OnTouchModeChangeListener { 201 /** 202 * Callback method to be invoked when the touch mode changes. 203 * 204 * @param isInTouchMode True if the view hierarchy is now in touch mode, false otherwise. 205 */ onTouchModeChanged(boolean isInTouchMode)206 public void onTouchModeChanged(boolean isInTouchMode); 207 } 208 209 /** 210 * Interface definition for a callback to be invoked when 211 * something in the view tree has been scrolled. 212 */ 213 public interface OnScrollChangedListener { 214 /** 215 * Callback method to be invoked when something in the view tree 216 * has been scrolled. 217 */ onScrollChanged()218 public void onScrollChanged(); 219 } 220 221 /** 222 * Interface definition for a callback noting when a system window has been displayed. 223 * This is only used for non-Activity windows. Activity windows can use 224 * Activity.onEnterAnimationComplete() to get the same signal. 225 * @hide 226 */ 227 public interface OnWindowShownListener { 228 /** 229 * Callback method to be invoked when a non-activity window is fully shown. 230 */ onWindowShown()231 void onWindowShown(); 232 } 233 234 /** 235 * Parameters used with OnComputeInternalInsetsListener. 236 * 237 * We are not yet ready to commit to this API and support it, so 238 * @hide 239 */ 240 public final static class InternalInsetsInfo { 241 242 @UnsupportedAppUsage InternalInsetsInfo()243 public InternalInsetsInfo() { 244 } 245 246 /** 247 * Offsets from the frame of the window at which the content of 248 * windows behind it should be placed. 249 */ 250 @UnsupportedAppUsage 251 public final Rect contentInsets = new Rect(); 252 253 /** 254 * Offsets from the frame of the window at which windows behind it 255 * are visible. 256 */ 257 @UnsupportedAppUsage 258 public final Rect visibleInsets = new Rect(); 259 260 /** 261 * Touchable region defined relative to the origin of the frame of the window. 262 * Only used when {@link #setTouchableInsets(int)} is called with 263 * the option {@link #TOUCHABLE_INSETS_REGION}. 264 */ 265 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 266 public final Region touchableRegion = new Region(); 267 268 /** 269 * Option for {@link #setTouchableInsets(int)}: the entire window frame 270 * can be touched. 271 */ 272 public static final int TOUCHABLE_INSETS_FRAME = 0; 273 274 /** 275 * Option for {@link #setTouchableInsets(int)}: the area inside of 276 * the content insets can be touched. 277 */ 278 public static final int TOUCHABLE_INSETS_CONTENT = 1; 279 280 /** 281 * Option for {@link #setTouchableInsets(int)}: the area inside of 282 * the visible insets can be touched. 283 */ 284 public static final int TOUCHABLE_INSETS_VISIBLE = 2; 285 286 /** 287 * Option for {@link #setTouchableInsets(int)}: the area inside of 288 * the provided touchable region in {@link #touchableRegion} can be touched. 289 */ 290 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 291 public static final int TOUCHABLE_INSETS_REGION = 3; 292 293 /** 294 * Set which parts of the window can be touched: either 295 * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT}, 296 * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}. 297 */ 298 @UnsupportedAppUsage setTouchableInsets(int val)299 public void setTouchableInsets(int val) { 300 mTouchableInsets = val; 301 } 302 303 @UnsupportedAppUsage 304 int mTouchableInsets; 305 reset()306 void reset() { 307 contentInsets.setEmpty(); 308 visibleInsets.setEmpty(); 309 touchableRegion.setEmpty(); 310 mTouchableInsets = TOUCHABLE_INSETS_FRAME; 311 } 312 isEmpty()313 boolean isEmpty() { 314 return contentInsets.isEmpty() 315 && visibleInsets.isEmpty() 316 && touchableRegion.isEmpty() 317 && mTouchableInsets == TOUCHABLE_INSETS_FRAME; 318 } 319 320 @Override hashCode()321 public int hashCode() { 322 int result = contentInsets.hashCode(); 323 result = 31 * result + visibleInsets.hashCode(); 324 result = 31 * result + touchableRegion.hashCode(); 325 result = 31 * result + mTouchableInsets; 326 return result; 327 } 328 329 @Override equals(@ullable Object o)330 public boolean equals(@Nullable Object o) { 331 if (this == o) return true; 332 if (o == null || getClass() != o.getClass()) return false; 333 334 InternalInsetsInfo other = (InternalInsetsInfo)o; 335 return mTouchableInsets == other.mTouchableInsets && 336 contentInsets.equals(other.contentInsets) && 337 visibleInsets.equals(other.visibleInsets) && 338 touchableRegion.equals(other.touchableRegion); 339 } 340 341 @UnsupportedAppUsage set(InternalInsetsInfo other)342 void set(InternalInsetsInfo other) { 343 contentInsets.set(other.contentInsets); 344 visibleInsets.set(other.visibleInsets); 345 touchableRegion.set(other.touchableRegion); 346 mTouchableInsets = other.mTouchableInsets; 347 } 348 } 349 350 /** 351 * Interface definition for a callback to be invoked when layout has 352 * completed and the client can compute its interior insets. 353 * 354 * We are not yet ready to commit to this API and support it, so 355 * @hide 356 */ 357 public interface OnComputeInternalInsetsListener { 358 /** 359 * Callback method to be invoked when layout has completed and the 360 * client can compute its interior insets. 361 * 362 * @param inoutInfo Should be filled in by the implementation with 363 * the information about the insets of the window. This is called 364 * with whatever values the previous OnComputeInternalInsetsListener 365 * returned, if there are multiple such listeners in the window. 366 */ onComputeInternalInsets(InternalInsetsInfo inoutInfo)367 public void onComputeInternalInsets(InternalInsetsInfo inoutInfo); 368 } 369 370 /** 371 * @hide 372 */ 373 public interface OnEnterAnimationCompleteListener { onEnterAnimationComplete()374 public void onEnterAnimationComplete(); 375 } 376 377 /** 378 * Creates a new ViewTreeObserver. This constructor should not be called 379 */ ViewTreeObserver(Context context)380 ViewTreeObserver(Context context) { 381 sIllegalOnDrawModificationIsFatal = 382 context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O; 383 } 384 385 /** 386 * Merges all the listeners registered on the specified observer with the listeners 387 * registered on this object. After this method is invoked, the specified observer 388 * will return false in {@link #isAlive()} and should not be used anymore. 389 * 390 * @param observer The ViewTreeObserver whose listeners must be added to this observer 391 */ merge(ViewTreeObserver observer)392 void merge(ViewTreeObserver observer) { 393 if (observer.mOnWindowAttachListeners != null) { 394 if (mOnWindowAttachListeners != null) { 395 mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners); 396 } else { 397 mOnWindowAttachListeners = observer.mOnWindowAttachListeners; 398 } 399 } 400 401 if (observer.mOnWindowFocusListeners != null) { 402 if (mOnWindowFocusListeners != null) { 403 mOnWindowFocusListeners.addAll(observer.mOnWindowFocusListeners); 404 } else { 405 mOnWindowFocusListeners = observer.mOnWindowFocusListeners; 406 } 407 } 408 409 if (observer.mOnWindowVisibilityListeners != null) { 410 if (mOnWindowVisibilityListeners != null) { 411 mOnWindowVisibilityListeners.addAll(observer.mOnWindowVisibilityListeners); 412 } else { 413 mOnWindowVisibilityListeners = observer.mOnWindowVisibilityListeners; 414 } 415 } 416 417 if (observer.mOnGlobalFocusListeners != null) { 418 if (mOnGlobalFocusListeners != null) { 419 mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners); 420 } else { 421 mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners; 422 } 423 } 424 425 if (observer.mOnGlobalLayoutListeners != null) { 426 if (mOnGlobalLayoutListeners != null) { 427 mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners); 428 } else { 429 mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners; 430 } 431 } 432 433 if (observer.mOnPreDrawListeners != null) { 434 if (mOnPreDrawListeners != null) { 435 mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners); 436 } else { 437 mOnPreDrawListeners = observer.mOnPreDrawListeners; 438 } 439 } 440 441 if (observer.mOnDrawListeners != null) { 442 if (mOnDrawListeners != null) { 443 mOnDrawListeners.addAll(observer.mOnDrawListeners); 444 } else { 445 mOnDrawListeners = observer.mOnDrawListeners; 446 } 447 } 448 449 if (observer.mOnFrameCommitListeners != null) { 450 if (mOnFrameCommitListeners != null) { 451 mOnFrameCommitListeners.addAll(observer.captureFrameCommitCallbacks()); 452 } else { 453 mOnFrameCommitListeners = observer.captureFrameCommitCallbacks(); 454 } 455 } 456 457 if (observer.mOnTouchModeChangeListeners != null) { 458 if (mOnTouchModeChangeListeners != null) { 459 mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners); 460 } else { 461 mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners; 462 } 463 } 464 465 if (observer.mOnComputeInternalInsetsListeners != null) { 466 if (mOnComputeInternalInsetsListeners != null) { 467 mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners); 468 } else { 469 mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners; 470 } 471 } 472 473 if (observer.mOnScrollChangedListeners != null) { 474 if (mOnScrollChangedListeners != null) { 475 mOnScrollChangedListeners.addAll(observer.mOnScrollChangedListeners); 476 } else { 477 mOnScrollChangedListeners = observer.mOnScrollChangedListeners; 478 } 479 } 480 481 if (observer.mOnWindowShownListeners != null) { 482 if (mOnWindowShownListeners != null) { 483 mOnWindowShownListeners.addAll(observer.mOnWindowShownListeners); 484 } else { 485 mOnWindowShownListeners = observer.mOnWindowShownListeners; 486 } 487 } 488 489 if (observer.mGestureExclusionListeners != null) { 490 if (mGestureExclusionListeners != null) { 491 mGestureExclusionListeners.addAll(observer.mGestureExclusionListeners); 492 } else { 493 mGestureExclusionListeners = observer.mGestureExclusionListeners; 494 } 495 } 496 497 observer.kill(); 498 } 499 500 /** 501 * Register a callback to be invoked when the view hierarchy is attached to a window. 502 * 503 * @param listener The callback to add 504 * 505 * @throws IllegalStateException If {@link #isAlive()} returns false 506 */ addOnWindowAttachListener(OnWindowAttachListener listener)507 public void addOnWindowAttachListener(OnWindowAttachListener listener) { 508 checkIsAlive(); 509 510 if (mOnWindowAttachListeners == null) { 511 mOnWindowAttachListeners 512 = new CopyOnWriteArrayList<OnWindowAttachListener>(); 513 } 514 515 mOnWindowAttachListeners.add(listener); 516 } 517 518 /** 519 * Remove a previously installed window attach callback. 520 * 521 * @param victim The callback to remove 522 * 523 * @throws IllegalStateException If {@link #isAlive()} returns false 524 * 525 * @see #addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener) 526 */ removeOnWindowAttachListener(OnWindowAttachListener victim)527 public void removeOnWindowAttachListener(OnWindowAttachListener victim) { 528 checkIsAlive(); 529 if (mOnWindowAttachListeners == null) { 530 return; 531 } 532 mOnWindowAttachListeners.remove(victim); 533 } 534 535 /** 536 * Register a callback to be invoked when the window focus state within the view tree changes. 537 * 538 * @param listener The callback to add 539 * 540 * @throws IllegalStateException If {@link #isAlive()} returns false 541 */ addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener)542 public void addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) { 543 checkIsAlive(); 544 545 if (mOnWindowFocusListeners == null) { 546 mOnWindowFocusListeners 547 = new CopyOnWriteArrayList<OnWindowFocusChangeListener>(); 548 } 549 550 mOnWindowFocusListeners.add(listener); 551 } 552 553 /** 554 * Remove a previously installed window focus change callback. 555 * 556 * @param victim The callback to remove 557 * 558 * @throws IllegalStateException If {@link #isAlive()} returns false 559 * 560 * @see #addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener) 561 */ removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim)562 public void removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim) { 563 checkIsAlive(); 564 if (mOnWindowFocusListeners == null) { 565 return; 566 } 567 mOnWindowFocusListeners.remove(victim); 568 } 569 570 /** 571 * Register a callback to be invoked when the window visibility changes. 572 * 573 * @param listener The callback to add 574 * 575 * @throws IllegalStateException If {@link #isAlive()} returns false 576 */ addOnWindowVisibilityChangeListener( @onNull OnWindowVisibilityChangeListener listener)577 public void addOnWindowVisibilityChangeListener( 578 @NonNull OnWindowVisibilityChangeListener listener) { 579 checkIsAlive(); 580 581 if (mOnWindowVisibilityListeners == null) { 582 mOnWindowVisibilityListeners = 583 new CopyOnWriteArrayList<OnWindowVisibilityChangeListener>(); 584 } 585 586 mOnWindowVisibilityListeners.add(listener); 587 } 588 589 /** 590 * Remove a previously installed window visibility callback. 591 * 592 * @param victim The callback to remove 593 * 594 * @throws IllegalStateException If {@link #isAlive()} returns false 595 * 596 * @see #addOnWindowVisibilityChangeListener( 597 * android.view.ViewTreeObserver.OnWindowVisibilityChangeListener) 598 */ removeOnWindowVisibilityChangeListener( @onNull OnWindowVisibilityChangeListener victim)599 public void removeOnWindowVisibilityChangeListener( 600 @NonNull OnWindowVisibilityChangeListener victim) { 601 checkIsAlive(); 602 if (mOnWindowVisibilityListeners == null) { 603 return; 604 } 605 606 mOnWindowVisibilityListeners.remove(victim); 607 } 608 609 /* 610 * Register a callback to be invoked when the focus state within the view tree changes. 611 * 612 * @param listener The callback to add 613 * 614 * @throws IllegalStateException If {@link #isAlive()} returns false 615 */ addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener)616 public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) { 617 checkIsAlive(); 618 619 if (mOnGlobalFocusListeners == null) { 620 mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>(); 621 } 622 623 mOnGlobalFocusListeners.add(listener); 624 } 625 626 /** 627 * Remove a previously installed focus change callback. 628 * 629 * @param victim The callback to remove 630 * 631 * @throws IllegalStateException If {@link #isAlive()} returns false 632 * 633 * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener) 634 */ removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim)635 public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) { 636 checkIsAlive(); 637 if (mOnGlobalFocusListeners == null) { 638 return; 639 } 640 mOnGlobalFocusListeners.remove(victim); 641 } 642 643 /** 644 * Register a callback to be invoked when the global layout state or the visibility of views 645 * within the view tree changes 646 * 647 * @param listener The callback to add 648 * 649 * @throws IllegalStateException If {@link #isAlive()} returns false 650 */ addOnGlobalLayoutListener(OnGlobalLayoutListener listener)651 public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) { 652 checkIsAlive(); 653 654 if (mOnGlobalLayoutListeners == null) { 655 mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>(); 656 } 657 658 mOnGlobalLayoutListeners.add(listener); 659 } 660 661 /** 662 * Remove a previously installed global layout callback 663 * 664 * @param victim The callback to remove 665 * 666 * @throws IllegalStateException If {@link #isAlive()} returns false 667 * 668 * @deprecated Use #removeOnGlobalLayoutListener instead 669 * 670 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener) 671 */ 672 @Deprecated removeGlobalOnLayoutListener(OnGlobalLayoutListener victim)673 public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) { 674 removeOnGlobalLayoutListener(victim); 675 } 676 677 /** 678 * Remove a previously installed global layout callback 679 * 680 * @param victim The callback to remove 681 * 682 * @throws IllegalStateException If {@link #isAlive()} returns false 683 * 684 * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener) 685 */ removeOnGlobalLayoutListener(OnGlobalLayoutListener victim)686 public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) { 687 checkIsAlive(); 688 if (mOnGlobalLayoutListeners == null) { 689 return; 690 } 691 mOnGlobalLayoutListeners.remove(victim); 692 } 693 694 /** 695 * Register a callback to be invoked when the view tree is about to be drawn 696 * 697 * @param listener The callback to add 698 * 699 * @throws IllegalStateException If {@link #isAlive()} returns false 700 */ addOnPreDrawListener(OnPreDrawListener listener)701 public void addOnPreDrawListener(OnPreDrawListener listener) { 702 checkIsAlive(); 703 704 if (mOnPreDrawListeners == null) { 705 mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>(); 706 } 707 708 mOnPreDrawListeners.add(listener); 709 } 710 711 /** 712 * Remove a previously installed pre-draw callback 713 * 714 * @param victim The callback to remove 715 * 716 * @throws IllegalStateException If {@link #isAlive()} returns false 717 * 718 * @see #addOnPreDrawListener(OnPreDrawListener) 719 */ removeOnPreDrawListener(OnPreDrawListener victim)720 public void removeOnPreDrawListener(OnPreDrawListener victim) { 721 checkIsAlive(); 722 if (mOnPreDrawListeners == null) { 723 return; 724 } 725 mOnPreDrawListeners.remove(victim); 726 } 727 728 /** 729 * Register a callback to be invoked when the view tree window has been shown 730 * 731 * @param listener The callback to add 732 * 733 * @throws IllegalStateException If {@link #isAlive()} returns false 734 * @hide 735 */ addOnWindowShownListener(OnWindowShownListener listener)736 public void addOnWindowShownListener(OnWindowShownListener listener) { 737 checkIsAlive(); 738 739 if (mOnWindowShownListeners == null) { 740 mOnWindowShownListeners = new CopyOnWriteArray<OnWindowShownListener>(); 741 } 742 743 mOnWindowShownListeners.add(listener); 744 if (mWindowShown) { 745 listener.onWindowShown(); 746 } 747 } 748 749 /** 750 * Remove a previously installed window shown callback 751 * 752 * @param victim The callback to remove 753 * 754 * @throws IllegalStateException If {@link #isAlive()} returns false 755 * 756 * @see #addOnWindowShownListener(OnWindowShownListener) 757 * @hide 758 */ removeOnWindowShownListener(OnWindowShownListener victim)759 public void removeOnWindowShownListener(OnWindowShownListener victim) { 760 checkIsAlive(); 761 if (mOnWindowShownListeners == null) { 762 return; 763 } 764 mOnWindowShownListeners.remove(victim); 765 } 766 767 /** 768 * <p>Register a callback to be invoked when the view tree is about to be drawn.</p> 769 * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from 770 * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> 771 * 772 * @param listener The callback to add 773 * 774 * @throws IllegalStateException If {@link #isAlive()} returns false 775 */ addOnDrawListener(OnDrawListener listener)776 public void addOnDrawListener(OnDrawListener listener) { 777 checkIsAlive(); 778 779 if (mOnDrawListeners == null) { 780 mOnDrawListeners = new ArrayList<OnDrawListener>(); 781 } 782 783 if (mInDispatchOnDraw) { 784 IllegalStateException ex = new IllegalStateException( 785 "Cannot call addOnDrawListener inside of onDraw"); 786 if (sIllegalOnDrawModificationIsFatal) { 787 throw ex; 788 } else { 789 Log.e("ViewTreeObserver", ex.getMessage(), ex); 790 } 791 } 792 mOnDrawListeners.add(listener); 793 } 794 795 /** 796 * <p>Remove a previously installed pre-draw callback.</p> 797 * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from 798 * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> 799 * 800 * @param victim The callback to remove 801 * 802 * @throws IllegalStateException If {@link #isAlive()} returns false 803 * 804 * @see #addOnDrawListener(OnDrawListener) 805 */ removeOnDrawListener(OnDrawListener victim)806 public void removeOnDrawListener(OnDrawListener victim) { 807 checkIsAlive(); 808 if (mOnDrawListeners == null) { 809 return; 810 } 811 if (mInDispatchOnDraw) { 812 IllegalStateException ex = new IllegalStateException( 813 "Cannot call removeOnDrawListener inside of onDraw"); 814 if (sIllegalOnDrawModificationIsFatal) { 815 throw ex; 816 } else { 817 Log.e("ViewTreeObserver", ex.getMessage(), ex); 818 } 819 } 820 mOnDrawListeners.remove(victim); 821 } 822 823 /** 824 * Adds a frame commit callback. This callback will be invoked when the current rendering 825 * content has been rendered into a frame and submitted to the swap chain. The frame may 826 * not currently be visible on the display when this is invoked, but it has been submitted. 827 * This callback is useful in combination with {@link PixelCopy} to capture the current 828 * rendered content of the UI reliably. 829 * 830 * Note: Only works with hardware rendering. Does nothing otherwise. 831 * 832 * @param callback The callback to invoke when the frame is committed. 833 */ registerFrameCommitCallback(@onNull Runnable callback)834 public void registerFrameCommitCallback(@NonNull Runnable callback) { 835 checkIsAlive(); 836 if (mOnFrameCommitListeners == null) { 837 mOnFrameCommitListeners = new ArrayList<>(); 838 } 839 mOnFrameCommitListeners.add(callback); 840 } 841 captureFrameCommitCallbacks()842 @Nullable ArrayList<Runnable> captureFrameCommitCallbacks() { 843 ArrayList<Runnable> ret = mOnFrameCommitListeners; 844 mOnFrameCommitListeners = null; 845 return ret; 846 } 847 848 /** 849 * Attempts to remove the given callback from the list of pending frame complete callbacks. 850 * 851 * @param callback The callback to remove 852 * @return Whether or not the callback was removed. If this returns true the callback will 853 * not be invoked. If false is returned then the callback was either never added 854 * or may already be pending execution and was unable to be removed 855 */ unregisterFrameCommitCallback(@onNull Runnable callback)856 public boolean unregisterFrameCommitCallback(@NonNull Runnable callback) { 857 checkIsAlive(); 858 if (mOnFrameCommitListeners == null) { 859 return false; 860 } 861 return mOnFrameCommitListeners.remove(callback); 862 } 863 864 /** 865 * Register a callback to be invoked when a view has been scrolled. 866 * 867 * @param listener The callback to add 868 * 869 * @throws IllegalStateException If {@link #isAlive()} returns false 870 */ addOnScrollChangedListener(OnScrollChangedListener listener)871 public void addOnScrollChangedListener(OnScrollChangedListener listener) { 872 checkIsAlive(); 873 874 if (mOnScrollChangedListeners == null) { 875 mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>(); 876 } 877 878 mOnScrollChangedListeners.add(listener); 879 } 880 881 /** 882 * Remove a previously installed scroll-changed callback 883 * 884 * @param victim The callback to remove 885 * 886 * @throws IllegalStateException If {@link #isAlive()} returns false 887 * 888 * @see #addOnScrollChangedListener(OnScrollChangedListener) 889 */ removeOnScrollChangedListener(OnScrollChangedListener victim)890 public void removeOnScrollChangedListener(OnScrollChangedListener victim) { 891 checkIsAlive(); 892 if (mOnScrollChangedListeners == null) { 893 return; 894 } 895 mOnScrollChangedListeners.remove(victim); 896 } 897 898 /** 899 * Register a callback to be invoked when the invoked when the touch mode changes. 900 * 901 * @param listener The callback to add 902 * 903 * @throws IllegalStateException If {@link #isAlive()} returns false 904 */ addOnTouchModeChangeListener(OnTouchModeChangeListener listener)905 public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) { 906 checkIsAlive(); 907 908 if (mOnTouchModeChangeListeners == null) { 909 mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>(); 910 } 911 912 mOnTouchModeChangeListeners.add(listener); 913 } 914 915 /** 916 * Remove a previously installed touch mode change callback 917 * 918 * @param victim The callback to remove 919 * 920 * @throws IllegalStateException If {@link #isAlive()} returns false 921 * 922 * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener) 923 */ removeOnTouchModeChangeListener(OnTouchModeChangeListener victim)924 public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) { 925 checkIsAlive(); 926 if (mOnTouchModeChangeListeners == null) { 927 return; 928 } 929 mOnTouchModeChangeListeners.remove(victim); 930 } 931 932 /** 933 * Register a callback to be invoked when the invoked when it is time to 934 * compute the window's internal insets. 935 * 936 * @param listener The callback to add 937 * 938 * @throws IllegalStateException If {@link #isAlive()} returns false 939 * 940 * We are not yet ready to commit to this API and support it, so 941 * @hide 942 */ 943 @UnsupportedAppUsage addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener)944 public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) { 945 checkIsAlive(); 946 947 if (mOnComputeInternalInsetsListeners == null) { 948 mOnComputeInternalInsetsListeners = 949 new CopyOnWriteArray<OnComputeInternalInsetsListener>(); 950 } 951 952 mOnComputeInternalInsetsListeners.add(listener); 953 } 954 955 /** 956 * Remove a previously installed internal insets computation callback 957 * 958 * @param victim The callback to remove 959 * 960 * @throws IllegalStateException If {@link #isAlive()} returns false 961 * 962 * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener) 963 * 964 * We are not yet ready to commit to this API and support it, so 965 * @hide 966 */ 967 @UnsupportedAppUsage removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim)968 public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) { 969 checkIsAlive(); 970 if (mOnComputeInternalInsetsListeners == null) { 971 return; 972 } 973 mOnComputeInternalInsetsListeners.remove(victim); 974 } 975 976 /** 977 * @hide 978 */ addOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener)979 public void addOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) { 980 checkIsAlive(); 981 if (mOnEnterAnimationCompleteListeners == null) { 982 mOnEnterAnimationCompleteListeners = 983 new CopyOnWriteArrayList<OnEnterAnimationCompleteListener>(); 984 } 985 mOnEnterAnimationCompleteListeners.add(listener); 986 } 987 988 /** 989 * @hide 990 */ removeOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener)991 public void removeOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) { 992 checkIsAlive(); 993 if (mOnEnterAnimationCompleteListeners == null) { 994 return; 995 } 996 mOnEnterAnimationCompleteListeners.remove(listener); 997 } 998 999 /** 1000 * Add a listener to be notified when the tree's <em>transformed</em> gesture exclusion rects 1001 * change. This could be the result of an animation or other layout change, or a view calling 1002 * {@link View#setSystemGestureExclusionRects(List)}. 1003 * 1004 * @param listener listener to add 1005 * @see View#setSystemGestureExclusionRects(List) 1006 */ addOnSystemGestureExclusionRectsChangedListener( @onNull Consumer<List<Rect>> listener)1007 public void addOnSystemGestureExclusionRectsChangedListener( 1008 @NonNull Consumer<List<Rect>> listener) { 1009 checkIsAlive(); 1010 if (mGestureExclusionListeners == null) { 1011 mGestureExclusionListeners = new CopyOnWriteArray<>(); 1012 } 1013 mGestureExclusionListeners.add(listener); 1014 } 1015 1016 /** 1017 * Unsubscribe the given listener from gesture exclusion rect changes. 1018 * @see #addOnSystemGestureExclusionRectsChangedListener(Consumer) 1019 * @see View#setSystemGestureExclusionRects(List) 1020 */ removeOnSystemGestureExclusionRectsChangedListener( @onNull Consumer<List<Rect>> listener)1021 public void removeOnSystemGestureExclusionRectsChangedListener( 1022 @NonNull Consumer<List<Rect>> listener) { 1023 checkIsAlive(); 1024 if (mGestureExclusionListeners == null) { 1025 return; 1026 } 1027 mGestureExclusionListeners.remove(listener); 1028 } 1029 checkIsAlive()1030 private void checkIsAlive() { 1031 if (!mAlive) { 1032 throw new IllegalStateException("This ViewTreeObserver is not alive, call " 1033 + "getViewTreeObserver() again"); 1034 } 1035 } 1036 1037 /** 1038 * Indicates whether this ViewTreeObserver is alive. When an observer is not alive, 1039 * any call to a method (except this one) will throw an exception. 1040 * 1041 * If an application keeps a long-lived reference to this ViewTreeObserver, it should 1042 * always check for the result of this method before calling any other method. 1043 * 1044 * @return True if this object is alive and be used, false otherwise. 1045 */ isAlive()1046 public boolean isAlive() { 1047 return mAlive; 1048 } 1049 1050 /** 1051 * Marks this ViewTreeObserver as not alive. After invoking this method, invoking 1052 * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception. 1053 * 1054 * @hide 1055 */ kill()1056 private void kill() { 1057 mAlive = false; 1058 } 1059 1060 /** 1061 * Notifies registered listeners that window has been attached/detached. 1062 */ dispatchOnWindowAttachedChange(boolean attached)1063 final void dispatchOnWindowAttachedChange(boolean attached) { 1064 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 1065 // perform the dispatching. The iterator is a safe guard against listeners that 1066 // could mutate the list by calling the various add/remove methods. This prevents 1067 // the array from being modified while we iterate it. 1068 final CopyOnWriteArrayList<OnWindowAttachListener> listeners 1069 = mOnWindowAttachListeners; 1070 if (listeners != null && listeners.size() > 0) { 1071 for (OnWindowAttachListener listener : listeners) { 1072 if (attached) listener.onWindowAttached(); 1073 else listener.onWindowDetached(); 1074 } 1075 } 1076 } 1077 1078 /** 1079 * Notifies registered listeners that window focus has changed. 1080 */ dispatchOnWindowFocusChange(boolean hasFocus)1081 final void dispatchOnWindowFocusChange(boolean hasFocus) { 1082 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 1083 // perform the dispatching. The iterator is a safe guard against listeners that 1084 // could mutate the list by calling the various add/remove methods. This prevents 1085 // the array from being modified while we iterate it. 1086 final CopyOnWriteArrayList<OnWindowFocusChangeListener> listeners 1087 = mOnWindowFocusListeners; 1088 if (listeners != null && listeners.size() > 0) { 1089 for (OnWindowFocusChangeListener listener : listeners) { 1090 listener.onWindowFocusChanged(hasFocus); 1091 } 1092 } 1093 } 1094 1095 /** 1096 * Notifies registered listeners that window visibility has changed. 1097 */ dispatchOnWindowVisibilityChange(int visibility)1098 void dispatchOnWindowVisibilityChange(int visibility) { 1099 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 1100 // perform the dispatching. The iterator is a safe guard against listeners that 1101 // could mutate the list by calling the various add/remove methods. This prevents 1102 // the array from being modified while we iterate it. 1103 final CopyOnWriteArrayList<OnWindowVisibilityChangeListener> listeners = 1104 mOnWindowVisibilityListeners; 1105 if (listeners != null && listeners.size() > 0) { 1106 for (OnWindowVisibilityChangeListener listener : listeners) { 1107 listener.onWindowVisibilityChanged(visibility); 1108 } 1109 } 1110 } 1111 1112 /** 1113 * Notifies registered listeners that focus has changed. 1114 */ 1115 @UnsupportedAppUsage dispatchOnGlobalFocusChange(View oldFocus, View newFocus)1116 final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) { 1117 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 1118 // perform the dispatching. The iterator is a safe guard against listeners that 1119 // could mutate the list by calling the various add/remove methods. This prevents 1120 // the array from being modified while we iterate it. 1121 final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners; 1122 if (listeners != null && listeners.size() > 0) { 1123 for (OnGlobalFocusChangeListener listener : listeners) { 1124 listener.onGlobalFocusChanged(oldFocus, newFocus); 1125 } 1126 } 1127 } 1128 1129 /** 1130 * Notifies registered listeners that a global layout happened. This can be called 1131 * manually if you are forcing a layout on a View or a hierarchy of Views that are 1132 * not attached to a Window or in the GONE state. 1133 */ dispatchOnGlobalLayout()1134 public final void dispatchOnGlobalLayout() { 1135 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 1136 // perform the dispatching. The iterator is a safe guard against listeners that 1137 // could mutate the list by calling the various add/remove methods. This prevents 1138 // the array from being modified while we iterate it. 1139 final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners; 1140 if (listeners != null && listeners.size() > 0) { 1141 CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start(); 1142 try { 1143 int count = access.size(); 1144 for (int i = 0; i < count; i++) { 1145 access.get(i).onGlobalLayout(); 1146 } 1147 } finally { 1148 listeners.end(); 1149 } 1150 } 1151 } 1152 1153 /** 1154 * Returns whether there are listeners for on pre-draw events. 1155 */ hasOnPreDrawListeners()1156 final boolean hasOnPreDrawListeners() { 1157 return mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0; 1158 } 1159 1160 /** 1161 * Notifies registered listeners that the drawing pass is about to start. If a 1162 * listener returns true, then the drawing pass is canceled and rescheduled. This can 1163 * be called manually if you are forcing the drawing on a View or a hierarchy of Views 1164 * that are not attached to a Window or in the GONE state. 1165 * 1166 * @return True if the current draw should be canceled and rescheduled, false otherwise. 1167 */ 1168 @SuppressWarnings("unchecked") dispatchOnPreDraw()1169 public final boolean dispatchOnPreDraw() { 1170 mLastDispatchOnPreDrawCanceledReason = null; 1171 boolean cancelDraw = false; 1172 final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners; 1173 if (listeners != null && listeners.size() > 0) { 1174 CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start(); 1175 try { 1176 int count = access.size(); 1177 for (int i = 0; i < count; i++) { 1178 final OnPreDrawListener preDrawListener = access.get(i); 1179 final boolean listenerCanceledDraw = !(preDrawListener.onPreDraw()); 1180 cancelDraw |= listenerCanceledDraw; 1181 if (listenerCanceledDraw) { 1182 final String className = preDrawListener.getClass().getName(); 1183 if (mLastDispatchOnPreDrawCanceledReason == null) { 1184 mLastDispatchOnPreDrawCanceledReason = new StringBuilder(className); 1185 } else { 1186 mLastDispatchOnPreDrawCanceledReason.append("|").append(className); 1187 } 1188 } 1189 } 1190 } finally { 1191 listeners.end(); 1192 } 1193 } 1194 return cancelDraw; 1195 } 1196 1197 /** 1198 * @return the reason that the last call to dispatchOnPreDraw() returned true to cancel the 1199 * current draw, or null if the last call did not cancel. 1200 * @hide 1201 */ getLastDispatchOnPreDrawCanceledReason()1202 final String getLastDispatchOnPreDrawCanceledReason() { 1203 if (mLastDispatchOnPreDrawCanceledReason != null) { 1204 return mLastDispatchOnPreDrawCanceledReason.toString(); 1205 } 1206 return null; 1207 } 1208 1209 /** 1210 * Notifies registered listeners that the window is now shown 1211 * @hide 1212 */ 1213 @SuppressWarnings("unchecked") dispatchOnWindowShown()1214 public final void dispatchOnWindowShown() { 1215 mWindowShown = true; 1216 final CopyOnWriteArray<OnWindowShownListener> listeners = mOnWindowShownListeners; 1217 if (listeners != null && listeners.size() > 0) { 1218 CopyOnWriteArray.Access<OnWindowShownListener> access = listeners.start(); 1219 try { 1220 int count = access.size(); 1221 for (int i = 0; i < count; i++) { 1222 access.get(i).onWindowShown(); 1223 } 1224 } finally { 1225 listeners.end(); 1226 } 1227 } 1228 } 1229 1230 /** 1231 * Notifies registered listeners that the drawing pass is about to start. 1232 */ dispatchOnDraw()1233 public final void dispatchOnDraw() { 1234 if (mOnDrawListeners != null) { 1235 mInDispatchOnDraw = true; 1236 final ArrayList<OnDrawListener> listeners = mOnDrawListeners; 1237 int numListeners = listeners.size(); 1238 for (int i = 0; i < numListeners; ++i) { 1239 listeners.get(i).onDraw(); 1240 } 1241 mInDispatchOnDraw = false; 1242 } 1243 } 1244 1245 /** 1246 * Notifies registered listeners that the touch mode has changed. 1247 * 1248 * @param inTouchMode True if the touch mode is now enabled, false otherwise. 1249 */ 1250 @UnsupportedAppUsage dispatchOnTouchModeChanged(boolean inTouchMode)1251 final void dispatchOnTouchModeChanged(boolean inTouchMode) { 1252 final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners = 1253 mOnTouchModeChangeListeners; 1254 if (listeners != null && listeners.size() > 0) { 1255 for (OnTouchModeChangeListener listener : listeners) { 1256 listener.onTouchModeChanged(inTouchMode); 1257 } 1258 } 1259 } 1260 1261 /** 1262 * Notifies registered listeners that something has scrolled. 1263 */ 1264 @FlaggedApi(FLAG_ENABLE_DISPATCH_ON_SCROLL_CHANGED) 1265 @UnsupportedAppUsage dispatchOnScrollChanged()1266 public final void dispatchOnScrollChanged() { 1267 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 1268 // perform the dispatching. The iterator is a safe guard against listeners that 1269 // could mutate the list by calling the various add/remove methods. This prevents 1270 // the array from being modified while we iterate it. 1271 final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners; 1272 if (listeners != null && listeners.size() > 0) { 1273 CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start(); 1274 try { 1275 int count = access.size(); 1276 for (int i = 0; i < count; i++) { 1277 access.get(i).onScrollChanged(); 1278 } 1279 } finally { 1280 listeners.end(); 1281 } 1282 } 1283 } 1284 1285 /** 1286 * Returns whether there are listeners for computing internal insets. 1287 */ 1288 @UnsupportedAppUsage hasComputeInternalInsetsListeners()1289 final boolean hasComputeInternalInsetsListeners() { 1290 final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners = 1291 mOnComputeInternalInsetsListeners; 1292 return (listeners != null && listeners.size() > 0); 1293 } 1294 1295 /** 1296 * Calls all listeners to compute the current insets. 1297 */ 1298 @UnsupportedAppUsage dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo)1299 final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) { 1300 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 1301 // perform the dispatching. The iterator is a safe guard against listeners that 1302 // could mutate the list by calling the various add/remove methods. This prevents 1303 // the array from being modified while we iterate it. 1304 final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners = 1305 mOnComputeInternalInsetsListeners; 1306 if (listeners != null && listeners.size() > 0) { 1307 CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start(); 1308 try { 1309 int count = access.size(); 1310 for (int i = 0; i < count; i++) { 1311 access.get(i).onComputeInternalInsets(inoutInfo); 1312 } 1313 } finally { 1314 listeners.end(); 1315 } 1316 } 1317 } 1318 1319 /** 1320 * @hide 1321 */ dispatchOnEnterAnimationComplete()1322 public final void dispatchOnEnterAnimationComplete() { 1323 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to 1324 // perform the dispatching. The iterator is a safe guard against listeners that 1325 // could mutate the list by calling the various add/remove methods. This prevents 1326 // the array from being modified while we iterate it. 1327 final CopyOnWriteArrayList<OnEnterAnimationCompleteListener> listeners = 1328 mOnEnterAnimationCompleteListeners; 1329 if (listeners != null && !listeners.isEmpty()) { 1330 for (OnEnterAnimationCompleteListener listener : listeners) { 1331 listener.onEnterAnimationComplete(); 1332 } 1333 } 1334 } 1335 dispatchOnSystemGestureExclusionRectsChanged(@onNull List<Rect> rects)1336 void dispatchOnSystemGestureExclusionRectsChanged(@NonNull List<Rect> rects) { 1337 final CopyOnWriteArray<Consumer<List<Rect>>> listeners = mGestureExclusionListeners; 1338 if (listeners != null && listeners.size() > 0) { 1339 CopyOnWriteArray.Access<Consumer<List<Rect>>> access = listeners.start(); 1340 try { 1341 final int count = access.size(); 1342 for (int i = 0; i < count; i++) { 1343 access.get(i).accept(rects); 1344 } 1345 } finally { 1346 listeners.end(); 1347 } 1348 } 1349 } 1350 1351 /** 1352 * Copy on write array. This array is not thread safe, and only one loop can 1353 * iterate over this array at any given time. This class avoids allocations 1354 * until a concurrent modification happens. 1355 * 1356 * Usage: 1357 * 1358 * CopyOnWriteArray.Access<MyData> access = array.start(); 1359 * try { 1360 * for (int i = 0; i < access.size(); i++) { 1361 * MyData d = access.get(i); 1362 * } 1363 * } finally { 1364 * access.end(); 1365 * } 1366 */ 1367 static class CopyOnWriteArray<T> { 1368 private ArrayList<T> mData = new ArrayList<T>(); 1369 private ArrayList<T> mDataCopy; 1370 1371 private final Access<T> mAccess = new Access<T>(); 1372 1373 private boolean mStart; 1374 1375 static class Access<T> { 1376 private ArrayList<T> mData; 1377 private int mSize; 1378 get(int index)1379 T get(int index) { 1380 return mData.get(index); 1381 } 1382 size()1383 int size() { 1384 return mSize; 1385 } 1386 } 1387 CopyOnWriteArray()1388 CopyOnWriteArray() { 1389 } 1390 getArray()1391 private ArrayList<T> getArray() { 1392 if (mStart) { 1393 if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData); 1394 return mDataCopy; 1395 } 1396 return mData; 1397 } 1398 start()1399 Access<T> start() { 1400 if (mStart) throw new IllegalStateException("Iteration already started"); 1401 mStart = true; 1402 mDataCopy = null; 1403 mAccess.mData = mData; 1404 mAccess.mSize = mData.size(); 1405 return mAccess; 1406 } 1407 end()1408 void end() { 1409 if (!mStart) throw new IllegalStateException("Iteration not started"); 1410 mStart = false; 1411 if (mDataCopy != null) { 1412 mData = mDataCopy; 1413 mAccess.mData.clear(); 1414 mAccess.mSize = 0; 1415 } 1416 mDataCopy = null; 1417 } 1418 size()1419 int size() { 1420 return getArray().size(); 1421 } 1422 add(T item)1423 void add(T item) { 1424 getArray().add(item); 1425 } 1426 addAll(CopyOnWriteArray<T> array)1427 void addAll(CopyOnWriteArray<T> array) { 1428 getArray().addAll(array.mData); 1429 } 1430 remove(T item)1431 void remove(T item) { 1432 getArray().remove(item); 1433 } 1434 clear()1435 void clear() { 1436 getArray().clear(); 1437 } 1438 } 1439 } 1440