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