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