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