Merging  ub-launcher3-qt-dev, build 5619253

Test: Manual
Bug:112282235 P3 Starting an app from Launcher very rarely takes > 10 sec
Bug:121279417 P2 Why LauncherInstrumentation.WAIT_TIME_MS == 60000?
Bug:123892607 P1 Test broken: WellbeingTests.testPauseAppFromAllApps
Bug:123900446 P1 App to home animation should zoom into the app icon
Bug:125844074 P2 Final UX and animations for Launcher DWB integration
Bug:131360075 P1 [Gesture Nav] Polish/finish landscape
Bug:131698989 P2 Add task callback for locked state change
Bug:131741395 P2 Allow windows to scale/move past overview positioning
Bug:131768436 P1 Bad placement of search bar
Bug:131854153 P1 Lots of Cuttlefish (and not only) tests are broken
Bug:131867841 P1 Changing display size does not update the grid
Bug:132460627 P1 Unable to swipe to all apps screen on devices
Bug:132687470 P1 Swiping home from forced landscape app creates cutoff task thumbnail
Bug:132756514 P1 Sometimes (when quick switching?) user gets stuck in full-screen recents view
Bug:132900132 P1 Apparently, tests start running while provisioning is still in progress
Bug:132917885 P1 Reduce swipe-up gesture region height in landscape
Bug:132975416 P1 Flake in NexusPredictionAppTracker.getAppPredictionContextExtras
Bug:132993129 P1 If predictions disabled, app predictions loading bar appears briefly on device restart
Bug:133113732 P1 [B1/C1][QT][CTS_Verifier_9.0_r1]Device Owner Tests-LockTask UI-Enable Overview button failure
Bug:133167096 P1 It is way too easy to dismiss apps from the lock screen
Bug:133651528 P1 [QT]"Pixel Launcher isn't responding" dialog pop up ,after DUT restored.
Bug:133765434 P1 [Flaky test] Launching task didn't open a new window
Bug:133765491 P1 App docked in split screen flickers with emptiness observed while rotating the device
Bug:133783088 P1 Footer showing up on all overview task snapshots even with no items showing.
Bug:133867119 P2 Lab-only flake: want to switch from workspace to all apps; Swipe failed to receive an event for the swipe end
Bug:64712476 P3 Import translations for dev branches

Change-Id: Ib4bcefdbb4027992e75e2742d72f199e13467875
(cherry picked from commit 0ef6fe00b0e2a7ef59db149b5031a0fbcdde86b2)
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index be275e0..332e0fa 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -73,14 +73,6 @@
             </intent-filter>
         </provider>
 
-        <provider
-            android:name="com.android.quickstep.TestInformationProvider"
-            android:authorities="${packageName}.TestInfo"
-            android:readPermission="android.permission.WRITE_SECURE_SETTINGS"
-            android:writePermission="android.permission.WRITE_SECURE_SETTINGS"
-            android:exported="true">
-        </provider>
-
         <service
             android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
             tools:node="remove" />
@@ -92,6 +84,11 @@
             android:clearTaskOnLaunch="true"
             android:exported="false" />
 
+        <activity android:name="com.android.quickstep.LockScreenRecentsActivity"
+                  android:theme="@android:style/Theme.NoDisplay"
+                  android:showOnLockScreen="true"
+                  android:directBootAware="true" />
+
     </application>
 
 </manifest>
diff --git a/quickstep/recents_ui_overrides/res/layout/prediction_load_progress.xml b/quickstep/recents_ui_overrides/res/layout/prediction_load_progress.xml
deleted file mode 100644
index 20c4004..0000000
--- a/quickstep/recents_ui_overrides/res/layout/prediction_load_progress.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
-    style="?android:attr/progressBarStyleHorizontal"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:layout_marginLeft="20dp"
-    android:layout_marginRight="20dp"
-    android:indeterminate="true"
-    android:indeterminateOnly="true"
-    android:indeterminateTint="?workspaceTextColor" />
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index b316edd..c80e531 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -27,4 +27,5 @@
     <!-- Swipe up to home related -->
     <dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
     <dimen name="swipe_up_y_overshoot">10dp</dimen>
+    <dimen name="swipe_up_max_workspace_trans_y">-80dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
index af67e1b..8f1282d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
@@ -56,7 +56,7 @@
     private static final int MSG_LAUNCH = 2;
     private static final int MSG_PREDICT = 3;
 
-    private final Context mContext;
+    protected final Context mContext;
     private final Handler mMessageHandler;
 
     // Accessed only on worker thread
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index 55f4c98..4a486f8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -28,7 +28,6 @@
 import android.util.AttributeSet;
 import android.util.IntProperty;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
@@ -115,8 +114,6 @@
     private final AnimatedFloat mOverviewScrollFactor =
             new AnimatedFloat(this::updateTranslationAndAlpha);
 
-    private View mLoadingProgress;
-
     private boolean mPredictionsEnabled = false;
 
     public PredictionRowView(@NonNull Context context) {
@@ -165,7 +162,6 @@
 
     public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
         mParent = parent;
-        setPredictionsEnabled(mPredictionUiStateManager.arePredictionsEnabled());
     }
 
     private void setPredictionsEnabled(boolean predictionsEnabled) {
@@ -205,7 +201,7 @@
 
     @Override
     public boolean hasVisibleContent() {
-        return mPredictionUiStateManager.arePredictionsEnabled();
+        return mPredictionsEnabled;
     }
 
     /**
@@ -241,9 +237,6 @@
     }
 
     private void applyPredictionApps() {
-        if (mLoadingProgress != null) {
-            removeView(mLoadingProgress);
-        }
         if (!mPredictionsEnabled) {
             mParent.onHeightUpdated();
             return;
@@ -290,15 +283,8 @@
         }
 
         if (predictionCount == 0) {
-            if (mLoadingProgress == null) {
-                mLoadingProgress = LayoutInflater.from(getContext())
-                        .inflate(R.layout.prediction_load_progress, this, false);
-            }
-            addView(mLoadingProgress);
-        } else {
-            mLoadingProgress = null;
+            setPredictionsEnabled(false);
         }
-
         mParent.onHeightUpdated();
     }
 
@@ -342,11 +328,8 @@
     public void setTextAlpha(int alpha) {
         mIconCurrentTextAlpha = alpha;
         int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha);
-
-        if (mLoadingProgress == null) {
-            for (int i = 0; i < getChildCount(); i++) {
-                ((BubbleTextView) getChildAt(i)).setTextColor(iconColor);
-            }
+        for (int i = 0; i < getChildCount(); i++) {
+            ((BubbleTextView) getChildAt(i)).setTextColor(iconColor);
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index 6dad9af..64cb4b4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -24,7 +24,6 @@
 import android.app.prediction.AppTarget;
 import android.content.ComponentName;
 import android.content.Context;
-import android.os.Handler;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 
 import com.android.launcher3.AppInfo;
@@ -63,7 +62,6 @@
         OnIDPChangeListener, OnUpdateListener {
 
     public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
-    private static final long INITIAL_CALLBACK_WAIT_TIMEOUT_MS = 5000;
 
     // TODO (b/129421797): Update the client constants
     public enum Client {
@@ -110,13 +108,8 @@
         for (int i = 0; i < mPredictionServicePredictions.length; i++) {
             mPredictionServicePredictions[i] = Collections.emptyList();
         }
-
         mGettingValidPredictionResults = Utilities.getDevicePrefs(context)
                 .getBoolean(LAST_PREDICTION_ENABLED_STATE, true);
-        if (mGettingValidPredictionResults) {
-            new Handler().postDelayed(
-                    this::updatePredictionStateAfterCallback, INITIAL_CALLBACK_WAIT_TIMEOUT_MS);
-        }
 
         // Call this last
         mCurrentState = parseLastState();
@@ -293,10 +286,6 @@
         dispatchOnChange(false);
     }
 
-    public boolean arePredictionsEnabled() {
-        return mCurrentState.isEnabled;
-    }
-
     private boolean canApplyPredictions(PredictionState newState) {
         if (mAppsView == null) {
             // If there is no apps view, no need to schedule.
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 7a6cd2d..e3e339a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
 import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -34,8 +35,10 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.QuickStepContract;
 
 /**
  * Touch controller which handles swipe and hold to go to Overview
@@ -99,7 +102,8 @@
      * having it as part of the existing animation to the target state.
      */
     private boolean handlingOverviewAnim() {
-        return mStartState == NORMAL;
+        int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+        return mStartState == NORMAL && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index e1dd124..18b8af4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -30,6 +30,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.view.MotionEvent;
 
@@ -44,10 +45,12 @@
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.QuickStepContract;
 
 /**
  * Handles quick switching to a recent task from the home screen.
@@ -80,6 +83,10 @@
 
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+        if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
+            return NORMAL;
+        }
         return isDragTowardPositive ? QUICK_SWITCH : NORMAL;
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 4b2e487..5af09f7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -30,6 +30,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -56,6 +57,7 @@
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -68,6 +70,8 @@
  */
 public final class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
 
+    private Runnable mAdjustInterpolatorsRunnable;
+
     @Override
     public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
         LayoutUtils.calculateLauncherTaskSize(context, dp, outRect);
@@ -148,8 +152,21 @@
             @NonNull
             @Override
             public AnimatorPlaybackController createActivityAnimationToHome() {
+                // Return an empty APC here since we have an non-user controlled animation to home.
                 long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
-                return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy);
+                AnimatorSet as = new AnimatorSet();
+                as.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        activity.getStateManager().goToState(NORMAL, false);
+                    }
+                });
+                return AnimatorPlaybackController.wrap(as, accuracy);
+            }
+
+            @Override
+            public void playAtomicAnimation(float velocity) {
+                new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start();
             }
         };
     }
@@ -194,6 +211,13 @@
             }
 
             @Override
+            public void adjustActivityControllerInterpolators() {
+                if (mAdjustInterpolatorsRunnable != null) {
+                    mAdjustInterpolatorsRunnable.run();
+                }
+            }
+
+            @Override
             public void onTransitionCancelled() {
                 activity.getStateManager().goToState(startState, false /* animate */);
             }
@@ -275,6 +299,7 @@
         playScaleDownAnim(anim, activity, fromState, endState);
 
         anim.setDuration(transitionLength * 2);
+        anim.setInterpolator(LINEAR);
         AnimatorPlaybackController controller =
                 AnimatorPlaybackController.wrap(anim, transitionLength * 2);
         activity.getStateManager().setCurrentUserControlledAnimation(controller);
@@ -291,7 +316,6 @@
         Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
                 "allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(),
                 SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
-        shiftAnim.setInterpolator(LINEAR);
         return shiftAnim;
     }
 
@@ -310,19 +334,37 @@
                 = fromState.getOverviewScaleAndTranslation(launcher);
         LauncherState.ScaleAndTranslation endScaleAndTranslation
                 = endState.getOverviewScaleAndTranslation(launcher);
+        float fromTranslationY = fromScaleAndTranslation.translationY;
+        float endTranslationY = endScaleAndTranslation.translationY;
         float fromFullscreenProgress = fromState.getOverviewFullscreenProgress();
         float endFullscreenProgress = endState.getOverviewFullscreenProgress();
 
         Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
                 fromScaleAndTranslation.scale, endScaleAndTranslation.scale);
         Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y,
-                fromScaleAndTranslation.translationY, endScaleAndTranslation.translationY);
+                fromTranslationY, endTranslationY);
         Animator applyFullscreenProgress = ObjectAnimator.ofFloat(recentsView,
                 RecentsView.FULLSCREEN_PROGRESS, fromFullscreenProgress, endFullscreenProgress);
-        scale.setInterpolator(LINEAR);
-        translateY.setInterpolator(LINEAR);
-        applyFullscreenProgress.setInterpolator(LINEAR);
         anim.playTogether(scale, translateY, applyFullscreenProgress);
+
+        mAdjustInterpolatorsRunnable = () -> {
+            // Adjust the translateY interpolator to account for the running task's top inset.
+            // When progress <= 1, this is handled by each task view as they set their fullscreen
+            // progress. However, once we go to progress > 1, fullscreen progress stays at 0, so
+            // recents as a whole needs to translate further to keep up with the app window.
+            TaskView runningTaskView = recentsView.getRunningTaskView();
+            if (runningTaskView == null) {
+                runningTaskView = recentsView.getTaskViewAt(recentsView.getCurrentPage());
+            }
+            TimeInterpolator oldInterpolator = translateY.getInterpolator();
+            Rect fallbackInsets = launcher.getDeviceProfile().getInsets();
+            float extraTranslationY = runningTaskView.getThumbnail().getInsets(fallbackInsets).top;
+            float normalizedTranslationY = extraTranslationY / (fromTranslationY - endTranslationY);
+            translateY.setInterpolator(t -> {
+                float newT = oldInterpolator.getInterpolation(t);
+                return newT <= 1f ? newT : newT + normalizedTranslationY * (newT - 1);
+            });
+        };
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java
new file mode 100644
index 0000000..65f323c
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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.quickstep;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity to start a recents transition
+ */
+public class LockScreenRecentsActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        finish();
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 0c997dd..7563c3f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -22,8 +22,11 @@
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
@@ -79,6 +82,7 @@
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InputMonitorCompat;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 
 import java.io.FileDescriptor;
@@ -197,6 +201,8 @@
 
         public void onSystemUiStateChanged(int stateFlags) {
             mSystemUiStateFlags = stateFlags;
+            mOverviewInteractionState.setSystemUiStateFlags(stateFlags);
+            mOverviewComponentObserver.onSystemUiStateChanged(stateFlags);
         }
 
         /** Deprecated methods **/
@@ -331,16 +337,8 @@
         defaultDisplay.getRealSize(realSize);
         mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y);
         if (mMode == Mode.NO_BUTTON) {
-            switch (defaultDisplay.getRotation()) {
-                case Surface.ROTATION_90:
-                case Surface.ROTATION_270:
-                    mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - getNavbarSize(
-                            ResourceUtils.NAVBAR_LANDSCAPE_BOTTOM_SIZE);
-                    break;
-                default:
-                    mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - getNavbarSize(
-                            ResourceUtils.NAVBAR_PORTRAIT_BOTTOM_SIZE);
-            }
+            mSwipeTouchRegion.top = mSwipeTouchRegion.bottom -
+                    getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
         } else {
             switch (defaultDisplay.getRotation()) {
                 case Surface.ROTATION_90:
@@ -353,7 +351,7 @@
                     break;
                 default:
                     mSwipeTouchRegion.top = mSwipeTouchRegion.bottom
-                            - getNavbarSize(ResourceUtils.NAVBAR_PORTRAIT_BOTTOM_SIZE);
+                            - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
             }
         }
     }
@@ -472,22 +470,19 @@
 
     private boolean validSystemUiFlags() {
         return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0;
-    }
-
-    private boolean topTaskLocked() {
-        return ActivityManagerWrapper.getInstance().isLockToAppActive();
+                && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
+                && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
+                        || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
     }
 
     private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
-        boolean topTaskLocked = topTaskLocked();
-        boolean isInValidSystemUiState = validSystemUiFlags() && !topTaskLocked;
+        boolean isInValidSystemUiState = validSystemUiFlags();
 
         if (!mIsUserUnlocked) {
             if (isInValidSystemUiState) {
                 // This handles apps launched in direct boot mode (e.g. dialer) as well as apps
                 // launched while device is locked even after exiting direct boot mode (e.g. camera).
-                return new DeviceLockedInputConsumer(this);
+                return createDeviceLockedInputConsumer(mAM.getRunningTask(0));
             } else {
                 return InputConsumer.NO_OP;
             }
@@ -498,13 +493,15 @@
         if (mMode == Mode.NO_BUTTON) {
             final ActivityControlHelper activityControl =
                     mOverviewComponentObserver.getActivityControlHelper();
-            if (mAssistantAvailable && !topTaskLocked
-                    && AssistantTouchConsumer.withinTouchRegion(this, event)) {
+            if (mAssistantAvailable
+                    && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
+                    && AssistantTouchConsumer.withinTouchRegion(this, event)
+                    && !ActivityManagerWrapper.getInstance().isLockToAppActive()) {
                 base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base,
                         mInputMonitorCompat);
             }
 
-            if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
+            if ((mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0) {
                 // Note: we only allow accessibility to wrap this, and it replaces the previous
                 // base input consumer (which should be NO_OP anyway since topTaskLocked == true).
                 base = new ScreenPinnedInputConsumer(this, mISystemUiProxy, activityControl);
@@ -520,16 +517,15 @@
     }
 
     private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) {
-        if (mKM.isDeviceLocked()) {
-            // This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched
-            // while device is locked even after exiting direct boot mode (e.g. camera).
-            return new DeviceLockedInputConsumer(this);
-        }
-
         final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
         if (!useSharedState) {
             mSwipeSharedState.clearAllState();
         }
+        if (mKM.isDeviceLocked()) {
+            // This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched
+            // while device is locked even after exiting direct boot mode (e.g. camera).
+            return createDeviceLockedInputConsumer(runningTaskInfo);
+        }
 
         final ActivityControlHelper activityControl =
                 mOverviewComponentObserver.getActivityControlHelper();
@@ -567,6 +563,15 @@
                 mSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion);
     }
 
+    private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
+        if (mMode == Mode.NO_BUTTON && taskInfo != null) {
+            return new DeviceLockedInputConsumer(this, mSwipeSharedState, mInputMonitorCompat,
+                    mSwipeTouchRegion, taskInfo.taskId);
+        } else {
+            return InputConsumer.NO_OP;
+        }
+    }
+
     /**
      * To be called by the consumer when it's no longer active.
      */
@@ -593,17 +598,14 @@
             // Dump everything
             pw.println("TouchState:");
             pw.println("  navMode=" + mMode);
-            pw.println("  validSystemUiFlags=" + validSystemUiFlags()
-                    + " flags=" + mSystemUiStateFlags);
-            pw.println("  topTaskLocked=" + topTaskLocked());
+            pw.println("  validSystemUiFlags=" + validSystemUiFlags());
+            pw.println("  systemUiFlags=" + mSystemUiStateFlags);
+            pw.println("  systemUiFlagsDesc="
+                    + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags));
             pw.println("  isDeviceLocked=" + mKM.isDeviceLocked());
-            pw.println("  screenPinned=" +
-                    ActivityManagerWrapper.getInstance().isScreenPinningActive());
             pw.println("  assistantAvailable=" + mAssistantAvailable);
-            pw.println("  a11yClickable="
-                    + ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0));
-            pw.println("  a11yLongClickable="
-                    + ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0));
+            pw.println("  assistantDisabled="
+                    + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
             pw.println("  resumed="
                     + mOverviewComponentObserver.getActivityControlHelper().isResumed());
             pw.println("  useSharedState=" + mConsumer.useSharedSwipeState());
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 2ff5c0c..0d0478a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -39,6 +39,7 @@
 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK;
 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -105,6 +106,7 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.WindowCallbacksCompat;
@@ -170,7 +172,8 @@
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
 
     public enum GestureEndTarget {
-        HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE, false),
+        HOME(1, STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT, true, false,
+                ContainerType.WORKSPACE, false),
 
         RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
                 | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true),
@@ -216,6 +219,12 @@
     private static final long SHELF_ANIM_DURATION = 120;
     public static final long RECENTS_ATTACH_DURATION = 300;
 
+    // Start resisting when swiping past this factor of mTransitionDragLength.
+    private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
+    // This is how far down we can scale down, where 0f is full screen and 1f is recents.
+    private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f;
+    private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
+
     /**
      * Used as the page index for logging when we return to the last task at the end of the gesture.
      */
@@ -230,7 +239,10 @@
     private RunningWindowAnim mRunningWindowAnim;
     private boolean mIsShelfPeeking;
     private DeviceProfile mDp;
+    // The distance needed to drag to reach the task size in recents.
     private int mTransitionDragLength;
+    // How much further we can drag past recents, as a factor of mTransitionDragLength.
+    private float mDragLengthFactor = 1;
 
     // Shift in the range of [0, 1].
     // 0 => preview snapShot is completely visible, and hotseat is completely translated down
@@ -331,9 +343,8 @@
                         | STATE_SCALED_CONTROLLER_RECENTS,
                 this::finishCurrentTransitionToRecents);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_COMPLETED
-                        | STATE_SCALED_CONTROLLER_HOME | STATE_APP_CONTROLLER_RECEIVED
-                        | STATE_LAUNCHER_DRAWN,
+        mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
+                        | STATE_SCALED_CONTROLLER_HOME,
                 this::finishCurrentTransitionToHome);
         mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
                 this::reset);
@@ -375,6 +386,10 @@
         mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
                 dp, mContext, tempRect);
         mClipAnimationHelper.updateTargetRect(tempRect);
+        if (mMode == Mode.NO_BUTTON) {
+            // We can drag all the way to the top of the screen.
+            mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
+        }
     }
 
     private long getFadeInDuration() {
@@ -546,11 +561,18 @@
     public void updateDisplacement(float displacement) {
         // We are moving in the negative x/y direction
         displacement = -displacement;
-        if (displacement > mTransitionDragLength && mTransitionDragLength > 0) {
-            mCurrentShift.updateValue(1);
+        if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
+            mCurrentShift.updateValue(mDragLengthFactor);
         } else {
             float translation = Math.max(displacement, 0);
             float shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
+            if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) {
+                float pullbackProgress = Utilities.getProgress(shift,
+                        DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor);
+                pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
+                shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress
+                        * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK);
+            }
             mCurrentShift.updateValue(shift);
         }
     }
@@ -638,6 +660,8 @@
 
     private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
         mLauncherTransitionController = anim;
+        mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
+        mAnimationFactory.adjustActivityControllerInterpolators();
         mLauncherTransitionController.dispatchOnStart();
         updateLauncherTransitionProgress();
     }
@@ -690,7 +714,9 @@
         if (mGestureEndTarget == HOME) {
             return;
         }
-        float progress = mCurrentShift.value;
+        // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
+        // anyway. The controller mimics the drag length factor by applying it to its interpolators.
+        float progress = mCurrentShift.value / mDragLengthFactor;
         mLauncherTransitionController.setPlayFraction(
                 progress <= mShiftAtGestureStart || mShiftAtGestureStart >= 1
                         ? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart));
@@ -835,16 +861,9 @@
         }
     }
 
-    @UiThread
-    private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
+    private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
             boolean isCancel) {
-        PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
-        long duration = MAX_SWIPE_DURATION;
-        float currentShift = mCurrentShift.value;
         final GestureEndTarget endTarget;
-        float endShift;
-        final float startShift;
-        Interpolator interpolator = DEACCEL;
         final boolean goingToNewTask;
         if (mRecentsView != null) {
             if (!mRecentsAnimationWrapper.hasTargets()) {
@@ -859,7 +878,7 @@
         } else {
             goingToNewTask = false;
         }
-        final boolean reachedOverviewThreshold = currentShift >= MIN_PROGRESS_FOR_OVERVIEW;
+        final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
         if (!isFling) {
             if (isCancel) {
                 endTarget = LAST_TASK;
@@ -869,7 +888,7 @@
                 } else if (goingToNewTask) {
                     endTarget = NEW_TASK;
                 } else {
-                    endTarget = currentShift < MIN_PROGRESS_FOR_OVERVIEW ? LAST_TASK : HOME;
+                    endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME;
                 }
             } else {
                 endTarget = reachedOverviewThreshold && mGestureStarted
@@ -878,12 +897,6 @@
                                 ? NEW_TASK
                                 : LAST_TASK;
             }
-            endShift = endTarget.endShift;
-            long expectedDuration = Math.abs(Math.round((endShift - currentShift)
-                    * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
-            duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
-            startShift = currentShift;
-            interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
         } else {
             if (mMode == Mode.NO_BUTTON && endVelocity < 0 && !mIsShelfPeeking) {
                 // If swiping at a diagonal, base end target on the faster velocity.
@@ -896,9 +909,36 @@
             } else {
                 endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
             }
-            endShift = endTarget.endShift;
+        }
+
+        int stateFlags = OverviewInteractionState.INSTANCE.get(mActivity).getSystemUiStateFlags();
+        if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0
+                && (endTarget == RECENTS || endTarget == LAST_TASK)) {
+            return LAST_TASK;
+        }
+        return endTarget;
+    }
+
+    @UiThread
+    private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
+            boolean isCancel) {
+        PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
+        long duration = MAX_SWIPE_DURATION;
+        float currentShift = mCurrentShift.value;
+        final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
+                isFling, isCancel);
+        float endShift = endTarget.endShift;
+        final float startShift;
+        Interpolator interpolator = DEACCEL;
+        if (!isFling) {
+            long expectedDuration = Math.abs(Math.round((endShift - currentShift)
+                    * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
+            duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
+            startShift = currentShift;
+            interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
+        } else {
             startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
-                    * SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
+                    * SINGLE_FRAME_MS / mTransitionDragLength, 0, mDragLengthFactor);
             float minFlingVelocity = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_min_velocity);
             if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
@@ -936,8 +976,10 @@
         } else if (endTarget == RECENTS) {
             mLiveTileOverlay.startIconAnimation();
             if (mRecentsView != null) {
-                duration = Utilities.boundToRange(mRecentsView.getScroller().getDuration(),
-                        duration, MAX_SWIPE_DURATION);
+                if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) {
+                    mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION);
+                }
+                duration = Math.max(duration, mRecentsView.getScroller().getDuration());
             }
             if (mMode == Mode.NO_BUTTON) {
                 setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
@@ -1015,6 +1057,7 @@
                     setStateOnUiThread(target.endState);
                 }
             });
+            homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
             windowAnim.start(velocityPxPerMs);
             mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
             mLauncherTransitionController = null;
@@ -1055,6 +1098,7 @@
             mLauncherTransitionController.getAnimationPlayer().end();
         } else {
             mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator);
+            mAnimationFactory.adjustActivityControllerInterpolators();
             mLauncherTransitionController.getAnimationPlayer().setDuration(duration);
 
             if (QUICKSTEP_SPRINGS.get()) {
@@ -1091,12 +1135,10 @@
         // FolderIconView can be seen morphing into the icon shape.
         final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
         anim.addOnUpdateListener((currentRect, progress) -> {
-            float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress);
-
             homeAnim.setPlayFraction(progress);
 
-            float windowAlpha = Utilities.mapToRange(interpolatedProgress, 0,
-                    windowAlphaThreshold, 1f, 0f, Interpolators.LINEAR);
+            float windowAlpha = Math.max(0, Utilities.mapToRange(progress, 0,
+                    windowAlphaThreshold, 1f, 0f, Interpolators.LINEAR));
             mTransformParams.setProgress(progress)
                     .setCurrentRectAndTargetAlpha(currentRect, windowAlpha);
             mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
@@ -1251,8 +1293,15 @@
                 if (mTaskSnapshot == null) {
                     mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
                 }
-                TaskView taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot);
-                if (taskView != null) {
+                final TaskView taskView;
+                if (mGestureEndTarget == HOME) {
+                    // Capture the screenshot before finishing the transition to home to ensure it's
+                    // taken in the correct orientation, but no need to update the thumbnail.
+                    taskView = null;
+                } else {
+                    taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot);
+                }
+                if (taskView != null && !mCanceled) {
                     // Defer finishing the animation until the next launcher frame with the
                     // new thumbnail
                     finishTransitionPosted = new WindowCallbacksCompat(taskView) {
@@ -1262,6 +1311,13 @@
 
                         @Override
                         public void onPostDraw(Canvas canvas) {
+                            // If we were cancelled after this was attached, do not update
+                            // the state.
+                            if (mCanceled) {
+                                detach();
+                                return;
+                            }
+
                             if (mDeferFrameCount > 0) {
                                 mDeferFrameCount--;
                                 // Workaround, detach and reattach to invalidate the root node for
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index 09d323e..1820729 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -20,7 +20,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.view.WindowInsets;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.R;
@@ -71,7 +70,7 @@
         // Update device profile before notifying the children.
         mActivity.getDeviceProfile().updateInsets(insets);
         setInsets(insets);
-        return true; // I'll take it from here
+        return false; // Let children get the full insets
     }
 
     @Override
@@ -89,10 +88,4 @@
         mActivity.getDeviceProfile().updateInsets(mInsets);
         super.setInsets(mInsets);
     }
-
-    @Override
-    public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
-        updateTouchExcludeRegion(insets);
-        return super.dispatchApplyWindowInsets(insets);
-    }
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index d01b5ec..db2af59 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -15,26 +15,102 @@
  */
 package com.android.quickstep.inputconsumers;
 
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
+import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Point;
 import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.quickstep.LockScreenRecentsActivity;
+import com.android.quickstep.MultiStateCallback;
+import com.android.quickstep.SwipeSharedState;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.RecentsAnimationListenerSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.InputMonitorCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 /**
  * A dummy input consumer used when the device is still locked, e.g. from secure camera.
  */
-public class DeviceLockedInputConsumer implements InputConsumer {
+public class DeviceLockedInputConsumer implements InputConsumer,
+        SwipeAnimationTargetSet.SwipeAnimationListener {
+
+    private static final float SCALE_DOWN = 0.75f;
+
+    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[2] : null;
+    private static int getFlagForIndex(int index, String name) {
+        if (DEBUG_STATES) {
+            STATE_NAMES[index] = name;
+        }
+        return 1 << index;
+    }
+
+    private static final int STATE_TARGET_RECEIVED =
+            getFlagForIndex(0, "STATE_TARGET_RECEIVED");
+    private static final int STATE_HANDLER_INVALIDATED =
+            getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
 
     private final Context mContext;
     private final float mTouchSlopSquared;
-    private final PointF mTouchDown = new PointF();
+    private final SwipeSharedState mSwipeSharedState;
+    private final InputMonitorCompat mInputMonitorCompat;
 
-    public DeviceLockedInputConsumer(Context context) {
+    private final PointF mTouchDown = new PointF();
+    private final ClipAnimationHelper mClipAnimationHelper;
+    private final ClipAnimationHelper.TransformParams mTransformParams;
+    private final Point mDisplaySize;
+    private final MultiStateCallback mStateCallback;
+    private final RectF mSwipeTouchRegion;
+    public final int mRunningTaskId;
+
+    private VelocityTracker mVelocityTracker;
+    private float mProgress;
+
+    private boolean mThresholdCrossed = false;
+
+    private SwipeAnimationTargetSet mTargetSet;
+
+    public DeviceLockedInputConsumer(Context context, SwipeSharedState swipeSharedState,
+            InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, int runningTaskId) {
         mContext = context;
         mTouchSlopSquared = squaredTouchSlop(context);
+        mSwipeSharedState = swipeSharedState;
+        mClipAnimationHelper = new ClipAnimationHelper(context);
+        mTransformParams = new ClipAnimationHelper.TransformParams();
+        mInputMonitorCompat = inputMonitorCompat;
+        mSwipeTouchRegion = swipeTouchRegion;
+        mRunningTaskId = runningTaskId;
+
+        // Do not use DeviceProfile as the user data might be locked
+        mDisplaySize = new Point();
+        context.getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(mDisplaySize);
+
+        // Init states
+        mStateCallback = new MultiStateCallback(STATE_NAMES);
+        mStateCallback.addCallback(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED,
+                this::endRemoteAnimation);
+
+        mVelocityTracker = VelocityTracker.obtain();
     }
 
     @Override
@@ -44,17 +120,137 @@
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            return;
+        }
+        mVelocityTracker.addMovement(ev);
+
         float x = ev.getX();
         float y = ev.getY();
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mTouchDown.set(x, y);
-        } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
-            if (squaredHypot(x - mTouchDown.x, y - mTouchDown.y) > mTouchSlopSquared) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mTouchDown.set(x, y);
+                break;
+            case ACTION_POINTER_DOWN: {
+                if (!mThresholdCrossed) {
+                    // Cancel interaction in case of multi-touch interaction
+                    int ptrIdx = ev.getActionIndex();
+                    if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
+                        int action = ev.getAction();
+                        ev.setAction(ACTION_CANCEL);
+                        finishTouchTracking(ev);
+                        ev.setAction(action);
+                    }
+                }
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                if (!mThresholdCrossed) {
+                    if (squaredHypot(x - mTouchDown.x, y - mTouchDown.y) > mTouchSlopSquared) {
+                        startRecentsTransition();
+                    }
+                } else {
+                    float dy = Math.max(mTouchDown.y - y, 0);
+                    mProgress = dy / mDisplaySize.y;
+                    mTransformParams.setProgress(mProgress);
+                    if (mTargetSet != null) {
+                        mClipAnimationHelper.applyTransform(mTargetSet, mTransformParams);
+                    }
+                }
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                finishTouchTracking(ev);
+                break;
+        }
+    }
+
+    /**
+     * Called when the gesture has ended. Does not correlate to the completion of the interaction as
+     * the animation can still be running.
+     */
+    private void finishTouchTracking(MotionEvent ev) {
+        mStateCallback.setState(STATE_HANDLER_INVALIDATED);
+        if (mThresholdCrossed && ev.getAction() == ACTION_UP) {
+            mVelocityTracker.computeCurrentVelocity(1000,
+                    ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
+
+            float velocityY = mVelocityTracker.getYVelocity();
+            float flingThreshold = mContext.getResources()
+                    .getDimension(R.dimen.quickstep_fling_threshold_velocity);
+
+            boolean dismissTask;
+            if (Math.abs(velocityY) > flingThreshold) {
+                // Is fling
+                dismissTask = velocityY < 0;
+            } else {
+                dismissTask = mProgress >= (1 - MIN_PROGRESS_FOR_OVERVIEW);
+            }
+            if (dismissTask) {
                 // For now, just start the home intent so user is prompted to unlock the device.
                 mContext.startActivity(new Intent(Intent.ACTION_MAIN)
                         .addCategory(Intent.CATEGORY_HOME)
                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
             }
         }
+        mVelocityTracker.recycle();
+        mVelocityTracker = null;
+    }
+
+    private void startRecentsTransition() {
+        mThresholdCrossed = true;
+        RecentsAnimationListenerSet newListenerSet =
+                mSwipeSharedState.newRecentsAnimationListenerSet();
+        newListenerSet.addListener(this);
+        Intent intent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_DEFAULT)
+                .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class))
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+        mInputMonitorCompat.pilferPointers();
+        BackgroundExecutor.get().submit(
+                () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
+                        intent, null, newListenerSet, null, null));
+    }
+
+    @Override
+    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
+        mTargetSet = targetSet;
+
+        Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
+        RemoteAnimationTargetCompat targetCompat = targetSet.findTask(mRunningTaskId);
+        if (targetCompat != null) {
+            mClipAnimationHelper.updateSource(displaySize, targetCompat);
+        }
+
+        Utilities.scaleRectAboutCenter(displaySize, SCALE_DOWN);
+        displaySize.offsetTo(displaySize.left, 0);
+        mClipAnimationHelper.updateTargetRect(displaySize);
+        mClipAnimationHelper.applyTransform(mTargetSet, mTransformParams);
+
+        mStateCallback.setState(STATE_TARGET_RECEIVED);
+    }
+
+    @Override
+    public void onRecentsAnimationCanceled() {
+        mTargetSet = null;
+    }
+
+    private void endRemoteAnimation() {
+        if (mTargetSet != null) {
+            mTargetSet.finishController(
+                    false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */);
+        }
+    }
+
+    @Override
+    public void onConsumerAboutToBeSwitched() {
+        mStateCallback.setState(STATE_HANDLER_INVALIDATED);
+    }
+
+    @Override
+    public boolean allowInterceptByParent() {
+        return !mThresholdCrossed;
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index 805cf33..0ae469c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -23,7 +23,6 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Matrix.ScaleToFit;
 import android.graphics.Rect;
@@ -160,14 +159,16 @@
 
     public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params,
             boolean launcherOnTop) {
+        float progress = params.progress;
         if (params.currentRect == null) {
             RectF currentRect;
             mTmpRectF.set(mTargetRect);
             Utilities.scaleRectFAboutCenter(mTmpRectF, params.offsetScale);
-            float progress = params.progress;
             currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
             currentRect.offset(params.offsetX, 0);
 
+            // Don't clip past progress > 1.
+            progress = Math.min(1, progress);
             final RectF sourceWindowClipInsets = params.forLiveTile
                     ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets;
             mClipRectF.left = sourceWindowClipInsets.left * progress;
@@ -189,7 +190,7 @@
             float alpha = 1f;
             int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
             float cornerRadius = 0f;
-            float scale = params.currentRect.width() / crop.width();
+            float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width();
             if (app.mode == targetSet.targetMode) {
                 if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
                     mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
@@ -198,7 +199,7 @@
                     if (mSupportsRoundedCornersOnWindows) {
                         float windowCornerRadius = mUseRoundedCornersOnWindows
                                 ? mWindowCornerRadius : 0;
-                        cornerRadius = Utilities.mapRange(params.progress, windowCornerRadius,
+                        cornerRadius = Utilities.mapRange(progress, windowCornerRadius,
                                 mTaskCornerRadius);
                         mCurrentCornerRadius = cornerRadius;
                     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index 3f4ad58..77dc6f3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -16,26 +16,22 @@
 package com.android.quickstep.util;
 
 import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
 import android.content.res.Resources;
 import android.graphics.PointF;
 import android.graphics.RectF;
-import android.util.FloatProperty;
 
 import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
 import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.FlingSpringAnim;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
 
 /**
  * Applies spring forces to animate from a starting rect to a target rect,
@@ -43,14 +39,6 @@
  */
 public class RectFSpringAnim {
 
-    /**
-     * Although the rect position animation takes an indefinite amount of time since it depends on
-     * the initial velocity and applied forces, scaling from the starting rect to the target rect
-     * can be done in parallel at a fixed duration. Update callbacks are sent based on the progress
-     * of this animation, while the end callback is sent after all animations finish.
-     */
-    private static final long RECT_SCALE_DURATION = 250;
-
     private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X =
             new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") {
                 @Override
@@ -79,17 +67,17 @@
                 }
             };
 
-    private static final FloatProperty<RectFSpringAnim> RECT_SCALE_PROGRESS =
-            new FloatProperty<RectFSpringAnim>("rectScaleProgress") {
+    private static final FloatPropertyCompat<RectFSpringAnim> RECT_SCALE_PROGRESS =
+            new FloatPropertyCompat<RectFSpringAnim>("rectScaleProgress") {
                 @Override
-                public Float get(RectFSpringAnim anim) {
-                    return anim.mCurrentScaleProgress;
+                public float getValue(RectFSpringAnim object) {
+                    return object.mCurrentScaleProgress;
                 }
 
                 @Override
-                public void setValue(RectFSpringAnim anim, float currentScaleProgress) {
-                    anim.mCurrentScaleProgress = currentScaleProgress;
-                    anim.onUpdate();
+                public void setValue(RectFSpringAnim object, float value) {
+                    object.mCurrentScaleProgress = value;
+                    object.onUpdate();
                 }
             };
 
@@ -106,7 +94,7 @@
     private float mCurrentScaleProgress;
     private FlingSpringAnim mRectXAnim;
     private FlingSpringAnim mRectYAnim;
-    private ValueAnimator mRectScaleAnim;
+    private SpringAnimation mRectScaleAnim;
     private boolean mAnimsStarted;
     private boolean mRectXAnimEnded;
     private boolean mRectYAnimEnded;
@@ -177,17 +165,18 @@
         mRectYAnim = new FlingSpringAnim(this, RECT_Y, startY, endY, startVelocityY,
                 mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener);
 
-        mRectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
-                PropertyValuesHolder.ofFloat(RECT_SCALE_PROGRESS, 1))
-                .setDuration(RECT_SCALE_DURATION);
-        mRectScaleAnim.setInterpolator(DEACCEL);
-        mRectScaleAnim.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mRectScaleAnimEnded = true;
-                maybeOnEnd();
-            }
-        });
+        float minVisibleChange = 1f / mStartRect.height();
+        mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
+                .setSpring(new SpringForce(1f)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                .setStiffness(SpringForce.STIFFNESS_LOW))
+                .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
+                .setMaxValue(1f)
+                .setMinimumVisibleChange(minVisibleChange)
+                .addEndListener((animation, canceled, value, velocity) -> {
+                    mRectScaleAnimEnded = true;
+                    maybeOnEnd();
+                });
 
         mRectXAnim.start();
         mRectYAnim.start();
@@ -202,7 +191,9 @@
         if (mAnimsStarted) {
             mRectXAnim.end();
             mRectYAnim.end();
-            mRectScaleAnim.end();
+            if (mRectScaleAnim.canSkipToEnd()) {
+                mRectScaleAnim.skipToEnd();
+            }
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
new file mode 100644
index 0000000..93b6e4b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 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.quickstep.util;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils.ViewProgressProperty;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.anim.SpringObjectAnimator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+/**
+ * Creates an animation where all the workspace items are moved into their final location,
+ * staggered row by row from the bottom up.
+ * This is used in conjunction with the swipe up to home animation.
+ */
+public class StaggeredWorkspaceAnim {
+
+    private static final int APP_CLOSE_ROW_START_DELAY_MS = 16;
+    private static final int ALPHA_DURATION_MS = 200;
+
+    private static final float MAX_VELOCITY_PX_PER_S = 22f;
+
+    private static final float DAMPING_RATIO =
+            (SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY + SpringForce.DAMPING_RATIO_LOW_BOUNCY) / 2f;
+    private static final float STIFFNESS = SpringForce.STIFFNESS_LOW;
+
+    private final float mVelocity;
+    private final float mSpringTransY;
+    private final View mViewToIgnore;
+
+    private final List<ValueAnimator> mAnimators = new ArrayList<>();
+
+    /**
+     * @param floatingViewOriginalView The FloatingIconView's original view.
+     */
+    public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView,
+            float velocity) {
+        mVelocity = velocity;
+        // We ignore this view since it's visibility and position is controlled by
+        // the FloatingIconView.
+        mViewToIgnore = floatingViewOriginalView;
+
+        // Scale the translationY based on the initial velocity to better sync the workspace items
+        // with the floating view.
+        float transFactor = 0.1f + 0.9f * Math.abs(velocity) / MAX_VELOCITY_PX_PER_S;
+        mSpringTransY = transFactor * launcher.getResources()
+                .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y);;
+
+        DeviceProfile grid = launcher.getDeviceProfile();
+        ShortcutAndWidgetContainer currentPage = ((CellLayout) launcher.getWorkspace()
+                .getChildAt(launcher.getWorkspace().getCurrentPage()))
+                .getShortcutsAndWidgets();
+
+        // Hotseat and QSB takes up two additional rows.
+        int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
+
+        // Set up springs on workspace items.
+        for (int i = currentPage.getChildCount() - 1; i >= 0; i--) {
+            View child = currentPage.getChildAt(i);
+            CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
+            addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows);
+        }
+
+        // Set up springs for the hotseat and qsb.
+        if (grid.isVerticalBarLayout()) {
+            ViewGroup hotseat = (ViewGroup) launcher.getHotseat().getChildAt(0);
+            for (int i = hotseat.getChildCount() - 1; i >= 0; i--) {
+                View child = hotseat.getChildAt(i);
+                CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
+                addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
+            }
+        } else {
+            View hotseat = launcher.getHotseat().getChildAt(0);
+            addStaggeredAnimationForView(hotseat, grid.inv.numRows + 1, totalRows);
+
+            View qsb = launcher.findViewById(R.id.search_container_all_apps);
+            addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows);
+        }
+    }
+
+    /**
+     * Starts the animation.
+     */
+    public void start() {
+        for (Animator a : mAnimators) {
+            if (a instanceof SpringObjectAnimator) {
+                ((SpringObjectAnimator) a).startSpring(1f, mVelocity, null);
+            } else {
+                a.start();
+            }
+        }
+    }
+
+    /**
+     * Adds an alpha/trans animator for {@param v}, with a start delay based on the view's row.
+     *
+     * @param v A view on the workspace.
+     * @param row The bottom-most row that contains the view.
+     * @param totalRows Total number of rows.
+     */
+    private void addStaggeredAnimationForView(View v, int row, int totalRows) {
+        if (v == mViewToIgnore) {
+            return;
+        }
+
+        // Invert the rows, because we stagger starting from the bottom of the screen.
+        int invertedRow = totalRows - row;
+        // Add 1 to the inverted row so that the bottom most row has a start delay.
+        long startDelay = (long) ((invertedRow + 1) * APP_CLOSE_ROW_START_DELAY_MS);
+
+        v.setTranslationY(mSpringTransY);
+        SpringObjectAnimator springTransY = new SpringObjectAnimator<>(
+                new ViewProgressProperty(v, View.TRANSLATION_Y), "staggeredSpringTransY", 1f,
+                DAMPING_RATIO, STIFFNESS, mSpringTransY, 0);
+        springTransY.setStartDelay(startDelay);
+        mAnimators.add(springTransY);
+
+        v.setAlpha(0);
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f);
+        alpha.setInterpolator(LINEAR);
+        alpha.setDuration(ALPHA_DURATION_MS);
+        alpha.setStartDelay(startDelay);
+        mAnimators.add(alpha);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 053b738..f8d454f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -52,6 +52,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -145,11 +146,13 @@
             };
 
     private final TaskOutlineProvider mOutlineProvider;
+    private final FooterOutlineProvider mFooterOutlineProvider;
 
     private Task mTask;
     private TaskThumbnailView mSnapshotView;
     private TaskMenuView mMenuView;
     private IconView mIconView;
+    private View mTaskFooterContainer;
     private DigitalWellBeingToast mDigitalWellBeingToast;
     private float mCurveScale;
     private float mFullscreenProgress;
@@ -180,6 +183,9 @@
         super(context, attrs, defStyleAttr);
         mActivity = BaseDraggingActivity.fromContext(context);
         setOnClickListener((view) -> {
+            if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
+                android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "TaskView onClick");
+            }
             if (getTask() == null) {
                 return;
             }
@@ -203,6 +209,7 @@
         mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
         mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius);
         mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams);
+        mFooterOutlineProvider = new FooterOutlineProvider(mCurrentFullscreenParams);
         setOutlineProvider(mOutlineProvider);
     }
 
@@ -212,6 +219,9 @@
         mSnapshotView = findViewById(R.id.snapshot);
         mIconView = findViewById(R.id.icon);
         mDigitalWellBeingToast = findViewById(R.id.digital_well_being_toast);
+        mTaskFooterContainer = findViewById(R.id.task_footer_container);
+        mTaskFooterContainer.setOutlineProvider(mFooterOutlineProvider);
+        mTaskFooterContainer.setClipToOutline(true);
     }
 
     public TaskMenuView getMenuView() {
@@ -279,6 +289,9 @@
 
     public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
             Handler resultCallbackHandler) {
+        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
+            android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "launchTask");
+        }
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (isRunningTask()) {
                 getRecentsView().finishRecentsAnimation(false /* toRecents */,
@@ -293,6 +306,9 @@
 
     private void launchTaskInternal(boolean animate, boolean freezeTaskList,
             Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
+        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
+            android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "launchTaskInternal");
+        }
         if (mTask != null) {
             final ActivityOptions opts;
             if (animate) {
@@ -410,6 +426,15 @@
                 .getInterpolation(progress);
         mIconView.setScaleX(scale);
         mIconView.setScaleY(scale);
+
+        int footerVerticalOffset = (int) (mTaskFooterContainer.getHeight() * (1.0f - scale));
+        mTaskFooterContainer.setTranslationY(
+                mCurrentFullscreenParams.mCurrentDrawnInsets.bottom +
+                mCurrentFullscreenParams.mCurrentDrawnInsets.top +
+                footerVerticalOffset);
+        mFooterOutlineProvider.setFullscreenDrawParams(
+                mCurrentFullscreenParams, footerVerticalOffset);
+        mTaskFooterContainer.invalidateOutline();
     }
 
     public void setIconScaleAnimStartProgress(float startProgress) {
@@ -550,6 +575,29 @@
         }
     }
 
+    private static final class FooterOutlineProvider extends ViewOutlineProvider {
+
+        private FullscreenDrawParams mFullscreenDrawParams;
+        private int mVerticalOffset;
+        private final Rect mOutlineRect = new Rect();
+
+        FooterOutlineProvider(FullscreenDrawParams params) {
+            mFullscreenDrawParams = params;
+        }
+
+        void setFullscreenDrawParams(FullscreenDrawParams params, int verticalOffset) {
+            mFullscreenDrawParams = params;
+            mVerticalOffset = verticalOffset;
+        }
+
+        @Override
+        public void getOutline(View view, Outline outline) {
+            mOutlineRect.set(0, 0, view.getWidth(), view.getHeight());
+            mOutlineRect.offset(0, -mVerticalOffset);
+            outline.setRoundRect(mOutlineRect, mFullscreenDrawParams.mCurrentDrawnCornerRadius);
+        }
+    }
+
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
@@ -633,12 +681,12 @@
      * @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
      */
     public void setFullscreenProgress(float progress) {
+        progress = Utilities.boundToRange(progress, 0, 1);
         if (progress == mFullscreenProgress) {
             return;
         }
         mFullscreenProgress = progress;
         boolean isFullscreen = mFullscreenProgress > 0;
-        setIconScaleAndDim(progress, true /* invert */);
         mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
         setClipChildren(!isFullscreen);
         setClipToPadding(!isFullscreen);
@@ -662,6 +710,9 @@
                     / (getWidth() + currentInsetsLeft + currentInsetsRight));
         }
 
+        // Some of the items in here are dependent on the current fullscreen params
+        setIconScaleAndDim(progress, true /* invert */);
+
         thumbnail.setFullscreenParams(mCurrentFullscreenParams);
         mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams);
         invalidateOutline();
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index ba4ea8b..1869188 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -44,7 +44,7 @@
         <FrameLayout
             android:id="@+id/proactive_suggest_container"
             android:layout_width="match_parent"
-            android:layout_height="48dp"
+            android:layout_height="wrap_content"
             android:gravity="center"
             android:visibility="gone"
             />
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index 73c7c5c..b036bc1 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -27,7 +27,7 @@
     <string name="accessibility_close_task" msgid="5354563209433803643">"إغلاق"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"إعدادات استخدام التطبيق"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"محو الكل"</string>
-    <string name="accessibility_recent_apps" msgid="4058661986695117371">"التطبيقات التي تمّ استخدامها مؤخرًا"</string>
+    <string name="accessibility_recent_apps" msgid="4058661986695117371">"التطبيقات المستخدمة مؤخرًا"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>، <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"أقل من دقيقة"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"يتبقى اليوم <xliff:g id="TIME">%1$s</xliff:g>."</string>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index 7f4e56d..449cc8c 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -20,7 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Splitscreen"</string>
-    <string name="recent_task_option_pin" msgid="7929860679018978258">"Fixieren"</string>
+    <string name="recent_task_option_pin" msgid="7929860679018978258">"Anpinnen"</string>
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform-Modus"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Übersicht"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Keine kürzlich verwendeten Elemente"</string>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index c6698bb..356d10d 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -27,7 +27,7 @@
     <string name="accessibility_close_task" msgid="5354563209433803643">"Pecha a aplicación"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración do uso de aplicacións"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Borrar todo"</string>
-    <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicacións recentes"</string>
+    <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apps recentes"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Tempo restante hoxe <xliff:g id="TIME">%1$s</xliff:g>"</string>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index e84543b..5c4d6d8 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -25,6 +25,8 @@
 
     <string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
 
+    <string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
+
     <!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
          determines how many thumbnails will be fetched in the background. -->
     <integer name="recentsThumbnailCacheSize">3</integer>
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 95ae312..79540c1 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -278,7 +278,7 @@
         if (launcherClosing) {
             Pair<AnimatorSet, Runnable> launcherContentAnimator =
                     getLauncherContentAnimator(true /* isAppOpening */,
-                            new float[] {0, mContentTransY});
+                            new float[] {0, -mContentTransY});
             anim.play(launcherContentAnimator.first);
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 0c29fcf..6030cea 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
@@ -46,9 +47,11 @@
 import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.systemui.shared.system.QuickStepContract;
 
 /**
  * Touch controller for handling various state transitions in portrait UI.
@@ -135,7 +138,10 @@
         } else if (fromState == OVERVIEW) {
             return isDragTowardPositive ? ALL_APPS : NORMAL;
         } else if (fromState == NORMAL && isDragTowardPositive) {
+            int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher)
+                    .getSystemUiStateFlags();
             return mAllowDragToOverview && TouchInteractionService.isConnected()
+                    && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0
                     ? OVERVIEW : ALL_APPS;
         }
         return fromState;
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 17f88c9..279a946 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -118,6 +118,8 @@
 
         void createActivityController(long transitionLength);
 
+        default void adjustActivityControllerInterpolators() { }
+
         default void onTransitionCancelled() { }
 
         default void setShelfState(ShelfAnimState animState, Interpolator interpolator,
@@ -141,5 +143,9 @@
         @NonNull RectF getWindowTargetRect();
 
         @NonNull AnimatorPlaybackController createActivityAnimationToHome();
+
+        default void playAtomicAnimation(float velocity) {
+            // No-op
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index a2f07e3..b5da836 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -22,6 +22,7 @@
 
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -32,6 +33,7 @@
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
+import com.android.systemui.shared.system.QuickStepContract;
 import java.util.ArrayList;
 
 /**
@@ -56,6 +58,7 @@
     private String mUpdateRegisteredPackage;
     private ActivityControlHelper mActivityControlHelper;
     private Intent mOverviewIntent;
+    private int mSystemUiStateFlags;
 
     public OverviewComponentObserver(Context context) {
         mContext = context;
@@ -71,6 +74,15 @@
         updateOverviewTargets();
     }
 
+    public void onSystemUiStateChanged(int stateFlags) {
+        boolean homeDisabledChanged = (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED)
+                != (stateFlags & SYSUI_STATE_HOME_DISABLED);
+        mSystemUiStateFlags = stateFlags;
+        if (homeDisabledChanged) {
+            updateOverviewTargets();
+        }
+    }
+
     /**
      * Update overview intent and {@link ActivityControlHelper} based off the current launcher home
      * component.
@@ -81,7 +93,8 @@
 
         final String overviewIntentCategory;
         ComponentName overviewComponent;
-        if (defaultHome == null || mMyHomeComponent.equals(defaultHome)) {
+        if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 &&
+                (defaultHome == null || mMyHomeComponent.equals(defaultHome))) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
             overviewComponent = mMyHomeComponent;
             mActivityControlHelper = new LauncherActivityControllerHelper();
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 79d922c..78b48d7 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -53,6 +53,8 @@
     private ISystemUiProxy mISystemUiProxy;
     private float mBackButtonAlpha = 1;
 
+    private int mSystemUiStateFlags;
+
     private OverviewInteractionState(Context context) {
         mContext = context;
 
@@ -83,6 +85,14 @@
         mBgHandler.obtainMessage(MSG_SET_PROXY, proxy).sendToTarget();
     }
 
+    public void setSystemUiStateFlags(int stateFlags) {
+        mSystemUiStateFlags = stateFlags;
+    }
+
+    public int getSystemUiStateFlags() {
+        return mSystemUiStateFlags;
+    }
+
     private boolean handleUiMessage(Message msg) {
         if (msg.what == MSG_SET_BACK_BUTTON_ALPHA) {
             mBackButtonAlpha = (float) msg.obj;
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
new file mode 100644
index 0000000..8951363
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -0,0 +1,36 @@
+package com.android.quickstep;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.launcher3.testing.TestInformationHandler;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.uioverrides.states.OverviewState;
+import com.android.quickstep.util.LayoutUtils;
+
+public class QuickstepTestInformationHandler extends TestInformationHandler {
+
+    public QuickstepTestInformationHandler(Context context) { }
+
+    @Override
+    public Bundle call(String method) {
+        final Bundle response = new Bundle();
+        switch (method) {
+            case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
+                final float swipeHeight =
+                        OverviewState.getDefaultSwipeHeight(mDeviceProfile);
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
+                return response;
+            }
+
+            case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
+                final float swipeHeight =
+                        LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile);
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
+                return response;
+            }
+        }
+
+        return super.call(method);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TestInformationProvider.java b/quickstep/src/com/android/quickstep/TestInformationProvider.java
deleted file mode 100644
index d96f9af..0000000
--- a/quickstep/src/com/android/quickstep/TestInformationProvider.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2019 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.quickstep;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.TestProtocol;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.uioverrides.states.OverviewState;
-import com.android.quickstep.util.LayoutUtils;
-
-public class TestInformationProvider extends ContentProvider {
-    @Override
-    public boolean onCreate() {
-        return true;
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
-        return 0;
-    }
-
-    @Override
-    public int delete(Uri uri, String s, String[] strings) {
-        return 0;
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues contentValues) {
-        return null;
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        return null;
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
-        return null;
-    }
-
-    @Override
-    public Bundle call(String method, String arg, Bundle extras) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            final Bundle response = new Bundle();
-            final Context context = getContext();
-            final DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE.
-                    get(context).getDeviceProfile(context);
-            final LauncherAppState launcherAppState = LauncherAppState.getInstanceNoCreate();
-            final Launcher launcher = launcherAppState != null ?
-                    (Launcher) launcherAppState.getModel().getCallback() : null;
-
-            switch (method) {
-                case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
-                    final float swipeHeight =
-                            OverviewState.getDefaultSwipeHeight(deviceProfile);
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
-                    break;
-                }
-
-                case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
-                    final float swipeHeight =
-                            LayoutUtils.getShelfTrackingDistance(context, deviceProfile);
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
-                    break;
-                }
-
-                case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
-                    if (launcher == null) return null;
-
-                    final float progress = LauncherState.OVERVIEW.getVerticalProgress(launcher)
-                            - LauncherState.ALL_APPS.getVerticalProgress(launcher);
-                    final float distance =
-                            launcher.getAllAppsController().getShiftRange() * progress;
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
-                    break;
-                }
-
-                case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
-                    if (launcher == null) return null;
-
-                    final float progress = LauncherState.NORMAL.getVerticalProgress(launcher)
-                            - LauncherState.ALL_APPS.getVerticalProgress(launcher);
-                    final float distance =
-                            launcher.getAllAppsController().getShiftRange() * progress;
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
-                    break;
-                }
-
-                case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
-                    TestProtocol.sDebugTracing = true;
-                    break;
-
-                case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
-                    TestProtocol.sDebugTracing = false;
-                    break;
-            }
-            return response;
-        }
-        return null;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 893c053..801a560 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -21,6 +21,7 @@
 
 import com.android.launcher3.Alarm;
 import com.android.launcher3.R;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 
 /**
  * Given positions along x- or y-axis, tracks velocity and acceleration and determines when there is
@@ -47,6 +48,7 @@
     private final float mSpeedFast;
     private final Alarm mForcePauseTimeout;
     private final boolean mMakePauseHarderToTrigger;
+    private final Context mContext;
 
     private Long mPreviousTime = null;
     private Float mPreviousPosition = null;
@@ -71,6 +73,7 @@
      * @param makePauseHarderToTrigger Used for gestures that require a more explicit pause.
      */
     public MotionPauseDetector(Context context, boolean makePauseHarderToTrigger) {
+        mContext = context;
         Resources res = context.getResources();
         mSpeedVerySlow = res.getDimension(R.dimen.motion_pause_detector_speed_very_slow);
         mSpeedSlow = res.getDimension(R.dimen.motion_pause_detector_speed_slow);
@@ -165,6 +168,7 @@
         if (mIsPaused != isPaused) {
             mIsPaused = isPaused;
             if (mIsPaused) {
+                AccessibilityManagerCompat.sendPauseDetectedEventToTest(mContext);
                 mHasEverBeenPaused = true;
             }
             if (mOnMotionPauseListener != null) {
diff --git a/quickstep/tests/OWNERS b/quickstep/tests/OWNERS
new file mode 100644
index 0000000..046d871
--- /dev/null
+++ b/quickstep/tests/OWNERS
@@ -0,0 +1 @@
+vadimt@google.com
diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
index 5e20e56..d9fcf4d 100644
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
@@ -68,7 +68,6 @@
 
         // Disable app tracker
         AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
-
         PredictionUiStateManager.INSTANCE.initializeForTesting(null);
 
         mCallback = PredictionUiStateManager.INSTANCE.get(mTargetContext).appPredictorCallback(
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index e552f56..c3e46ea 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -133,7 +133,7 @@
 
                     for (int i = 0; i != 100; ++i) {
                         if (mLauncher.getNavigationModel() == expectedMode) {
-                            Thread.sleep(1000);
+                            Thread.sleep(5000);
                             return;
                         }
                         Thread.sleep(100);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 43d6311..f02859f 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -127,7 +127,7 @@
         assertNotNull("OverviewTask.open returned null", task.open());
         assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject(
                 By.pkg(getAppPackageName()).text("TestActivity2")),
-                LONG_WAIT_TIME_MS));
+                DEFAULT_UI_TIMEOUT));
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",