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