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