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