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