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