content: package com.android.launcher3.allapps;
  
  import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
  import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
  import static com.android.launcher3.LauncherState.OVERVIEW;
  import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
  import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
  import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
  import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
  import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
  import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
  import static com.android.launcher3.anim.Interpolators.LINEAR;
  import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
  import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
  
  import android.animation.Animator;
  import android.animation.AnimatorListenerAdapter;
  import android.util.FloatProperty;
  import android.view.animation.Interpolator;
  
  import com.android.launcher3.DeviceProfile;
  import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
  import com.android.launcher3.Launcher;
  import com.android.launcher3.LauncherState;
  import com.android.launcher3.LauncherStateManager.AnimationConfig;
  import com.android.launcher3.LauncherStateManager.StateHandler;
  import com.android.launcher3.R;
  import com.android.launcher3.anim.AnimationSuccessListener;
  import com.android.launcher3.anim.AnimatorSetBuilder;
  import com.android.launcher3.anim.PropertySetter;
  import com.android.launcher3.anim.SpringObjectAnimator;
  import com.android.launcher3.util.Themes;
  import com.android.launcher3.views.ScrimView;
  
  /**
   * Handles AllApps view transition.
   * 1) Slides all apps view using direct manipulation
   * 2) When finger is released, animate to either top or bottom accordingly.
   * <p/>
   * Algorithm:
   * If release velocity > THRES1, snap according to the direction of movement.
   * If release velocity < THRES1, snap according to either top or bottom depending on whether it's
   * closer to top or closer to the page indicator.
   */
  public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener {
  
      private static final float SPRING_DAMPING_RATIO = 0.9f;
      private static final float SPRING_STIFFNESS = 600f;
  
      public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
              new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
  
          @Override
          public Float get(AllAppsTransitionController controller) {
              return controller.mProgress;
          }
  
          @Override
          public void setValue(AllAppsTransitionController controller, float progress) {
              controller.setProgress(progress);
          }
      };
  
      private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 0;
  
      private AllAppsContainerView mAppsView;
      private ScrimView mScrimView;
  
      private final Launcher mLauncher;
      private final boolean mIsDarkTheme;
      private boolean mIsVerticalLayout;
  
      // Animation in this class is controlled by a single variable {@link mProgress}.
      // Visually, it represents top y coordinate of the all apps container if multiplied with
      // {@link mShiftRange}.
  
      // When {@link mProgress} is 0, all apps container is pulled up.
      // When {@link mProgress} is 1, all apps container is pulled down.
      private float mShiftRange;      // changes depending on the orientation
      private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
  
      private float mScrollRangeDelta = 0;
  
      public AllAppsTransitionController(Launcher l) {
          mLauncher = l;
          mShiftRange = mLauncher.getDeviceProfile().heightPx;
          mProgress = 1f;
  
          mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
          mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout();
          mLauncher.addOnDeviceProfileChangeListener(this);
      }
  
      public float getShiftRange() {
          return mShiftRange;
      }
  
      @Override
      public void onDeviceProfileChanged(DeviceProfile dp) {
          mIsVerticalLayout = dp.isVerticalBarLayout();
          setScrollRangeDelta(mScrollRangeDelta);
  
          if (mIsVerticalLayout) {
              mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(1);
              mLauncher.getHotseat().setTranslationY(0);
              mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
          }
      }
  
      /**
       * Note this method should not be called outside this class. This is public because it is used
       * in xml-based animations which also handle updating the appropriate UI.
       *
       * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
       *
       * @see #setState(LauncherState)
       * @see #setStateWithAnimation(LauncherState, AnimatorSetBuilder, AnimationConfig)
       */
      public void setProgress(float progress) {
          mProgress = progress;
          mScrimView.setProgress(progress);
          float shiftCurrent = progress * mShiftRange;
  
          mAppsView.setTranslationY(shiftCurrent);
  
          // Use a light system UI (dark icons) if all apps is behind at least half of the
          // status bar.
          boolean forceChange = shiftCurrent - mScrimView.getDragHandleSize()
                  <= mLauncher.getDeviceProfile().getInsets().top / 2;
          if (forceChange) {
              mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, !mIsDarkTheme);
          } else {
              mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
          }
      }
  
      public float getProgress() {
          return mProgress;
      }
  
      /**
       * Sets the vertical transition progress to {@param state} and updates all the dependent UI
       * accordingly.
       */
      @Override
      public void setState(LauncherState state) {
          setProgress(state.getVerticalProgress(mLauncher));
          setAlphas(state, null, new AnimatorSetBuilder());
          onProgressAnimationEnd();
      }
  
      /**
       * Creates an animation which updates the vertical transition progress and updates all the
       * dependent UI using various animation events
       */
      @Override
      public void setStateWithAnimation(LauncherState toState,
              AnimatorSetBuilder builder, AnimationConfig config) {
          float targetProgress = toState.getVerticalProgress(mLauncher);
          if (Float.compare(mProgress, targetProgress) == 0) {
              setAlphas(toState, config, builder);
              // Fail fast
              onProgressAnimationEnd();
              return;
          }
  
          if (!config.playNonAtomicComponent()) {
              // There is no atomic component for the all apps transition, so just return early.
              return;
          }
  
          Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW
                  ? builder.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN)
                  : FAST_OUT_SLOW_IN;
          Animator anim = createSpringAnimation(mProgress, targetProgress);
          anim.setDuration(config.duration);
          anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
          anim.addListener(getProgressAnimatorListener());
  
          builder.play(anim);
  
          setAlphas(toState, config, builder);
      }
  
      public Animator createSpringAnimation(float... progressValues) {
          return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange,
                  SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
      }
  
      private void setAlphas(LauncherState toState, AnimationConfig config,
              AnimatorSetBuilder builder) {
          setAlphas(toState.getVisibleElements(mLauncher), config, builder);
      }
  
      public void setAlphas(int visibleElements, AnimationConfig config, AnimatorSetBuilder builder) {
          PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER
                  : config.getPropertySetter(builder);
          boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
          boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
  
          Interpolator allAppsFade = builder.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
          Interpolator headerFade = builder.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
          setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
          setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
          mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasAllAppsContent,
                  setter, headerFade, allAppsFade);
          mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
  
          setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
                  (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade);
      }
  
      public AnimatorListenerAdapter getProgressAnimatorListener() {
          return new AnimationSuccessListener() {
              @Override
              public void onAnimationSuccess(Animator animator) {
                  onProgressAnimationEnd();
              }
          };
      }
  
      public void setupViews(AllAppsContainerView appsView) {
          mAppsView = appsView;
          mScrimView = mLauncher.findViewById(R.id.scrim_view);
      }
  
      /**
       * Updates the total scroll range but does not update the UI.
       */
      void setScrollRangeDelta(float delta) {
          mScrollRangeDelta = delta;
          mShiftRange = mLauncher.getDeviceProfile().heightPx - mScrollRangeDelta;
  
          if (mScrimView != null) {
              mScrimView.reInitUi();
          }
      }
  
      /**
       * Set the final view states based on the progress.
       * TODO: This logic should go in {@link LauncherState}
       */
      private void onProgressAnimationEnd() {
          if (Float.compare(mProgress, 1f) == 0) {
              mAppsView.reset(false /* animate */);
          } else if (isAllAppsExpanded()) {
              mAppsView.onScrollUpEnd();
          }
      }
  
      private boolean isAllAppsExpanded() {
          return Float.compare(mProgress, 0f) == 0;
      }
  
      public void highlightWorkTabIfNecessary() {
          if (isAllAppsExpanded()) {
              mAppsView.highlightWorkTabIfNecessary();
          }
      }
  }
  