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