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