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    private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
36    private CopyOnWriteArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
37    private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
38    private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
39    private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners;
40    private ArrayList<OnPreDrawListener> mOnPreDrawListeners;
41    private ArrayList<OnDrawListener> mOnDrawListeners;
42
43    private boolean mAlive = true;
44
45    /**
46     * Interface definition for a callback to be invoked when the focus state within
47     * the view tree changes.
48     */
49    public interface OnGlobalFocusChangeListener {
50        /**
51         * Callback method to be invoked when the focus changes in the view tree. When
52         * the view tree transitions from touch mode to non-touch mode, oldFocus is null.
53         * When the view tree transitions from non-touch mode to touch mode, newFocus is
54         * null. When focus changes in non-touch mode (without transition from or to
55         * touch mode) either oldFocus or newFocus can be null.
56         *
57         * @param oldFocus The previously focused view, if any.
58         * @param newFocus The newly focused View, if any.
59         */
60        public void onGlobalFocusChanged(View oldFocus, View newFocus);
61    }
62
63    /**
64     * Interface definition for a callback to be invoked when the global layout state
65     * or the visibility of views within the view tree changes.
66     */
67    public interface OnGlobalLayoutListener {
68        /**
69         * Callback method to be invoked when the global layout state or the visibility of views
70         * within the view tree changes
71         */
72        public void onGlobalLayout();
73    }
74
75    /**
76     * Interface definition for a callback to be invoked when the view tree is about to be drawn.
77     */
78    public interface OnPreDrawListener {
79        /**
80         * Callback method to be invoked when the view tree is about to be drawn. At this point, all
81         * views in the tree have been measured and given a frame. Clients can use this to adjust
82         * their scroll bounds or even to request a new layout before drawing occurs.
83         *
84         * @return Return true to proceed with the current drawing pass, or false to cancel.
85         *
86         * @see android.view.View#onMeasure
87         * @see android.view.View#onLayout
88         * @see android.view.View#onDraw
89         */
90        public boolean onPreDraw();
91    }
92
93    /**
94     * Interface definition for a callback to be invoked when the view tree is about to be drawn.
95     */
96    public interface OnDrawListener {
97        /**
98         * <p>Callback method to be invoked when the view tree is about to be drawn. At this point,
99         * views cannot be modified in any way.</p>
100         *
101         * <p>Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the
102         * current drawing pass.</p>
103         *
104         * <p>An {@link OnDrawListener} listener <strong>cannot be added or removed</strong>
105         * from this method.</p>
106         *
107         * @see android.view.View#onMeasure
108         * @see android.view.View#onLayout
109         * @see android.view.View#onDraw
110         */
111        public void onDraw();
112    }
113
114    /**
115     * Interface definition for a callback to be invoked when the touch mode changes.
116     */
117    public interface OnTouchModeChangeListener {
118        /**
119         * Callback method to be invoked when the touch mode changes.
120         *
121         * @param isInTouchMode True if the view hierarchy is now in touch mode, false  otherwise.
122         */
123        public void onTouchModeChanged(boolean isInTouchMode);
124    }
125
126    /**
127     * Interface definition for a callback to be invoked when
128     * something in the view tree has been scrolled.
129     */
130    public interface OnScrollChangedListener {
131        /**
132         * Callback method to be invoked when something in the view tree
133         * has been scrolled.
134         */
135        public void onScrollChanged();
136    }
137
138    /**
139     * Parameters used with OnComputeInternalInsetsListener.
140     *
141     * We are not yet ready to commit to this API and support it, so
142     * @hide
143     */
144    public final static class InternalInsetsInfo {
145        /**
146         * Offsets from the frame of the window at which the content of
147         * windows behind it should be placed.
148         */
149        public final Rect contentInsets = new Rect();
150
151        /**
152         * Offsets from the frame of the window at which windows behind it
153         * are visible.
154         */
155        public final Rect visibleInsets = new Rect();
156
157        /**
158         * Touchable region defined relative to the origin of the frame of the window.
159         * Only used when {@link #setTouchableInsets(int)} is called with
160         * the option {@link #TOUCHABLE_INSETS_REGION}.
161         */
162        public final Region touchableRegion = new Region();
163
164        /**
165         * Option for {@link #setTouchableInsets(int)}: the entire window frame
166         * can be touched.
167         */
168        public static final int TOUCHABLE_INSETS_FRAME = 0;
169
170        /**
171         * Option for {@link #setTouchableInsets(int)}: the area inside of
172         * the content insets can be touched.
173         */
174        public static final int TOUCHABLE_INSETS_CONTENT = 1;
175
176        /**
177         * Option for {@link #setTouchableInsets(int)}: the area inside of
178         * the visible insets can be touched.
179         */
180        public static final int TOUCHABLE_INSETS_VISIBLE = 2;
181
182        /**
183         * Option for {@link #setTouchableInsets(int)}: the area inside of
184         * the provided touchable region in {@link #touchableRegion} can be touched.
185         */
186        public static final int TOUCHABLE_INSETS_REGION = 3;
187
188        /**
189         * Set which parts of the window can be touched: either
190         * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT},
191         * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}.
192         */
193        public void setTouchableInsets(int val) {
194            mTouchableInsets = val;
195        }
196
197        int mTouchableInsets;
198
199        void reset() {
200            contentInsets.setEmpty();
201            visibleInsets.setEmpty();
202            touchableRegion.setEmpty();
203            mTouchableInsets = TOUCHABLE_INSETS_FRAME;
204        }
205
206        @Override
207        public int hashCode() {
208            int result = contentInsets != null ? contentInsets.hashCode() : 0;
209            result = 31 * result + (visibleInsets != null ? visibleInsets.hashCode() : 0);
210            result = 31 * result + (touchableRegion != null ? touchableRegion.hashCode() : 0);
211            result = 31 * result + mTouchableInsets;
212            return result;
213        }
214
215        @Override
216        public boolean equals(Object o) {
217            if (this == o) return true;
218            if (o == null || getClass() != o.getClass()) return false;
219
220            InternalInsetsInfo other = (InternalInsetsInfo)o;
221            return mTouchableInsets == other.mTouchableInsets &&
222                    contentInsets.equals(other.contentInsets) &&
223                    visibleInsets.equals(other.visibleInsets) &&
224                    touchableRegion.equals(other.touchableRegion);
225        }
226
227        void set(InternalInsetsInfo other) {
228            contentInsets.set(other.contentInsets);
229            visibleInsets.set(other.visibleInsets);
230            touchableRegion.set(other.touchableRegion);
231            mTouchableInsets = other.mTouchableInsets;
232        }
233    }
234
235    /**
236     * Interface definition for a callback to be invoked when layout has
237     * completed and the client can compute its interior insets.
238     *
239     * We are not yet ready to commit to this API and support it, so
240     * @hide
241     */
242    public interface OnComputeInternalInsetsListener {
243        /**
244         * Callback method to be invoked when layout has completed and the
245         * client can compute its interior insets.
246         *
247         * @param inoutInfo Should be filled in by the implementation with
248         * the information about the insets of the window.  This is called
249         * with whatever values the previous OnComputeInternalInsetsListener
250         * returned, if there are multiple such listeners in the window.
251         */
252        public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
253    }
254
255    /**
256     * Creates a new ViewTreeObserver. This constructor should not be called
257     */
258    ViewTreeObserver() {
259    }
260
261    /**
262     * Merges all the listeners registered on the specified observer with the listeners
263     * registered on this object. After this method is invoked, the specified observer
264     * will return false in {@link #isAlive()} and should not be used anymore.
265     *
266     * @param observer The ViewTreeObserver whose listeners must be added to this observer
267     */
268    void merge(ViewTreeObserver observer) {
269        if (observer.mOnGlobalFocusListeners != null) {
270            if (mOnGlobalFocusListeners != null) {
271                mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
272            } else {
273                mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners;
274            }
275        }
276
277        if (observer.mOnGlobalLayoutListeners != null) {
278            if (mOnGlobalLayoutListeners != null) {
279                mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
280            } else {
281                mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
282            }
283        }
284
285        if (observer.mOnPreDrawListeners != null) {
286            if (mOnPreDrawListeners != null) {
287                mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners);
288            } else {
289                mOnPreDrawListeners = observer.mOnPreDrawListeners;
290            }
291        }
292
293        if (observer.mOnTouchModeChangeListeners != null) {
294            if (mOnTouchModeChangeListeners != null) {
295                mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners);
296            } else {
297                mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners;
298            }
299        }
300
301        if (observer.mOnComputeInternalInsetsListeners != null) {
302            if (mOnComputeInternalInsetsListeners != null) {
303                mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners);
304            } else {
305                mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners;
306            }
307        }
308
309        if (observer.mOnScrollChangedListeners != null) {
310            if (mOnScrollChangedListeners != null) {
311                mOnScrollChangedListeners.addAll(observer.mOnScrollChangedListeners);
312            } else {
313                mOnScrollChangedListeners = observer.mOnScrollChangedListeners;
314            }
315        }
316
317        observer.kill();
318    }
319
320    /**
321     * Register a callback to be invoked when the focus state within the view tree changes.
322     *
323     * @param listener The callback to add
324     *
325     * @throws IllegalStateException If {@link #isAlive()} returns false
326     */
327    public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) {
328        checkIsAlive();
329
330        if (mOnGlobalFocusListeners == null) {
331            mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>();
332        }
333
334        mOnGlobalFocusListeners.add(listener);
335    }
336
337    /**
338     * Remove a previously installed focus change callback.
339     *
340     * @param victim The callback to remove
341     *
342     * @throws IllegalStateException If {@link #isAlive()} returns false
343     *
344     * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener)
345     */
346    public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) {
347        checkIsAlive();
348        if (mOnGlobalFocusListeners == null) {
349            return;
350        }
351        mOnGlobalFocusListeners.remove(victim);
352    }
353
354    /**
355     * Register a callback to be invoked when the global layout state or the visibility of views
356     * within the view tree changes
357     *
358     * @param listener The callback to add
359     *
360     * @throws IllegalStateException If {@link #isAlive()} returns false
361     */
362    public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
363        checkIsAlive();
364
365        if (mOnGlobalLayoutListeners == null) {
366            mOnGlobalLayoutListeners = new CopyOnWriteArrayList<OnGlobalLayoutListener>();
367        }
368
369        mOnGlobalLayoutListeners.add(listener);
370    }
371
372    /**
373     * Remove a previously installed global layout callback
374     *
375     * @param victim The callback to remove
376     *
377     * @throws IllegalStateException If {@link #isAlive()} returns false
378     *
379     * @deprecated Use #removeOnGlobalLayoutListener instead
380     *
381     * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
382     */
383    @Deprecated
384    public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {
385        removeOnGlobalLayoutListener(victim);
386    }
387
388    /**
389     * Remove a previously installed global layout callback
390     *
391     * @param victim The callback to remove
392     *
393     * @throws IllegalStateException If {@link #isAlive()} returns false
394     *
395     * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
396     */
397    public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {
398        checkIsAlive();
399        if (mOnGlobalLayoutListeners == null) {
400            return;
401        }
402        mOnGlobalLayoutListeners.remove(victim);
403    }
404
405    /**
406     * Register a callback to be invoked when the view tree is about to be drawn
407     *
408     * @param listener The callback to add
409     *
410     * @throws IllegalStateException If {@link #isAlive()} returns false
411     */
412    public void addOnPreDrawListener(OnPreDrawListener listener) {
413        checkIsAlive();
414
415        if (mOnPreDrawListeners == null) {
416            mOnPreDrawListeners = new ArrayList<OnPreDrawListener>();
417        }
418
419        mOnPreDrawListeners.add(listener);
420    }
421
422    /**
423     * Remove a previously installed pre-draw callback
424     *
425     * @param victim The callback to remove
426     *
427     * @throws IllegalStateException If {@link #isAlive()} returns false
428     *
429     * @see #addOnPreDrawListener(OnPreDrawListener)
430     */
431    public void removeOnPreDrawListener(OnPreDrawListener victim) {
432        checkIsAlive();
433        if (mOnPreDrawListeners == null) {
434            return;
435        }
436        mOnPreDrawListeners.remove(victim);
437    }
438
439    /**
440     * <p>Register a callback to be invoked when the view tree is about to be drawn.</p>
441     * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
442     * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
443     *
444     * @param listener The callback to add
445     *
446     * @throws IllegalStateException If {@link #isAlive()} returns false
447     */
448    public void addOnDrawListener(OnDrawListener listener) {
449        checkIsAlive();
450
451        if (mOnDrawListeners == null) {
452            mOnDrawListeners = new ArrayList<OnDrawListener>();
453        }
454
455        mOnDrawListeners.add(listener);
456    }
457
458    /**
459     * <p>Remove a previously installed pre-draw callback.</p>
460     * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
461     * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
462     *
463     * @param victim The callback to remove
464     *
465     * @throws IllegalStateException If {@link #isAlive()} returns false
466     *
467     * @see #addOnDrawListener(OnDrawListener)
468     */
469    public void removeOnDrawListener(OnDrawListener victim) {
470        checkIsAlive();
471        if (mOnDrawListeners == null) {
472            return;
473        }
474        mOnDrawListeners.remove(victim);
475    }
476
477    /**
478     * Register a callback to be invoked when a view has been scrolled.
479     *
480     * @param listener The callback to add
481     *
482     * @throws IllegalStateException If {@link #isAlive()} returns false
483     */
484    public void addOnScrollChangedListener(OnScrollChangedListener listener) {
485        checkIsAlive();
486
487        if (mOnScrollChangedListeners == null) {
488            mOnScrollChangedListeners = new CopyOnWriteArrayList<OnScrollChangedListener>();
489        }
490
491        mOnScrollChangedListeners.add(listener);
492    }
493
494    /**
495     * Remove a previously installed scroll-changed callback
496     *
497     * @param victim The callback to remove
498     *
499     * @throws IllegalStateException If {@link #isAlive()} returns false
500     *
501     * @see #addOnScrollChangedListener(OnScrollChangedListener)
502     */
503    public void removeOnScrollChangedListener(OnScrollChangedListener victim) {
504        checkIsAlive();
505        if (mOnScrollChangedListeners == null) {
506            return;
507        }
508        mOnScrollChangedListeners.remove(victim);
509    }
510
511    /**
512     * Register a callback to be invoked when the invoked when the touch mode changes.
513     *
514     * @param listener The callback to add
515     *
516     * @throws IllegalStateException If {@link #isAlive()} returns false
517     */
518    public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) {
519        checkIsAlive();
520
521        if (mOnTouchModeChangeListeners == null) {
522            mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>();
523        }
524
525        mOnTouchModeChangeListeners.add(listener);
526    }
527
528    /**
529     * Remove a previously installed touch mode change callback
530     *
531     * @param victim The callback to remove
532     *
533     * @throws IllegalStateException If {@link #isAlive()} returns false
534     *
535     * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener)
536     */
537    public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) {
538        checkIsAlive();
539        if (mOnTouchModeChangeListeners == null) {
540            return;
541        }
542        mOnTouchModeChangeListeners.remove(victim);
543    }
544
545    /**
546     * Register a callback to be invoked when the invoked when it is time to
547     * compute the window's internal insets.
548     *
549     * @param listener The callback to add
550     *
551     * @throws IllegalStateException If {@link #isAlive()} returns false
552     *
553     * We are not yet ready to commit to this API and support it, so
554     * @hide
555     */
556    public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) {
557        checkIsAlive();
558
559        if (mOnComputeInternalInsetsListeners == null) {
560            mOnComputeInternalInsetsListeners =
561                    new CopyOnWriteArrayList<OnComputeInternalInsetsListener>();
562        }
563
564        mOnComputeInternalInsetsListeners.add(listener);
565    }
566
567    /**
568     * Remove a previously installed internal insets computation callback
569     *
570     * @param victim The callback to remove
571     *
572     * @throws IllegalStateException If {@link #isAlive()} returns false
573     *
574     * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener)
575     *
576     * We are not yet ready to commit to this API and support it, so
577     * @hide
578     */
579    public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
580        checkIsAlive();
581        if (mOnComputeInternalInsetsListeners == null) {
582            return;
583        }
584        mOnComputeInternalInsetsListeners.remove(victim);
585    }
586
587    private void checkIsAlive() {
588        if (!mAlive) {
589            throw new IllegalStateException("This ViewTreeObserver is not alive, call "
590                    + "getViewTreeObserver() again");
591        }
592    }
593
594    /**
595     * Indicates whether this ViewTreeObserver is alive. When an observer is not alive,
596     * any call to a method (except this one) will throw an exception.
597     *
598     * If an application keeps a long-lived reference to this ViewTreeObserver, it should
599     * always check for the result of this method before calling any other method.
600     *
601     * @return True if this object is alive and be used, false otherwise.
602     */
603    public boolean isAlive() {
604        return mAlive;
605    }
606
607    /**
608     * Marks this ViewTreeObserver as not alive. After invoking this method, invoking
609     * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception.
610     *
611     * @hide
612     */
613    private void kill() {
614        mAlive = false;
615    }
616
617    /**
618     * Notifies registered listeners that focus has changed.
619     */
620    final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
621        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
622        // perform the dispatching. The iterator is a safe guard against listeners that
623        // could mutate the list by calling the various add/remove methods. This prevents
624        // the array from being modified while we iterate it.
625        final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
626        if (listeners != null && listeners.size() > 0) {
627            for (OnGlobalFocusChangeListener listener : listeners) {
628                listener.onGlobalFocusChanged(oldFocus, newFocus);
629            }
630        }
631    }
632
633    /**
634     * Notifies registered listeners that a global layout happened. This can be called
635     * manually if you are forcing a layout on a View or a hierarchy of Views that are
636     * not attached to a Window or in the GONE state.
637     */
638    public final void dispatchOnGlobalLayout() {
639        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
640        // perform the dispatching. The iterator is a safe guard against listeners that
641        // could mutate the list by calling the various add/remove methods. This prevents
642        // the array from being modified while we iterate it.
643        final CopyOnWriteArrayList<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
644        if (listeners != null && listeners.size() > 0) {
645            for (OnGlobalLayoutListener listener : listeners) {
646                listener.onGlobalLayout();
647            }
648        }
649    }
650
651    /**
652     * Notifies registered listeners that the drawing pass is about to start. If a
653     * listener returns true, then the drawing pass is canceled and rescheduled. This can
654     * be called manually if you are forcing the drawing on a View or a hierarchy of Views
655     * that are not attached to a Window or in the GONE state.
656     *
657     * @return True if the current draw should be canceled and resceduled, false otherwise.
658     */
659    @SuppressWarnings("unchecked")
660    public final boolean dispatchOnPreDraw() {
661        // NOTE: we *must* clone the listener list to perform the dispatching.
662        // The clone is a safe guard against listeners that
663        // could mutate the list by calling the various add/remove methods. This prevents
664        // the array from being modified while we process it.
665        boolean cancelDraw = false;
666        if (mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0) {
667            final ArrayList<OnPreDrawListener> listeners =
668                    (ArrayList<OnPreDrawListener>) mOnPreDrawListeners.clone();
669            int numListeners = listeners.size();
670            for (int i = 0; i < numListeners; ++i) {
671                cancelDraw |= !(listeners.get(i).onPreDraw());
672            }
673        }
674        return cancelDraw;
675    }
676
677    /**
678     * Notifies registered listeners that the drawing pass is about to start.
679     */
680    public final void dispatchOnDraw() {
681        if (mOnDrawListeners != null) {
682            final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
683            int numListeners = listeners.size();
684            for (int i = 0; i < numListeners; ++i) {
685                listeners.get(i).onDraw();
686            }
687        }
688    }
689
690    /**
691     * Notifies registered listeners that the touch mode has changed.
692     *
693     * @param inTouchMode True if the touch mode is now enabled, false otherwise.
694     */
695    final void dispatchOnTouchModeChanged(boolean inTouchMode) {
696        final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
697                mOnTouchModeChangeListeners;
698        if (listeners != null && listeners.size() > 0) {
699            for (OnTouchModeChangeListener listener : listeners) {
700                listener.onTouchModeChanged(inTouchMode);
701            }
702        }
703    }
704
705    /**
706     * Notifies registered listeners that something has scrolled.
707     */
708    final void dispatchOnScrollChanged() {
709        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
710        // perform the dispatching. The iterator is a safe guard against listeners that
711        // could mutate the list by calling the various add/remove methods. This prevents
712        // the array from being modified while we iterate it.
713        final CopyOnWriteArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
714        if (listeners != null && listeners.size() > 0) {
715            for (OnScrollChangedListener listener : listeners) {
716                listener.onScrollChanged();
717            }
718        }
719    }
720
721    /**
722     * Returns whether there are listeners for computing internal insets.
723     */
724    final boolean hasComputeInternalInsetsListeners() {
725        final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners =
726                mOnComputeInternalInsetsListeners;
727        return (listeners != null && listeners.size() > 0);
728    }
729
730    /**
731     * Calls all listeners to compute the current insets.
732     */
733    final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
734        // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
735        // perform the dispatching. The iterator is a safe guard against listeners that
736        // could mutate the list by calling the various add/remove methods. This prevents
737        // the array from being modified while we iterate it.
738        final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners =
739                mOnComputeInternalInsetsListeners;
740        if (listeners != null && listeners.size() > 0) {
741            for (OnComputeInternalInsetsListener listener : listeners) {
742                listener.onComputeInternalInsets(inoutInfo);
743            }
744        }
745    }
746}
747