Responsive caret drawable

Change-Id: I9d40052d001c80d99db511af6134227f8e4e4239
diff --git a/proguard.flags b/proguard.flags
index 19d2f0c..c5e9db1 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -66,6 +66,11 @@
   public void setAnimationProgress(float);
 }
 
+-keep class com.android.launcher3.pageindicators.CaretDrawable {
+  public float getCaretProgress();
+  public void setCaretProgress(float);
+}
+
 -keep class com.android.launcher3.Workspace {
   public float getBackgroundAlpha();
   public void setBackgroundAlpha(float);
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index b0358e8..b6e2e4c 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -705,7 +705,7 @@
         if (!animated || !initialized) {
             if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
                     fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
-                mAllAppsController.finishPullDown(false);
+                mAllAppsController.finishPullDown();
             }
             fromView.setVisibility(View.GONE);
             dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 0e9cac8..40244b2 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -59,6 +59,8 @@
     private ObjectAnimator mCaretAnimator;
     private final long mCaretAnimationDuration;
     private final Interpolator mCaretInterpolator;
+    private CaretDrawable mCaretDrawable;
+    private float mLastCaretProgress;
 
     private float mStatusBarHeight;
 
@@ -76,6 +78,8 @@
     private float mShiftRange;      // changes depending on the orientation
     private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
 
+    private float mVelocityForCaret;
+
     private static final float DEFAULT_SHIFT_RANGE = 10;
 
     private static final float RECATCH_REJECTION_FRACTION = .0875f;
@@ -203,8 +207,12 @@
         if (mAppsView == null) {
             return false;   // early termination.
         }
+
+        mVelocityForCaret = velocity;
+
         float shift = Math.min(Math.max(0, mShiftStart + displacement), mShiftRange);
         setProgress(shift / mShiftRange);
+
         return true;
     }
 
@@ -329,6 +337,7 @@
         mWorkspace.setWorkspaceYTranslationAndAlpha(
                 PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent),
                 interpolation);
+        updateCaret(progress);
         updateLightStatusBar(shiftCurrent);
     }
 
@@ -352,6 +361,7 @@
             return;
         }
         if (mDetector.isIdleState()) {
+            mVelocityForCaret = -VerticalPullDetector.RELEASE_VELOCITY_PX_MS;
             preparePull(true);
             mAnimationDuration = duration;
             mShiftStart = mAppsView.getTranslationY();
@@ -404,7 +414,7 @@
 
             @Override
             public void onAnimationEnd(Animator animator) {
-                finishPullDown(false);
+                finishPullDown();
                 mDiscoBounceAnimation = null;
                 mIsTranslateWithoutWorkspace = false;
             }
@@ -424,6 +434,7 @@
         }
         Interpolator interpolator;
         if (mDetector.isIdleState()) {
+            mVelocityForCaret = VerticalPullDetector.RELEASE_VELOCITY_PX_MS;
             preparePull(true);
             mAnimationDuration = duration;
             mShiftStart = mAppsView.getTranslationY();
@@ -452,7 +463,7 @@
                 if (canceled) {
                     return;
                 } else {
-                    finishPullDown(true);
+                    finishPullDown();
                     cleanUpAnimation();
                     mDetector.finishedScrolling();
                 }
@@ -464,21 +475,14 @@
     public void finishPullUp() {
         mHotseat.setVisibility(View.INVISIBLE);
         setProgress(0f);
-        animateCaret();
     }
 
-    public void finishPullDown(boolean animated) {
+    public void finishPullDown() {
         mAppsView.setVisibility(View.INVISIBLE);
         mHotseat.setBackgroundTransparent(false /* transparent */);
         mHotseat.setVisibility(View.VISIBLE);
         mAppsView.reset();
         setProgress(1f);
-        if (animated) {
-            animateCaret();
-        } else {
-            mWorkspace.getPageIndicator().getCaretDrawable()
-                    .setLevel(CaretDrawable.LEVEL_CARET_POINTING_UP);
-        }
     }
 
     private void cancelAnimation() {
@@ -501,17 +505,41 @@
         mCurrentAnimation = null;
     }
 
-    private void animateCaret() {
+    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(
+                    mVelocityForCaret / 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
+            mCaretAnimator.cancel(); // Stop the animator in its tracks
         }
 
-        if (mLauncher.isAllAppsVisible()) {
-            mCaretAnimator.setIntValues(CaretDrawable.LEVEL_CARET_POINTING_DOWN);
-        } else {
-            mCaretAnimator.setIntValues(CaretDrawable.LEVEL_CARET_POINTING_UP);
-        }
-
+        // Update the progress and start the animation
+        mLastCaretProgress = newCaretProgress;
+        mCaretAnimator.setFloatValues(newCaretProgress);
         mCaretAnimator.start();
     }
 
@@ -519,12 +547,14 @@
         mAppsView = appsView;
         mHotseat = hotseat;
         mWorkspace = workspace;
-        mCaretAnimator = ObjectAnimator.ofInt(mWorkspace.getPageIndicator().getCaretDrawable(),
-                "level", CaretDrawable.LEVEL_CARET_POINTING_UP); // we will set values later
-        mCaretAnimator.setDuration(mCaretAnimationDuration);
-        mCaretAnimator.setInterpolator(mCaretInterpolator);
+        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);
     }
 
     @Override
@@ -537,6 +567,4 @@
         }
         setProgress(mProgress);
     }
-
-
 }
diff --git a/src/com/android/launcher3/allapps/VerticalPullDetector.java b/src/com/android/launcher3/allapps/VerticalPullDetector.java
index 8bb845a..6eab1c0 100644
--- a/src/com/android/launcher3/allapps/VerticalPullDetector.java
+++ b/src/com/android/launcher3/allapps/VerticalPullDetector.java
@@ -26,7 +26,7 @@
     /**
      * The minimum release velocity in pixels per millisecond that triggers fling..
      */
-    private static final float RELEASE_VELOCITY_PX_MS = 1.0f;
+    public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
 
     /**
      * The time constant used to calculate dampening in the low-pass filter of scroll velocity.
@@ -87,6 +87,10 @@
         return mState == ScrollState.SETTLING;
     }
 
+    public boolean isDraggingState() {
+        return mState == ScrollState.DRAGGING;
+    }
+
     private float mDownX;
     private float mDownY;
     private float mDownMillis;
diff --git a/src/com/android/launcher3/pageindicators/CaretDrawable.java b/src/com/android/launcher3/pageindicators/CaretDrawable.java
index fcf3d23..1451773 100644
--- a/src/com/android/launcher3/pageindicators/CaretDrawable.java
+++ b/src/com/android/launcher3/pageindicators/CaretDrawable.java
@@ -28,11 +28,11 @@
 import android.graphics.drawable.Drawable;
 
 public class CaretDrawable extends Drawable {
-    public static final int LEVEL_CARET_POINTING_UP = 0; // minimum possible level value
-    public static final int LEVEL_CARET_POINTING_DOWN = 10000; // maximum possible level value
-    public static final int LEVEL_CARET_NEUTRAL = LEVEL_CARET_POINTING_DOWN / 2;
+    public static final float PROGRESS_CARET_POINTING_UP = -1f;
+    public static final float PROGRESS_CARET_POINTING_DOWN = 1f;
+    public static final float PROGRESS_CARET_NEUTRAL = 0;
 
-    private float mCaretProgress;
+    private float mCaretProgress = PROGRESS_CARET_NEUTRAL;
 
     private Paint mShadowPaint = new Paint();
     private Paint mCaretPaint = new Paint();
@@ -72,23 +72,48 @@
         final float left = getBounds().left + (mShadowPaint.getStrokeWidth() / 2);
         final float top = getBounds().top + (mShadowPaint.getStrokeWidth() / 2);
 
+        // When the bounds are square, this will result in a caret with a right angle
         final float verticalInset = (height / 4);
         final float caretHeight = (height - (verticalInset * 2));
 
         mPath.reset();
-        mPath.moveTo(left, top + caretHeight * (1 - mCaretProgress));
-        mPath.lineTo(left + (width / 2), top + caretHeight * mCaretProgress);
-        mPath.lineTo(left + width, top + caretHeight * (1 - mCaretProgress));
+        mPath.moveTo(left, top + caretHeight * (1 - getNormalizedCaretProgress()));
+        mPath.lineTo(left + (width / 2), top + caretHeight * getNormalizedCaretProgress());
+        mPath.lineTo(left + width, top + caretHeight * (1 - getNormalizedCaretProgress()));
 
         canvas.drawPath(mPath, mShadowPaint);
         canvas.drawPath(mPath, mCaretPaint);
     }
 
-    @Override
-    protected boolean onLevelChange(int level) {
-        mCaretProgress = (float) level / (float) LEVEL_CARET_POINTING_DOWN;
+    /**
+     * Sets the caret progress
+     *
+     * @param progress The progress ({@value #PROGRESS_CARET_POINTING_UP} for pointing up,
+     * {@value #PROGRESS_CARET_POINTING_DOWN} for pointing down, {@value #PROGRESS_CARET_NEUTRAL}
+     * for neutral)
+     */
+    public void setCaretProgress(float progress) {
+        mCaretProgress = progress;
         invalidateSelf();
-        return true;
+    }
+
+    /**
+     * Returns the caret progress
+     *
+     * @return The progress
+     */
+    public float getCaretProgress() {
+        return mCaretProgress;
+    }
+
+    /**
+     * Returns the caret progress normalized to [0..1]
+     *
+     * @return The normalized progress
+     */
+    public float getNormalizedCaretProgress() {
+        return (mCaretProgress - PROGRESS_CARET_POINTING_UP) /
+                (PROGRESS_CARET_POINTING_DOWN - PROGRESS_CARET_POINTING_UP);
     }
 
     @Override