content: /*
   * Copyright (C) 2017 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 com.android.launcher3.folder;
  
  import android.animation.Animator;
  import android.animation.AnimatorListenerAdapter;
  import android.animation.AnimatorSet;
  import android.animation.ObjectAnimator;
  import android.animation.TimeInterpolator;
  import android.content.Context;
  import android.graphics.Color;
  import android.graphics.Rect;
  import android.graphics.drawable.GradientDrawable;
  import android.support.v4.graphics.ColorUtils;
  import android.util.Property;
  import android.view.View;
  import android.view.animation.AnimationUtils;
  
  import com.android.launcher3.BubbleTextView;
  import com.android.launcher3.CellLayout;
  import com.android.launcher3.Launcher;
  import com.android.launcher3.LauncherAnimUtils;
  import com.android.launcher3.R;
  import com.android.launcher3.ShortcutAndWidgetContainer;
  import com.android.launcher3.Utilities;
  import com.android.launcher3.anim.PropertyResetListener;
  import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
  import com.android.launcher3.dragndrop.DragLayer;
  import com.android.launcher3.util.Themes;
  
  import java.util.List;
  
  /**
   * Manages the opening and closing animations for a {@link Folder}.
   *
   * All of the animations are done in the Folder.
   * ie. When the user taps on the FolderIcon, we immediately hide the FolderIcon and show the Folder
   * in its place before starting the animation.
   */
  public class FolderAnimationManager {
  
      private Folder mFolder;
      private FolderPagedView mContent;
      private GradientDrawable mFolderBackground;
  
      private FolderIcon mFolderIcon;
      private PreviewBackground mPreviewBackground;
  
      private Context mContext;
      private Launcher mLauncher;
  
      private final boolean mIsOpening;
  
      private final int mDuration;
      private final int mDelay;
  
      private final TimeInterpolator mFolderInterpolator;
      private final TimeInterpolator mLargeFolderPreviewItemOpenInterpolator;
      private final TimeInterpolator mLargeFolderPreviewItemCloseInterpolator;
  
      private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
  
      private static final Property<View, Float> SCALE_PROPERTY =
              new Property<View, Float>(Float.class, "scale") {
                  @Override
                  public Float get(View view) {
                      return view.getScaleX();
                  }
  
                  @Override
                  public void set(View view, Float scale) {
                      view.setScaleX(scale);
                      view.setScaleY(scale);
                  }
              };
  
      public FolderAnimationManager(Folder folder, boolean isOpening) {
          mFolder = folder;
          mContent = folder.mContent;
          mFolderBackground = (GradientDrawable) mFolder.getBackground();
  
          mFolderIcon = folder.mFolderIcon;
          mPreviewBackground = mFolderIcon.mBackground;
  
          mContext = folder.getContext();
          mLauncher = folder.mLauncher;
  
          mIsOpening = isOpening;
  
          mDuration = mFolder.mMaterialExpandDuration;
          mDelay = mContext.getResources().getInteger(R.integer.config_folderDelay);
  
          mFolderInterpolator = AnimationUtils.loadInterpolator(mContext,
                  R.interpolator.folder_interpolator);
          mLargeFolderPreviewItemOpenInterpolator = AnimationUtils.loadInterpolator(mContext,
                  R.interpolator.large_folder_preview_item_open_interpolator);
          mLargeFolderPreviewItemCloseInterpolator = AnimationUtils.loadInterpolator(mContext,
                  R.interpolator.large_folder_preview_item_close_interpolator);
      }
  
  
      /**
       * Prepares the Folder for animating between open / closed states.
       */
      public AnimatorSet getAnimator() {
          final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams();
          FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule();
          final List<BubbleTextView> itemsInPreview = mFolderIcon.getPreviewItems();
  
          // Match position of the FolderIcon
          final Rect folderIconPos = new Rect();
          float scaleRelativeToDragLayer = mLauncher.getDragLayer()
                  .getDescendantRectRelativeToSelf(mFolderIcon, folderIconPos);
          int scaledRadius = mPreviewBackground.getScaledRadius();
          float initialSize = (scaledRadius * 2) * scaleRelativeToDragLayer;
  
          // Match size/scale of icons in the preview
          float previewScale = rule.scaleForItem(0, itemsInPreview.size());
          float previewSize = rule.getIconSize() * previewScale;
          float initialScale = previewSize / itemsInPreview.get(0).getIconSize()
                  * scaleRelativeToDragLayer;
          final float finalScale = 1f;
          float scale = mIsOpening ? initialScale : finalScale;
          mFolder.setScaleX(scale);
          mFolder.setScaleY(scale);
          mFolder.setPivotX(0);
          mFolder.setPivotY(0);
  
          // We want to create a small X offset for the preview items, so that they follow their
          // expected path to their final locations. ie. an icon should not move right, if it's final
          // location is to its left. This value is arbitrarily defined.
          int previewItemOffsetX = (int) (previewSize / 2);
          if (Utilities.isRtl(mContext.getResources())) {
              previewItemOffsetX = (int) (lp.width * initialScale - initialSize - previewItemOffsetX);
          }
  
          final int paddingOffsetX = (int) ((mFolder.getPaddingLeft() + mContent.getPaddingLeft())
                  * initialScale);
          final int paddingOffsetY = (int) ((mFolder.getPaddingTop() + mContent.getPaddingTop())
                  * initialScale);
  
          int initialX = folderIconPos.left + mPreviewBackground.getOffsetX() - paddingOffsetX
                  - previewItemOffsetX;
          int initialY = folderIconPos.top + mPreviewBackground.getOffsetY() - paddingOffsetY;
          final float xDistance = initialX - lp.x;
          final float yDistance = initialY - lp.y;
  
          // Set up the Folder background.
          final int finalColor = Themes.getAttrColor(mContext, android.R.attr.colorPrimary);
          final int initialColor =
                  ColorUtils.setAlphaComponent(finalColor, mPreviewBackground.getBackgroundAlpha());
          mFolderBackground.setColor(mIsOpening ? initialColor : finalColor);
  
          // Set up the reveal animation that clips the Folder.
          int totalOffsetX = paddingOffsetX + previewItemOffsetX;
          Rect startRect = new Rect(
                  Math.round(totalOffsetX / initialScale),
                  Math.round(paddingOffsetY / initialScale),
                  Math.round((totalOffsetX + initialSize) / initialScale),
                  Math.round((paddingOffsetY + initialSize) / initialScale));
          Rect endRect = new Rect(0, 0, lp.width, lp.height);
          float initialRadius = initialSize / initialScale / 2f;
          float finalRadius = Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics());
  
          // Create the animators.
          AnimatorSet a = LauncherAnimUtils.createAnimatorSet();
  
          // Initialize the Folder items' text.
          PropertyResetListener colorResetListener = new PropertyResetListener<>(
                  BubbleTextView.TEXT_ALPHA_PROPERTY,
                  Color.alpha(Themes.getAttrColor(mContext, android.R.attr.textColorSecondary)));
          for (BubbleTextView icon : mFolder.getItemsOnPage(mFolder.mContent.getCurrentPage())) {
              if (mIsOpening) {
                  icon.setTextVisibility(false);
              }
              ObjectAnimator anim = icon.createTextAlphaAnimator(mIsOpening);
              anim.addListener(colorResetListener);
              play(a, anim);
          }
  
          play(a, getAnimator(mFolder, View.TRANSLATION_X, xDistance, 0f));
          play(a, getAnimator(mFolder, View.TRANSLATION_Y, yDistance, 0f));
          play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale));
          play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor));
          play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening));
          RoundedRectRevealOutlineProvider outlineProvider = new RoundedRectRevealOutlineProvider(
                  initialRadius, finalRadius, startRect, endRect) {
              @Override
              public boolean shouldRemoveElevationDuringAnimation() {
                  return true;
              }
          };
          play(a, outlineProvider.createRevealAnimator(mFolder, !mIsOpening));
  
          // Animate the elevation midway so that the shadow is not noticeable in the background.
          int midDuration = mDuration / 2;
          Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
          play(a, z, mIsOpening ? midDuration : 0, midDuration);
  
          a.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationEnd(Animator animation) {
                  super.onAnimationEnd(animation);
                  mFolder.setTranslationX(0.0f);
                  mFolder.setTranslationY(0.0f);
                  mFolder.setTranslationZ(0.0f);
                  mFolder.setScaleX(1f);
                  mFolder.setScaleY(1f);
              }
          });
  
          // We set the interpolator on all current child animators here, because the preview item
          // animators may use a different interpolator.
          for (Animator animator : a.getChildAnimations()) {
              animator.setInterpolator(mFolderInterpolator);
          }
  
          int radiusDiff = scaledRadius - mPreviewBackground.getRadius();
          addPreviewItemAnimators(a, initialScale / scaleRelativeToDragLayer,
                  // Background can have a scaled radius in drag and drop mode, so we need to add the
                  // difference to keep the preview items centered.
                  previewItemOffsetX + radiusDiff, radiusDiff);
          return a;
      }
  
      /**
       * Animate the items on the current page.
       */
      private void addPreviewItemAnimators(AnimatorSet animatorSet, final float folderScale,
              int previewItemOffsetX, int previewItemOffsetY) {
          FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule();
          boolean isOnFirstPage = mFolder.mContent.getCurrentPage() == 0;
          final List<BubbleTextView> itemsInPreview = isOnFirstPage
                  ? mFolderIcon.getPreviewItems()
                  : mFolderIcon.getPreviewItemsOnPage(mFolder.mContent.getCurrentPage());
          final int numItemsInPreview = itemsInPreview.size();
          final int numItemsInFirstPagePreview = isOnFirstPage
                  ? numItemsInPreview
                  : FolderIcon.NUM_ITEMS_IN_PREVIEW;
  
          TimeInterpolator previewItemInterpolator = getPreviewItemInterpolator();
  
          ShortcutAndWidgetContainer cwc = mContent.getPageAt(0).getShortcutsAndWidgets();
          for (int i = 0; i < numItemsInPreview; ++i) {
              final BubbleTextView btv = itemsInPreview.get(i);
              CellLayout.LayoutParams btvLp = (CellLayout.LayoutParams) btv.getLayoutParams();
  
              // Calculate the final values in the LayoutParams.
              btvLp.isLockedToGrid = true;
              cwc.setupLp(btv);
  
              // Match scale of icons in the preview of the items on the first page.
              float previewScale = rule.scaleForItem(i, numItemsInFirstPagePreview);
              float previewSize = rule.getIconSize() * previewScale;
              float iconScale = previewSize / itemsInPreview.get(i).getIconSize();
  
              final float initialScale = iconScale / folderScale;
              final float finalScale = 1f;
              float scale = mIsOpening ? initialScale : finalScale;
              btv.setScaleX(scale);
              btv.setScaleY(scale);
  
              // Match positions of the icons in the folder with their positions in the preview
              rule.computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, mTmpParams);
              // The PreviewLayoutRule assumes that the icon size takes up the entire width so we
              // offset by the actual size.
              int iconOffsetX = (int) ((btvLp.width - btv.getIconSize()) * iconScale) / 2;
  
              final int previewPosX =
                      (int) ((mTmpParams.transX - iconOffsetX + previewItemOffsetX) / folderScale);
              final int previewPosY = (int) ((mTmpParams.transY + previewItemOffsetY) / folderScale);
  
              final float xDistance = previewPosX - btvLp.x;
              final float yDistance = previewPosY - btvLp.y;
  
              Animator translationX = getAnimator(btv, View.TRANSLATION_X, xDistance, 0f);
              translationX.setInterpolator(previewItemInterpolator);
              play(animatorSet, translationX);
  
              Animator translationY = getAnimator(btv, View.TRANSLATION_Y, yDistance, 0f);
              translationY.setInterpolator(previewItemInterpolator);
              play(animatorSet, translationY);
  
              Animator scaleAnimator = getAnimator(btv, SCALE_PROPERTY, initialScale, finalScale);
              scaleAnimator.setInterpolator(previewItemInterpolator);
              play(animatorSet, scaleAnimator);
  
              if (mFolder.getItemCount() > FolderIcon.NUM_ITEMS_IN_PREVIEW) {
                  // These delays allows the preview items to move as part of the Folder's motion,
                  // and its only necessary for large folders because of differing interpolators.
                  int delay = mIsOpening ? mDelay : mDelay * 2;
                  if (mIsOpening) {
                      translationX.setStartDelay(delay);
                      translationY.setStartDelay(delay);
                      scaleAnimator.setStartDelay(delay);
                  }
                  translationX.setDuration(translationX.getDuration() - delay);
                  translationY.setDuration(translationY.getDuration() - delay);
                  scaleAnimator.setDuration(scaleAnimator.getDuration() - delay);
              }
  
              animatorSet.addListener(new AnimatorListenerAdapter() {
                  @Override
                  public void onAnimationStart(Animator animation) {
                      super.onAnimationStart(animation);
                      // Necessary to initialize values here because of the start delay.
                      if (mIsOpening) {
                          btv.setTranslationX(xDistance);
                          btv.setTranslationY(yDistance);
                          btv.setScaleX(initialScale);
                          btv.setScaleY(initialScale);
                      }
                  }
  
                  @Override
                  public void onAnimationEnd(Animator animation) {
                      super.onAnimationEnd(animation);
                      btv.setTranslationX(0.0f);
                      btv.setTranslationY(0.0f);
                      btv.setScaleX(1f);
                      btv.setScaleY(1f);
                  }
              });
          }
      }
  
      private void play(AnimatorSet as, Animator a) {
          play(as, a, a.getStartDelay(), mDuration);
      }
  
      private void play(AnimatorSet as, Animator a, long startDelay, int duration) {
          a.setStartDelay(startDelay);
          a.setDuration(duration);
          as.play(a);
      }
  
      private TimeInterpolator getPreviewItemInterpolator() {
          if (mFolder.getItemCount() > FolderIcon.NUM_ITEMS_IN_PREVIEW) {
              // With larger folders, we want the preview items to reach their final positions faster
              // (when opening) and later (when closing) so that they appear aligned with the rest of
              // the folder items when they are both visible.
              return mIsOpening
                      ? mLargeFolderPreviewItemOpenInterpolator
                      : mLargeFolderPreviewItemCloseInterpolator;
          }
          return mFolderInterpolator;
      }
  
      private Animator getAnimator(View view, Property property, float v1, float v2) {
          return mIsOpening
                  ? ObjectAnimator.ofFloat(view, property, v1, v2)
                  : ObjectAnimator.ofFloat(view, property, v2, v1);
      }
  
      private Animator getAnimator(GradientDrawable drawable, String property, int v1, int v2) {
          return mIsOpening
                  ? ObjectAnimator.ofArgb(drawable, property, v1, v2)
                  : ObjectAnimator.ofArgb(drawable, property, v2, v1);
      }
  }
  