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