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