• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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