content: /*
   * Copyright (C) 2014 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.UnsupportedAppUsage;
  import android.graphics.Canvas;
  import android.graphics.Matrix;
  import android.graphics.RecordingCanvas;
  import android.graphics.RenderNode;
  import android.os.Build;
  import android.widget.FrameLayout;
  
  import java.util.ArrayList;
  
  /**
   * This view draws another View in an Overlay without changing the parent. It will not be drawn
   * by its parent because its visibility is set to INVISIBLE, but will be drawn
   * here using its render node. When the GhostView is set to INVISIBLE, the View it is
   * shadowing will become VISIBLE and when the GhostView becomes VISIBLE, the shadowed
   * view becomes INVISIBLE.
   * @hide
   */
  public class GhostView extends View {
      private final View mView;
      private int mReferences;
      private boolean mBeingMoved;
  
      private GhostView(View view) {
          super(view.getContext());
          mView = view;
          mView.mGhostView = this;
          final ViewGroup parent = (ViewGroup) mView.getParent();
          mView.setTransitionVisibility(View.INVISIBLE);
          parent.invalidate();
      }
  
      @Override
      protected void onDraw(Canvas canvas) {
          if (canvas instanceof RecordingCanvas) {
              RecordingCanvas dlCanvas = (RecordingCanvas) canvas;
              mView.mRecreateDisplayList = true;
              RenderNode renderNode = mView.updateDisplayListIfDirty();
              if (renderNode.hasDisplayList()) {
                  dlCanvas.insertReorderBarrier(); // enable shadow for this rendernode
                  dlCanvas.drawRenderNode(renderNode);
                  dlCanvas.insertInorderBarrier(); // re-disable reordering/shadows
              }
          }
      }
  
      public void setMatrix(Matrix matrix) {
          mRenderNode.setAnimationMatrix(matrix);
      }
  
      @Override
      public void setVisibility(@Visibility int visibility) {
          super.setVisibility(visibility);
          if (mView.mGhostView == this) {
              int inverseVisibility = (visibility == View.VISIBLE) ? View.INVISIBLE : View.VISIBLE;
              mView.setTransitionVisibility(inverseVisibility);
          }
      }
  
      @Override
      protected void onDetachedFromWindow() {
          super.onDetachedFromWindow();
          if (!mBeingMoved) {
              mView.setTransitionVisibility(View.VISIBLE);
              mView.mGhostView = null;
              final ViewGroup parent = (ViewGroup) mView.getParent();
              if (parent != null) {
                  parent.invalidate();
              }
          }
      }
  
      public static void calculateMatrix(View view, ViewGroup host, Matrix matrix) {
          ViewGroup parent = (ViewGroup) view.getParent();
          matrix.reset();
          parent.transformMatrixToGlobal(matrix);
          matrix.preTranslate(-parent.getScrollX(), -parent.getScrollY());
          host.transformMatrixToLocal(matrix);
      }
  
      @UnsupportedAppUsage
      public static GhostView addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
          if (!(view.getParent() instanceof ViewGroup)) {
              throw new IllegalArgumentException("Ghosted views must be parented by a ViewGroup");
          }
          ViewGroupOverlay overlay = viewGroup.getOverlay();
          ViewOverlay.OverlayViewGroup overlayViewGroup = overlay.mOverlayViewGroup;
          GhostView ghostView = view.mGhostView;
          int previousRefCount = 0;
          if (ghostView != null) {
              View oldParent = (View) ghostView.getParent();
              ViewGroup oldGrandParent = (ViewGroup) oldParent.getParent();
              if (oldGrandParent != overlayViewGroup) {
                  previousRefCount = ghostView.mReferences;
                  oldGrandParent.removeView(oldParent);
                  ghostView = null;
              }
          }
          if (ghostView == null) {
              if (matrix == null) {
                  matrix = new Matrix();
                  calculateMatrix(view, viewGroup, matrix);
              }
              ghostView = new GhostView(view);
              ghostView.setMatrix(matrix);
              FrameLayout parent = new FrameLayout(view.getContext());
              parent.setClipChildren(false);
              copySize(viewGroup, parent);
              copySize(viewGroup, ghostView);
              parent.addView(ghostView);
              ArrayList<View> tempViews = new ArrayList<View>();
              int firstGhost = moveGhostViewsToTop(overlay.mOverlayViewGroup, tempViews);
              insertIntoOverlay(overlay.mOverlayViewGroup, parent, ghostView, tempViews, firstGhost);
              ghostView.mReferences = previousRefCount;
          } else if (matrix != null) {
              ghostView.setMatrix(matrix);
          }
          ghostView.mReferences++;
          return ghostView;
      }
  
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
      public static GhostView addGhost(View view, ViewGroup viewGroup) {
          return addGhost(view, viewGroup, null);
      }
  
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
      public static void removeGhost(View view) {
          GhostView ghostView = view.mGhostView;
          if (ghostView != null) {
              ghostView.mReferences--;
              if (ghostView.mReferences == 0) {
                  ViewGroup parent = (ViewGroup) ghostView.getParent();
                  ViewGroup grandParent = (ViewGroup) parent.getParent();
                  grandParent.removeView(parent);
              }
          }
      }
  
      public static GhostView getGhost(View view) {
          return view.mGhostView;
      }
  
      private static void copySize(View from, View to) {
          to.setLeft(0);
          to.setTop(0);
          to.setRight(from.getWidth());
          to.setBottom(from.getHeight());
      }
  
      /**
       * Move the GhostViews to the end so that they are on top of other views and it is easier
       * to do binary search for the correct location for the GhostViews in insertIntoOverlay.
       *
       * @return The index of the first GhostView or -1 if no GhostView is in the ViewGroup
       */
      private static int moveGhostViewsToTop(ViewGroup viewGroup, ArrayList<View> tempViews) {
          final int numChildren = viewGroup.getChildCount();
          if (numChildren == 0) {
              return -1;
          } else if (isGhostWrapper(viewGroup.getChildAt(numChildren - 1))) {
              // GhostViews are already at the end
              int firstGhost = numChildren - 1;
              for (int i = numChildren - 2; i >= 0; i--) {
                  if (!isGhostWrapper(viewGroup.getChildAt(i))) {
                      break;
                  }
                  firstGhost = i;
              }
              return firstGhost;
          }
  
          // Remove all GhostViews from the middle
          for (int i = numChildren - 2; i >= 0; i--) {
              View child = viewGroup.getChildAt(i);
              if (isGhostWrapper(child)) {
                  tempViews.add(child);
                  GhostView ghostView = (GhostView)((ViewGroup)child).getChildAt(0);
                  ghostView.mBeingMoved = true;
                  viewGroup.removeViewAt(i);
                  ghostView.mBeingMoved = false;
              }
          }
  
          final int firstGhost;
          if (tempViews.isEmpty()) {
              firstGhost = -1;
          } else {
              firstGhost = viewGroup.getChildCount();
              // Add the GhostViews to the end
              for (int i = tempViews.size() - 1; i >= 0; i--) {
                  viewGroup.addView(tempViews.get(i));
              }
              tempViews.clear();
          }
          return firstGhost;
      }
  
      /**
       * Inserts a GhostView into the overlay's ViewGroup in the order in which they
       * should be displayed by the UI.
       */
      private static void insertIntoOverlay(ViewGroup viewGroup, ViewGroup wrapper,
              GhostView ghostView, ArrayList<View> tempParents, int firstGhost) {
          if (firstGhost == -1) {
              viewGroup.addView(wrapper);
          } else {
              ArrayList<View> viewParents = new ArrayList<View>();
              getParents(ghostView.mView, viewParents);
  
              int index = getInsertIndex(viewGroup, viewParents, tempParents, firstGhost);
              if (index < 0 || index >= viewGroup.getChildCount()) {
                  viewGroup.addView(wrapper);
              } else {
                  viewGroup.addView(wrapper, index);
              }
          }
      }
  
      /**
       * Find the index into the overlay to insert the GhostView based on the order that the
       * views should be drawn. This keeps GhostViews layered in the same order
       * that they are ordered in the UI.
       */
      private static int getInsertIndex(ViewGroup overlayViewGroup, ArrayList<View> viewParents,
              ArrayList<View> tempParents, int firstGhost) {
          int low = firstGhost;
          int high = overlayViewGroup.getChildCount() - 1;
  
          while (low <= high) {
              int mid = (low + high) / 2;
              ViewGroup wrapper = (ViewGroup) overlayViewGroup.getChildAt(mid);
              GhostView midView = (GhostView) wrapper.getChildAt(0);
              getParents(midView.mView, tempParents);
              if (isOnTop(viewParents, tempParents)) {
                  low = mid + 1;
              } else {
                  high = mid - 1;
              }
              tempParents.clear();
          }
  
          return low;
      }
  
      /**
       * Returns true if view is a GhostView's FrameLayout wrapper.
       */
      private static boolean isGhostWrapper(View view) {
          if (view instanceof FrameLayout) {
              FrameLayout frameLayout = (FrameLayout) view;
              if (frameLayout.getChildCount() == 1) {
                  View child = frameLayout.getChildAt(0);
                  return child instanceof GhostView;
              }
          }
          return false;
      }
  
      /**
       * Returns true if viewParents is from a View that is on top of the comparedWith's view.
       * The ArrayLists contain the ancestors of views in order from top most grandparent, to
       * the view itself, in order. The goal is to find the first matching parent and then
       * compare the draw order of the siblings.
       */
      private static boolean isOnTop(ArrayList<View> viewParents, ArrayList<View> comparedWith) {
          if (viewParents.isEmpty() || comparedWith.isEmpty() ||
                  viewParents.get(0) != comparedWith.get(0)) {
              // Not the same decorView -- arbitrary ordering
              return true;
          }
          int depth = Math.min(viewParents.size(), comparedWith.size());
          for (int i = 1; i < depth; i++) {
              View viewParent = viewParents.get(i);
              View comparedWithParent = comparedWith.get(i);
  
              if (viewParent != comparedWithParent) {
                  // i - 1 is the same parent, but these are different children.
                  return isOnTop(viewParent, comparedWithParent);
              }
          }
  
          // one of these is the parent of the other
          boolean isComparedWithTheParent = (comparedWith.size() == depth);
          return isComparedWithTheParent;
      }
  
      /**
       * Adds all the parents, grandparents, etc. of view to parents.
       */
      private static void getParents(View view, ArrayList<View> parents) {
          ViewParent parent = view.getParent();
          if (parent != null && parent instanceof ViewGroup) {
              getParents((View) parent, parents);
          }
          parents.add(view);
      }
  
      /**
       * Returns true if view would be drawn on top of comparedWith or false otherwise.
       * view and comparedWith are siblings with the same parent. This uses the logic
       * that dispatchDraw uses to determine which View should be drawn first.
       */
      private static boolean isOnTop(View view, View comparedWith) {
          ViewGroup parent = (ViewGroup) view.getParent();
  
          final int childrenCount = parent.getChildCount();
          final ArrayList<View> preorderedList = parent.buildOrderedChildList();
          final boolean customOrder = preorderedList == null
                  && parent.isChildrenDrawingOrderEnabled();
  
          // This default value shouldn't be used because both view and comparedWith
          // should be in the list. If there is an error, then just return an arbitrary
          // view is on top.
          boolean isOnTop = true;
          for (int i = 0; i < childrenCount; i++) {
              int childIndex = customOrder ? parent.getChildDrawingOrder(childrenCount, i) : i;
              final View child = (preorderedList == null)
                      ? parent.getChildAt(childIndex) : preorderedList.get(childIndex);
              if (child == view) {
                  isOnTop = false;
                  break;
              } else if (child == comparedWith) {
                  isOnTop = true;
                  break;
              }
          }
  
          if (preorderedList != null) {
              preorderedList.clear();
          }
          return isOnTop;
      }
  }
  