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