Simpler caret

Bug: 30527159
Change-Id: I674de149e613c7adb567a13a288baa9877d36112
diff --git a/res/interpolator/caret_animation_interpolator.xml b/res/interpolator/caret_animation_interpolator.xml
deleted file mode 100644
index 25af4bc..0000000
--- a/res/interpolator/caret_animation_interpolator.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<pathInterpolator
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:pathData="M 0.0,0.0 c 0.0001,0.0 0.0,1.0 1.0,1.0" />
diff --git a/src/com/android/launcher3/allapps/AllAppsCaretController.java b/src/com/android/launcher3/allapps/AllAppsCaretController.java
new file mode 100644
index 0000000..622322b
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsCaretController.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 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.allapps;
+
+import android.animation.ObjectAnimator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.pageindicators.CaretDrawable;
+
+public class AllAppsCaretController {
+    // Determines when the caret should flip. Should be accessed via getThreshold()
+    private static final float CARET_THRESHOLD = 0.015f;
+    private static final float CARET_THRESHOLD_LAND = 0.5f;
+    // The velocity at which the caret will peak (i.e. exhibit a 90 degree bend)
+    private static final float PEAK_VELOCITY = VerticalPullDetector.RELEASE_VELOCITY_PX_MS * .7f;
+
+    private Launcher mLauncher;
+
+    private ObjectAnimator mCaretAnimator;
+    private CaretDrawable mCaretDrawable;
+    private float mLastCaretProgress;
+    private boolean mThresholdCrossed;
+
+    public AllAppsCaretController(CaretDrawable caret, Launcher launcher) {
+        mLauncher = launcher;
+        mCaretDrawable = caret;
+
+        final long caretAnimationDuration = launcher.getResources().getInteger(
+                R.integer.config_caretAnimationDuration);
+        final Interpolator caretInterpolator = AnimationUtils.loadInterpolator(launcher,
+                android.R.interpolator.fast_out_slow_in);
+
+        // We will set values later
+        mCaretAnimator = ObjectAnimator.ofFloat(mCaretDrawable, "caretProgress", 0);
+        mCaretAnimator.setDuration(caretAnimationDuration);
+        mCaretAnimator.setInterpolator(caretInterpolator);
+    }
+
+    /**
+     * Updates the state of the caret based on the progress of the {@link AllAppsContainerView}, as
+     * defined by the {@link AllAppsTransitionController}. Uses the container's velocity to adjust
+     * angle of caret.
+     *
+     * @param containerProgress The progress of the container in the range [0..1]
+     * @param velocity The velocity of the container
+     * @param dragging {@code true} if the container is being dragged
+     */
+    public void updateCaret(float containerProgress, float velocity, boolean dragging) {
+        // If we're in portrait and the shift is not 0 or 1, adjust the caret based on velocity
+        if (getThreshold() < containerProgress && containerProgress < 1 - getThreshold() &&
+                !mLauncher.useVerticalBarLayout()) {
+            mThresholdCrossed = true;
+
+            // How fast are we moving as a percentage of the peak velocity?
+            final float pctOfFlingVelocity = Math.max(-1, Math.min(velocity / PEAK_VELOCITY, 1));
+
+            mCaretDrawable.setCaretProgress(pctOfFlingVelocity);
+
+            // Set the last caret progress to this progress to prevent animator cancellation
+            mLastCaretProgress = pctOfFlingVelocity;
+            // Animate to neutral. This is necessary so the caret doesn't "freeze" when the
+            // container stops moving (e.g., during a drag or when the threshold is reached).
+            animateCaretToProgress(CaretDrawable.PROGRESS_CARET_NEUTRAL);
+        } else if (!dragging) {
+            // Otherwise, if we're not dragging, match the caret to the appropriate state
+            if (containerProgress <= getThreshold()) { // All Apps is up
+                animateCaretToProgress(CaretDrawable.PROGRESS_CARET_POINTING_DOWN);
+            } else if (containerProgress >= 1 - getThreshold()) { // All Apps is down
+                animateCaretToProgress(CaretDrawable.PROGRESS_CARET_POINTING_UP);
+            }
+        }
+    }
+
+    private void animateCaretToProgress(float progress) {
+        // If the new progress is the same as the last progress we animated to, terminate early
+        if (Float.compare(mLastCaretProgress, progress) == 0) {
+            return;
+        }
+
+        if (mCaretAnimator.isRunning()) {
+            mCaretAnimator.cancel(); // Stop the animator in its tracks
+        }
+
+        // Update the progress and start the animation
+        mLastCaretProgress = progress;
+        mCaretAnimator.setFloatValues(progress);
+        mCaretAnimator.start();
+    }
+
+    private float getThreshold() {
+        // In landscape, just return the landscape threshold.
+        if (mLauncher.useVerticalBarLayout()) {
+            return CARET_THRESHOLD_LAND;
+        }
+
+        // Before the threshold is crossed, it is reported as zero. This makes the caret immediately
+        // responsive when a drag begins. After the threshold is crossed, we return the constant
+        // value. This means the caret will start its state-based adjustment sooner. That is, we
+        // won't have to wait until the panel is completely settled to begin animation.
+        return mThresholdCrossed ? CARET_THRESHOLD : 0f;
+    }
+
+    public void onDragStart() {
+        mThresholdCrossed = false;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index eb6c926..a8c0422 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -11,7 +11,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.AccelerateInterpolator;
-import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
@@ -22,7 +21,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.pageindicators.CaretDrawable;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.TouchController;
 
@@ -56,11 +54,7 @@
     private Hotseat mHotseat;
     private int mHotseatBackgroundColor;
 
-    private ObjectAnimator mCaretAnimator;
-    private final long mCaretAnimationDuration;
-    private final Interpolator mCaretInterpolator;
-    private CaretDrawable mCaretDrawable;
-    private float mLastCaretProgress;
+    private AllAppsCaretController mCaretController;
 
     private float mStatusBarHeight;
 
@@ -105,11 +99,6 @@
         mBezelSwipeUpHeight = l.getResources().getDimensionPixelSize(
                 R.dimen.all_apps_bezel_swipe_height);
 
-        mCaretAnimationDuration = l.getResources().getInteger(
-                R.integer.config_caretAnimationDuration);
-        mCaretInterpolator = AnimationUtils.loadInterpolator(l,
-                R.interpolator.caret_animation_interpolator);
-
         mEvaluator = new ArgbEvaluator();
         mAllAppsBackgroundColor = l.getColor(R.color.all_apps_container_color);
     }
@@ -196,6 +185,7 @@
 
     @Override
     public void onDragStart(boolean start) {
+        mCaretController.onDragStart();
         cancelAnimation();
         mCurrentAnimation = LauncherAnimUtils.createAnimatorSet();
         mShiftStart = mAppsView.getTranslationY();
@@ -336,19 +326,15 @@
             return;
         }
         mWorkspace.setWorkspaceYTranslationAndAlpha(
-                PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent),
-                interpolation);
-        updateCaret(progress);
-        updateLightStatusBar(shiftCurrent);
+                PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), interpolation);
 
         if (!mDetector.isDraggingState()) {
             mContainerVelocity = mDetector.computeVelocity(shiftCurrent - shiftPrevious,
                     System.currentTimeMillis());
         }
-    }
 
-    public float getContainerVelocity() {
-        return mContainerVelocity;
+        mCaretController.updateCaret(progress, mContainerVelocity, mDetector.isDraggingState());
+        updateLightStatusBar(shiftCurrent);
     }
 
     public float getProgress() {
@@ -513,56 +499,14 @@
         mCurrentAnimation = null;
     }
 
-    private void updateCaret(float shift) {
-        // Animate to a neutral state by default
-        float newCaretProgress = CaretDrawable.PROGRESS_CARET_NEUTRAL;
-
-        // If we're in portrait and the shift is not 0 or 1, adjust the caret based on velocity
-        if (0f < shift && shift < 1f && !mLauncher.useVerticalBarLayout()) {
-            // How fast are we moving as a percentage of the minimum fling velocity?
-            final float pctOfFlingVelocity = Math.max(-1, Math.min(
-                    mContainerVelocity / VerticalPullDetector.RELEASE_VELOCITY_PX_MS, 1));
-
-            mCaretDrawable.setCaretProgress(pctOfFlingVelocity);
-
-            // Set the last caret progress to this progress to prevent animator cancellation
-            mLastCaretProgress = pctOfFlingVelocity;
-        } else if (!mDetector.isDraggingState()) {
-            // Otherwise, if we're not dragging, match the caret to the appropriate state
-            if (Float.compare(shift, 0f) == 0) { // All Apps is up
-                newCaretProgress = CaretDrawable.PROGRESS_CARET_POINTING_DOWN;
-            } else if (Float.compare(shift, 1f) == 0) { // All Apps is down
-                newCaretProgress = CaretDrawable.PROGRESS_CARET_POINTING_UP;
-            }
-        }
-
-        // If the new progress is the same as the last progress we animated to, terminate early
-        if (Float.compare(mLastCaretProgress, newCaretProgress) == 0) {
-            return;
-        }
-
-        if (mCaretAnimator.isRunning()) {
-            mCaretAnimator.cancel(); // Stop the animator in its tracks
-        }
-
-        // Update the progress and start the animation
-        mLastCaretProgress = newCaretProgress;
-        mCaretAnimator.setFloatValues(newCaretProgress);
-        mCaretAnimator.start();
-    }
-
     public void setupViews(AllAppsContainerView appsView, Hotseat hotseat, Workspace workspace) {
         mAppsView = appsView;
         mHotseat = hotseat;
         mWorkspace = workspace;
-        mCaretDrawable = mWorkspace.getPageIndicator().getCaretDrawable();
         mHotseat.addOnLayoutChangeListener(this);
         mHotseat.bringToFront();
-
-        // we will set values later
-        mCaretAnimator = ObjectAnimator.ofFloat(mCaretDrawable, "caretProgress", 0);
-        mCaretAnimator.setDuration(mCaretAnimationDuration);
-        mCaretAnimator.setInterpolator(mCaretInterpolator);
+        mCaretController = new AllAppsCaretController(
+                mWorkspace.getPageIndicator().getCaretDrawable(), mLauncher);
     }
 
     @Override