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