Home | History | Annotate | Download | only in view
      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          */
     86         public void onWindowAttached();
     87 
     88         /**
     89          * Callback method to be invoked when the view hierarchy is detached from a window
     90          */
     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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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
    274         public void setTouchableInsets(int val) {
    275             mTouchableInsets = val;
    276         }
    277 
    278         @UnsupportedAppUsage
    279         int mTouchableInsets;
    280 
    281         void reset() {
    282             contentInsets.setEmpty();
    283             visibleInsets.setEmpty();
    284             touchableRegion.setEmpty();
    285             mTouchableInsets = TOUCHABLE_INSETS_FRAME;
    286         }
    287 
    288         boolean isEmpty() {
    289             return contentInsets.isEmpty()
    290                     && visibleInsets.isEmpty()
    291                     && touchableRegion.isEmpty()
    292                     && mTouchableInsets == TOUCHABLE_INSETS_FRAME;
    293         }
    294 
    295         @Override
    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
    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
    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          */
    342         public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
    343     }
    344 
    345     /**
    346      * @hide
    347      */
    348     public interface OnEnterAnimationCompleteListener {
    349         public void onEnterAnimationComplete();
    350     }
    351 
    352     /**
    353      * Creates a new ViewTreeObserver. This constructor should not be called
    354      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    762     public void registerFrameCommitCallback(@NonNull Runnable callback) {
    763         checkIsAlive();
    764         if (mOnFrameCommitListeners == null) {
    765             mOnFrameCommitListeners = new ArrayList<>();
    766         }
    767         mOnFrameCommitListeners.add(callback);
    768     }
    769 
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    984     private void kill() {
    985         mAlive = false;
    986     }
    987 
    988     /**
    989      * Notifies registered listeners that window has been attached/detached.
    990      */
    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      */
   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
   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      */
   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      */
   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")
   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")
   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      */
   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
   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
   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
   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
   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      */
   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 
   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 
   1266             T get(int index) {
   1267                 return mData.get(index);
   1268             }
   1269 
   1270             int size() {
   1271                 return mSize;
   1272             }
   1273         }
   1274 
   1275         CopyOnWriteArray() {
   1276         }
   1277 
   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 
   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 
   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 
   1306         int size() {
   1307             return getArray().size();
   1308         }
   1309 
   1310         void add(T item) {
   1311             getArray().add(item);
   1312         }
   1313 
   1314         void addAll(CopyOnWriteArray<T> array) {
   1315             getArray().addAll(array.mData);
   1316         }
   1317 
   1318         void remove(T item) {
   1319             getArray().remove(item);
   1320         }
   1321 
   1322         void clear() {
   1323             getArray().clear();
   1324         }
   1325     }
   1326 }
   1327