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
17package android.view;
18
19import android.content.Context;
20import android.graphics.Rect;
21import android.graphics.Region;
22import android.os.Build;
23import android.util.Log;
24
25import java.util.ArrayList;
26import 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 */
37public 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         */
73        public void onWindowAttached();
74
75        /**
76         * Callback method to be invoked when the view hierarchy is detached from a window
77         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
256        public void setTouchableInsets(int val) {
257            mTouchableInsets = val;
258        }
259
260        int mTouchableInsets;
261
262        void reset() {
263            contentInsets.setEmpty();
264            visibleInsets.setEmpty();
265            touchableRegion.setEmpty();
266            mTouchableInsets = TOUCHABLE_INSETS_FRAME;
267        }
268
269        boolean isEmpty() {
270            return contentInsets.isEmpty()
271                    && visibleInsets.isEmpty()
272                    && touchableRegion.isEmpty()
273                    && mTouchableInsets == TOUCHABLE_INSETS_FRAME;
274        }
275
276        @Override
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
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
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         */
322        public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
323    }
324
325    /**
326     * @hide
327     */
328    public interface OnEnterAnimationCompleteListener {
329        public void onEnterAnimationComplete();
330    }
331
332    /**
333     * Creates a new ViewTreeObserver. This constructor should not be called
334     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
840    public void removeOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) {
841        checkIsAlive();
842        if (mOnEnterAnimationCompleteListeners == null) {
843            return;
844        }
845        mOnEnterAnimationCompleteListeners.remove(listener);
846    }
847
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     */
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     */
874    private void kill() {
875        mAlive = false;
876    }
877
878    /**
879     * Notifies registered listeners that window has been attached/detached.
880     */
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     */
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     */
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     */
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     */
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")
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")
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     */
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     */
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     */
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     */
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     */
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     */
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
1136            T get(int index) {
1137                return mData.get(index);
1138            }
1139
1140            int size() {
1141                return mSize;
1142            }
1143        }
1144
1145        CopyOnWriteArray() {
1146        }
1147
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
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
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
1176        int size() {
1177            return getArray().size();
1178        }
1179
1180        void add(T item) {
1181            getArray().add(item);
1182        }
1183
1184        void addAll(CopyOnWriteArray<T> array) {
1185            getArray().addAll(array.mData);
1186        }
1187
1188        void remove(T item) {
1189            getArray().remove(item);
1190        }
1191
1192        void clear() {
1193            getArray().clear();
1194        }
1195    }
1196}
1197