Merge "Fix bug where Workspace page alpha stays 0 in SpringLoaded mode." into ub-launcher3-edmonton
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index cd404d6..065663d 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -90,6 +90,7 @@
   TASKSWITCHER = 12; // Recents UI Container (QuickStep)
   APP = 13; // Foreground activity is another app (QuickStep)
   TIP = 14; // Onboarding texts (QuickStep)
+  SIDELOADED_LAUNCHER = 15;
 }
 
 // Used to define what type of control a Target would represent.
@@ -148,11 +149,12 @@
   enum Command {
     HOME_INTENT = 0;
     BACK = 1;
-    ENTRY = 2;    // Indicates entry to one of Launcher container type target
-                  // not using the HOME_INTENT
-    CANCEL = 3;   // Indicates that a confirmation screen was cancelled
-    CONFIRM = 4;  // Indicates thata confirmation screen was accepted
-    STOP = 5;     // Indicates onStop() was called (screen time out, power off)
+    ENTRY = 2;          // Indicates entry to one of Launcher container type target
+                        // not using the HOME_INTENT
+    CANCEL = 3;         // Indicates that a confirmation screen was cancelled
+    CONFIRM = 4;        // Indicates thata confirmation screen was accepted
+    STOP = 5;           // Indicates onStop() was called (screen time out, power off)
+    RECENTS_BUTTON = 6; // Indicates that Recents button was pressed
   }
 
   optional Type type = 1;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index dc3f79c..a405735 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -21,16 +21,17 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.FlingBlockCheck;
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
@@ -46,8 +47,6 @@
 
     private static final String TAG = "OverviewSwipeController";
 
-    private static final float ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS = 0.1f;
-
     // Progress after which the transition is assumed to be a success in case user does not fling
     private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
 
@@ -65,6 +64,7 @@
     private float mDisplacementShift;
     private float mProgressMultiplier;
     private float mEndDisplacement;
+    private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
 
     private TaskView mTaskBeingDragged;
 
@@ -93,7 +93,6 @@
     @Override
     public void onAnimationCancel(Animator animation) {
         if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
-            Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
             clearState();
         }
     }
@@ -158,16 +157,16 @@
         return mDetector.onTouchEvent(ev);
     }
 
-    private boolean reInitAnimationController(boolean goingUp) {
+    private void reInitAnimationController(boolean goingUp) {
         if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) {
             // No need to init
-            return false;
+            return;
         }
         int scrollDirections = mDetector.getScrollDirections();
         if (goingUp && ((scrollDirections & SwipeDetector.DIRECTION_POSITIVE) == 0)
                 || !goingUp && ((scrollDirections & SwipeDetector.DIRECTION_NEGATIVE) == 0)) {
             // Trying to re-init in an unsupported direction.
-            return false;
+            return;
         }
         if (mCurrentAnimation != null) {
             mCurrentAnimation.setPlayFraction(0);
@@ -205,7 +204,6 @@
         mCurrentAnimation.getTarget().addListener(this);
         mCurrentAnimation.dispatchOnStart();
         mProgressMultiplier = 1 / mEndDisplacement;
-        return true;
     }
 
     @Override
@@ -217,6 +215,7 @@
             mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
             mCurrentAnimation.pause();
         }
+        mFlingBlockCheck.unblockFling();
     }
 
     @Override
@@ -226,6 +225,9 @@
                 totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
         if (isGoingUp != mCurrentAnimationIsGoingUp) {
             reInitAnimationController(isGoingUp);
+            mFlingBlockCheck.blockFling();
+        } else {
+            mFlingBlockCheck.onEvent();
         }
         mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
         return true;
@@ -235,22 +237,14 @@
     public void onDragEnd(float velocity, boolean fling) {
         final boolean goingToEnd;
         final int logAction;
+        boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
+        if (blockedFling) {
+            fling = false;
+        }
         if (fling) {
             logAction = Touch.FLING;
             boolean goingUp = velocity < 0;
-            if (goingUp != mCurrentAnimationIsGoingUp) {
-                // In case the fling is in opposite direction, make sure if is close enough
-                // from the start position
-                if (mCurrentAnimation.getProgressFraction()
-                        >= ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS) {
-                    // Not allowed
-                    goingToEnd = false;
-                } else {
-                    goingToEnd = reInitAnimationController(goingUp);
-                }
-            } else {
-                goingToEnd = true;
-            }
+            goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
         } else {
             logAction = Touch.SWIPE;
             goingToEnd = mCurrentAnimation.getProgressFraction() > SUCCESS_TRANSITION_PROGRESS;
@@ -259,6 +253,9 @@
         float progress = mCurrentAnimation.getProgressFraction();
         long animationDuration = SwipeDetector.calculateDuration(
                 velocity, goingToEnd ? (1 - progress) : progress);
+        if (blockedFling && !goingToEnd) {
+            animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
+        }
 
         float nextFrameProgress = Utilities.boundToRange(
                 progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index f9dcee0..ae0affe 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -48,6 +48,7 @@
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RemoteAnimationProvider;
@@ -99,7 +100,7 @@
     RecentsView getVisibleRecentsView();
 
     @UiThread
-    boolean switchToRecentsIfVisible();
+    boolean switchToRecentsIfVisible(boolean fromRecentsButton);
 
     Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target);
 
@@ -120,6 +121,11 @@
      */
     LongSwipeHelper getLongSwipeController(T activity, RemoteAnimationTargetSet targetSet);
 
+    /**
+     * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
+     */
+    int getContainerType();
+
     class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
 
         @Override
@@ -277,9 +283,15 @@
         }
 
         @Override
-        public boolean switchToRecentsIfVisible() {
+        public boolean switchToRecentsIfVisible(boolean fromRecentsButton) {
             Launcher launcher = getVisibleLaucher();
             if (launcher != null) {
+                if (fromRecentsButton) {
+                    launcher.getUserEventDispatcher().logActionCommand(
+                            LauncherLogProto.Action.Command.RECENTS_BUTTON,
+                            getContainerType(),
+                            LauncherLogProto.ContainerType.TASKSWITCHER);
+                }
                 launcher.getStateManager().goToState(OVERVIEW);
                 return true;
             }
@@ -319,6 +331,13 @@
         public AlphaProperty getAlphaProperty(Launcher activity) {
             return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
         }
+
+        @Override
+        public int getContainerType() {
+            final Launcher launcher = getVisibleLaucher();
+            return launcher != null ? launcher.getStateManager().getState().containerType
+                    : LauncherLogProto.ContainerType.APP;
+        }
     }
 
     class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> {
@@ -457,7 +476,7 @@
         }
 
         @Override
-        public boolean switchToRecentsIfVisible() {
+        public boolean switchToRecentsIfVisible(boolean fromRecentsButton) {
             return false;
         }
 
@@ -495,6 +514,10 @@
             return activity.getDragLayer().getAlphaProperty(0);
         }
 
+        @Override
+        public int getContainerType() {
+            return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
+        }
     }
 
     interface LayoutListener {
diff --git a/quickstep/src/com/android/quickstep/LongSwipeHelper.java b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
index edc7457..fbcde8b 100644
--- a/quickstep/src/com/android/quickstep/LongSwipeHelper.java
+++ b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
@@ -26,6 +27,7 @@
 import android.view.Surface;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
@@ -33,7 +35,9 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.FlingBlockCheck;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TransactionCompat;
 
@@ -44,7 +48,6 @@
  */
 public class LongSwipeHelper {
 
-    private static final float MIN_PROGRESS_TO_ALL_APPS = 0.35f;
     private static final float SWIPE_DURATION_MULTIPLIER =
             Math.min(1 / MIN_PROGRESS_TO_ALL_APPS, 1 / (1 - MIN_PROGRESS_TO_ALL_APPS));
 
@@ -53,6 +56,7 @@
 
     private float mMaxSwipeDistance = 1;
     private AnimatorPlaybackController mAnimator;
+    private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
 
     LongSwipeHelper(Launcher launcher, RemoteAnimationTargetSet targetSet) {
         mLauncher = launcher;
@@ -62,6 +66,7 @@
 
     private void init() {
         setTargetAlpha(0, true);
+        mFlingBlockCheck.blockFling();
 
         // Init animations
         AllAppsTransitionController controller = mLauncher.getAllAppsController();
@@ -74,6 +79,7 @@
 
     public void onMove(float displacement) {
         mAnimator.setPlayFraction(displacement / mMaxSwipeDistance);
+        mFlingBlockCheck.onEvent();
     }
 
     public void destroy() {
@@ -90,6 +96,11 @@
         final boolean toAllApps;
         float endProgress;
 
+        boolean blockedFling = isFling && mFlingBlockCheck.isBlocked();
+        if (blockedFling) {
+            isFling = false;
+        }
+
         if (!isFling) {
             toAllApps = currentFraction > MIN_PROGRESS_TO_ALL_APPS;
             endProgress = toAllApps ? 1 : 0;
@@ -114,7 +125,11 @@
             }
         }
 
-        mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, isFling, callback));
+        if (blockedFling && !toAllApps) {
+            duration *= LauncherAnimUtils.blockedFlingDurationFactor(0);
+        }
+        final boolean finalIsFling = isFling;
+        mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback));
         ValueAnimator animator = mAnimator.getAnimationPlayer();
         animator.setDuration(duration).setInterpolator(DEACCEL);
         animator.setFloatValues(currentFraction, endProgress);
@@ -150,6 +165,7 @@
         mLauncher.getStateManager().goToState(toAllApps ? ALL_APPS : OVERVIEW, false);
         if (!toAllApps) {
             DiscoveryBounce.showForOverviewIfNeeded(mLauncher);
+            mLauncher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(true);
         }
 
         mLauncher.getUserEventDispatcher().logStateChangeAction(
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 81a73fc..7b29323 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -52,6 +52,8 @@
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.ActivityControlHelper.AnimationFactory;
 import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
@@ -224,6 +226,7 @@
         private T mActivity;
         private RecentsView mRecentsView;
         private final long mToggleClickedTime = SystemClock.uptimeMillis();
+        private boolean mUserEventLogged;
 
         public RecentsActivityCommand() {
             mHelper = getActivityControlHelper();
@@ -241,7 +244,7 @@
 
             if (!handleCommand(elapsedTime)) {
                 // Start overview
-                if (!mHelper.switchToRecentsIfVisible()) {
+                if (!mHelper.switchToRecentsIfVisible(true)) {
                     mListener = mHelper.createActivityInitListener(this::onActivityReady);
                     mListener.registerAndStartActivity(overviewIntent, this::createWindowAnimation,
                             mContext, mMainThreadExecutor.getHandler(), RECENTS_LAUNCH_DURATION);
@@ -284,6 +287,11 @@
             mActivity = activity;
             mRecentsView = mActivity.getOverviewPanel();
             mRecentsView.setRunningTaskIconScaledDown(true /* isScaledDown */, false /* animate */);
+            if (!mUserEventLogged) {
+                activity.getUserEventDispatcher().logActionCommand(Action.Command.RECENTS_BUTTON,
+                        mHelper.getContainerType(), ContainerType.TASKSWITCHER);
+                mUserEventLogged = true;
+            }
             return false;
         }
 
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index 5ddd904..28b06fb 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -133,7 +133,7 @@
      */
     private void breakOutOfQuickScrub() {
         if (mRecentsView.getChildCount() == 0 || mActivityControlHelper == null
-                || !mActivityControlHelper.switchToRecentsIfVisible()) {
+                || !mActivityControlHelper.switchToRecentsIfVisible(false)) {
             mActivity.onBackPressed();
         }
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 68432ab..01e5cba 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -345,14 +345,22 @@
 
     private float calculateClearAllButtonAlpha() {
         final int childCount = getChildCount();
-        if (mShowEmptyMessage || childCount == 0) return 0;
+        if (mShowEmptyMessage || childCount == 0 || mPageScrolls == null
+                || childCount != mPageScrolls.length) {
+            return 0;
+        }
 
         final int scrollEnd = getScrollEnd();
         final int oldestChildScroll = getScrollForPage(childCount - 1);
 
-        return Utilities.boundToRange(
-                ((float) (getScrollX() - oldestChildScroll)) /
-                        (scrollEnd - oldestChildScroll), 0, 1);
+        final int clearAllButtonMotionRange = scrollEnd - oldestChildScroll;
+        if (clearAllButtonMotionRange == 0) return 0;
+
+        final float alphaUnbound = ((float) (getScrollX() - oldestChildScroll)) /
+                clearAllButtonMotionRange;
+        if (alphaUnbound > 1) return 0;
+
+        return Math.max(alphaUnbound, 0);
     }
 
     private void updateClearAllButtonAlpha() {
@@ -418,7 +426,6 @@
         final int requiredChildCount = tasks.size();
         for (int i = getChildCount(); i < requiredChildCount; i++) {
             final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
-            taskView.setOnClickListener(this::onTaskClicked);
             addView(taskView);
         }
         while (getChildCount() > requiredChildCount) {
@@ -444,17 +451,6 @@
         onTaskStackUpdated();
     }
 
-    private void onTaskClicked(View v) {
-        TaskView taskView = (TaskView) v;
-        if (taskView.getTask() == null) {
-            return;
-        }
-        taskView.launchTask(true /* animate */);
-        mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
-                Touch.TAP, Direction.NONE, indexOfChild(taskView),
-                TaskUtils.getComponentKeyForTask(taskView.getTask().key));
-    }
-
     protected void onTaskStackUpdated() { }
 
     public void resetTaskVisuals() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 0ddeae7..82aa45a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -110,6 +110,15 @@
 
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+        setOnClickListener((view) -> {
+            if (getTask() == null) {
+                return;
+            }
+            launchTask(true /* animate */);
+            BaseActivity.fromContext(context).getUserEventDispatcher().logTaskLaunchOrDismiss(
+                    Touch.TAP, Direction.NONE, ((RecentsView) getParent()).indexOfChild(this),
+                    TaskUtils.getComponentKeyForTask(getTask().key));
+        });
         setOutlineProvider(new TaskOutlineProvider(getResources()));
     }
 
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 9869fdf..03ffded 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -39,6 +39,9 @@
     public static final int SPRING_LOADED_TRANSITION_MS = 150;
     public static final int SPRING_LOADED_EXIT_DELAY = 500;
 
+    // The progress of an animation to all apps must be at least this far along to snap to all apps.
+    public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f;
+
     static WeakHashMap<Animator, Object> sAnimators = new WeakHashMap<Animator, Object>();
     static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
         public void onAnimationStart(Animator animation) {
@@ -165,16 +168,8 @@
                 }
             };
 
-    public static final Property<View, Float> ELEVATION =
-            new Property<View, Float>(Float.class, "elevation") {
-                @Override
-                public Float get(View view) {
-                    return view.getElevation();
-                }
-
-                @Override
-                public void set(View view, Float elevation) {
-                    view.setElevation(elevation);
-                }
-            };
+    /** Increase the duration if we prevented the fling, as we are going against a high velocity. */
+    public static int blockedFlingDurationFactor(float velocity) {
+        return (int) Utilities.boundToRange(Math.abs(velocity) / 2, 2f, 6f);
+    }
 }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index eb816c5..a98867d 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -109,7 +109,7 @@
     private float mLastMotionXRemainder;
     private float mTotalMotionX;
 
-    private int[] mPageScrolls;
+    protected int[] mPageScrolls;
 
     protected final static int TOUCH_STATE_REST = 0;
     protected final static int TOUCH_STATE_SCROLLING = 1;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 113571a..0ae58db 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -128,7 +128,8 @@
 
         // Use a light system UI (dark icons) if all apps is behind at least half of the
         // status bar.
-        boolean forceChange = shiftCurrent <= mShiftRange / 4;
+        boolean forceChange = shiftCurrent - mScrimView.getDragHandleSize()
+                <= mLauncher.getDeviceProfile().getInsets().top / 2;
         if (forceChange) {
             mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, !mIsDarkTheme);
         } else {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 6e48c36..087549e 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.touch;
 
+import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -32,6 +33,7 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationComponents;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -43,6 +45,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.FlingBlockCheck;
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.TouchController;
 
@@ -80,7 +83,7 @@
     private float mProgressMultiplier;
     private float mDisplacementShift;
     private boolean mCanBlockFling;
-    private boolean mBlockFling;
+    private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
 
     private AnimatorSet mAtomicAnim;
     private boolean mPassedOverviewAtomicThreshold;
@@ -241,7 +244,7 @@
             mStartProgress = mCurrentAnimation.getProgressFraction();
         }
         mCanBlockFling = mFromState == NORMAL;
-        mBlockFling = false;
+        mFlingBlockCheck.unblockFling();
     }
 
     @Override
@@ -253,16 +256,19 @@
         if (progress <= 0) {
             if (reinitCurrentAnimation(false, isDragTowardPositive)) {
                 mDisplacementShift = displacement;
-                mBlockFling = mCanBlockFling;
+                if (mCanBlockFling) {
+                    mFlingBlockCheck.blockFling();
+                }
             }
         } else if (progress >= 1) {
             if (reinitCurrentAnimation(true, isDragTowardPositive)) {
                 mDisplacementShift = displacement;
-                mBlockFling = mCanBlockFling;
+                if (mCanBlockFling) {
+                    mFlingBlockCheck.blockFling();
+                }
             }
-        } else if (Math.abs(velocity) < SwipeDetector.RELEASE_VELOCITY_PX_MS) {
-            // We prevent flinging after passing a state, but allow it if the user pauses briefly.
-            mBlockFling = false;
+        } else {
+            mFlingBlockCheck.onEvent();
         }
 
         return true;
@@ -325,7 +331,7 @@
         final LauncherState targetState;
         final float progress = mCurrentAnimation.getProgressFraction();
 
-        boolean blockedFling = fling && mBlockFling;
+        boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
         if (blockedFling) {
             fling = false;
         }
@@ -338,14 +344,17 @@
             // snap to top or bottom using the release velocity
         } else {
             logAction = Touch.SWIPE;
-            targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
+            float successProgress = mToState == ALL_APPS
+                    ? MIN_PROGRESS_TO_ALL_APPS : SUCCESS_TRANSITION_PROGRESS;
+            targetState = (progress > successProgress) ? mToState : mFromState;
         }
 
         final float endProgress;
         final float startProgress;
         final long duration;
         // Increase the duration if we prevented the fling, as we are going against a high velocity.
-        final long durationMultiplier = blockedFling && targetState == mFromState ? 6 : 1;
+        final int durationMultiplier = blockedFling && targetState == mFromState
+                ? LauncherAnimUtils.blockedFlingDurationFactor(velocity) : 1;
 
         if (targetState == mToState) {
             endProgress = 1;
diff --git a/src/com/android/launcher3/util/FlingBlockCheck.java b/src/com/android/launcher3/util/FlingBlockCheck.java
new file mode 100644
index 0000000..f9575b9
--- /dev/null
+++ b/src/com/android/launcher3/util/FlingBlockCheck.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.util;
+
+import android.os.SystemClock;
+
+/**
+ * Determines whether a fling should be blocked. Currently we block flings when crossing thresholds
+ * to new states, and unblock after a short duration.
+ */
+public class FlingBlockCheck {
+    // Allow flinging to a new state after waiting this many milliseconds.
+    private static final long UNBLOCK_FLING_PAUSE_DURATION = 200;
+
+    private boolean mBlockFling;
+    private long mBlockFlingTime;
+
+    public void blockFling() {
+        mBlockFling = true;
+        mBlockFlingTime = SystemClock.uptimeMillis();
+    }
+
+    public void unblockFling() {
+        mBlockFling = false;
+        mBlockFlingTime = 0;
+    }
+
+    public void onEvent() {
+        // We prevent flinging after passing a state, but allow it if the user pauses briefly.
+        if (SystemClock.uptimeMillis() - mBlockFlingTime >= UNBLOCK_FLING_PAUSE_DURATION) {
+            mBlockFling = false;
+        }
+    }
+
+    public boolean isBlocked() {
+        return mBlockFling;
+    }
+}
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 49e96be..6bbce00 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -397,4 +397,8 @@
             return false;
         }
     }
+
+    public int getDragHandleSize() {
+        return mDragHandleSize;
+    }
 }