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         boolean isEmpty() {
    245             return contentInsets.isEmpty()
    246                     && visibleInsets.isEmpty()
    247                     && touchableRegion.isEmpty()
    248                     && mTouchableInsets == TOUCHABLE_INSETS_FRAME;
    249         }
    250 
    251         @Override
    252         public int hashCode() {
    253             int result = contentInsets.hashCode();
    254             result = 31 * result + visibleInsets.hashCode();
    255             result = 31 * result + touchableRegion.hashCode();
    256             result = 31 * result + mTouchableInsets;
    257             return result;
    258         }
    259 
    260         @Override
    261         public boolean equals(Object o) {
    262             if (this == o) return true;
    263             if (o == null || getClass() != o.getClass()) return false;
    264 
    265             InternalInsetsInfo other = (InternalInsetsInfo)o;
    266             return mTouchableInsets == other.mTouchableInsets &&
    267                     contentInsets.equals(other.contentInsets) &&
    268                     visibleInsets.equals(other.visibleInsets) &&
    269                     touchableRegion.equals(other.touchableRegion);
    270         }
    271 
    272         void set(InternalInsetsInfo other) {
    273             contentInsets.set(other.contentInsets);
    274             visibleInsets.set(other.visibleInsets);
    275             touchableRegion.set(other.touchableRegion);
    276             mTouchableInsets = other.mTouchableInsets;
    277         }
    278     }
    279 
    280     /**
    281      * Interface definition for a callback to be invoked when layout has
    282      * completed and the client can compute its interior insets.
    283      *
    284      * We are not yet ready to commit to this API and support it, so
    285      * @hide
    286      */
    287     public interface OnComputeInternalInsetsListener {
    288         /**
    289          * Callback method to be invoked when layout has completed and the
    290          * client can compute its interior insets.
    291          *
    292          * @param inoutInfo Should be filled in by the implementation with
    293          * the information about the insets of the window.  This is called
    294          * with whatever values the previous OnComputeInternalInsetsListener
    295          * returned, if there are multiple such listeners in the window.
    296          */
    297         public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
    298     }
    299 
    300     /**
    301      * Creates a new ViewTreeObserver. This constructor should not be called
    302      */
    303     ViewTreeObserver() {
    304     }
    305 
    306     /**
    307      * Merges all the listeners registered on the specified observer with the listeners
    308      * registered on this object. After this method is invoked, the specified observer
    309      * will return false in {@link #isAlive()} and should not be used anymore.
    310      *
    311      * @param observer The ViewTreeObserver whose listeners must be added to this observer
    312      */
    313     void merge(ViewTreeObserver observer) {
    314         if (observer.mOnWindowAttachListeners != null) {
    315             if (mOnWindowAttachListeners != null) {
    316                 mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners);
    317             } else {
    318                 mOnWindowAttachListeners = observer.mOnWindowAttachListeners;
    319             }
    320         }
    321 
    322         if (observer.mOnWindowFocusListeners != null) {
    323             if (mOnWindowFocusListeners != null) {
    324                 mOnWindowFocusListeners.addAll(observer.mOnWindowFocusListeners);
    325             } else {
    326                 mOnWindowFocusListeners = observer.mOnWindowFocusListeners;
    327             }
    328         }
    329 
    330         if (observer.mOnGlobalFocusListeners != null) {
    331             if (mOnGlobalFocusListeners != null) {
    332                 mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
    333             } else {
    334                 mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners;
    335             }
    336         }
    337 
    338         if (observer.mOnGlobalLayoutListeners != null) {
    339             if (mOnGlobalLayoutListeners != null) {
    340                 mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
    341             } else {
    342                 mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
    343             }
    344         }
    345 
    346         if (observer.mOnPreDrawListeners != null) {
    347             if (mOnPreDrawListeners != null) {
    348                 mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners);
    349             } else {
    350                 mOnPreDrawListeners = observer.mOnPreDrawListeners;
    351             }
    352         }
    353 
    354         if (observer.mOnTouchModeChangeListeners != null) {
    355             if (mOnTouchModeChangeListeners != null) {
    356                 mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners);
    357             } else {
    358                 mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners;
    359             }
    360         }
    361 
    362         if (observer.mOnComputeInternalInsetsListeners != null) {
    363             if (mOnComputeInternalInsetsListeners != null) {
    364                 mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners);
    365             } else {
    366                 mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners;
    367             }
    368         }
    369 
    370         if (observer.mOnScrollChangedListeners != null) {
    371             if (mOnScrollChangedListeners != null) {
    372                 mOnScrollChangedListeners.addAll(observer.mOnScrollChangedListeners);
    373             } else {
    374                 mOnScrollChangedListeners = observer.mOnScrollChangedListeners;
    375             }
    376         }
    377 
    378         observer.kill();
    379     }
    380 
    381     /**
    382      * Register a callback to be invoked when the view hierarchy is attached to a window.
    383      *
    384      * @param listener The callback to add
    385      *
    386      * @throws IllegalStateException If {@link #isAlive()} returns false
    387      */
    388     public void addOnWindowAttachListener(OnWindowAttachListener listener) {
    389         checkIsAlive();
    390 
    391         if (mOnWindowAttachListeners == null) {
    392             mOnWindowAttachListeners
    393                     = new CopyOnWriteArrayList<OnWindowAttachListener>();
    394         }
    395 
    396         mOnWindowAttachListeners.add(listener);
    397     }
    398 
    399     /**
    400      * Remove a previously installed window attach callback.
    401      *
    402      * @param victim The callback to remove
    403      *
    404      * @throws IllegalStateException If {@link #isAlive()} returns false
    405      *
    406      * @see #addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener)
    407      */
    408     public void removeOnWindowAttachListener(OnWindowAttachListener victim) {
    409         checkIsAlive();
    410         if (mOnWindowAttachListeners == null) {
    411             return;
    412         }
    413         mOnWindowAttachListeners.remove(victim);
    414     }
    415 
    416     /**
    417      * Register a callback to be invoked when the window focus state within the view tree changes.
    418      *
    419      * @param listener The callback to add
    420      *
    421      * @throws IllegalStateException If {@link #isAlive()} returns false
    422      */
    423     public void addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) {
    424         checkIsAlive();
    425 
    426         if (mOnWindowFocusListeners == null) {
    427             mOnWindowFocusListeners
    428                     = new CopyOnWriteArrayList<OnWindowFocusChangeListener>();
    429         }
    430 
    431         mOnWindowFocusListeners.add(listener);
    432     }
    433 
    434     /**
    435      * Remove a previously installed window focus change callback.
    436      *
    437      * @param victim The callback to remove
    438      *
    439      * @throws IllegalStateException If {@link #isAlive()} returns false
    440      *
    441      * @see #addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener)
    442      */
    443     public void removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim) {
    444         checkIsAlive();
    445         if (mOnWindowFocusListeners == null) {
    446             return;
    447         }
    448         mOnWindowFocusListeners.remove(victim);
    449     }
    450 
    451     /**
    452      * Register a callback to be invoked when the focus state within the view tree changes.
    453      *
    454      * @param listener The callback to add
    455      *
    456      * @throws IllegalStateException If {@link #isAlive()} returns false
    457      */
    458     public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) {
    459         checkIsAlive();
    460 
    461         if (mOnGlobalFocusListeners == null) {
    462             mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>();
    463         }
    464 
    465         mOnGlobalFocusListeners.add(listener);
    466     }
    467 
    468     /**
    469      * Remove a previously installed focus change callback.
    470      *
    471      * @param victim The callback to remove
    472      *
    473      * @throws IllegalStateException If {@link #isAlive()} returns false
    474      *
    475      * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener)
    476      */
    477     public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) {
    478         checkIsAlive();
    479         if (mOnGlobalFocusListeners == null) {
    480             return;
    481         }
    482         mOnGlobalFocusListeners.remove(victim);
    483     }
    484 
    485     /**
    486      * Register a callback to be invoked when the global layout state or the visibility of views
    487      * within the view tree changes
    488      *
    489      * @param listener The callback to add
    490      *
    491      * @throws IllegalStateException If {@link #isAlive()} returns false
    492      */
    493     public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
    494         checkIsAlive();
    495 
    496         if (mOnGlobalLayoutListeners == null) {
    497             mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
    498         }
    499 
    500         mOnGlobalLayoutListeners.add(listener);
    501     }
    502 
    503     /**
    504      * Remove a previously installed global layout callback
    505      *
    506      * @param victim The callback to remove
    507      *
    508      * @throws IllegalStateException If {@link #isAlive()} returns false
    509      *
    510      * @deprecated Use #removeOnGlobalLayoutListener instead
    511      *
    512      * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
    513      */
    514     @Deprecated
    515     public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {
    516         removeOnGlobalLayoutListener(victim);
    517     }
    518 
    519     /**
    520      * Remove a previously installed global layout callback
    521      *
    522      * @param victim The callback to remove
    523      *
    524      * @throws IllegalStateException If {@link #isAlive()} returns false
    525      *
    526      * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
    527      */
    528     public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {
    529         checkIsAlive();
    530         if (mOnGlobalLayoutListeners == null) {
    531             return;
    532         }
    533         mOnGlobalLayoutListeners.remove(victim);
    534     }
    535 
    536     /**
    537      * Register a callback to be invoked when the view tree is about to be drawn
    538      *
    539      * @param listener The callback to add
    540      *
    541      * @throws IllegalStateException If {@link #isAlive()} returns false
    542      */
    543     public void addOnPreDrawListener(OnPreDrawListener listener) {
    544         checkIsAlive();
    545 
    546         if (mOnPreDrawListeners == null) {
    547             mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
    548         }
    549 
    550         mOnPreDrawListeners.add(listener);
    551     }
    552 
    553     /**
    554      * Remove a previously installed pre-draw callback
    555      *
    556      * @param victim The callback to remove
    557      *
    558      * @throws IllegalStateException If {@link #isAlive()} returns false
    559      *
    560      * @see #addOnPreDrawListener(OnPreDrawListener)
    561      */
    562     public void removeOnPreDrawListener(OnPreDrawListener victim) {
    563         checkIsAlive();
    564         if (mOnPreDrawListeners == null) {
    565             return;
    566         }
    567         mOnPreDrawListeners.remove(victim);
    568     }
    569 
    570     /**
    571      * <p>Register a callback to be invoked when the view tree is about to be drawn.</p>
    572      * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
    573      * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
    574      *
    575      * @param listener The callback to add
    576      *
    577      * @throws IllegalStateException If {@link #isAlive()} returns false
    578      */
    579     public void addOnDrawListener(OnDrawListener listener) {
    580         checkIsAlive();
    581 
    582         if (mOnDrawListeners == null) {
    583             mOnDrawListeners = new ArrayList<OnDrawListener>();
    584         }
    585 
    586         mOnDrawListeners.add(listener);
    587     }
    588 
    589     /**
    590      * <p>Remove a previously installed pre-draw callback.</p>
    591      * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
    592      * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
    593      *
    594      * @param victim The callback to remove
    595      *
    596      * @throws IllegalStateException If {@link #isAlive()} returns false
    597      *
    598      * @see #addOnDrawListener(OnDrawListener)
    599      */
    600     public void removeOnDrawListener(OnDrawListener victim) {
    601         checkIsAlive();
    602         if (mOnDrawListeners == null) {
    603             return;
    604         }
    605         mOnDrawListeners.remove(victim);
    606     }
    607 
    608     /**
    609      * Register a callback to be invoked when a view has been scrolled.
    610      *
    611      * @param listener The callback to add
    612      *
    613      * @throws IllegalStateException If {@link #isAlive()} returns false
    614      */
    615     public void addOnScrollChangedListener(OnScrollChangedListener listener) {
    616         checkIsAlive();
    617 
    618         if (mOnScrollChangedListeners == null) {
    619             mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>();
    620         }
    621 
    622         mOnScrollChangedListeners.add(listener);
    623     }
    624 
    625     /**
    626      * Remove a previously installed scroll-changed callback
    627      *
    628      * @param victim The callback to remove
    629      *
    630      * @throws IllegalStateException If {@link #isAlive()} returns false
    631      *
    632      * @see #addOnScrollChangedListener(OnScrollChangedListener)
    633      */
    634     public void removeOnScrollChangedListener(OnScrollChangedListener victim) {
    635         checkIsAlive();
    636         if (mOnScrollChangedListeners == null) {
    637             return;
    638         }
    639         mOnScrollChangedListeners.remove(victim);
    640     }
    641 
    642     /**
    643      * Register a callback to be invoked when the invoked when the touch mode changes.
    644      *
    645      * @param listener The callback to add
    646      *
    647      * @throws IllegalStateException If {@link #isAlive()} returns false
    648      */
    649     public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) {
    650         checkIsAlive();
    651 
    652         if (mOnTouchModeChangeListeners == null) {
    653             mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>();
    654         }
    655 
    656         mOnTouchModeChangeListeners.add(listener);
    657     }
    658 
    659     /**
    660      * Remove a previously installed touch mode change callback
    661      *
    662      * @param victim The callback to remove
    663      *
    664      * @throws IllegalStateException If {@link #isAlive()} returns false
    665      *
    666      * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener)
    667      */
    668     public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) {
    669         checkIsAlive();
    670         if (mOnTouchModeChangeListeners == null) {
    671             return;
    672         }
    673         mOnTouchModeChangeListeners.remove(victim);
    674     }
    675 
    676     /**
    677      * Register a callback to be invoked when the invoked when it is time to
    678      * compute the window's internal insets.
    679      *
    680      * @param listener The callback to add
    681      *
    682      * @throws IllegalStateException If {@link #isAlive()} returns false
    683      *
    684      * We are not yet ready to commit to this API and support it, so
    685      * @hide
    686      */
    687     public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) {
    688         checkIsAlive();
    689 
    690         if (mOnComputeInternalInsetsListeners == null) {
    691             mOnComputeInternalInsetsListeners =
    692                     new CopyOnWriteArray<OnComputeInternalInsetsListener>();
    693         }
    694 
    695         mOnComputeInternalInsetsListeners.add(listener);
    696     }
    697 
    698     /**
    699      * Remove a previously installed internal insets computation callback
    700      *
    701      * @param victim The callback to remove
    702      *
    703      * @throws IllegalStateException If {@link #isAlive()} returns false
    704      *
    705      * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener)
    706      *
    707      * We are not yet ready to commit to this API and support it, so
    708      * @hide
    709      */
    710     public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
    711         checkIsAlive();
    712         if (mOnComputeInternalInsetsListeners == null) {
    713             return;
    714         }
    715         mOnComputeInternalInsetsListeners.remove(victim);
    716     }
    717 
    718     private void checkIsAlive() {
    719         if (!mAlive) {
    720             throw new IllegalStateException("This ViewTreeObserver is not alive, call "
    721                     + "getViewTreeObserver() again");
    722         }
    723     }
    724 
    725     /**
    726      * Indicates whether this ViewTreeObserver is alive. When an observer is not alive,
    727      * any call to a method (except this one) will throw an exception.
    728      *
    729      * If an application keeps a long-lived reference to this ViewTreeObserver, it should
    730      * always check for the result of this method before calling any other method.
    731      *
    732      * @return True if this object is alive and be used, false otherwise.
    733      */
    734     public boolean isAlive() {
    735         return mAlive;
    736     }
    737 
    738     /**
    739      * Marks this ViewTreeObserver as not alive. After invoking this method, invoking
    740      * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception.
    741      *
    742      * @hide
    743      */
    744     private void kill() {
    745         mAlive = false;
    746     }
    747 
    748     /**
    749      * Notifies registered listeners that window has been attached/detached.
    750      */
    751     final void dispatchOnWindowAttachedChange(boolean attached) {
    752         // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
    753         // perform the dispatching. The iterator is a safe guard against listeners that
    754         // could mutate the list by calling the various add/remove methods. This prevents
    755         // the array from being modified while we iterate it.
    756         final CopyOnWriteArrayList<OnWindowAttachListener> listeners
    757                 = mOnWindowAttachListeners;
    758         if (listeners != null && listeners.size() > 0) {
    759             for (OnWindowAttachListener listener : listeners) {
    760                 if (attached) listener.onWindowAttached();
    761                 else listener.onWindowDetached();
    762             }
    763         }
    764     }
    765 
    766     /**
    767      * Notifies registered listeners that window focus has changed.
    768      */
    769     final void dispatchOnWindowFocusChange(boolean hasFocus) {
    770         // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
    771         // perform the dispatching. The iterator is a safe guard against listeners that
    772         // could mutate the list by calling the various add/remove methods. This prevents
    773         // the array from being modified while we iterate it.
    774         final CopyOnWriteArrayList<OnWindowFocusChangeListener> listeners
    775                 = mOnWindowFocusListeners;
    776         if (listeners != null && listeners.size() > 0) {
    777             for (OnWindowFocusChangeListener listener : listeners) {
    778                 listener.onWindowFocusChanged(hasFocus);
    779             }
    780         }
    781     }
    782 
    783     /**
    784      * Notifies registered listeners that focus has changed.
    785      */
    786     final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
    787         // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
    788         // perform the dispatching. The iterator is a safe guard against listeners that
    789         // could mutate the list by calling the various add/remove methods. This prevents
    790         // the array from being modified while we iterate it.
    791         final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
    792         if (listeners != null && listeners.size() > 0) {
    793             for (OnGlobalFocusChangeListener listener : listeners) {
    794                 listener.onGlobalFocusChanged(oldFocus, newFocus);
    795             }
    796         }
    797     }
    798 
    799     /**
    800      * Notifies registered listeners that a global layout happened. This can be called
    801      * manually if you are forcing a layout on a View or a hierarchy of Views that are
    802      * not attached to a Window or in the GONE state.
    803      */
    804     public final void dispatchOnGlobalLayout() {
    805         // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
    806         // perform the dispatching. The iterator is a safe guard against listeners that
    807         // could mutate the list by calling the various add/remove methods. This prevents
    808         // the array from being modified while we iterate it.
    809         final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
    810         if (listeners != null && listeners.size() > 0) {
    811             CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
    812             try {
    813                 int count = access.size();
    814                 for (int i = 0; i < count; i++) {
    815                     access.get(i).onGlobalLayout();
    816                 }
    817             } finally {
    818                 listeners.end();
    819             }
    820         }
    821     }
    822 
    823     /**
    824      * Returns whether there are listeners for on pre-draw events.
    825      */
    826     final boolean hasOnPreDrawListeners() {
    827         return mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0;
    828     }
    829 
    830     /**
    831      * Notifies registered listeners that the drawing pass is about to start. If a
    832      * listener returns true, then the drawing pass is canceled and rescheduled. This can
    833      * be called manually if you are forcing the drawing on a View or a hierarchy of Views
    834      * that are not attached to a Window or in the GONE state.
    835      *
    836      * @return True if the current draw should be canceled and resceduled, false otherwise.
    837      */
    838     @SuppressWarnings("unchecked")
    839     public final boolean dispatchOnPreDraw() {
    840         boolean cancelDraw = false;
    841         final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
    842         if (listeners != null && listeners.size() > 0) {
    843             CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
    844             try {
    845                 int count = access.size();
    846                 for (int i = 0; i < count; i++) {
    847                     cancelDraw |= !(access.get(i).onPreDraw());
    848                 }
    849             } finally {
    850                 listeners.end();
    851             }
    852         }
    853         return cancelDraw;
    854     }
    855 
    856     /**
    857      * Notifies registered listeners that the drawing pass is about to start.
    858      */
    859     public final void dispatchOnDraw() {
    860         if (mOnDrawListeners != null) {
    861             final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
    862             int numListeners = listeners.size();
    863             for (int i = 0; i < numListeners; ++i) {
    864                 listeners.get(i).onDraw();
    865             }
    866         }
    867     }
    868 
    869     /**
    870      * Notifies registered listeners that the touch mode has changed.
    871      *
    872      * @param inTouchMode True if the touch mode is now enabled, false otherwise.
    873      */
    874     final void dispatchOnTouchModeChanged(boolean inTouchMode) {
    875         final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
    876                 mOnTouchModeChangeListeners;
    877         if (listeners != null && listeners.size() > 0) {
    878             for (OnTouchModeChangeListener listener : listeners) {
    879                 listener.onTouchModeChanged(inTouchMode);
    880             }
    881         }
    882     }
    883 
    884     /**
    885      * Notifies registered listeners that something has scrolled.
    886      */
    887     final void dispatchOnScrollChanged() {
    888         // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
    889         // perform the dispatching. The iterator is a safe guard against listeners that
    890         // could mutate the list by calling the various add/remove methods. This prevents
    891         // the array from being modified while we iterate it.
    892         final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
    893         if (listeners != null && listeners.size() > 0) {
    894             CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start();
    895             try {
    896                 int count = access.size();
    897                 for (int i = 0; i < count; i++) {
    898                     access.get(i).onScrollChanged();
    899                 }
    900             } finally {
    901                 listeners.end();
    902             }
    903         }
    904     }
    905 
    906     /**
    907      * Returns whether there are listeners for computing internal insets.
    908      */
    909     final boolean hasComputeInternalInsetsListeners() {
    910         final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
    911                 mOnComputeInternalInsetsListeners;
    912         return (listeners != null && listeners.size() > 0);
    913     }
    914 
    915     /**
    916      * Calls all listeners to compute the current insets.
    917      */
    918     final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
    919         // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
    920         // perform the dispatching. The iterator is a safe guard against listeners that
    921         // could mutate the list by calling the various add/remove methods. This prevents
    922         // the array from being modified while we iterate it.
    923         final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
    924                 mOnComputeInternalInsetsListeners;
    925         if (listeners != null && listeners.size() > 0) {
    926             CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start();
    927             try {
    928                 int count = access.size();
    929                 for (int i = 0; i < count; i++) {
    930                     access.get(i).onComputeInternalInsets(inoutInfo);
    931                 }
    932             } finally {
    933                 listeners.end();
    934             }
    935         }
    936     }
    937 
    938     /**
    939      * Copy on write array. This array is not thread safe, and only one loop can
    940      * iterate over this array at any given time. This class avoids allocations
    941      * until a concurrent modification happens.
    942      *
    943      * Usage:
    944      *
    945      * CopyOnWriteArray.Access<MyData> access = array.start();
    946      * try {
    947      *     for (int i = 0; i < access.size(); i++) {
    948      *         MyData d = access.get(i);
    949      *     }
    950      * } finally {
    951      *     access.end();
    952      * }
    953      */
    954     static class CopyOnWriteArray<T> {
    955         private ArrayList<T> mData = new ArrayList<T>();
    956         private ArrayList<T> mDataCopy;
    957 
    958         private final Access<T> mAccess = new Access<T>();
    959 
    960         private boolean mStart;
    961 
    962         static class Access<T> {
    963             private ArrayList<T> mData;
    964             private int mSize;
    965 
    966             T get(int index) {
    967                 return mData.get(index);
    968             }
    969 
    970             int size() {
    971                 return mSize;
    972             }
    973         }
    974 
    975         CopyOnWriteArray() {
    976         }
    977 
    978         private ArrayList<T> getArray() {
    979             if (mStart) {
    980                 if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData);
    981                 return mDataCopy;
    982             }
    983             return mData;
    984         }
    985 
    986         Access<T> start() {
    987             if (mStart) throw new IllegalStateException("Iteration already started");
    988             mStart = true;
    989             mDataCopy = null;
    990             mAccess.mData = mData;
    991             mAccess.mSize = mData.size();
    992             return mAccess;
    993         }
    994 
    995         void end() {
    996             if (!mStart) throw new IllegalStateException("Iteration not started");
    997             mStart = false;
    998             if (mDataCopy != null) {
    999                 mData = mDataCopy;
   1000                 mAccess.mData.clear();
   1001                 mAccess.mSize = 0;
   1002             }
   1003             mDataCopy = null;
   1004         }
   1005 
   1006         int size() {
   1007             return getArray().size();
   1008         }
   1009 
   1010         void add(T item) {
   1011             getArray().add(item);
   1012         }
   1013 
   1014         void addAll(CopyOnWriteArray<T> array) {
   1015             getArray().addAll(array.mData);
   1016         }
   1017 
   1018         void remove(T item) {
   1019             getArray().remove(item);
   1020         }
   1021 
   1022         void clear() {
   1023             getArray().clear();
   1024         }
   1025     }
   1026 }
   1027