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