Merge "Remove quick switch and improve quick scrub" into ub-launcher3-master
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 6e56055..e414fa0 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index 7c08ff6..2626e7c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -88,8 +88,10 @@
     }
 
     @Override
-    public float getOverviewTranslationFactor(Launcher launcher) {
-        return 0;
+    public float[] getOverviewTranslationFactor(Launcher launcher) {
+        // Keep the same translation as in overview, so that we don't slide around when
+        // transitioning to All Apps.
+        return LauncherState.OVERVIEW.getOverviewTranslationFactor(launcher);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
index 6543e8c..99bf264 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
@@ -28,10 +28,8 @@
     private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_DISABLE_RESTORE
             | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI | FLAG_HIDE_BACK_BUTTON;
 
-    private static final boolean DEBUG_DIFFERENT_UI = false;
-
     public FastOverviewState(int id) {
-        super(id, QuickScrubController.QUICK_SWITCH_START_DURATION, STATE_FLAGS);
+        super(id, QuickScrubController.QUICK_SCRUB_START_DURATION, STATE_FLAGS);
     }
 
     @Override
@@ -48,9 +46,11 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        if (DEBUG_DIFFERENT_UI) {
-            return NONE;
-        }
-        return super.getVisibleElements(launcher);
+        return NONE;
+    }
+
+    @Override
+    public float[] getOverviewTranslationFactor(Launcher launcher) {
+        return new float[] {0f, 0.5f};
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 68322b0..abbf45e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -58,8 +58,8 @@
     }
 
     @Override
-    public float getOverviewTranslationFactor(Launcher launcher) {
-        return 0;
+    public float[] getOverviewTranslationFactor(Launcher launcher) {
+        return new float[] {0f, 0f};
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index ae747d8..b993c3c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -20,7 +20,8 @@
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
-import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_FACTOR;
+import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_X_FACTOR;
+import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
 
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
@@ -50,7 +51,9 @@
     public void setState(LauncherState state) {
         mRecentsView.setAlpha(state.overviewUi ? 1 : 0);
         updateVisibility(mRecentsView, isAccessibilityEnabled(mLauncher));
-        mRecentsView.setTranslationFactor(state.getOverviewTranslationFactor(mLauncher));
+        float[] translationFactor = state.getOverviewTranslationFactor(mLauncher);
+        mRecentsView.setTranslationXFactor(translationFactor[0]);
+        mRecentsView.setTranslationYFactor(translationFactor[1]);
         if (state.overviewUi) {
             mRecentsView.resetTaskVisuals();
         }
@@ -73,8 +76,12 @@
         }
 
         PropertySetter setter = config.getProperSetter(builder);
-        setter.setFloat(mRecentsView, TRANSLATION_FACTOR,
-                toState.getOverviewTranslationFactor(mLauncher),
+        float[] translationFactor = toState.getOverviewTranslationFactor(mLauncher);
+        setter.setFloat(mRecentsView, TRANSLATION_X_FACTOR,
+                translationFactor[0],
+                builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
+        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR,
+                translationFactor[1],
                 builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
         setter.setViewAlpha(mRecentsView, toState.overviewUi ? 1 : 0, LINEAR);
 
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
index 72f8301..8e6e4c7 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -19,9 +19,7 @@
 import static android.view.MotionEvent.ACTION_MASK;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT;
-
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
-import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SWITCH;
 
 import android.annotation.TargetApi;
 import android.os.Build;
@@ -43,20 +41,18 @@
 
     private static final int ACTION_VIRTUAL = ACTION_MASK - 1;
 
-    private static final int ACTION_QUICK_SWITCH =
-            ACTION_VIRTUAL | (1 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_QUICK_SCRUB_START =
-            ACTION_VIRTUAL | (2 << ACTION_POINTER_INDEX_SHIFT);
+            ACTION_VIRTUAL | (1 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_QUICK_SCRUB_PROGRESS =
-            ACTION_VIRTUAL | (3 << ACTION_POINTER_INDEX_SHIFT);
+            ACTION_VIRTUAL | (2 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_QUICK_SCRUB_END =
-            ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
+            ACTION_VIRTUAL | (3 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_RESET =
-            ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
+            ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_DEFER_INIT =
-            ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
+            ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_SHOW_OVERVIEW_FROM_ALT_TAB =
-            ACTION_VIRTUAL | (7 << ACTION_POINTER_INDEX_SHIFT);
+            ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
 
     private final EventArray mEmptyArray = new EventArray();
     private final Object mExecutionLock = new Object();
@@ -145,9 +141,6 @@
                 MotionEvent event = array.get(i);
                 if (event.getActionMasked() == ACTION_VIRTUAL) {
                     switch (event.getAction()) {
-                        case ACTION_QUICK_SWITCH:
-                            mConsumer.updateTouchTracking(INTERACTION_QUICK_SWITCH);
-                            break;
                         case ACTION_QUICK_SCRUB_START:
                             mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
                             break;
@@ -195,10 +188,6 @@
         queueNoPreProcess(MotionEvent.obtain(0, 0, action, progress, 0, 0));
     }
 
-    public void onQuickSwitch() {
-        queueVirtualAction(ACTION_QUICK_SWITCH, 0);
-    }
-
     public void onQuickScrubStart() {
         queueVirtualAction(ACTION_QUICK_SCRUB_START, 0);
     }
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 93eafe4..4877abb 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -21,7 +21,6 @@
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
-
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
 
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index 986eb48..4f379e6 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -36,7 +36,7 @@
  */
 public class QuickScrubController implements OnAlarmListener {
 
-    public static final int QUICK_SWITCH_START_DURATION = 210;
+    public static final int QUICK_SCRUB_START_DURATION = 210;
 
     private static final boolean ENABLE_AUTO_ADVANCE = true;
     private static final int NUM_QUICK_SCRUB_SECTIONS = 3;
@@ -53,7 +53,6 @@
     private int mQuickScrubSection;
     private boolean mStartedFromHome;
     private boolean mHasAlarmRun;
-    private boolean mQuickSwitched;
     private boolean mFinishedTransitionToQuickScrub;
 
     public QuickScrubController(BaseActivity activity, RecentsView recentsView) {
@@ -70,7 +69,6 @@
         mStartedFromHome = startingFromHome;
         mQuickScrubSection = 0;
         mHasAlarmRun = false;
-        mQuickSwitched = false;
         mFinishedTransitionToQuickScrub = false;
 
         snapToNextTaskIfAvailable();
@@ -125,45 +123,21 @@
         }
     }
 
-    public void onQuickSwitch() {
-        mQuickSwitched = true;
-        quickSwitchIfReady();
-    }
-
     public void onFinishedTransitionToQuickScrub() {
         mFinishedTransitionToQuickScrub = true;
-        quickSwitchIfReady();
-    }
-
-    /**
-     * Immediately launches the current task (which we snapped to in onQuickScrubStart) if we've
-     * gotten the onQuickSwitch callback and the transition to quick scrub has completed.
-     */
-    private void quickSwitchIfReady() {
-        if (mQuickSwitched && mFinishedTransitionToQuickScrub) {
-            onQuickScrubEnd();
-            mActivity.getUserEventDispatcher().logActionOnControl(Touch.FLING,
-                    ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
-                            ContainerType.WORKSPACE : ContainerType.APP);
-        }
     }
 
     public void snapToNextTaskIfAvailable() {
         if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
             int toPage = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1;
-            goToPageWithHaptic(toPage, QUICK_SWITCH_START_DURATION);
+            mRecentsView.snapToPage(toPage, QUICK_SCRUB_START_DURATION);
         }
     }
 
     private void goToPageWithHaptic(int pageToGoTo) {
-        goToPageWithHaptic(pageToGoTo, -1);
-    }
-
-    private void goToPageWithHaptic(int pageToGoTo, int overrideDuration) {
         pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getPageCount() - 1);
         if (pageToGoTo != mRecentsView.getNextPage()) {
-            int duration = overrideDuration > -1 ? overrideDuration
-                    : Math.abs(pageToGoTo - mRecentsView.getNextPage())
+            int duration = Math.abs(pageToGoTo - mRecentsView.getNextPage())
                             * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
             mRecentsView.snapToPage(pageToGoTo, duration);
             mRecentsView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index 0554d9c..4e35159 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -29,21 +29,14 @@
 @FunctionalInterface
 public interface TouchConsumer extends Consumer<MotionEvent> {
 
-    static boolean isInteractionQuick(@InteractionType int interactionType) {
-        return interactionType == INTERACTION_QUICK_SCRUB ||
-                interactionType == INTERACTION_QUICK_SWITCH;
-    }
-
     @IntDef(flag = true, value = {
             INTERACTION_NORMAL,
-            INTERACTION_QUICK_SWITCH,
             INTERACTION_QUICK_SCRUB
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface InteractionType {}
     int INTERACTION_NORMAL = 0;
-    int INTERACTION_QUICK_SWITCH = 1;
-    int INTERACTION_QUICK_SCRUB = 2;
+    int INTERACTION_QUICK_SCRUB = 1;
 
     default void reset() { }
 
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index bbbeb14..cc49dc7 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -21,9 +21,9 @@
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
-
 import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -55,8 +55,6 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
 
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
-
 /**
  * Service connected by system-UI for handling touch interaction.
  */
@@ -112,12 +110,6 @@
         }
 
         @Override
-        public void onQuickSwitch() {
-            mEventQueue.onQuickSwitch();
-            TraceHelper.endSection("SysUiBinder", "onQuickSwitch");
-        }
-
-        @Override
         public void onQuickScrubStart() {
             mEventQueue.onQuickScrubStart();
             TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart");
@@ -160,7 +152,7 @@
         }
 
         @Override
-        public void onQuickStep(MotionEvent motionEvent) throws RemoteException {
+        public void onQuickStep(MotionEvent motionEvent) {
 
         }
     };
@@ -336,14 +328,10 @@
             if (mInvalidated) {
                 return;
             }
-            if (TouchConsumer.isInteractionQuick(interactionType)) {
+            if (interactionType == INTERACTION_QUICK_SCRUB) {
                 Runnable action = () -> {
-                    Runnable onComplete = null;
-                    if (interactionType == INTERACTION_QUICK_SWITCH) {
-                        onComplete = mQuickScrubController::onQuickSwitch;
-                    }
                     LauncherState fromState = mLauncher.getStateManager().getState();
-                    mLauncher.getStateManager().goToState(FAST_OVERVIEW, true, onComplete);
+                    mLauncher.getStateManager().goToState(FAST_OVERVIEW, true);
                     mQuickScrubController.onQuickScrubStart(fromState == NORMAL);
                 };
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index f0bb367..25f2f87 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -15,11 +15,11 @@
  */
 package com.android.quickstep;
 
-import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_START_DURATION;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
-import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SWITCH;
-import static com.android.quickstep.TouchConsumer.isInteractionQuick;
 import static com.android.systemui.shared.recents.utilities.Utilities
         .postAtFrontOfQueueAsynchronously;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -44,6 +44,7 @@
 import android.util.Log;
 import android.view.View;
 import android.view.ViewTreeObserver.OnDrawListener;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
@@ -101,9 +102,8 @@
 
     // States for quick switch/scrub
     private static final int STATE_SWITCH_TO_SCREENSHOT_COMPLETE = 1 << 10;
-    private static final int STATE_QUICK_SWITCH = 1 << 11;
-    private static final int STATE_QUICK_SCRUB_START = 1 << 12;
-    private static final int STATE_QUICK_SCRUB_END = 1 << 13;
+    private static final int STATE_QUICK_SCRUB_START = 1 << 11;
+    private static final int STATE_QUICK_SCRUB_END = 1 << 12;
 
     private static final int LAUNCHER_UI_STATES =
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE
@@ -250,13 +250,10 @@
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
                 this::invalidateHandlerWithLauncher);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_QUICK_SWITCH,
-                this::onQuickInteractionStart);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_QUICK_SCRUB_START,
-                this::onQuickInteractionStart);
-
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SWITCH_TO_SCREENSHOT_COMPLETE
-                | STATE_QUICK_SWITCH, this::switchToFinalAppAfterQuickSwitch);
+                this::onQuickScrubStart);
+        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_QUICK_SCRUB_START
+                | STATE_SCALED_CONTROLLER_RECENTS, this::onFinishedTransitionToQuickScrub);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SWITCH_TO_SCREENSHOT_COMPLETE
                 | STATE_QUICK_SCRUB_END, this::switchToFinalAppAfterQuickScrub);
     }
@@ -365,7 +362,6 @@
         mActivityControlHelper.prepareRecentsUI(mActivity, mWasLauncherAlreadyVisible);
         AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
 
-
         if (mWasLauncherAlreadyVisible) {
             mLauncherTransitionController = mActivityControlHelper
                     .createControllerForVisibleActivity(activity);
@@ -439,23 +435,16 @@
             throw new IllegalArgumentException(
                     "Can't change interaction type from " + mInteractionType);
         }
-        if (!isInteractionQuick(interactionType)) {
+        if (interactionType != INTERACTION_QUICK_SCRUB) {
             throw new IllegalArgumentException(
                     "Can't change interaction type to " + interactionType);
         }
         mInteractionType = interactionType;
 
-        setStateOnUiThread(interactionType == INTERACTION_QUICK_SWITCH
-                ? STATE_QUICK_SWITCH : STATE_QUICK_SCRUB_START);
+        setStateOnUiThread(STATE_QUICK_SCRUB_START);
 
         // Start the window animation without waiting for launcher.
-        animateToProgress(1f, QUICK_SWITCH_START_DURATION);
-    }
-
-    private void onQuickInteractionStart() {
-        mActivityControlHelper.onQuickInteractionStart(mActivity,
-                mWasLauncherAlreadyVisible || mGestureStarted);
-        mQuickScrubController.onQuickScrubStart(false);
+        animateToProgress(1f, QUICK_SCRUB_START_DURATION);
     }
 
     @WorkerThread
@@ -488,7 +477,12 @@
             if (mRecentsAnimationWrapper.controller != null) {
                 RectF currentRect;
                 synchronized (mTargetRect) {
-                    currentRect = mRectFEvaluator.evaluate(shift, mSourceRect, mTargetRect);
+                    Interpolator interpolator = mInteractionType == INTERACTION_QUICK_SCRUB
+                            ? ACCEL_2 : LINEAR;
+                    float interpolated = interpolator.getInterpolation(shift);
+                    currentRect = mRectFEvaluator.evaluate(interpolated, mSourceRect, mTargetRect);
+                    // Stay lined up with the center of the target, since it moves for quick scrub.
+                    currentRect.offset(mTargetRect.centerX() - currentRect.centerX(), 0);
                 }
 
                 mClipRect.left = (int) (mSourceWindowClipInsets.left * shift);
@@ -528,13 +522,12 @@
                 View firstTask = mRecentsView.getPageAt(0);
                 int scrollForFirstTask = mRecentsView.getScrollForPage(0);
                 int offsetFromFirstTask = (scrollForFirstTask - mRecentsView.getScrollX());
-                if (offsetFromFirstTask != 0) {
-                    synchronized (mTargetRect) {
-                        mTargetRect.set(mInitialTargetRect);
-                        Utilities.scaleRectFAboutCenter(mTargetRect, firstTask.getScaleX());
-                        float offsetX = offsetFromFirstTask + firstTask.getTranslationX();
-                        mTargetRect.offset(offsetX, 0);
-                    }
+                synchronized (mTargetRect) {
+                    mTargetRect.set(mInitialTargetRect);
+                    Utilities.scaleRectFAboutCenter(mTargetRect, firstTask.getScaleX());
+                    float offsetX = offsetFromFirstTask + firstTask.getTranslationX();
+                    float offsetY = mRecentsView.getTranslationY();
+                    mTargetRect.offset(offsetX, offsetY);
                 }
                 if (mRecentsAnimationWrapper.controller != null) {
 
@@ -764,21 +757,13 @@
         reset();
     }
 
-    public void onQuickScrubEnd() {
-        setStateOnUiThread(STATE_QUICK_SCRUB_END);
+    private void onQuickScrubStart() {
+        mActivityControlHelper.onQuickInteractionStart(mActivity, mWasLauncherAlreadyVisible);
+        mQuickScrubController.onQuickScrubStart(false);
     }
 
-    private void switchToFinalAppAfterQuickSwitch() {
-        mQuickScrubController.onQuickSwitch();
-    }
-
-    private void switchToFinalAppAfterQuickScrub() {
-        mQuickScrubController.onQuickScrubEnd();
-
-        // Normally this is handled in reset(), but since we are still scrubbing after the
-        // transition into recents, we need to defer the handler invalidation for quick scrub until
-        // after the gesture ends
-        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+    private void onFinishedTransitionToQuickScrub() {
+        mQuickScrubController.onFinishedTransitionToQuickScrub();
     }
 
     public void onQuickScrubProgress(float progress) {
@@ -791,6 +776,19 @@
         mQuickScrubController.onQuickScrubProgress(progress);
     }
 
+    public void onQuickScrubEnd() {
+        setStateOnUiThread(STATE_QUICK_SCRUB_END);
+    }
+
+    private void switchToFinalAppAfterQuickScrub() {
+        mQuickScrubController.onQuickScrubEnd();
+
+        // Normally this is handled in reset(), but since we are still scrubbing after the
+        // transition into recents, we need to defer the handler invalidation for quick scrub until
+        // after the gesture ends
+        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+    }
+
     private void debugNewState(int stateFlag) {
         if (!DEBUG_STATES) {
             return;
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index d490f82..130d34c 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -47,17 +47,31 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {
 
-    public static final FloatProperty<LauncherRecentsView> TRANSLATION_FACTOR =
-            new FloatProperty<LauncherRecentsView>("translationFactor") {
+    public static final FloatProperty<LauncherRecentsView> TRANSLATION_X_FACTOR =
+            new FloatProperty<LauncherRecentsView>("translationXFactor") {
 
                 @Override
                 public void setValue(LauncherRecentsView view, float v) {
-                    view.setTranslationFactor(v);
+                    view.setTranslationXFactor(v);
                 }
 
                 @Override
                 public Float get(LauncherRecentsView view) {
-                    return view.mTranslationFactor;
+                    return view.mTranslationXFactor;
+                }
+            };
+
+    public static final FloatProperty<LauncherRecentsView> TRANSLATION_Y_FACTOR =
+            new FloatProperty<LauncherRecentsView>("translationYFactor") {
+
+                @Override
+                public void setValue(LauncherRecentsView view, float v) {
+                    view.setTranslationYFactor(v);
+                }
+
+                @Override
+                public Float get(LauncherRecentsView view) {
+                    return view.mTranslationYFactor;
                 }
             };
 
@@ -67,7 +81,9 @@
     private Matrix mFadeMatrix;
     private boolean mScrimOnLeft;
 
-    private float mTranslationFactor;
+    private float mTranslationXFactor;
+    private float mTranslationYFactor;
+    private Rect mPagePadding = new Rect();
 
     public LauncherRecentsView(Context context) {
         this(context, null);
@@ -91,6 +107,8 @@
         setLayoutParams(lp);
 
         setPadding(padding.left, padding.top, padding.right, 0);
+        mPagePadding.set(padding);
+        mPagePadding.top += getResources().getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
 
         if (dp.isVerticalBarLayout()) {
             boolean wasScrimOnLeft = mScrimOnLeft;
@@ -157,15 +175,25 @@
         super.onLayout(changed, left, top, right, bottom);
 
         int width = right - left;
-        setTranslationX(mTranslationFactor * (mIsRtl ? -width : width));
+        setTranslationX(mTranslationXFactor * (mIsRtl ? -width : width));
+        setTranslationYFactor(mTranslationYFactor);
     }
 
-    public void setTranslationFactor(float translationFactor) {
-        mTranslationFactor = translationFactor;
+    public void setTranslationXFactor(float translationFactor) {
+        mTranslationXFactor = translationFactor;
         setTranslationX(translationFactor * (mIsRtl ? -getWidth() : getWidth()));
     }
 
-    public float getTranslationFactor() {
-        return mTranslationFactor;
+    public float getTranslationXFactor() {
+        return mTranslationXFactor;
+    }
+
+    public void setTranslationYFactor(float translationFactor) {
+        mTranslationYFactor = translationFactor;
+        setTranslationY(mTranslationYFactor * (mPagePadding.bottom - mPagePadding.top));
+    }
+
+    public float getTranslationYFactor() {
+        return mTranslationYFactor;
     }
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 5c16ca9..b1bf6ec 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -180,8 +180,13 @@
         return new float[] {1, 0, 0};
     }
 
-    public float getOverviewTranslationFactor(Launcher launcher) {
-        return 1;
+    /**
+     * Returns 2 floats designating how much to translate overview:
+     *   X factor is based on width, e.g. 0 is fully onscreen and 1 is fully offscreen
+     *   Y factor is based on padding, e.g. 0 is top aligned and 0.5 is centered vertically
+     */
+    public float[] getOverviewTranslationFactor(Launcher launcher) {
+        return new float[] {1f, 0f};
     }
 
     public void onStateEnabled(Launcher launcher) {