Merge "Dumping TIS state on failures" into ub-launcher3-rvc-dev
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index c037e44..38adf39 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -19,8 +19,8 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
-import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -34,6 +34,7 @@
 
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -63,9 +64,10 @@
         boolean skipLauncherChanges = !launcherClosing;
 
         TaskView taskView = findTaskViewToLaunch(mLauncher, v, appTargets);
-        Animator recentsAnimator = getRecentsWindowAnimator(taskView, skipLauncherChanges,
-                appTargets, wallpaperTargets, mLauncher.getDepthController());
-        anim.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
+        PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
+        createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
+                mLauncher.getDepthController(), pa);
+        anim.play(pa.buildAnim());
 
         Animator childStateAnimation = null;
         // Found a visible recents task that matches the opening app, lets launch the app from there
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 1fe3643..e2acf93 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.hybridhotseat;
 
+import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 
 import android.animation.Animator;
@@ -571,8 +572,10 @@
 
     @Override
     public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
-        this.mHotSeatItemsCount = profile.numHotseatIcons;
-        createPredictor();
+        if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
+            this.mHotSeatItemsCount = profile.numHotseatIcons;
+            createPredictor();
+        }
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 95087ba..9ae6080 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -94,6 +94,7 @@
         super.onCreate(savedInstanceState);
         if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
             mHotseatPredictionController = new HotseatPredictionController(this);
+            mHotseatPredictionController.createPredictor();
         }
     }
 
@@ -177,14 +178,6 @@
     }
 
     @Override
-    public void finishBindingItems(int pageBoundFirst) {
-        super.finishBindingItems(pageBoundFirst);
-        if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.createPredictor();
-        }
-    }
-
-    @Override
     public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) {
         super.bindPredictedItems(appInfos, ranks);
         if (mHotseatPredictionController != null) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index f4d1629..c18a0fd 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -136,10 +136,13 @@
                         new SyncRtSurfaceTransactionApplierCompat(mActivity.getRootView()));
 
         AnimatedFloat recentsAlpha = new AnimatedFloat(() -> { });
-        params.setBaseAlphaCallback((t, a) -> recentsAlpha.value);
+        params.setBaseBuilderProxy((builder, app, p)
+                -> builder.withAlpha(recentsAlpha.value));
 
         Interpolator taskInterpolator;
         if (targets.isAnimatingHome()) {
+            params.setHomeBuilderProxy((builder, app, p) -> builder.withAlpha(1 - p.getProgress()));
+
             taskInterpolator = TOUCH_RESPONSE_INTERPOLATOR;
             pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1, TOUCH_RESPONSE_INTERPOLATOR);
         } else {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
index 23cc6d5..46799ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
@@ -73,7 +73,6 @@
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
-import com.android.quickstep.util.TransformParams.TargetAlphaProvider;
 import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -671,10 +670,6 @@
     protected InputConsumer createNewInputProxyHandler() {
         endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
         endLauncherTransitionController();
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            // Hide the task view, if not already hidden
-            setTargetAlphaProvider(BaseSwipeUpHandlerV2::getHiddenTargetAlpha);
-        }
 
         StatefulActivity activity = mActivityInterface.getCreatedActivity();
         return activity == null ? InputConsumer.NO_OP
@@ -1257,11 +1252,6 @@
         reset();
     }
 
-    private void setTargetAlphaProvider(TargetAlphaProvider provider) {
-        mTransformParams.setTaskAlphaCallback(provider);
-        updateFinalShift();
-    }
-
     private void addLiveTileOverlay() {
         if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
             mRecentsView.setLiveTileOverlayAttached(true);
@@ -1273,13 +1263,6 @@
         mRecentsView.setLiveTileOverlayAttached(false);
     }
 
-    public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, float expectedAlpha) {
-        if (!isNotInRecents(app)) {
-            return 0;
-        }
-        return expectedAlpha;
-    }
-
     private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
         return app.isNotInRecents
                 || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index 7b614c2..96913c6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -15,23 +15,29 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
+import android.animation.ObjectAnimator;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Matrix;
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.TransformParams.BuilderProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 
 /**
  * Handles the navigation gestures when a 3rd party launcher is the default home activity.
@@ -42,6 +48,9 @@
     private FallbackHomeAnimationFactory mActiveAnimationFactory;
     private final boolean mRunningOverHome;
 
+    private final Matrix mTmpMatrix = new Matrix();
+    private float mMaxLauncherScale = 1;
+
     public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
             boolean continuingLastGesture, InputConsumerController inputConsumer) {
@@ -49,6 +58,31 @@
                 continuingLastGesture, inputConsumer);
 
         mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
+        if (mRunningOverHome) {
+            mTransformParams.setHomeBuilderProxy(this::updateHomeActivityTransformDuringSwipeUp);
+        }
+    }
+
+    @Override
+    protected void initTransitionEndpoints(DeviceProfile dp) {
+        super.initTransitionEndpoints(dp);
+        if (mRunningOverHome) {
+            mMaxLauncherScale = 1 / mTaskViewSimulator.getFullScreenScale();
+        }
+    }
+
+    private void updateHomeActivityTransformDuringSwipeUp(SurfaceParams.Builder builder,
+            RemoteAnimationTargetCompat app, TransformParams params) {
+        setHomeScaleAndAlpha(builder, app, mCurrentShift.value,
+                Utilities.boundToRange(1 - mCurrentShift.value, 0, 1));
+    }
+
+    private void setHomeScaleAndAlpha(SurfaceParams.Builder builder,
+            RemoteAnimationTargetCompat app, float verticalShift, float alpha) {
+        float scale = Utilities.mapRange(verticalShift, 1, mMaxLauncherScale);
+        mTmpMatrix.setScale(scale, scale,
+                app.localBounds.exactCenterX(), app.localBounds.exactCenterY());
+        builder.withMatrix(mTmpMatrix).withAlpha(alpha);
     }
 
     @Override
@@ -85,30 +119,61 @@
         }
     }
 
-    private class FallbackHomeAnimationFactory extends HomeAnimationFactory
-            implements TransformParams.BuilderProxy {
+    private class FallbackHomeAnimationFactory extends HomeAnimationFactory {
 
         private final TransformParams mHomeAlphaParams = new TransformParams();
-        private final AnimatedFloat mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
+        private final AnimatedFloat mHomeAlpha;
+
+        private final AnimatedFloat mVerticalShiftForScale = new AnimatedFloat();
+
+        private final AnimatedFloat mRecentsAlpha = new AnimatedFloat();
 
         private final long mDuration;
         FallbackHomeAnimationFactory(long duration) {
             super(null);
             mDuration = duration;
+
+            if (mRunningOverHome) {
+                mHomeAlpha = new AnimatedFloat();
+                mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1);
+                mVerticalShiftForScale.value = mCurrentShift.value;
+                mTransformParams.setHomeBuilderProxy(
+                        this::updateHomeActivityTransformDuringHomeAnim);
+            } else {
+                mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
+                mHomeAlpha.value = 0;
+
+                mHomeAlphaParams.setHomeBuilderProxy(
+                        this::updateHomeActivityTransformDuringHomeAnim);
+            }
+
+            mRecentsAlpha.value = 1;
+            mTransformParams.setBaseBuilderProxy(
+                    this::updateRecentsActivityTransformDuringHomeAnim);
+        }
+
+        private void updateRecentsActivityTransformDuringHomeAnim(SurfaceParams.Builder builder,
+                RemoteAnimationTargetCompat app, TransformParams params) {
+            builder.withAlpha(mRecentsAlpha.value);
+        }
+
+        private void updateHomeActivityTransformDuringHomeAnim(SurfaceParams.Builder builder,
+                RemoteAnimationTargetCompat app, TransformParams params) {
+            setHomeScaleAndAlpha(builder, app, mVerticalShiftForScale.value, mHomeAlpha.value);
         }
 
         @NonNull
         @Override
         public AnimatorPlaybackController createActivityAnimationToHome() {
             PendingAnimation pa = new PendingAnimation(mDuration);
-            pa.setFloat(mHomeAlpha, AnimatedFloat.VALUE, 1, LINEAR);
+            pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCEL);
             return pa.createPlaybackController();
         }
 
         private void updateHomeAlpha() {
-            mHomeAlphaParams.setProgress(mHomeAlpha.value);
             if (mHomeAlphaParams.getTargetSet() != null) {
-                mHomeAlphaParams.applySurfaceParams(mHomeAlphaParams.createSurfaceParams(this));
+                mHomeAlphaParams.applySurfaceParams(
+                        mHomeAlphaParams.createSurfaceParams(BuilderProxy.NO_OP));
             }
         }
 
@@ -125,7 +190,23 @@
         }
 
         @Override
-        public void onBuildParams(Builder builder, RemoteAnimationTargetCompat app, int targetMode,
-                TransformParams params) { }
+        public void playAtomicAnimation(float velocity) {
+            ObjectAnimator alphaAnim = mHomeAlpha.animateToValue(mHomeAlpha.value, 1);
+            alphaAnim.setDuration(mDuration).setInterpolator(ACCEL);
+            alphaAnim.start();
+
+            if (mRunningOverHome) {
+                // Spring back launcher scale
+                new SpringAnimationBuilder(mContext)
+                        .setStartValue(mVerticalShiftForScale.value)
+                        .setEndValue(0)
+                        .setStartVelocity(-velocity / mTransitionDragLength)
+                        .setMinimumVisibleChange(1f / mDp.heightPx)
+                        .setDampingRatio(0.6f)
+                        .setStiffness(800)
+                        .build(mVerticalShiftForScale, AnimatedFloat.VALUE)
+                        .start();
+            }
+        }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 33b7f12..852a51a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -23,7 +23,7 @@
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
-import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
+import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.animation.Animator;
@@ -34,7 +34,6 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.view.View;
 
@@ -44,13 +43,13 @@
 import com.android.launcher3.LauncherAnimationRunner;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.util.ActivityTracker;
-import com.android.launcher3.util.ObjectWrapper;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
@@ -61,7 +60,6 @@
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -76,8 +74,6 @@
  */
 public final class RecentsActivity extends StatefulActivity<RecentsState> {
 
-    public static final String EXTRA_THUMBNAIL = "thumbnailData";
-    public static final String EXTRA_TASK_ID = "taskID";
     public static final ActivityTracker<RecentsActivity> ACTIVITY_TRACKER =
             new ActivityTracker<>();
 
@@ -117,21 +113,6 @@
 
     @Override
     protected void onNewIntent(Intent intent) {
-        if (intent.getExtras() != null) {
-            int taskID = intent.getIntExtra(EXTRA_TASK_ID, 0);
-            IBinder thumbnail = intent.getExtras().getBinder(EXTRA_THUMBNAIL);
-            if (taskID != 0 && thumbnail instanceof ObjectWrapper) {
-                ObjectWrapper<ThumbnailData> obj = (ObjectWrapper<ThumbnailData>) thumbnail;
-                ThumbnailData thumbnailData = obj.get();
-                mFallbackRecentsView.showCurrentTask(taskID);
-                mFallbackRecentsView.updateThumbnail(taskID, thumbnailData);
-                // Clear the ref since any reference to the extras on the system side will still
-                // hold a reference to the wrapper
-                obj.clear();
-            }
-        }
-        intent.removeExtra(EXTRA_TASK_ID);
-        intent.removeExtra(EXTRA_THUMBNAIL);
         super.onNewIntent(intent);
         ACTIVITY_TRACKER.handleNewIntent(this, intent);
     }
@@ -225,9 +206,10 @@
             RemoteAnimationTargetCompat[] wallpaperTargets) {
         AnimatorSet target = new AnimatorSet();
         boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
-        Animator recentsAnimator = getRecentsWindowAnimator(taskView, !activityClosing, appTargets,
-                wallpaperTargets, null /* depthController */);
-        target.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
+        PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
+        createRecentsWindowAnimator(taskView, !activityClosing, appTargets,
+                wallpaperTargets, null /* depthController */, pa);
+        target.play(pa.buildAnim());
 
         // Found a visible recents task that matches the opening app, lets launch the app from there
         if (activityClosing) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index 9a7a491..c68d6e2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -15,44 +15,46 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
 import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
 import android.graphics.RectF;
+import android.os.Build;
 import android.view.View;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statehandlers.DepthController;
-import com.android.quickstep.util.AppWindowAnimationHelper;
-import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 
 /**
  * Utility class for helpful methods related to {@link TaskView} objects and their tasks.
  */
+@TargetApi(Build.VERSION_CODES.R)
 public final class TaskViewUtils {
 
     private TaskViewUtils() {}
@@ -118,97 +120,111 @@
     }
 
     /**
-     * @return Animator that controls the window of the opening targets for the recents launch
+     * Creates an animation that controls the window of the opening targets for the recents launch
      * animation.
      */
-    public static Animator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
+    public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
             RemoteAnimationTargetCompat[] appTargets,
-            RemoteAnimationTargetCompat[] wallpaperTargets,
-            DepthController depthController) {
-        AppWindowAnimationHelper inOutHelper = new AppWindowAnimationHelper(
-                v.getRecentsView().getPagedViewOrientedState(), v.getContext());
+            RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
+            PendingAnimation out) {
 
         SyncRtSurfaceTransactionApplierCompat applier =
                 new SyncRtSurfaceTransactionApplierCompat(v);
         final RemoteAnimationTargets targets =
                 new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING);
         targets.addDependentTransactionApplier(applier);
-        TransformParams params =
-                new TransformParams()
+
+        TransformParams params = new TransformParams()
                     .setSyncTransactionApplier(applier)
                     .setTargetSet(targets);
 
-        AnimatorSet animatorSet = new AnimatorSet();
         final RecentsView recentsView = v.getRecentsView();
-        final ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
-        appAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
-        appAnimator.addUpdateListener(new MultiValueUpdateListener() {
+        int taskIndex = recentsView.indexOfChild(v);
+        boolean parallaxCenterAndAdjacentTask = taskIndex != recentsView.getCurrentPage();
+        int startScroll = recentsView.getScrollOffset(taskIndex);
 
-            // Defer fading out the view until after the app window gets faded in
-            final FloatProp mViewAlpha = new FloatProp(1f, 0f, 75, 75, LINEAR);
-            final FloatProp mTaskAlpha = new FloatProp(0f, 1f, 0, 75, LINEAR);
-            final RectF mThumbnailRect;
+        Context context = v.getContext();
+        DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
+        // RecentsView never updates the display rotation until swipe-up so the value may be stale.
+        // Use the display value instead.
+        int displayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
 
-            {
-                params.setTaskAlphaCallback((t, alpha) -> mTaskAlpha.value);
-                inOutHelper.prepareAnimation(
-                        BaseActivity.fromContext(v.getContext()).getDeviceProfile());
-                inOutHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(),
-                        targets.apps.length == 0 ? null : targets.apps[0]);
+        TaskViewSimulator topMostSimulator = null;
+        if (targets.apps.length > 0) {
+            TaskViewSimulator tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
+            tsv.setDp(dp);
+            tsv.setLayoutRotation(displayRotation, displayRotation);
+            tsv.setPreview(targets.apps[targets.apps.length - 1]);
+            tsv.fullScreenProgress.value = 0;
+            tsv.recentsViewScale.value = 1;
+            tsv.setScroll(startScroll);
 
-                mThumbnailRect = new RectF(inOutHelper.getTargetRect());
-                mThumbnailRect.offset(-v.getTranslationX(), -v.getTranslationY());
-                Utilities.scaleRectFAboutCenter(mThumbnailRect, 1 / v.getScaleX());
-            }
+            out.setFloat(tsv.fullScreenProgress,
+                    AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
+            out.setFloat(tsv.recentsViewScale,
+                    AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
+            out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
 
-            @Override
-            public void onUpdate(float percent) {
-                // TODO: Take into account the current fullscreen progress for animating the insets
-                params.setProgress(1 - percent);
-                RectF taskBounds;
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                    List<SurfaceParams> surfaceParamsList = new ArrayList<>();
-                    // Append the surface transform params for the app that's being opened.
-                    Collections.addAll(surfaceParamsList, inOutHelper.computeSurfaceParams(params));
+            out.addOnFrameCallback(() -> tsv.apply(params));
+            topMostSimulator = tsv;
+        }
 
-                    AppWindowAnimationHelper liveTileAnimationHelper =
-                            v.getRecentsView().getClipAnimationHelper();
-                    if (liveTileAnimationHelper != null) {
-                        // Append the surface transform params for the live tile app.
-                        TransformParams liveTileParams =
-                                v.getRecentsView().getLiveTileParams(true /* mightNeedToRefill */);
-                        if (liveTileParams != null) {
-                            SurfaceParams[] liveTileSurfaceParams =
-                                    liveTileAnimationHelper.computeSurfaceParams(liveTileParams);
-                            if (liveTileSurfaceParams != null) {
-                                Collections.addAll(surfaceParamsList, liveTileSurfaceParams);
-                            }
-                        }
-                    }
-                    // Apply surface transform using the surface params list.
-                    params.applySurfaceParams(
-                            surfaceParamsList.toArray(new SurfaceParams[surfaceParamsList.size()]));
-                    // Get the task bounds for the app that's being opened after surface transform
-                    // update.
-                    taskBounds = inOutHelper.updateCurrentRect(params);
-                } else {
-                    taskBounds = inOutHelper.applyTransform(params);
+        // Fade in the task during the initial 20% of the animation
+        out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1, clampToProgress(LINEAR, 0, 0.2f));
+
+        if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) {
+            out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
+
+            TaskViewSimulator simulatorToCopy = topMostSimulator;
+            simulatorToCopy.apply(params);
+
+            // Mt represents the overall transformation on the thumbnailView relative to the
+            // Launcher's rootView
+            // K(t) represents transformation on the running window by the taskViewSimulator at
+            // any time t.
+            // at t = 0, we know that the simulator matches the thumbnailView. So if we apply K(0)`
+            // on the Launcher's rootView, the thumbnailView would match the full running task
+            // window. If we apply "K(0)` K(t)" thumbnailView will match the final transformed
+            // window at any time t. This gives the overall matrix on thumbnailView to be:
+            //    Mt K(0)` K(t)
+            // During animation we apply transformation on the thumbnailView (and not the rootView)
+            // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
+            //    Mt K(0)` K(t) Mt`
+            TaskThumbnailView ttv = v.getThumbnail();
+            RectF tvBounds = new RectF(0, 0,  ttv.getWidth(), ttv.getHeight());
+            float[] tvBoundsMapped = new float[]{0, 0,  ttv.getWidth(), ttv.getHeight()};
+            getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
+            RectF tvBoundsInRoot = new RectF(
+                    tvBoundsMapped[0], tvBoundsMapped[1],
+                    tvBoundsMapped[2], tvBoundsMapped[3]);
+
+            Matrix mt = new Matrix();
+            mt.setRectToRect(tvBounds, tvBoundsInRoot, ScaleToFit.FILL);
+
+            Matrix mti = new Matrix();
+            mt.invert(mti);
+
+            Matrix k0i = new Matrix();
+            simulatorToCopy.getCurrentMatrix().invert(k0i);
+
+            Matrix animationMatrix = new Matrix();
+            out.addOnFrameCallback(() -> {
+                animationMatrix.set(mt);
+                animationMatrix.postConcat(k0i);
+                animationMatrix.postConcat(simulatorToCopy.getCurrentMatrix());
+                animationMatrix.postConcat(mti);
+                ttv.setAnimationMatrix(animationMatrix);
+            });
+
+            out.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    ttv.setAnimationMatrix(null);
                 }
+            });
+        }
 
-                int taskIndex = recentsView.indexOfChild(v);
-                int centerTaskIndex = recentsView.getCurrentPage();
-                boolean parallaxCenterAndAdjacentTask = taskIndex != centerTaskIndex;
-                if (!skipViewChanges && parallaxCenterAndAdjacentTask) {
-                    float scale = taskBounds.width() / mThumbnailRect.width();
-                    v.setScaleX(scale);
-                    v.setScaleY(scale);
-                    v.setTranslationX(taskBounds.centerX() - mThumbnailRect.centerX());
-                    v.setTranslationY(taskBounds.centerY() - mThumbnailRect.centerY());
-                    v.setAlpha(mViewAlpha.value);
-                }
-            }
-        });
-        appAnimator.addListener(new AnimatorListenerAdapter() {
+        out.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 targets.release();
@@ -216,12 +232,8 @@
         });
 
         if (depthController != null) {
-            ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController,
-                    DEPTH, BACKGROUND_APP.getDepth(v.getContext()));
-            animatorSet.playTogether(appAnimator, backgroundRadiusAnim);
-        } else {
-            animatorSet.play(appAnimator);
+            out.setFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(context),
+                    TOUCH_RESPONSE_INTERPOLATOR);
         }
-        return animatorSet;
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index ab9ec21..8b08ea7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -112,6 +112,9 @@
     private boolean mPassedWindowMoveSlop;
     // Slop used to determine when we say that the gesture has started.
     private boolean mPassedPilferInputSlop;
+    // Same as mPassedPilferInputSlop, except when continuing a gesture mPassedPilferInputSlop is
+    // initially true while this one is false.
+    private boolean mPassedSlopOnThisGesture;
 
     // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
     private float mStartDisplacement;
@@ -244,6 +247,7 @@
                 mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
                 float displacement = getDisplacement(ev);
                 float displacementX = mLastPos.x - mDownPos.x;
+                float displacementY = mLastPos.y - mDownPos.y;
 
                 if (!mPassedWindowMoveSlop) {
                     if (!mIsDeferredDownTarget) {
@@ -258,11 +262,18 @@
 
                 float horizontalDist = Math.abs(displacementX);
                 float upDist = -displacement;
-                boolean isLikelyToStartNewTask = horizontalDist > upDist;
+                boolean passedSlop = squaredHypot(displacementX, displacementY)
+                        >= mSquaredTouchSlop;
+                if (!mPassedSlopOnThisGesture && passedSlop) {
+                    mPassedSlopOnThisGesture = true;
+                }
+                // Until passing slop, we don't know what direction we're going, so assume we might
+                // be quick switching to avoid translating recents away when continuing the gesture.
+                boolean isLikelyToStartNewTask = !mPassedSlopOnThisGesture
+                        || horizontalDist > upDist;
 
                 if (!mPassedPilferInputSlop) {
-                    float displacementY = mLastPos.y - mDownPos.y;
-                    if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
+                    if (passedSlop) {
                         if (mDisableHorizontalSwipe
                                 && Math.abs(displacementX) > Math.abs(displacementY)) {
                             // Horizontal gesture is not allowed in this region
@@ -339,6 +350,7 @@
             mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
             mActiveCallbacks.addListener(mInteractionHandler);
             mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
+            mInteractionHandler.setIsLikelyToStartNewTask(true);
             notifyGestureStarted();
         } else {
             intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
deleted file mode 100644
index b743d3f..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static com.android.launcher3.Utilities.boundToRange;
-import static com.android.launcher3.Utilities.mapRange;
-import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
-import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Matrix;
-import android.graphics.Matrix.ScaleToFit;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.Build;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskThumbnailView;
-import com.android.systemui.shared.recents.utilities.RectFEvaluator;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-/**
- * Utility class to handle window clip animation
- */
-@TargetApi(Build.VERSION_CODES.P)
-public class AppWindowAnimationHelper implements TransformParams.BuilderProxy {
-
-    // The bounds of the source app in device coordinates
-    private final RectF mSourceStackBounds = new RectF();
-    // The insets of the source app
-    private final Rect mSourceInsets = new Rect();
-    // The source app bounds with the source insets applied, in the device coordinates
-    private final RectF mSourceRect = new RectF();
-    // The bounds of the task view in device coordinates
-    private final RectF mTargetRect = new RectF();
-    // The bounds of the app window (between mSourceRect and mTargetRect) in device coordinates
-    private final RectF mCurrentRect = new RectF();
-    // The insets to be used for clipping the app window, which can be larger than mSourceInsets
-    // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
-    // app window coordinates.
-    private final RectF mSourceWindowClipInsets = new RectF();
-    // The clip rect in source app window coordinates. The app window surface will only be drawn
-    // within these bounds. This clip rect starts at the full mSourceStackBounds, and insets by
-    // mSourceWindowClipInsets as the transform progress goes to 1.
-    private final RectF mCurrentClipRectF = new RectF();
-
-    // The bounds of launcher (not including insets) in device coordinates
-    public final Rect mHomeStackBounds = new Rect();
-    private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
-    private final Matrix mTmpMatrix = new Matrix();
-    private final Rect mTmpRect = new Rect();
-    private final RectF mTmpRectF = new RectF();
-    private RecentsOrientedState mOrientedState;
-    // Corner radius of windows, in pixels
-    private final float mWindowCornerRadius;
-    // Corner radius of windows when they're in overview mode.
-    private final float mTaskCornerRadius;
-    // If windows can have real time rounded corners.
-    private final boolean mSupportsRoundedCornersOnWindows;
-    // Whether or not to actually use the rounded cornders on windows
-    private boolean mUseRoundedCornersOnWindows;
-
-    public AppWindowAnimationHelper(RecentsOrientedState orientedState, Context context) {
-        Resources res = context.getResources();
-        mOrientedState = orientedState;
-        mWindowCornerRadius = getWindowCornerRadius(res);
-        mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(res);
-        mTaskCornerRadius = TaskCornerRadius.get(context);
-        mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows;
-    }
-
-    private void updateSourceStack(RemoteAnimationTargetCompat target) {
-        mSourceInsets.set(target.contentInsets);
-        mSourceStackBounds.set(target.screenSpaceBounds);
-    }
-
-    public void updateTargetRect(Rect targetRect) {
-        mSourceRect.set(mSourceInsets.left, mSourceInsets.top,
-                mSourceStackBounds.width() - mSourceInsets.right,
-                mSourceStackBounds.height() - mSourceInsets.bottom);
-        mTargetRect.set(targetRect);
-        mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
-                mHomeStackBounds.top - mSourceStackBounds.top);
-
-        // Calculate the clip based on the target rect (since the content insets and the
-        // launcher insets may differ, so the aspect ratio of the target rect can differ
-        // from the source rect. The difference between the target rect (scaled to the
-        // source rect) is the amount to clip on each edge.
-        RectF scaledTargetRect = new RectF(mTargetRect);
-        float scale = getSrcToTargetScale();
-        Utilities.scaleRectFAboutCenter(scaledTargetRect, scale);
-
-        scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top);
-        mSourceWindowClipInsets.set(
-                Math.max(scaledTargetRect.left, 0),
-                Math.max(scaledTargetRect.top, 0),
-                Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
-                Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
-        mSourceRect.set(scaledTargetRect);
-    }
-
-    public float getSrcToTargetScale() {
-        return LayoutUtils.getTaskScale(mOrientedState,
-                mSourceRect.width(), mSourceRect.height(),
-                mTargetRect.width(), mTargetRect.height());
-    }
-
-    public void prepareAnimation(DeviceProfile dp) {
-        mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows && !dp.isMultiWindowMode;
-    }
-
-    public RectF applyTransform(TransformParams params) {
-        SurfaceParams[] surfaceParams = computeSurfaceParams(params);
-        if (surfaceParams == null) {
-            return null;
-        }
-        params.applySurfaceParams(surfaceParams);
-        return mCurrentRect;
-    }
-
-    /**
-     * Updates this AppWindowAnimationHelper's state based on the given TransformParams, and returns
-     * the SurfaceParams to apply via {@link SyncRtSurfaceTransactionApplierCompat#applyParams}.
-     */
-    public SurfaceParams[] computeSurfaceParams(TransformParams params) {
-        if (params.getTargetSet() == null) {
-            return null;
-        }
-
-        updateCurrentRect(params);
-        return params.createSurfaceParams(this);
-    }
-
-    @Override
-    public void onBuildParams(Builder builder, RemoteAnimationTargetCompat app,
-            int targetMode, TransformParams params) {
-        Rect crop = mTmpRect;
-        crop.set(app.screenSpaceBounds);
-        crop.offsetTo(0, 0);
-        float cornerRadius = 0f;
-        float scale = Math.max(mCurrentRect.width(), mTargetRect.width()) / crop.width();
-        mTmpMatrix.setTranslate(0, 0);
-        if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
-            mTmpMatrix.setTranslate(app.localBounds.left, app.localBounds.top);
-        }
-        if (app.mode == targetMode
-                && app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
-            mTmpMatrix.setRectToRect(mSourceRect, mCurrentRect, ScaleToFit.FILL);
-            mTmpMatrix.postTranslate(app.localBounds.left, app.localBounds.top);
-            mCurrentClipRectF.roundOut(crop);
-            if (mSupportsRoundedCornersOnWindows) {
-                if (params.getCornerRadius() > -1) {
-                    cornerRadius = params.getCornerRadius();
-                    scale = mCurrentRect.width() / crop.width();
-                } else {
-                    float windowCornerRadius = mUseRoundedCornersOnWindows
-                            ? mWindowCornerRadius : 0;
-                    cornerRadius = mapRange(boundToRange(params.getProgress(), 0, 1),
-                            windowCornerRadius, mTaskCornerRadius);
-                }
-            }
-
-            builder.withMatrix(mTmpMatrix)
-                    .withWindowCrop(crop)
-                    // Since radius is in Surface space, but we draw the rounded corners in screen
-                    // space, we have to undo the scale
-                    .withCornerRadius(cornerRadius / scale);
-
-        }
-    }
-
-    public RectF updateCurrentRect(TransformParams params) {
-        if (params.getCurrentRect() != null) {
-            mCurrentRect.set(params.getCurrentRect());
-        } else {
-            mTmpRectF.set(mTargetRect);
-            mCurrentRect.set(mRectFEvaluator.evaluate(
-                    params.getProgress(), mSourceRect, mTmpRectF));
-        }
-
-        updateClipRect(params);
-        return mCurrentRect;
-    }
-
-    private void updateClipRect(TransformParams params) {
-        // Don't clip past progress > 1.
-        float progress = Math.min(1, params.getProgress());
-        mCurrentClipRectF.left = mSourceWindowClipInsets.left * progress;
-        mCurrentClipRectF.top = mSourceWindowClipInsets.top * progress;
-        mCurrentClipRectF.right =
-                mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress);
-        mCurrentClipRectF.bottom =
-                mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress);
-    }
-
-    public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv,
-            @Nullable RemoteAnimationTargetCompat target) {
-        BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext());
-        BaseDragLayer dl = activity.getDragLayer();
-
-        int[] pos = new int[2];
-        dl.getLocationOnScreen(pos);
-        mHomeStackBounds.set(0, 0, dl.getWidth(), dl.getHeight());
-        mHomeStackBounds.offset(pos[0], pos[1]);
-
-        if (target != null) {
-            updateSourceStack(target);
-        } else  if (rv.shouldUseMultiWindowTaskSizeStrategy()) {
-            updateStackBoundsToMultiWindowTaskSize(activity);
-        } else {
-            mSourceStackBounds.set(mHomeStackBounds);
-            Rect fallback = dl.getInsets();
-            mSourceInsets.set(ttv.getInsets(fallback));
-        }
-
-        Rect targetRect = new Rect();
-        dl.getDescendantRectRelativeToSelf(ttv, targetRect);
-        updateTargetRect(targetRect);
-
-        if (target == null) {
-            // Transform the clip relative to the target rect. Only do this in the case where we
-            // aren't applying the insets to the app windows (where the clip should be in target app
-            // space)
-            float scale = mTargetRect.width() / mSourceRect.width();
-            mSourceWindowClipInsets.left = mSourceWindowClipInsets.left * scale;
-            mSourceWindowClipInsets.top = mSourceWindowClipInsets.top * scale;
-            mSourceWindowClipInsets.right = mSourceWindowClipInsets.right * scale;
-            mSourceWindowClipInsets.bottom = mSourceWindowClipInsets.bottom * scale;
-        }
-    }
-
-    private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
-        SystemUiProxy proxy = SystemUiProxy.INSTANCE.get(activity);
-        if (proxy.isActive()) {
-            mSourceStackBounds.set(proxy.getNonMinimizedSplitScreenSecondaryBounds());
-            return;
-        }
-
-        // Assume that the task size is half screen size (minus the insets and the divider size)
-        DeviceProfile fullDp = activity.getDeviceProfile().getFullScreenProfile();
-        // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
-        // account for system insets
-        int taskWidth = fullDp.availableWidthPx;
-        int taskHeight = fullDp.availableHeightPx;
-        int halfDividerSize = activity.getResources()
-                .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
-
-        Rect insets = new Rect();
-        WindowManagerWrapper.getInstance().getStableInsets(insets);
-        if (fullDp.isLandscape) {
-            taskWidth = taskWidth / 2 - halfDividerSize;
-        } else {
-            taskHeight = taskHeight / 2 - halfDividerSize;
-        }
-
-        // Align the task to bottom left/right edge (closer to nav bar).
-        int left = activity.getDeviceProfile().isSeascape() ? insets.left
-                : (insets.left + fullDp.availableWidthPx - taskWidth);
-        mSourceStackBounds.set(0, 0, taskWidth, taskHeight);
-        mSourceStackBounds.offset(left, insets.top + fullDp.availableHeightPx - taskHeight);
-    }
-
-    public RectF getTargetRect() {
-        return mTargetRect;
-    }
-
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
index 348c22d..3b08675 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -26,6 +26,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.util.IntProperty;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
@@ -47,6 +48,19 @@
  */
 public class TaskViewSimulator implements TransformParams.BuilderProxy {
 
+    public static final IntProperty<TaskViewSimulator> SCROLL =
+            new IntProperty<TaskViewSimulator>("scroll") {
+        @Override
+        public void setValue(TaskViewSimulator simulator, int i) {
+            simulator.setScroll(i);
+        }
+
+        @Override
+        public Integer get(TaskViewSimulator simulator) {
+            return simulator.mScrollState.scroll;
+        }
+    };
+
     private final Rect mTmpCropRect = new Rect();
     private final RectF mTempRectF = new RectF();
     private final float[] mTempPoint = new float[2];
@@ -73,8 +87,8 @@
     private float mCurveScale = 1;
 
     // RecentsView properties
-    public final AnimatedFloat recentsViewScale = new AnimatedFloat(() -> { });
-    public final AnimatedFloat fullScreenProgress = new AnimatedFloat(() -> { });
+    public final AnimatedFloat recentsViewScale = new AnimatedFloat();
+    public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
     private final ScrollState mScrollState = new ScrollState();
     private final int mPageSpacing;
 
@@ -279,4 +293,5 @@
         // Ideally we should use square-root. This is an optimization as one of the dimension is 0.
         return Math.max(Math.abs(mTempPoint[0]), Math.abs(mTempPoint[1]));
     }
+
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
index d837e54..9bb508e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
@@ -15,11 +15,8 @@
  */
 package com.android.quickstep.util;
 
-import android.graphics.RectF;
 import android.util.FloatProperty;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
 import com.android.quickstep.RemoteAnimationTargets;
@@ -43,19 +40,30 @@
         }
     };
 
+    public static FloatProperty<TransformParams> TARGET_ALPHA =
+            new FloatProperty<TransformParams>("targetAlpha") {
+        @Override
+        public void setValue(TransformParams params, float v) {
+            params.setTargetAlpha(v);
+        }
+
+        @Override
+        public Float get(TransformParams params) {
+            return params.getTargetAlpha();
+        }
+    };
+
     private float mProgress;
-    private @Nullable RectF mCurrentRect;
     private float mTargetAlpha;
     private float mCornerRadius;
     private RemoteAnimationTargets mTargetSet;
     private SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
 
-    private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a;
-    private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1;
+    private BuilderProxy mHomeBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
+    private BuilderProxy mBaseBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
 
     public TransformParams() {
         mProgress = 0;
-        mCurrentRect = null;
         mTargetAlpha = 1;
         mCornerRadius = -1;
     }
@@ -81,17 +89,6 @@
     }
 
     /**
-     * Sets the current rect to show the transformed window, in device coordinates. This gives
-     * the caller manual control of where to show the window. If unspecified (null), we
-     * interpolate between {@link AppWindowAnimationHelper#mSourceRect} and
-     * {@link AppWindowAnimationHelper#mTargetRect}, based on {@link #mProgress}.
-     */
-    public TransformParams setCurrentRect(RectF currentRect) {
-        mCurrentRect = currentRect;
-        return this;
-    }
-
-    /**
      * Specifies the alpha of the transformed window. Default is 1.
      */
     public TransformParams setTargetAlpha(float targetAlpha) {
@@ -121,18 +118,20 @@
     }
 
     /**
-     * Sets an alternate function which can be used to control the alpha of target app
+     * Sets an alternate function to control transform for non-target apps. The default
+     * implementation keeps the targets visible with alpha=1
      */
-    public TransformParams setTaskAlphaCallback(TargetAlphaProvider callback) {
-        mTaskAlphaCallback = callback;
+    public TransformParams setBaseBuilderProxy(BuilderProxy proxy) {
+        mBaseBuilderProxy = proxy;
         return this;
     }
 
     /**
-     * Sets an alternate function which can be used to control the alpha of non-target app
+     * Sets an alternate function to control transform for home target. The default
+     * implementation keeps the targets visible with alpha=1
      */
-    public TransformParams setBaseAlphaCallback(TargetAlphaProvider callback) {
-        mBaseAlphaCallback = callback;
+    public TransformParams setHomeBuilderProxy(BuilderProxy proxy) {
+        mHomeBuilderProxy = proxy;
         return this;
     }
 
@@ -143,25 +142,24 @@
             RemoteAnimationTargetCompat app = targets.unfilteredApps[i];
             SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash);
 
-            float progress = Utilities.boundToRange(getProgress(), 0, 1);
-            float alpha;
             if (app.mode == targets.targetMode) {
-                alpha = mTaskAlphaCallback.getAlpha(app, getTargetAlpha());
-                if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+                if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+                    mHomeBuilderProxy.onBuildTargetParams(builder, app, this);
+                } else {
                     // Fade out Assistant overlay.
                     if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT
                             && app.isNotInRecents) {
-                        alpha = 1 - Interpolators.DEACCEL_2_5.getInterpolation(progress);
+                        float progress = Utilities.boundToRange(getProgress(), 0, 1);
+                        builder.withAlpha(1 - Interpolators.DEACCEL_2_5.getInterpolation(progress));
+                    } else {
+                        builder.withAlpha(getTargetAlpha());
                     }
-                } else if (targets.hasRecents) {
-                    // If home has a different target then recents, reverse anim the
-                    // home target.
-                    alpha = 1 - (progress * getTargetAlpha());
+
+                    proxy.onBuildTargetParams(builder, app, this);
                 }
             } else {
-                alpha = mBaseAlphaCallback.getAlpha(app, progress);
+                mBaseBuilderProxy.onBuildTargetParams(builder, app, this);
             }
-            proxy.onBuildParams(builder.withAlpha(alpha), app, targets.targetMode, this);
             surfaceParams[i] = builder.build();
         }
         return surfaceParams;
@@ -173,11 +171,6 @@
         return mProgress;
     }
 
-    @Nullable
-    public RectF getCurrentRect() {
-        return mCurrentRect;
-    }
-
     public float getTargetAlpha() {
         return mTargetAlpha;
     }
@@ -203,21 +196,13 @@
         }
     }
 
-    public interface TargetAlphaProvider {
-        float getAlpha(RemoteAnimationTargetCompat target, float expectedAlpha);
-    }
-
+    @FunctionalInterface
     public interface BuilderProxy {
 
-        default void onBuildParams(SurfaceParams.Builder builder,
-                RemoteAnimationTargetCompat app, int targetMode, TransformParams params) {
-            if (app.mode == targetMode
-                    && app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
-                onBuildTargetParams(builder, app, params);
-            }
-        }
+        BuilderProxy NO_OP = (builder, app, params) -> { };
+        BuilderProxy ALWAYS_VISIBLE = (builder, app, params) ->builder.withAlpha(1);
 
-        default void onBuildTargetParams(SurfaceParams.Builder builder,
-                RemoteAnimationTargetCompat app, TransformParams params) { }
+        void onBuildTargetParams(SurfaceParams.Builder builder,
+                RemoteAnimationTargetCompat app, TransformParams params);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 3d89403..68cc481 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -157,9 +157,8 @@
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (tv.isRunningTask()) {
                 mTransformParams.setProgress(1 - progress)
-                        .setCurrentRect(null)
                         .setSyncTransactionApplier(mSyncTransactionApplier);
-                mAppWindowAnimationHelper.applyTransform(mTransformParams);
+                // TODO: Revisit live tiles
             } else {
                 redrawLiveTile(true);
             }
@@ -191,18 +190,10 @@
     }
 
     @Override
-    public void redrawLiveTile(boolean mightNeedToRefill) {
-        TransformParams transformParams = getLiveTileParams(mightNeedToRefill);
-        if (transformParams != null) {
-            mAppWindowAnimationHelper.applyTransform(transformParams);
-        }
-    }
-
-    @Override
     public TransformParams getLiveTileParams(
             boolean mightNeedToRefill) {
         if (!mEnableDrawingLiveTile || mRecentsAnimationController == null
-                || mRecentsAnimationTargets == null || mAppWindowAnimationHelper == null) {
+                || mRecentsAnimationTargets == null) {
             return null;
         }
         TaskView taskView = getRunningTaskView();
@@ -222,9 +213,7 @@
             if (mightNeedToRefill && offsetY > 0) {
                 mTempRect.top -= offsetY;
             }
-            mTempRectF.set(mTempRect);
             mTransformParams.setProgress(1f)
-                    .setCurrentRect(mTempRectF)
                     .setTargetAlpha(taskView.getAlpha())
                     .setSyncTransactionApplier(mSyncTransactionApplier)
                     .setTargetSet(mRecentsAnimationTargets);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 3273e85..2097b1d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -63,7 +63,6 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -126,7 +125,6 @@
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.ViewUtils;
-import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.SplitScreenBounds;
@@ -214,15 +212,12 @@
     protected final BaseActivityInterface mSizeStrategy;
     protected RecentsAnimationController mRecentsAnimationController;
     protected RecentsAnimationTargets mRecentsAnimationTargets;
-    protected AppWindowAnimationHelper mAppWindowAnimationHelper;
     protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
     protected int mTaskWidth;
     protected int mTaskHeight;
     protected boolean mEnableDrawingLiveTile = false;
     protected final Rect mTempRect = new Rect();
-    protected final RectF mTempRectF = new RectF();
     private final PointF mTempPointF = new PointF();
-    private final float[] mTempFloatPoint = new float[2];
 
     private static final int DISMISS_TASK_DURATION = 300;
     private static final int ADDITION_TASK_DURATION = 200;
@@ -980,7 +975,6 @@
 
         mRecentsAnimationController = null;
         mRecentsAnimationTargets = null;
-        mAppWindowAnimationHelper = null;
 
         unloadVisibleTaskData();
         setCurrentPage(0);
@@ -2009,11 +2003,6 @@
         mRecentsAnimationTargets = recentsAnimationTargets;
     }
 
-    // TODO: To be removed in a follow up CL
-    public void setAppWindowAnimationHelper(AppWindowAnimationHelper appWindowAnimationHelper) {
-        mAppWindowAnimationHelper = appWindowAnimationHelper;
-    }
-
     public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
         mLiveTileOverlayAttached = liveTileOverlayAttached;
     }
@@ -2096,13 +2085,20 @@
 
 
     /**
-     * @return How many pixels the running task is offset on the x-axis due to the current scrollX.
+     * @return How many pixels the running task is offset on the currently laid out dominant axis.
      */
     public int getScrollOffset() {
-        if (getRunningTaskIndex() == -1) {
+        return getScrollOffset(getRunningTaskIndex());
+    }
+
+    /**
+     * @return How many pixels the page is offset on the currently laid out dominant axis.
+     */
+    public int getScrollOffset(int pageIndex) {
+        if (pageIndex == -1) {
             return 0;
         }
-        return getScrollForPage(getRunningTaskIndex()) - mOrientationHandler.getPrimaryScroll(this);
+        return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this);
     }
 
     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
@@ -2134,10 +2130,6 @@
         };
     }
 
-    public AppWindowAnimationHelper getClipAnimationHelper() {
-        return mAppWindowAnimationHelper;
-    }
-
     public TransformParams getLiveTileParams(
             boolean mightNeedToRefill) {
         return null;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index e3c1b42..78ac1cc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -444,7 +444,6 @@
             int thumbnailRotation = thumbnailData.rotation;
             int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
 
-            Rect deviceInsets = dp.getInsets();
             // Landscape vs portrait change
             boolean windowingModeSupportsRotation = !dp.isMultiWindowMode
                     && thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
@@ -463,9 +462,16 @@
                         : canvasWidth / thumbnailWidth;
             }
 
+            Rect splitScreenInsets = dp.getInsets();
             if (!isRotated) {
                 // No Rotation
-                mClippedInsets.offsetTo(deviceInsets.left * scale, deviceInsets.top * scale);
+                if (dp.isMultiWindowMode) {
+                    mClippedInsets.offsetTo(splitScreenInsets.left * scale,
+                            splitScreenInsets.top * scale);
+                } else {
+                    mClippedInsets.offsetTo(thumbnailInsets.left * scale,
+                            thumbnailInsets.top * scale);
+                }
                 mMatrix.setTranslate(
                         -thumbnailInsets.left * scale,
                         -thumbnailInsets.top * scale);
@@ -486,8 +492,8 @@
             mClippedInsets.top *= thumbnailScale;
 
             if (dp.isMultiWindowMode) {
-                mClippedInsets.right = deviceInsets.right * scale * thumbnailScale;
-                mClippedInsets.bottom = deviceInsets.bottom * scale * thumbnailScale;
+                mClippedInsets.right = splitScreenInsets.right * scale * thumbnailScale;
+                mClippedInsets.bottom = splitScreenInsets.bottom * scale * thumbnailScale;
             } else {
                 mClippedInsets.right = Math.max(0,
                         widthWithInsets - mClippedInsets.left - canvasWidth);
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index e718598..25c07f1 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -32,6 +32,7 @@
 import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
+import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -647,6 +648,9 @@
      */
     @Override
     public void registerRemoteAnimations() {
+        if (SEPARATE_RECENTS_ACTIVITY.get()) {
+            return;
+        }
         if (hasControlRemoteAppTransitionPermission()) {
             mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */);
 
@@ -677,6 +681,9 @@
      */
     @Override
     public void unregisterRemoteAnimations() {
+        if (SEPARATE_RECENTS_ACTIVITY.get()) {
+            return;
+        }
         if (hasControlRemoteAppTransitionPermission()) {
             new ActivityCompat(mLauncher).unregisterRemoteAnimations();
 
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index c3b90e3..f7e8781 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -38,11 +38,17 @@
                 }
             };
 
+    private static final Runnable NO_OP = () -> { };
+
     private final Runnable mUpdateCallback;
     private ObjectAnimator mValueAnimator;
 
     public float value;
 
+    public AnimatedFloat() {
+        this(NO_OP);
+    }
+
     public AnimatedFloat(Runnable updateCallback) {
         mUpdateCallback = updateCallback;
     }
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index ffa41fd..188072a 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -48,7 +48,7 @@
 
         NEW_TASK(false, ContainerType.APP, true),
 
-        LAST_TASK(false, ContainerType.APP, false);
+        LAST_TASK(false, ContainerType.APP, true);
 
         GestureEndTarget(boolean isLauncher, int containerType,
                 boolean recentsAttachedToAppWindow) {
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0449d0c..07f838b 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -20,6 +20,7 @@
 import static android.content.Intent.ACTION_PACKAGE_CHANGED;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 
+import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 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_ASSIST_GESTURE_CONSTRAINED;
@@ -138,6 +139,13 @@
             mActivityInterface.onAssistantVisibilityChanged(0.f);
         }
 
+        if (SEPARATE_RECENTS_ACTIVITY.get()) {
+            mIsDefaultHome = false;
+            if (defaultHome == null) {
+                defaultHome = mMyHomeIntent.getComponent();
+            }
+        }
+
         if (!mDeviceState.isHomeDisabled() && (defaultHome == null || mIsDefaultHome)) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
             mActivityInterface = LauncherActivityInterface.INSTANCE;
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 8889560..a950597 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -22,6 +22,8 @@
 import android.content.Context;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logger.LauncherAtom;
@@ -43,9 +45,9 @@
  * This class calls StatsLog compile time generated methods.
  *
  * To see if the logs are properly sent to statsd, execute following command.
- * $ adb root && adb shell statsd
- * $ adb shell cmd stats print-logs
- * $ adb logcat | grep statsd  OR $ adb logcat -b stats
+ * $ wwdebug (to turn on the logcat printout)
+ * $ wwlogcat (see logcat with grep filter on)
+ * $ statsd_testdrive (see how ww is writing the proto to statsd buffer)
  */
 public class StatsLogCompatManager extends StatsLogManager {
 
@@ -54,10 +56,8 @@
 
     private static Context sContext;
 
-    private static final int DEFAULT_WIDGET_SPAN_XY = 1;
-    private static final int DEFAULT_WORKSPACE_GRID_XY = -1;
-    private static final int DEFAULT_PAGE_INDEX = -2;
     private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
+    private static final int FOLDER_HIERARCHY_OFFSET = 100;
 
     public StatsLogCompatManager(Context context) {
         sContext = context;
@@ -83,19 +83,22 @@
      * Logs an event and accompanying {@link ItemInfo}.
      */
     @Override
-    public void log(LauncherEvent event, LauncherAtom.ItemInfo itemInfo) {
-        log(event, DEFAULT_INSTANCE_ID, itemInfo);
+    public void log(LauncherEvent event, @Nullable LauncherAtom.ItemInfo info) {
+        logInternal(event, DEFAULT_INSTANCE_ID, info != null ? info
+                : LauncherAtom.ItemInfo.getDefaultInstance(),
+                SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME,
+                SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND);
     }
 
     /**
      * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}.
      */
-    @Override
-    public void log(LauncherEvent event, InstanceId instanceId, LauncherAtom.ItemInfo itemInfo) {
+    private void logInternal(LauncherEvent event, InstanceId instanceId,
+            LauncherAtom.ItemInfo info, int startState, int endState) {
         if (IS_VERBOSE) {
             Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID
-                    ? String.format("\n%s\n%s", event.name(), itemInfo)
-                    : String.format("%s(InstanceId:%s)\n%s", event.name(), instanceId, itemInfo));
+                    ? String.format("\n%s\n%s", event.name(), info)
+                    : String.format("%s(InstanceId:%s)\n%s", event.name(), instanceId, info));
         }
 
         if (!Utilities.ATLEAST_R) {
@@ -105,25 +108,25 @@
         SysUiStatsLog.write(
                 SysUiStatsLog.LAUNCHER_EVENT,
                 SysUiStatsLog.LAUNCHER_UICHANGED__ACTION__DEFAULT_ACTION /* deprecated */,
-                SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME /* TODO */,
-                SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND /* TODO */,
+                startState,
+                endState,
                 null /* launcher extensions, deprecated */,
                 false /* quickstep_enabled, deprecated */,
                 event.getId() /* event_id */,
-                itemInfo.getItemCase().getNumber() /* target_id */,
+                info.getItemCase().getNumber() /* target_id */,
                 instanceId.getId() /* instance_id TODO */,
                 0 /* uid TODO */,
-                getPackageName(itemInfo) /* package_name */,
-                getComponentName(itemInfo) /* component_name */,
-                getGridX(itemInfo, false) /* grid_x */,
-                getGridY(itemInfo, false) /* grid_y */,
-                getPageId(itemInfo, false) /* page_id */,
-                getGridX(itemInfo, true) /* grid_x_parent */,
-                getGridY(itemInfo, true) /* grid_y_parent */,
-                getPageId(itemInfo, true) /* page_id_parent */,
-                getHierarchy(itemInfo) /* hierarchy */,
-                itemInfo.getIsWork() /* is_work_profile */,
-                itemInfo.getRank() /* rank */,
+                getPackageName(info) /* package_name */,
+                getComponentName(info) /* component_name */,
+                getGridX(info, false) /* grid_x */,
+                getGridY(info, false) /* grid_y */,
+                getPageId(info, false) /* page_id */,
+                getGridX(info, true) /* grid_x_parent */,
+                getGridY(info, true) /* grid_y_parent */,
+                getPageId(info, true) /* page_id_parent */,
+                getHierarchy(info) /* hierarchy */,
+                info.getIsWork() /* is_work_profile */,
+                info.getRank() /* rank */,
                 0 /* fromState */,
                 0 /* toState */,
                 null /* edittext */,
@@ -164,144 +167,104 @@
         }
     }
 
-    private static void writeSnapshot(LauncherAtom.ItemInfo itemInfo) {
+    private static void writeSnapshot(LauncherAtom.ItemInfo info) {
         if (IS_VERBOSE) {
-            Log.d(TAG, "\nwriteSnapshot:" + itemInfo);
+            Log.d(TAG, "\nwriteSnapshot:" + info);
         }
         if (!Utilities.ATLEAST_R) {
             return;
         }
         SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_SNAPSHOT,
                 0 /* event_id */,
-                itemInfo.getItemCase().getNumber() /* target_id */,
+                info.getItemCase().getNumber() /* target_id */,
                 0 /* instance_id */,
                 0 /* uid */,
-                getPackageName(itemInfo) /* package_name */,
-                getComponentName(itemInfo) /* component_name */,
-                getGridX(itemInfo, false) /* grid_x */,
-                getGridY(itemInfo, false) /* grid_y */,
-                getPageId(itemInfo, false) /* page_id */,
-                getGridX(itemInfo, true) /* grid_x_parent */,
-                getGridY(itemInfo, true) /* grid_y_parent */,
-                getPageId(itemInfo, true) /* page_id_parent */,
-                getHierarchy(itemInfo) /* hierarchy */,
-                itemInfo.getIsWork() /* is_work_profile */,
+                getPackageName(info) /* package_name */,
+                getComponentName(info) /* component_name */,
+                getGridX(info, false) /* grid_x */,
+                getGridY(info, false) /* grid_y */,
+                getPageId(info, false) /* page_id */,
+                getGridX(info, true) /* grid_x_parent */,
+                getGridY(info, true) /* grid_y_parent */,
+                getPageId(info, true) /* page_id_parent */,
+                getHierarchy(info) /* hierarchy */,
+                info.getIsWork() /* is_work_profile */,
                 0 /* origin TODO */,
                 0 /* cardinality */,
-                getSpanX(itemInfo),
-                getSpanY(itemInfo));
+                info.getWidget().getSpanX(),
+                info.getWidget().getSpanY());
     }
 
-    private static int getSpanX(LauncherAtom.ItemInfo atomInfo) {
-        if (atomInfo.getItemCase() != WIDGET) {
-            return DEFAULT_WIDGET_SPAN_XY;
-        }
-        return atomInfo.getWidget().getSpanX();
-    }
-
-    private static int getSpanY(LauncherAtom.ItemInfo atomInfo) {
-        if (atomInfo.getItemCase() != WIDGET) {
-            return DEFAULT_WIDGET_SPAN_XY;
-        }
-        return atomInfo.getWidget().getSpanY();
-    }
-
-    private static String getPackageName(LauncherAtom.ItemInfo atomInfo) {
-        switch (atomInfo.getItemCase()) {
+    private static String getPackageName(LauncherAtom.ItemInfo info) {
+        switch (info.getItemCase()) {
             case APPLICATION:
-                return atomInfo.getApplication().getPackageName();
+                return info.getApplication().getPackageName();
             case SHORTCUT:
-                return atomInfo.getShortcut().getShortcutName();
+                return info.getShortcut().getShortcutName();
             case WIDGET:
-                return atomInfo.getWidget().getPackageName();
+                return info.getWidget().getPackageName();
             case TASK:
-                return atomInfo.getTask().getPackageName();
+                return info.getTask().getPackageName();
             default:
                 return null;
         }
     }
 
-    private static String getComponentName(LauncherAtom.ItemInfo atomInfo) {
-        switch (atomInfo.getItemCase()) {
+    private static String getComponentName(LauncherAtom.ItemInfo info) {
+        switch (info.getItemCase()) {
             case APPLICATION:
-                return atomInfo.getApplication().getComponentName();
+                return info.getApplication().getComponentName();
             case SHORTCUT:
-                return atomInfo.getShortcut().getShortcutName();
+                return info.getShortcut().getShortcutName();
             case WIDGET:
-                return atomInfo.getWidget().getComponentName();
+                return info.getWidget().getComponentName();
             case TASK:
-                return atomInfo.getTask().getComponentName();
+                return info.getTask().getComponentName();
             default:
                 return null;
         }
     }
 
     private static int getGridX(LauncherAtom.ItemInfo info, boolean parent) {
-        switch (info.getContainerInfo().getContainerCase()) {
-            case WORKSPACE:
-                if (parent) {
-                    return DEFAULT_WORKSPACE_GRID_XY;
-                } else {
-                    return info.getContainerInfo().getWorkspace().getGridX();
-                }
-            case FOLDER:
-                if (parent) {
-                    switch (info.getContainerInfo().getFolder().getParentContainerCase()) {
-                        case WORKSPACE:
-                            return info.getContainerInfo().getFolder().getWorkspace().getGridX();
-                        default:
-                            return DEFAULT_WORKSPACE_GRID_XY;
-                    }
-                } else {
-                    return info.getContainerInfo().getFolder().getGridX();
-                }
-            default:
-                return DEFAULT_WORKSPACE_GRID_XY;
+        if (info.getContainerInfo().getContainerCase() == FOLDER) {
+            if (parent) {
+                return info.getContainerInfo().getFolder().getWorkspace().getGridX();
+            } else {
+                return info.getContainerInfo().getFolder().getGridX();
+            }
+        } else {
+            return info.getContainerInfo().getWorkspace().getGridX();
         }
     }
 
     private static int getGridY(LauncherAtom.ItemInfo info, boolean parent) {
-        switch (info.getContainerInfo().getContainerCase()) {
-            case WORKSPACE:
-                if (parent) {
-                    return DEFAULT_WORKSPACE_GRID_XY;
-                } else {
-                    return info.getContainerInfo().getWorkspace().getGridY();
-                }
-            case FOLDER:
-                if (parent) {
-                    switch (info.getContainerInfo().getFolder().getParentContainerCase()) {
-                        case WORKSPACE:
-                            return info.getContainerInfo().getFolder().getWorkspace().getGridY();
-                        default:
-                            return DEFAULT_WORKSPACE_GRID_XY;
-                    }
-                } else {
-                    return info.getContainerInfo().getFolder().getGridY();
-                }
-            default:
-                return DEFAULT_WORKSPACE_GRID_XY;
+        if (info.getContainerInfo().getContainerCase() == FOLDER) {
+            if (parent) {
+                return info.getContainerInfo().getFolder().getWorkspace().getGridY();
+            } else {
+                return info.getContainerInfo().getFolder().getGridY();
+            }
+        } else {
+            return info.getContainerInfo().getWorkspace().getGridY();
         }
     }
 
     private static int getPageId(LauncherAtom.ItemInfo info, boolean parent) {
-        switch (info.getContainerInfo().getContainerCase()) {
-            case HOTSEAT:
-                return info.getContainerInfo().getHotseat().getIndex();
-            case WORKSPACE:
-                return info.getContainerInfo().getWorkspace().getPageIndex();
-            default:
-                return DEFAULT_PAGE_INDEX;
+        if (info.getContainerInfo().getContainerCase() == FOLDER) {
+            if (parent) {
+                return info.getContainerInfo().getFolder().getWorkspace().getPageIndex();
+            } else {
+                return info.getContainerInfo().getFolder().getPageIndex();
+            }
+        } else {
+            return info.getContainerInfo().getWorkspace().getPageIndex();
         }
     }
 
-    /**
-     *
-     */
     private static int getHierarchy(LauncherAtom.ItemInfo info) {
-        // TODO
         if (info.getContainerInfo().getContainerCase() == FOLDER) {
-            return info.getContainerInfo().getFolder().getParentContainerCase().getNumber() + 100;
+            return info.getContainerInfo().getFolder().getParentContainerCase().getNumber()
+                    + FOLDER_HIERARCHY_OFFSET;
         } else {
             return info.getContainerInfo().getContainerCase().getNumber();
         }
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 226a924..9ac370f 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -1,5 +1,6 @@
 package com.android.launcher3;
 
+import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 import static com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ROOT_VIEW;
 
@@ -177,7 +178,7 @@
 
     @TargetApi(Build.VERSION_CODES.Q)
     public void setDisallowBackGesture(boolean disallowBackGesture) {
-        if (!Utilities.ATLEAST_Q) {
+        if (!Utilities.ATLEAST_Q || SEPARATE_RECENTS_ACTIVITY.get()) {
             return;
         }
         mDisallowBackGesture = disallowBackGesture;
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index db2a6cd..31bfc09 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.HintState;
 import com.android.launcher3.states.SpringLoadedState;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.states.AllAppsState;
 import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -260,7 +261,7 @@
 
     @Override
     public String toString() {
-        return "Ordinal-" + ordinal;
+        return TestProtocol.stateOrdinalToString(ordinal);
     }
 
     public void onBackPressed(Launcher launcher) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 359190c..dc1ff66 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1198,6 +1198,8 @@
             if (mIsBeingDragged) {
                 final int activePointerId = mActivePointerId;
                 final int pointerIndex = ev.findPointerIndex(activePointerId);
+                if (pointerIndex == -1) return true;
+
                 final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev,
                     pointerIndex);
                 final VelocityTracker velocityTracker = mVelocityTracker;
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 869dbbc..376b531 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -36,8 +36,6 @@
     private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
 
     public static final String FLAGS_PREF_NAME = "featureFlags";
-    public static final String FLAG_ENABLE_FIXED_ROTATION_TRANSFORM =
-            "ENABLE_FIXED_ROTATION_TRANSFORM";
 
     private FeatureFlags() { }
 
@@ -168,6 +166,10 @@
             "ENABLE_ALL_APPS_EDU", true,
             "Shows user a tutorial on how to get to All Apps after X amount of attempts.");
 
+    public static final BooleanFlag SEPARATE_RECENTS_ACTIVITY = getDebugFlag(
+            "SEPARATE_RECENTS_ACTIVITY", false,
+            "Uses a separate recents activity instead of using the integrated recents+Launcher UI");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 4439284..e85b056 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -31,6 +31,7 @@
 import android.graphics.Path;
 import android.graphics.PathMeasure;
 import android.graphics.Rect;
+import android.util.Pair;
 import android.util.Property;
 import android.util.SparseArray;
 
@@ -73,7 +74,8 @@
 
     private static final float SMALL_SCALE = 0.6f;
 
-    private static final SparseArray<WeakReference<Bitmap>> sShadowCache = new SparseArray<>();
+    private static final SparseArray<WeakReference<Pair<Path, Bitmap>>> sShadowCache =
+            new SparseArray<>();
 
     private final Matrix mTmpMatrix = new Matrix();
     private final PathMeasure mPathMeasure = new PathMeasure();
@@ -81,7 +83,7 @@
     private final ItemInfoWithIcon mItem;
 
     // Path in [0, 100] bounds.
-    private final Path mProgressPath;
+    private final Path mShapePath;
 
     private final Path mScaledTrackPath;
     private final Path mScaledProgressPath;
@@ -105,7 +107,7 @@
     public PreloadIconDrawable(ItemInfoWithIcon info, Context context) {
         super(info.bitmap);
         mItem = info;
-        mProgressPath = getShapePath();
+        mShapePath = getShapePath();
         mScaledTrackPath = new Path();
         mScaledProgressPath = new Path();
 
@@ -127,7 +129,7 @@
                 bounds.left + PROGRESS_WIDTH + PROGRESS_GAP,
                 bounds.top + PROGRESS_WIDTH + PROGRESS_GAP);
 
-        mProgressPath.transform(mTmpMatrix, mScaledTrackPath);
+        mShapePath.transform(mTmpMatrix, mScaledTrackPath);
         float scale = bounds.width() / DEFAULT_PATH_SIZE;
         mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale);
 
@@ -141,8 +143,9 @@
 
     private Bitmap getShadowBitmap(int width, int height, float shadowRadius) {
         int key = (width << 16) | height;
-        WeakReference<Bitmap> shadowRef = sShadowCache.get(key);
-        Bitmap shadow = shadowRef != null ? shadowRef.get() : null;
+        WeakReference<Pair<Path, Bitmap>> shadowRef = sShadowCache.get(key);
+        Pair<Path, Bitmap> cache = shadowRef != null ? shadowRef.get() : null;
+        Bitmap shadow = cache != null && cache.first.equals(mShapePath) ? cache.second : null;
         if (shadow != null) {
             return shadow;
         }
@@ -155,7 +158,7 @@
         mProgressPaint.clearShadowLayer();
         c.setBitmap(null);
 
-        sShadowCache.put(key, new WeakReference<>(shadow));
+        sShadowCache.put(key, new WeakReference<>(Pair.create(mShapePath, shadow)));
         return shadow;
     }
 
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 475305f..4c2c79d 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -17,6 +17,8 @@
 
 import android.content.Context;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.R;
 import com.android.launcher3.logger.LauncherAtom.ItemInfo;
 import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
@@ -125,13 +127,13 @@
     /**
      * Logs an event and accompanying {@link ItemInfo}.
      */
-    public void log(LauncherEvent event, ItemInfo itemInfo) {
+    public void log(LauncherEvent event, @Nullable ItemInfo info) {
     }
 
     /**
      * Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}.
      */
-    public void log(LauncherEvent event, InstanceId instanceId, ItemInfo itemInfo) {
+    public void log(LauncherEvent event, InstanceId instanceId, @Nullable ItemInfo info) {
     }
 
     /**
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 0aab495..44eae56 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -49,7 +49,7 @@
             case OVERVIEW_PEEK_STATE_ORDINAL:
                 return "OverviewPeek";
             case OVERVIEW_MODAL_TASK_STATE_ORDINAL:
-                return "OverviewModalState";
+                return "OverviewModal";
             case QUICK_SWITCH_STATE_ORDINAL:
                 return "QuickSwitch";
             case ALL_APPS_STATE_ORDINAL:
@@ -59,7 +59,7 @@
             case HINT_STATE_ORDINAL:
                 return "Hint";
             default:
-                return null;
+                return "Unknown";
         }
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index ef69448..1e0aa48 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -332,26 +332,31 @@
 
     private String getSystemAnomalyMessage() {
         try {
-            final StringBuilder sb = new StringBuilder();
+            {
+                final StringBuilder sb = new StringBuilder();
 
-            UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
-            if (object != null) {
-                sb.append("TITLE: ").append(object.getText());
-            }
+                UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
+                if (object != null) {
+                    sb.append("TITLE: ").append(object.getText());
+                }
 
-            object = mDevice.findObject(By.res("android", "message"));
-            if (object != null) {
-                sb.append(" PACKAGE: ").append(object.getApplicationPackage())
-                        .append(" MESSAGE: ").append(object.getText());
-            }
+                object = mDevice.findObject(By.res("android", "message"));
+                if (object != null) {
+                    sb.append(" PACKAGE: ").append(object.getApplicationPackage())
+                            .append(" MESSAGE: ").append(object.getText());
+                }
 
-            if (sb.length() != 0) {
-                return "System alert popup is visible: " + sb;
+                if (sb.length() != 0) {
+                    return "System alert popup is visible: " + sb;
+                }
             }
 
             if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
 
             if (!mDevice.hasObject(By.textStartsWith(""))) return "Screen is empty";
+
+            final String navigationModeError = getNavigationModeMismatchError();
+            if (navigationModeError != null) return navigationModeError;
         } catch (Throwable e) {
             Log.w(TAG, "getSystemAnomalyMessage failed", e);
         }