content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.view;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.UnsupportedAppUsage;
  import android.content.Context;
  import android.graphics.Rect;
  import android.graphics.Region;
  import android.os.Build;
  import android.util.Log;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.CopyOnWriteArrayList;
  import java.util.function.Consumer;
  
  /**
   * A view tree observer is used to register listeners that can be notified of global
   * changes in the view tree. Such global events include, but are not limited to,
   * layout of the whole tree, beginning of the drawing pass, touch mode change....
   *
   * A ViewTreeObserver should never be instantiated by applications as it is provided
   * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()}
   * for more information.
   */
  public final class ViewTreeObserver {
      // Recursive listeners use CopyOnWriteArrayList
      private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
      private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
      private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
      @UnsupportedAppUsage
      private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
      private CopyOnWriteArrayList<OnEnterAnimationCompleteListener>
              mOnEnterAnimationCompleteListeners;
  
      // Non-recursive listeners use CopyOnWriteArray
      // Any listener invoked from ViewRootImpl.performTraversals() should not be recursive
      @UnsupportedAppUsage
      private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
      @UnsupportedAppUsage
      private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
      @UnsupportedAppUsage
      private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
      private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
      private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners;
      private CopyOnWriteArray<Consumer<List<Rect>>> mGestureExclusionListeners;
  
      // These listeners cannot be mutated during dispatch
      private boolean mInDispatchOnDraw;
      private ArrayList<OnDrawListener> mOnDrawListeners;
      private static boolean sIllegalOnDrawModificationIsFatal;
  
      // These listeners are one-shot
      private ArrayList<Runnable> mOnFrameCommitListeners;
  
      /** Remains false until #dispatchOnWindowShown() is called. If a listener registers after
       * that the listener will be immediately called. */
      private boolean mWindowShown;
  
      private boolean mAlive = true;
  
      /**
       * Interface definition for a callback to be invoked when the view hierarchy is
       * attached to and detached from its window.
       */
      public interface OnWindowAttachListener {
          /**
           * Callback method to be invoked when the view hierarchy is attached to a window
           */
          public void onWindowAttached();
  
          /**
           * Callback method to be invoked when the view hierarchy is detached from a window
           */
          public void onWindowDetached();
      }
  
      /**
       * Interface definition for a callback to be invoked when the view hierarchy's window
       * focus state changes.
       */
      public interface OnWindowFocusChangeListener {
          /**
           * Callback method to be invoked when the window focus changes in the view tree.
           *
           * @param hasFocus Set to true if the window is gaining focus, false if it is
           * losing focus.
           */
          public void onWindowFocusChanged(boolean hasFocus);
      }
  
      /**
       * Interface definition for a callback to be invoked when the focus state within
       * the view tree changes.
       */
      public interface OnGlobalFocusChangeListener {
          /**
           * Callback method to be invoked when the focus changes in the view tree. When
           * the view tree transitions from touch mode to non-touch mode, oldFocus is null.
           * When the view tree transitions from non-touch mode to touch mode, newFocus is
           * null. When focus changes in non-touch mode (without transition from or to
           * touch mode) either oldFocus or newFocus can be null.
           *
           * @param oldFocus The previously focused view, if any.
           * @param newFocus The newly focused View, if any.
           */
          public void onGlobalFocusChanged(View oldFocus, View newFocus);
      }
  
      /**
       * Interface definition for a callback to be invoked when the global layout state
       * or the visibility of views within the view tree changes.
       */
      public interface OnGlobalLayoutListener {
          /**
           * Callback method to be invoked when the global layout state or the visibility of views
           * within the view tree changes
           */
          public void onGlobalLayout();
      }
  
      /**
       * Interface definition for a callback to be invoked when the view tree is about to be drawn.
       */
      public interface OnPreDrawListener {
          /**
           * Callback method to be invoked when the view tree is about to be drawn. At this point, all
           * views in the tree have been measured and given a frame. Clients can use this to adjust
           * their scroll bounds or even to request a new layout before drawing occurs.
           *
           * @return Return true to proceed with the current drawing pass, or false to cancel.
           *
           * @see android.view.View#onMeasure
           * @see android.view.View#onLayout
           * @see android.view.View#onDraw
           */
          public boolean onPreDraw();
      }
  
      /**
       * Interface definition for a callback to be invoked when the view tree is about to be drawn.
       */
      public interface OnDrawListener {
          /**
           * <p>Callback method to be invoked when the view tree is about to be drawn. At this point,
           * views cannot be modified in any way.</p>
           * 
           * <p>Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the
           * current drawing pass.</p>
           * 
           * <p>An {@link OnDrawListener} listener <strong>cannot be added or removed</strong>
           * from this method.</p>
           *
           * @see android.view.View#onMeasure
           * @see android.view.View#onLayout
           * @see android.view.View#onDraw
           */
          public void onDraw();
      }
  
      /**
       * Interface definition for a callback to be invoked when the touch mode changes.
       */
      public interface OnTouchModeChangeListener {
          /**
           * Callback method to be invoked when the touch mode changes.
           *
           * @param isInTouchMode True if the view hierarchy is now in touch mode, false  otherwise.
           */
          public void onTouchModeChanged(boolean isInTouchMode);
      }
  
      /**
       * Interface definition for a callback to be invoked when
       * something in the view tree has been scrolled.
       */
      public interface OnScrollChangedListener {
          /**
           * Callback method to be invoked when something in the view tree
           * has been scrolled.
           */
          public void onScrollChanged();
      }
  
      /**
       * Interface definition for a callback noting when a system window has been displayed.
       * This is only used for non-Activity windows. Activity windows can use
       * Activity.onEnterAnimationComplete() to get the same signal.
       * @hide
       */
      public interface OnWindowShownListener {
          /**
           * Callback method to be invoked when a non-activity window is fully shown.
           */
          void onWindowShown();
      }
  
      /**
       * Parameters used with OnComputeInternalInsetsListener.
       * 
       * We are not yet ready to commit to this API and support it, so
       * @hide
       */
      public final static class InternalInsetsInfo {
          /**
           * Offsets from the frame of the window at which the content of
           * windows behind it should be placed.
           */
          @UnsupportedAppUsage
          public final Rect contentInsets = new Rect();
  
          /**
           * Offsets from the frame of the window at which windows behind it
           * are visible.
           */
          @UnsupportedAppUsage
          public final Rect visibleInsets = new Rect();
  
          /**
           * Touchable region defined relative to the origin of the frame of the window.
           * Only used when {@link #setTouchableInsets(int)} is called with
           * the option {@link #TOUCHABLE_INSETS_REGION}.
           */
          @UnsupportedAppUsage
          public final Region touchableRegion = new Region();
  
          /**
           * Option for {@link #setTouchableInsets(int)}: the entire window frame
           * can be touched.
           */
          public static final int TOUCHABLE_INSETS_FRAME = 0;
  
          /**
           * Option for {@link #setTouchableInsets(int)}: the area inside of
           * the content insets can be touched.
           */
          public static final int TOUCHABLE_INSETS_CONTENT = 1;
  
          /**
           * Option for {@link #setTouchableInsets(int)}: the area inside of
           * the visible insets can be touched.
           */
          public static final int TOUCHABLE_INSETS_VISIBLE = 2;
  
          /**
           * Option for {@link #setTouchableInsets(int)}: the area inside of
           * the provided touchable region in {@link #touchableRegion} can be touched.
           */
          @UnsupportedAppUsage
          public static final int TOUCHABLE_INSETS_REGION = 3;
  
          /**
           * Set which parts of the window can be touched: either
           * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT},
           * {@link #TOUCHABLE_INSETS_VISIBLE}, or {@link #TOUCHABLE_INSETS_REGION}.
           */
          @UnsupportedAppUsage
          public void setTouchableInsets(int val) {
              mTouchableInsets = val;
          }
  
          @UnsupportedAppUsage
          int mTouchableInsets;
  
          void reset() {
              contentInsets.setEmpty();
              visibleInsets.setEmpty();
              touchableRegion.setEmpty();
              mTouchableInsets = TOUCHABLE_INSETS_FRAME;
          }
  
          boolean isEmpty() {
              return contentInsets.isEmpty()
                      && visibleInsets.isEmpty()
                      && touchableRegion.isEmpty()
                      && mTouchableInsets == TOUCHABLE_INSETS_FRAME;
          }
  
          @Override
          public int hashCode() {
              int result = contentInsets.hashCode();
              result = 31 * result + visibleInsets.hashCode();
              result = 31 * result + touchableRegion.hashCode();
              result = 31 * result + mTouchableInsets;
              return result;
          }
  
          @Override
          public boolean equals(Object o) {
              if (this == o) return true;
              if (o == null || getClass() != o.getClass()) return false;
  
              InternalInsetsInfo other = (InternalInsetsInfo)o;
              return mTouchableInsets == other.mTouchableInsets &&
                      contentInsets.equals(other.contentInsets) &&
                      visibleInsets.equals(other.visibleInsets) &&
                      touchableRegion.equals(other.touchableRegion);
          }
  
          @UnsupportedAppUsage
          void set(InternalInsetsInfo other) {
              contentInsets.set(other.contentInsets);
              visibleInsets.set(other.visibleInsets);
              touchableRegion.set(other.touchableRegion);
              mTouchableInsets = other.mTouchableInsets;
          }
      }
  
      /**
       * Interface definition for a callback to be invoked when layout has
       * completed and the client can compute its interior insets.
       * 
       * We are not yet ready to commit to this API and support it, so
       * @hide
       */
      public interface OnComputeInternalInsetsListener {
          /**
           * Callback method to be invoked when layout has completed and the
           * client can compute its interior insets.
           *
           * @param inoutInfo Should be filled in by the implementation with
           * the information about the insets of the window.  This is called
           * with whatever values the previous OnComputeInternalInsetsListener
           * returned, if there are multiple such listeners in the window.
           */
          public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
      }
  
      /**
       * @hide
       */
      public interface OnEnterAnimationCompleteListener {
          public void onEnterAnimationComplete();
      }
  
      /**
       * Creates a new ViewTreeObserver. This constructor should not be called
       */
      ViewTreeObserver(Context context) {
          sIllegalOnDrawModificationIsFatal =
                  context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O;
      }
  
      /**
       * Merges all the listeners registered on the specified observer with the listeners
       * registered on this object. After this method is invoked, the specified observer
       * will return false in {@link #isAlive()} and should not be used anymore.
       *
       * @param observer The ViewTreeObserver whose listeners must be added to this observer
       */
      void merge(ViewTreeObserver observer) {
          if (observer.mOnWindowAttachListeners != null) {
              if (mOnWindowAttachListeners != null) {
                  mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners);
              } else {
                  mOnWindowAttachListeners = observer.mOnWindowAttachListeners;
              }
          }
  
          if (observer.mOnWindowFocusListeners != null) {
              if (mOnWindowFocusListeners != null) {
                  mOnWindowFocusListeners.addAll(observer.mOnWindowFocusListeners);
              } else {
                  mOnWindowFocusListeners = observer.mOnWindowFocusListeners;
              }
          }
  
          if (observer.mOnGlobalFocusListeners != null) {
              if (mOnGlobalFocusListeners != null) {
                  mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
              } else {
                  mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners;
              }
          }
  
          if (observer.mOnGlobalLayoutListeners != null) {
              if (mOnGlobalLayoutListeners != null) {
                  mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners);
              } else {
                  mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners;
              }
          }
  
          if (observer.mOnPreDrawListeners != null) {
              if (mOnPreDrawListeners != null) {
                  mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners);
              } else {
                  mOnPreDrawListeners = observer.mOnPreDrawListeners;
              }
          }
  
          if (observer.mOnDrawListeners != null) {
              if (mOnDrawListeners != null) {
                  mOnDrawListeners.addAll(observer.mOnDrawListeners);
              } else {
                  mOnDrawListeners = observer.mOnDrawListeners;
              }
          }
  
          if (observer.mOnFrameCommitListeners != null) {
              if (mOnFrameCommitListeners != null) {
                  mOnFrameCommitListeners.addAll(observer.captureFrameCommitCallbacks());
              } else {
                  mOnFrameCommitListeners = observer.captureFrameCommitCallbacks();
              }
          }
  
          if (observer.mOnTouchModeChangeListeners != null) {
              if (mOnTouchModeChangeListeners != null) {
                  mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners);
              } else {
                  mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners;
              }
          }
  
          if (observer.mOnComputeInternalInsetsListeners != null) {
              if (mOnComputeInternalInsetsListeners != null) {
                  mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners);
              } else {
                  mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners;
              }
          }
  
          if (observer.mOnScrollChangedListeners != null) {
              if (mOnScrollChangedListeners != null) {
                  mOnScrollChangedListeners.addAll(observer.mOnScrollChangedListeners);
              } else {
                  mOnScrollChangedListeners = observer.mOnScrollChangedListeners;
              }
          }
  
          if (observer.mOnWindowShownListeners != null) {
              if (mOnWindowShownListeners != null) {
                  mOnWindowShownListeners.addAll(observer.mOnWindowShownListeners);
              } else {
                  mOnWindowShownListeners = observer.mOnWindowShownListeners;
              }
          }
  
          if (observer.mGestureExclusionListeners != null) {
              if (mGestureExclusionListeners != null) {
                  mGestureExclusionListeners.addAll(observer.mGestureExclusionListeners);
              } else {
                  mGestureExclusionListeners = observer.mGestureExclusionListeners;
              }
          }
  
          observer.kill();
      }
  
      /**
       * Register a callback to be invoked when the view hierarchy is attached to a window.
       *
       * @param listener The callback to add
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       */
      public void addOnWindowAttachListener(OnWindowAttachListener listener) {
          checkIsAlive();
  
          if (mOnWindowAttachListeners == null) {
              mOnWindowAttachListeners
                      = new CopyOnWriteArrayList<OnWindowAttachListener>();
          }
  
          mOnWindowAttachListeners.add(listener);
      }
  
      /**
       * Remove a previously installed window attach callback.
       *
       * @param victim The callback to remove
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       *
       * @see #addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener)
       */
      public void removeOnWindowAttachListener(OnWindowAttachListener victim) {
          checkIsAlive();
          if (mOnWindowAttachListeners == null) {
              return;
          }
          mOnWindowAttachListeners.remove(victim);
      }
  
      /**
       * Register a callback to be invoked when the window focus state within the view tree changes.
       *
       * @param listener The callback to add
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       */
      public void addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) {
          checkIsAlive();
  
          if (mOnWindowFocusListeners == null) {
              mOnWindowFocusListeners
                      = new CopyOnWriteArrayList<OnWindowFocusChangeListener>();
          }
  
          mOnWindowFocusListeners.add(listener);
      }
  
      /**
       * Remove a previously installed window focus change callback.
       *
       * @param victim The callback to remove
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       *
       * @see #addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener)
       */
      public void removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim) {
          checkIsAlive();
          if (mOnWindowFocusListeners == null) {
              return;
          }
          mOnWindowFocusListeners.remove(victim);
      }
  
      /**
       * Register a callback to be invoked when the focus state within the view tree changes.
       *
       * @param listener The callback to add
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       */
      public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) {
          checkIsAlive();
  
          if (mOnGlobalFocusListeners == null) {
              mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>();
          }
  
          mOnGlobalFocusListeners.add(listener);
      }
  
      /**
       * Remove a previously installed focus change callback.
       *
       * @param victim The callback to remove
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       *
       * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener)
       */
      public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) {
          checkIsAlive();
          if (mOnGlobalFocusListeners == null) {
              return;
          }
          mOnGlobalFocusListeners.remove(victim);
      }
  
      /**
       * Register a callback to be invoked when the global layout state or the visibility of views
       * within the view tree changes
       *
       * @param listener The callback to add
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       */
      public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
          checkIsAlive();
  
          if (mOnGlobalLayoutListeners == null) {
              mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
          }
  
          mOnGlobalLayoutListeners.add(listener);
      }
  
      /**
       * Remove a previously installed global layout callback
       *
       * @param victim The callback to remove
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       * 
       * @deprecated Use #removeOnGlobalLayoutListener instead
       *
       * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
       */
      @Deprecated
      public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {
          removeOnGlobalLayoutListener(victim);
      }
  
      /**
       * Remove a previously installed global layout callback
       *
       * @param victim The callback to remove
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       * 
       * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
       */
      public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {
          checkIsAlive();
          if (mOnGlobalLayoutListeners == null) {
              return;
          }
          mOnGlobalLayoutListeners.remove(victim);
      }
  
      /**
       * Register a callback to be invoked when the view tree is about to be drawn
       *
       * @param listener The callback to add
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       */
      public void addOnPreDrawListener(OnPreDrawListener listener) {
          checkIsAlive();
  
          if (mOnPreDrawListeners == null) {
              mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
          }
  
          mOnPreDrawListeners.add(listener);
      }
  
      /**
       * Remove a previously installed pre-draw callback
       *
       * @param victim The callback to remove
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       *
       * @see #addOnPreDrawListener(OnPreDrawListener)
       */
      public void removeOnPreDrawListener(OnPreDrawListener victim) {
          checkIsAlive();
          if (mOnPreDrawListeners == null) {
              return;
          }
          mOnPreDrawListeners.remove(victim);
      }
  
      /**
       * Register a callback to be invoked when the view tree window has been shown
       *
       * @param listener The callback to add
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       * @hide
       */
      public void addOnWindowShownListener(OnWindowShownListener listener) {
          checkIsAlive();
  
          if (mOnWindowShownListeners == null) {
              mOnWindowShownListeners = new CopyOnWriteArray<OnWindowShownListener>();
          }
  
          mOnWindowShownListeners.add(listener);
          if (mWindowShown) {
              listener.onWindowShown();
          }
      }
  
      /**
       * Remove a previously installed window shown callback
       *
       * @param victim The callback to remove
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       *
       * @see #addOnWindowShownListener(OnWindowShownListener)
       * @hide
       */
      public void removeOnWindowShownListener(OnWindowShownListener victim) {
          checkIsAlive();
          if (mOnWindowShownListeners == null) {
              return;
          }
          mOnWindowShownListeners.remove(victim);
      }
  
      /**
       * <p>Register a callback to be invoked when the view tree is about to be drawn.</p>
       * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
       * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
       *
       * @param listener The callback to add
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       */
      public void addOnDrawListener(OnDrawListener listener) {
          checkIsAlive();
  
          if (mOnDrawListeners == null) {
              mOnDrawListeners = new ArrayList<OnDrawListener>();
          }
  
          if (mInDispatchOnDraw) {
              IllegalStateException ex = new IllegalStateException(
                      "Cannot call addOnDrawListener inside of onDraw");
              if (sIllegalOnDrawModificationIsFatal) {
                  throw ex;
              } else {
                  Log.e("ViewTreeObserver", ex.getMessage(), ex);
              }
          }
          mOnDrawListeners.add(listener);
      }
  
      /**
       * <p>Remove a previously installed pre-draw callback.</p>
       * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from
       * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p>
       *
       * @param victim The callback to remove
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       *
       * @see #addOnDrawListener(OnDrawListener)
       */
      public void removeOnDrawListener(OnDrawListener victim) {
          checkIsAlive();
          if (mOnDrawListeners == null) {
              return;
          }
          if (mInDispatchOnDraw) {
              IllegalStateException ex = new IllegalStateException(
                      "Cannot call removeOnDrawListener inside of onDraw");
              if (sIllegalOnDrawModificationIsFatal) {
                  throw ex;
              } else {
                  Log.e("ViewTreeObserver", ex.getMessage(), ex);
              }
          }
          mOnDrawListeners.remove(victim);
      }
  
      /**
       * Adds a frame commit callback. This callback will be invoked when the current rendering
       * content has been rendered into a frame and submitted to the swap chain. The frame may
       * not currently be visible on the display when this is invoked, but it has been submitted.
       * This callback is useful in combination with {@link PixelCopy} to capture the current
       * rendered content of the UI reliably.
       *
       * Note: Only works with hardware rendering. Does nothing otherwise.
       *
       * @param callback The callback to invoke when the frame is committed.
       */
      public void registerFrameCommitCallback(@NonNull Runnable callback) {
          checkIsAlive();
          if (mOnFrameCommitListeners == null) {
              mOnFrameCommitListeners = new ArrayList<>();
          }
          mOnFrameCommitListeners.add(callback);
      }
  
      @Nullable ArrayList<Runnable> captureFrameCommitCallbacks() {
          ArrayList<Runnable> ret = mOnFrameCommitListeners;
          mOnFrameCommitListeners = null;
          return ret;
      }
  
      /**
       * Attempts to remove the given callback from the list of pending frame complete callbacks.
       *
       * @param callback The callback to remove
       * @return Whether or not the callback was removed. If this returns true the callback will
       *         not be invoked. If false is returned then the callback was either never added
       *         or may already be pending execution and was unable to be removed
       */
      public boolean unregisterFrameCommitCallback(@NonNull Runnable callback) {
          checkIsAlive();
          if (mOnFrameCommitListeners == null) {
              return false;
          }
          return mOnFrameCommitListeners.remove(callback);
      }
  
      /**
       * Register a callback to be invoked when a view has been scrolled.
       *
       * @param listener The callback to add
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       */
      public void addOnScrollChangedListener(OnScrollChangedListener listener) {
          checkIsAlive();
  
          if (mOnScrollChangedListeners == null) {
              mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>();
          }
  
          mOnScrollChangedListeners.add(listener);
      }
  
      /**
       * Remove a previously installed scroll-changed callback
       *
       * @param victim The callback to remove
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       *
       * @see #addOnScrollChangedListener(OnScrollChangedListener)
       */
      public void removeOnScrollChangedListener(OnScrollChangedListener victim) {
          checkIsAlive();
          if (mOnScrollChangedListeners == null) {
              return;
          }
          mOnScrollChangedListeners.remove(victim);
      }
  
      /**
       * Register a callback to be invoked when the invoked when the touch mode changes.
       *
       * @param listener The callback to add
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       */
      public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) {
          checkIsAlive();
  
          if (mOnTouchModeChangeListeners == null) {
              mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>();
          }
  
          mOnTouchModeChangeListeners.add(listener);
      }
  
      /**
       * Remove a previously installed touch mode change callback
       *
       * @param victim The callback to remove
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       *
       * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener)
       */
      public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) {
          checkIsAlive();
          if (mOnTouchModeChangeListeners == null) {
              return;
          }
          mOnTouchModeChangeListeners.remove(victim);
      }
  
      /**
       * Register a callback to be invoked when the invoked when it is time to
       * compute the window's internal insets.
       *
       * @param listener The callback to add
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       * 
       * We are not yet ready to commit to this API and support it, so
       * @hide
       */
      @UnsupportedAppUsage
      public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) {
          checkIsAlive();
  
          if (mOnComputeInternalInsetsListeners == null) {
              mOnComputeInternalInsetsListeners =
                      new CopyOnWriteArray<OnComputeInternalInsetsListener>();
          }
  
          mOnComputeInternalInsetsListeners.add(listener);
      }
  
      /**
       * Remove a previously installed internal insets computation callback
       *
       * @param victim The callback to remove
       *
       * @throws IllegalStateException If {@link #isAlive()} returns false
       *
       * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener)
       * 
       * We are not yet ready to commit to this API and support it, so
       * @hide
       */
      @UnsupportedAppUsage
      public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
          checkIsAlive();
          if (mOnComputeInternalInsetsListeners == null) {
              return;
          }
          mOnComputeInternalInsetsListeners.remove(victim);
      }
  
      /**
       * @hide
       */
      public void addOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) {
          checkIsAlive();
          if (mOnEnterAnimationCompleteListeners == null) {
              mOnEnterAnimationCompleteListeners =
                      new CopyOnWriteArrayList<OnEnterAnimationCompleteListener>();
          }
          mOnEnterAnimationCompleteListeners.add(listener);
      }
  
      /**
       * @hide
       */
      public void removeOnEnterAnimationCompleteListener(OnEnterAnimationCompleteListener listener) {
          checkIsAlive();
          if (mOnEnterAnimationCompleteListeners == null) {
              return;
          }
          mOnEnterAnimationCompleteListeners.remove(listener);
      }
  
      /**
       * Add a listener to be notified when the tree's <em>transformed</em> gesture exclusion rects
       * change. This could be the result of an animation or other layout change, or a view calling
       * {@link View#setSystemGestureExclusionRects(List)}.
       *
       * @param listener listener to add
       * @see View#setSystemGestureExclusionRects(List)
       */
      public void addOnSystemGestureExclusionRectsChangedListener(
              @NonNull Consumer<List<Rect>> listener) {
          checkIsAlive();
          if (mGestureExclusionListeners == null) {
              mGestureExclusionListeners = new CopyOnWriteArray<>();
          }
          mGestureExclusionListeners.add(listener);
      }
  
      /**
       * Unsubscribe the given listener from gesture exclusion rect changes.
       * @see #addOnSystemGestureExclusionRectsChangedListener(Consumer)
       * @see View#setSystemGestureExclusionRects(List)
       */
      public void removeOnSystemGestureExclusionRectsChangedListener(
              @NonNull Consumer<List<Rect>> listener) {
          checkIsAlive();
          if (mGestureExclusionListeners == null) {
              return;
          }
          mGestureExclusionListeners.remove(listener);
      }
  
      private void checkIsAlive() {
          if (!mAlive) {
              throw new IllegalStateException("This ViewTreeObserver is not alive, call "
                      + "getViewTreeObserver() again");
          }
      }
  
      /**
       * Indicates whether this ViewTreeObserver is alive. When an observer is not alive,
       * any call to a method (except this one) will throw an exception.
       *
       * If an application keeps a long-lived reference to this ViewTreeObserver, it should
       * always check for the result of this method before calling any other method.
       *
       * @return True if this object is alive and be used, false otherwise.
       */
      public boolean isAlive() {
          return mAlive;
      }
  
      /**
       * Marks this ViewTreeObserver as not alive. After invoking this method, invoking
       * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception.
       *
       * @hide
       */
      private void kill() {
          mAlive = false;
      }
  
      /**
       * Notifies registered listeners that window has been attached/detached.
       */
      final void dispatchOnWindowAttachedChange(boolean attached) {
          // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
          // perform the dispatching. The iterator is a safe guard against listeners that
          // could mutate the list by calling the various add/remove methods. This prevents
          // the array from being modified while we iterate it.
          final CopyOnWriteArrayList<OnWindowAttachListener> listeners
                  = mOnWindowAttachListeners;
          if (listeners != null && listeners.size() > 0) {
              for (OnWindowAttachListener listener : listeners) {
                  if (attached) listener.onWindowAttached();
                  else listener.onWindowDetached();
              }
          }
      }
  
      /**
       * Notifies registered listeners that window focus has changed.
       */
      final void dispatchOnWindowFocusChange(boolean hasFocus) {
          // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
          // perform the dispatching. The iterator is a safe guard against listeners that
          // could mutate the list by calling the various add/remove methods. This prevents
          // the array from being modified while we iterate it.
          final CopyOnWriteArrayList<OnWindowFocusChangeListener> listeners
                  = mOnWindowFocusListeners;
          if (listeners != null && listeners.size() > 0) {
              for (OnWindowFocusChangeListener listener : listeners) {
                  listener.onWindowFocusChanged(hasFocus);
              }
          }
      }
  
      /**
       * Notifies registered listeners that focus has changed.
       */
      @UnsupportedAppUsage
      final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
          // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
          // perform the dispatching. The iterator is a safe guard against listeners that
          // could mutate the list by calling the various add/remove methods. This prevents
          // the array from being modified while we iterate it.
          final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
          if (listeners != null && listeners.size() > 0) {
              for (OnGlobalFocusChangeListener listener : listeners) {
                  listener.onGlobalFocusChanged(oldFocus, newFocus);
              }
          }
      }
  
      /**
       * Notifies registered listeners that a global layout happened. This can be called
       * manually if you are forcing a layout on a View or a hierarchy of Views that are
       * not attached to a Window or in the GONE state.
       */
      public final void dispatchOnGlobalLayout() {
          // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
          // perform the dispatching. The iterator is a safe guard against listeners that
          // could mutate the list by calling the various add/remove methods. This prevents
          // the array from being modified while we iterate it.
          final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
          if (listeners != null && listeners.size() > 0) {
              CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
              try {
                  int count = access.size();
                  for (int i = 0; i < count; i++) {
                      access.get(i).onGlobalLayout();
                  }
              } finally {
                  listeners.end();
              }
          }
      }
  
      /**
       * Returns whether there are listeners for on pre-draw events.
       */
      final boolean hasOnPreDrawListeners() {
          return mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0;
      }
  
      /**
       * Notifies registered listeners that the drawing pass is about to start. If a
       * listener returns true, then the drawing pass is canceled and rescheduled. This can
       * be called manually if you are forcing the drawing on a View or a hierarchy of Views
       * that are not attached to a Window or in the GONE state.
       *
       * @return True if the current draw should be canceled and rescheduled, false otherwise.
       */
      @SuppressWarnings("unchecked")
      public final boolean dispatchOnPreDraw() {
          boolean cancelDraw = false;
          final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
          if (listeners != null && listeners.size() > 0) {
              CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
              try {
                  int count = access.size();
                  for (int i = 0; i < count; i++) {
                      cancelDraw |= !(access.get(i).onPreDraw());
                  }
              } finally {
                  listeners.end();
              }
          }
          return cancelDraw;
      }
  
      /**
       * Notifies registered listeners that the window is now shown
       * @hide
       */
      @SuppressWarnings("unchecked")
      public final void dispatchOnWindowShown() {
          mWindowShown = true;
          final CopyOnWriteArray<OnWindowShownListener> listeners = mOnWindowShownListeners;
          if (listeners != null && listeners.size() > 0) {
              CopyOnWriteArray.Access<OnWindowShownListener> access = listeners.start();
              try {
                  int count = access.size();
                  for (int i = 0; i < count; i++) {
                      access.get(i).onWindowShown();
                  }
              } finally {
                  listeners.end();
              }
          }
      }
  
      /**
       * Notifies registered listeners that the drawing pass is about to start.
       */
      public final void dispatchOnDraw() {
          if (mOnDrawListeners != null) {
              mInDispatchOnDraw = true;
              final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
              int numListeners = listeners.size();
              for (int i = 0; i < numListeners; ++i) {
                  listeners.get(i).onDraw();
              }
              mInDispatchOnDraw = false;
          }
      }
  
      /**
       * Notifies registered listeners that the touch mode has changed.
       *
       * @param inTouchMode True if the touch mode is now enabled, false otherwise.
       */
      @UnsupportedAppUsage
      final void dispatchOnTouchModeChanged(boolean inTouchMode) {
          final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
                  mOnTouchModeChangeListeners;
          if (listeners != null && listeners.size() > 0) {
              for (OnTouchModeChangeListener listener : listeners) {
                  listener.onTouchModeChanged(inTouchMode);
              }
          }
      }
  
      /**
       * Notifies registered listeners that something has scrolled.
       */
      @UnsupportedAppUsage
      final void dispatchOnScrollChanged() {
          // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
          // perform the dispatching. The iterator is a safe guard against listeners that
          // could mutate the list by calling the various add/remove methods. This prevents
          // the array from being modified while we iterate it.
          final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
          if (listeners != null && listeners.size() > 0) {
              CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start();
              try {
                  int count = access.size();
                  for (int i = 0; i < count; i++) {
                      access.get(i).onScrollChanged();
                  }
              } finally {
                  listeners.end();
              }
          }
      }
  
      /**
       * Returns whether there are listeners for computing internal insets.
       */
      @UnsupportedAppUsage
      final boolean hasComputeInternalInsetsListeners() {
          final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
                  mOnComputeInternalInsetsListeners;
          return (listeners != null && listeners.size() > 0);
      }
  
      /**
       * Calls all listeners to compute the current insets.
       */
      @UnsupportedAppUsage
      final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
          // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
          // perform the dispatching. The iterator is a safe guard against listeners that
          // could mutate the list by calling the various add/remove methods. This prevents
          // the array from being modified while we iterate it.
          final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
                  mOnComputeInternalInsetsListeners;
          if (listeners != null && listeners.size() > 0) {
              CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start();
              try {
                  int count = access.size();
                  for (int i = 0; i < count; i++) {
                      access.get(i).onComputeInternalInsets(inoutInfo);
                  }
              } finally {
                  listeners.end();
              }
          }
      }
  
      /**
       * @hide
       */
      public final void dispatchOnEnterAnimationComplete() {
          // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
          // perform the dispatching. The iterator is a safe guard against listeners that
          // could mutate the list by calling the various add/remove methods. This prevents
          // the array from being modified while we iterate it.
          final CopyOnWriteArrayList<OnEnterAnimationCompleteListener> listeners =
                  mOnEnterAnimationCompleteListeners;
          if (listeners != null && !listeners.isEmpty()) {
              for (OnEnterAnimationCompleteListener listener : listeners) {
                  listener.onEnterAnimationComplete();
              }
          }
      }
  
      void dispatchOnSystemGestureExclusionRectsChanged(@NonNull List<Rect> rects) {
          final CopyOnWriteArray<Consumer<List<Rect>>> listeners = mGestureExclusionListeners;
          if (listeners != null && listeners.size() > 0) {
              CopyOnWriteArray.Access<Consumer<List<Rect>>> access = listeners.start();
              try {
                  final int count = access.size();
                  for (int i = 0; i < count; i++) {
                      access.get(i).accept(rects);
                  }
              } finally {
                  listeners.end();
              }
          }
      }
  
      /**
       * Copy on write array. This array is not thread safe, and only one loop can
       * iterate over this array at any given time. This class avoids allocations
       * until a concurrent modification happens.
       * 
       * Usage:
       * 
       * CopyOnWriteArray.Access<MyData> access = array.start();
       * try {
       *     for (int i = 0; i < access.size(); i++) {
       *         MyData d = access.get(i);
       *     }
       * } finally {
       *     access.end();
       * }
       */
      static class CopyOnWriteArray<T> {
          private ArrayList<T> mData = new ArrayList<T>();
          private ArrayList<T> mDataCopy;
  
          private final Access<T> mAccess = new Access<T>();
  
          private boolean mStart;
  
          static class Access<T> {
              private ArrayList<T> mData;
              private int mSize;
  
              T get(int index) {
                  return mData.get(index);
              }
  
              int size() {
                  return mSize;
              }
          }
  
          CopyOnWriteArray() {
          }
  
          private ArrayList<T> getArray() {
              if (mStart) {
                  if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData);
                  return mDataCopy;
              }
              return mData;
          }
  
          Access<T> start() {
              if (mStart) throw new IllegalStateException("Iteration already started");
              mStart = true;
              mDataCopy = null;
              mAccess.mData = mData;
              mAccess.mSize = mData.size();
              return mAccess;
          }
  
          void end() {
              if (!mStart) throw new IllegalStateException("Iteration not started");
              mStart = false;
              if (mDataCopy != null) {
                  mData = mDataCopy;
                  mAccess.mData.clear();
                  mAccess.mSize = 0;
              }
              mDataCopy = null;
          }
  
          int size() {
              return getArray().size();
          }
  
          void add(T item) {
              getArray().add(item);
          }
  
          void addAll(CopyOnWriteArray<T> array) {
              getArray().addAll(array.mData);
          }
  
          void remove(T item) {
              getArray().remove(item);
          }
  
          void clear() {
              getArray().clear();
          }
      }
  }
  