Merge "Ignoring crash if the 3p icon surface is no longer valid"
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index c724318..f02acab 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -167,9 +167,14 @@
     }
 
     @Override
-    protected void handlePendingActivityRequest() {
-        super.handlePendingActivityRequest();
-        if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
+    public void onStateSetEnd(LauncherState state) {
+        super.onStateSetEnd(state);
+        handlePendingActivityRequest();
+    }
+
+    private void handlePendingActivityRequest() {
+        if (mPendingActivityRequestCode != -1 && isInState(NORMAL)
+                && ((getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
             // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
             onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
             // ProxyActivityStarter is started with clear task to reset the task after which it
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 3e9f82b..cc3b7b6 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -57,7 +57,6 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.Trace;
 import android.util.Pair;
 import android.view.View;
 
@@ -140,7 +139,6 @@
 
     // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
     public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
-    public static final String TRANSITION_OPEN_LAUNCHER = "transition:OpenLauncher";
 
     protected final BaseQuickstepLauncher mLauncher;
 
@@ -805,11 +803,10 @@
                 == PackageManager.PERMISSION_GRANTED;
     }
 
-    private void addCujInstrumentation(Animator anim, int cuj, String transition) {
+    private void addCujInstrumentation(Animator anim, int cuj) {
         anim.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationStart(Animator animation) {
-                Trace.beginAsyncSection(transition, 0);
                 InteractionJankMonitorWrapper.begin(cuj);
                 super.onAnimationStart(animation);
             }
@@ -824,12 +821,6 @@
             public void onAnimationSuccess(Animator animator) {
                 InteractionJankMonitorWrapper.end(cuj);
             }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                Trace.endAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
-            }
         });
     }
 
@@ -898,8 +889,7 @@
                 if (launcherIsATargetWithMode(appTargets, MODE_OPENING)
                         || mLauncher.isForceInvisible()) {
                     addCujInstrumentation(
-                            anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME,
-                            TRANSITION_OPEN_LAUNCHER);
+                            anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
                     // Only register the content animation for cancellation when state changes
                     mLauncher.getStateManager().setCurrentAnimation(anim);
 
@@ -971,9 +961,7 @@
             addCujInstrumentation(anim,
                     launchingFromRecents
                             ? InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS
-                            : InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_ICON,
-                    launchingFromRecents
-                            ? TRANSITION_LAUNCH_FROM_RECENTS : TRANSITION_LAUNCH_FROM_ICON);
+                            : InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_ICON);
 
             if (launcherClosing) {
                 anim.addListener(mForceInvisibleListener);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 950598c..2d704f8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -35,7 +35,6 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
-import android.util.Log;
 import android.view.View;
 
 import com.android.launcher3.BaseQuickstepLauncher;
@@ -55,7 +54,6 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
 import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
 import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
@@ -273,9 +271,6 @@
 
     @Override
     public TouchController[] createTouchControllers() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.1");
-        }
         Mode mode = SysUINavigationMode.getMode(this);
 
         ArrayList<TouchController> list = new ArrayList<>();
@@ -283,9 +278,6 @@
         if (mode == NO_BUTTON) {
             list.add(new NoButtonQuickSwitchTouchController(this));
             list.add(new NavBarToHomeTouchController(this));
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.2");
-            }
             list.add(new NoButtonNavbarToOverviewTouchController(this));
         } else {
             if (getDeviceProfile().isVerticalBarLayout()) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index f73e2f2..6b9c340 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -29,7 +29,6 @@
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.ValueAnimator;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.animation.Interpolator;
 
@@ -47,7 +46,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.TaskUtils;
@@ -103,37 +101,19 @@
     }
 
     private boolean canInterceptTouch(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NavBarToHomeTouchController.canInterceptTouch "
-                    + ev);
-        }
         boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
         if (!cameFromNavBar) {
             return false;
         }
         if (mStartState.overviewUi || mStartState == ALL_APPS) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.PAUSE_NOT_DETECTED,
-                        "NavBarToHomeTouchController.canInterceptTouch true 1 "
-                                + mStartState.overviewUi + " " + (mStartState == ALL_APPS));
-            }
             return true;
         }
         int typeToClose = ENABLE_ALL_APPS_EDU.get() ? TYPE_ALL & ~TYPE_ALL_APPS_EDU : TYPE_ALL;
         if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, typeToClose) != null) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.PAUSE_NOT_DETECTED,
-                        "NavBarToHomeTouchController.canInterceptTouch true 2 "
-                                + AbstractFloatingView.getTopOpenView(mLauncher), new Exception());
-            }
             return true;
         }
         if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
                 && AssistantUtilities.isExcludedAssistantRunning()) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.PAUSE_NOT_DETECTED,
-                        "NavBarToHomeTouchController.canInterceptTouch true 3");
-            }
             return true;
         }
         return false;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 702c519..addfe92 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -37,7 +37,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.graphics.PointF;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -49,7 +48,6 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -90,9 +88,6 @@
         mRecentsView = l.getOverviewPanel();
         mMotionPauseDetector = new MotionPauseDetector(l);
         mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController.ctor");
-        }
     }
 
     @Override
@@ -221,9 +216,6 @@
 
     @Override
     public boolean onDrag(float yDisplacement, float xDisplacement, MotionEvent event) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController");
-        }
         if (mStartedOverview) {
             if (!mReachedOverview) {
                 mStartDisplacement.set(xDisplacement, yDisplacement);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 9729695..9a3a67f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -18,8 +18,6 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -59,6 +57,8 @@
 
     private AnimatorPlaybackController mCurrentAnimation;
     private boolean mCurrentAnimationIsGoingUp;
+    private boolean mAllowGoingUp;
+    private boolean mAllowGoingDown;
 
     private boolean mNoIntercept;
 
@@ -74,7 +74,7 @@
         mRecentsView = activity.getOverviewPanel();
         mIsRtl = Utilities.isRtl(activity.getResources());
         SingleAxisSwipeDetector.Direction dir =
-            mRecentsView.getPagedOrientationHandler().getOppositeSwipeDirection();
+                mRecentsView.getPagedOrientationHandler().getUpDownSwipeDirection();
         mDetector = new SingleAxisSwipeDetector(activity, this, dir);
     }
 
@@ -148,15 +148,24 @@
                             break;
                         }
                         mTaskBeingDragged = view;
+                        int upDirection = mRecentsView.getPagedOrientationHandler()
+                                .getUpDirection(mIsRtl);
                         if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
                             // Don't allow swipe down to open if we don't support swipe up
                             // to enter overview.
-                            directionsToDetectScroll = DIRECTION_POSITIVE;
+                            directionsToDetectScroll = upDirection;
+                            mAllowGoingUp = true;
+                            mAllowGoingDown = false;
                         } else {
                             // The task can be dragged up to dismiss it,
                             // and down to open if it's the current page.
-                            directionsToDetectScroll = i == mRecentsView.getCurrentPage()
-                                    ? DIRECTION_BOTH : DIRECTION_POSITIVE;
+                            mAllowGoingUp = true;
+                            if (i == mRecentsView.getCurrentPage()) {
+                                mAllowGoingDown = true;
+                                directionsToDetectScroll = DIRECTION_BOTH;
+                            } else {
+                                directionsToDetectScroll = upDirection;
+                            }
                         }
                         break;
                     }
@@ -189,9 +198,7 @@
             // No need to init
             return;
         }
-        int scrollDirections = mDetector.getScrollDirections();
-        if (goingUp && ((scrollDirections & DIRECTION_POSITIVE) == 0)
-                || !goingUp && ((scrollDirections & DIRECTION_NEGATIVE) == 0)) {
+        if ((goingUp && !mAllowGoingUp) || (!goingUp && !mAllowGoingDown)) {
             // Trying to re-init in an unsupported direction.
             return;
         }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 4dbc0f3..3c6299f 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.TRANSITION_OPEN_LAUNCHER;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
@@ -33,6 +32,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
 import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
@@ -59,8 +59,6 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.SystemClock;
-import android.os.Trace;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
@@ -81,7 +79,6 @@
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.tracing.InputConsumerProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
 import com.android.launcher3.util.TraceHelper;
@@ -325,13 +322,7 @@
 
     protected boolean onActivityInit(Boolean alreadyOnHome) {
         T createdActivity = mActivityInterface.getCreatedActivity();
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1");
-        }
         if (createdActivity != null) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
-            }
             initTransitionEndpoints(createdActivity.getDeviceProfile());
         }
         final T activity = mActivityInterface.getCreatedActivity();
@@ -1217,7 +1208,6 @@
         anim.addAnimatorListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationStart(Animator animation) {
-                Trace.beginAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
                 InteractionJankMonitorWrapper.begin(cuj);
                 if (mActivity != null) {
                     removeLiveTileOverlay();
@@ -1240,12 +1230,6 @@
                 super.onAnimationCancel(animation);
                 InteractionJankMonitorWrapper.cancel(cuj);
             }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                Trace.endAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
-            }
         });
         if (mRecentsAnimationTargets != null) {
             mRecentsAnimationTargets.addReleaseCheck(anim);
@@ -1395,34 +1379,55 @@
             if (mRecentsAnimationController != null) {
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
-                    mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
+                    UI_HELPER_EXECUTOR.execute(() -> {
+                        final ThumbnailData taskSnapshot =
+                                mRecentsAnimationController.screenshotTask(runningTaskId);
+                        MAIN_EXECUTOR.execute(() -> {
+                            mTaskSnapshot = taskSnapshot;
+                            if (!updateThumbnail(runningTaskId)) {
+                                setScreenshotCapturedState();
+                            }
+                        });
+                    });
+                    return;
                 }
-                final TaskView taskView;
-                if (mGestureState.getEndTarget() == HOME) {
-                    // Capture the screenshot before finishing the transition to home to ensure it's
-                    // taken in the correct orientation, but no need to update the thumbnail.
-                    taskView = null;
-                } else {
-                    taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot);
-                }
-                if (taskView != null && !mCanceled) {
-                    // Defer finishing the animation until the next launcher frame with the
-                    // new thumbnail
-                    finishTransitionPosted = ViewUtils.postFrameDrawn(taskView,
-                            () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
-                            this::isCanceled);
-                }
+                finishTransitionPosted = updateThumbnail(runningTaskId);
             }
             if (!finishTransitionPosted) {
-                // If we haven't posted a draw callback, set the state immediately.
-                Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
-                        TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
-                mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
-                TraceHelper.INSTANCE.endSection(traceToken);
+                setScreenshotCapturedState();
             }
         }
     }
 
+    // Returns whether finish transition was posted.
+    private boolean updateThumbnail(int runningTaskId) {
+        boolean finishTransitionPosted = false;
+        final TaskView taskView;
+        if (mGestureState.getEndTarget() == HOME) {
+            // Capture the screenshot before finishing the transition to home to ensure it's
+            // taken in the correct orientation, but no need to update the thumbnail.
+            taskView = null;
+        } else {
+            taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot);
+        }
+        if (taskView != null && !mCanceled) {
+            // Defer finishing the animation until the next launcher frame with the
+            // new thumbnail
+            finishTransitionPosted = ViewUtils.postFrameDrawn(taskView,
+                    () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
+                    this::isCanceled);
+        }
+        return finishTransitionPosted;
+    }
+
+    private void setScreenshotCapturedState() {
+        // If we haven't posted a draw callback, set the state immediately.
+        Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
+                TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
+        mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+        TraceHelper.INSTANCE.endSection(traceToken);
+    }
+
     private void finishCurrentTransitionToRecents() {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
@@ -1733,6 +1738,8 @@
             LiveTileOverlay.INSTANCE.update(
                     mTaskViewSimulator.getCurrentRect(),
                     mTaskViewSimulator.getCurrentCornerRadius());
+            LiveTileOverlay.INSTANCE.setRotation(
+                    mRecentsView.getPagedViewOrientedState().getDisplayRotation());
         }
         ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
     }
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 43581ca..2559a6f 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.view.ViewConfiguration;
 
 import androidx.annotation.BinderThread;
@@ -141,6 +142,7 @@
 
     private class RecentsActivityCommand<T extends StatefulActivity<?>> implements Runnable {
 
+        private static final String TRANSITION_NAME = "Transition:toOverview";
         protected final BaseActivityInterface<?, T> mActivityInterface;
         private final long mCreateTime;
         private final AppToOverviewAnimationProvider<T> mAnimationProvider;
@@ -224,8 +226,15 @@
                     wallpaperTargets);
             animatorSet.addListener(new AnimatorListenerAdapter() {
                 @Override
+                public void onAnimationStart(Animator animation) {
+                    Trace.beginAsyncSection(TRANSITION_NAME, 0);
+                    super.onAnimationStart(animation);
+                }
+
+                @Override
                 public void onAnimationEnd(Animator animation) {
                     onTransitionComplete();
+                    Trace.endAsyncSection(TRANSITION_NAME, 0);
                 }
             });
             return animatorSet;
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 8ce1f51..1e0a00a 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -627,10 +627,6 @@
     }
 
     private void handleOrientationSetup(InputConsumer baseInputConsumer) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "handleOrientationSetup.1");
-        }
-
         baseInputConsumer.notifyOrientationSetup();
     }
 
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 498e561..aad70c4 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -18,7 +18,6 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
-import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
@@ -80,9 +79,6 @@
             ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
         }
         ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "OverviewInputConsumer");
-        }
         boolean handled = mTarget.proxyTouchEvent(ev, mStartingInActivityBounds);
         ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
         ev.setEdgeFlags(flags);
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index da5f59e..8cde5f2 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -19,14 +19,12 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 
 import com.android.launcher3.Alarm;
 import com.android.launcher3.R;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.testing.TestProtocol;
 
 /**
  * Given positions along x- or y-axis, tracks velocity and acceleration and determines when there is
@@ -87,9 +85,6 @@
         mSpeedSlow = res.getDimension(R.dimen.motion_pause_detector_speed_slow);
         mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
         mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "creating alarm");
-        }
         mForcePauseTimeout = new Alarm();
         mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
         mMakePauseHarderToTrigger = makePauseHarderToTrigger;
@@ -126,9 +121,6 @@
      * @param pointerIndex Index for the pointer being tracked in the motion event
      */
     public void addPosition(MotionEvent ev, int pointerIndex) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "setting alarm");
-        }
         mForcePauseTimeout.setAlarm(mMakePauseHarderToTrigger
                 ? HARDER_TRIGGER_TIMEOUT
                 : FORCE_PAUSE_TIMEOUT);
@@ -176,9 +168,6 @@
     }
 
     private void updatePaused(boolean isPaused) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "updatePaused: " + isPaused);
-        }
         if (mDisallowPause) {
             isPaused = false;
         }
@@ -207,9 +196,6 @@
         setOnMotionPauseListener(null);
         mIsPaused = mHasEverBeenPaused = false;
         mSlowStartTime = 0;
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "canceling alarm");
-        }
         mForcePauseTimeout.cancelAlarm();
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index e273aeb..facc99a 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -519,6 +519,29 @@
         }
     }
 
+    /**
+     * Contrary to {@link #postDisplayRotation}.
+     */
+    public static void preDisplayRotation(@SurfaceRotation int displayRotation,
+            float screenWidth, float screenHeight, Matrix out) {
+        switch (displayRotation) {
+            case ROTATION_0:
+                return;
+            case ROTATION_90:
+                out.postRotate(90);
+                out.postTranslate(screenWidth, 0);
+                break;
+            case ROTATION_180:
+                out.postRotate(180);
+                out.postTranslate(screenHeight, screenWidth);
+                break;
+            case ROTATION_270:
+                out.postRotate(270);
+                out.postTranslate(0, screenHeight);
+                break;
+        }
+    }
+
     @NonNull
     @Override
     public String toString() {
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 2f4bb8e..5a7f541 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
+import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
 
 import android.animation.TimeInterpolator;
@@ -80,6 +81,7 @@
     private DeviceProfile mDp;
 
     private final Matrix mMatrix = new Matrix();
+    private final Matrix mMatrixTmp = new Matrix();
     private final Point mRunningTargetWindowPosition = new Point();
 
     // Thumbnail view properties
@@ -211,7 +213,10 @@
      */
     public RectF getCurrentRect() {
         RectF result = getCurrentCropRect();
-        mMatrix.mapRect(result);
+        mMatrixTmp.set(mMatrix);
+        preDisplayRotation(mOrientationState.getDisplayRotation(), mDp.widthPx, mDp.heightPx,
+                mMatrixTmp);
+        mMatrixTmp.mapRect(result);
         return result;
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
index f6eb0e2..747c3f2 100644
--- a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -1,5 +1,10 @@
 package com.android.quickstep.views;
 
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 
@@ -19,6 +24,7 @@
 import android.view.ViewOverlay;
 
 import com.android.launcher3.anim.Interpolators;
+import com.android.quickstep.util.RecentsOrientedState.SurfaceRotation;
 
 public class LiveTileOverlay extends Drawable {
 
@@ -43,6 +49,8 @@
     private final RectF mCurrentRect = new RectF();
     private final Rect mBoundsRect = new Rect();
 
+    private @SurfaceRotation int mRotation = ROTATION_0;
+
     private float mCornerRadius;
     private Drawable mIcon;
     private Animator mIconAnimator;
@@ -69,6 +77,10 @@
         mCurrentRect.set(left, top, right, bottom);
     }
 
+    public void setRotation(@SurfaceRotation int rotation) {
+        mRotation = rotation;
+    }
+
     public void setIcon(Drawable icon) {
         mIcon = icon;
     }
@@ -103,8 +115,35 @@
             canvas.save();
             float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
                     1f).getInterpolation(mIconAnimationProgress);
-            canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
-                    mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
+
+            int iconRadius = mIcon.getBounds().width() / 2;
+            float dx = 0;
+            float dy = 0;
+
+            switch (mRotation) {
+                case ROTATION_0:
+                    dx = mCurrentRect.centerX() - iconRadius * scale;
+                    dy = mCurrentRect.top - iconRadius * scale;
+                    break;
+                case ROTATION_90:
+                    dx = mCurrentRect.right - iconRadius * scale;
+                    dy = mCurrentRect.centerY() - iconRadius * scale;
+                    break;
+                case ROTATION_270:
+                    dx = mCurrentRect.left - iconRadius * scale;
+                    dy = mCurrentRect.centerY() - iconRadius * scale;
+                    break;
+                case ROTATION_180:
+                    dx = mCurrentRect.centerX() - iconRadius * scale;
+                    dy = mCurrentRect.bottom - iconRadius * scale;
+                    break;
+            }
+
+            int rotationDegrees = mRotation * 90;
+            if (mRotation == ROTATION_90 || mRotation == ROTATION_270) {
+                canvas.rotate(rotationDegrees, dx + iconRadius, dy + iconRadius);
+            }
+            canvas.translate(dx, dy);
             canvas.scale(scale, scale);
             mIcon.draw(canvas);
             canvas.restore();
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 1b55496..d94e623 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -251,13 +251,14 @@
      * Builds proto for logging
      */
     public WorkspaceItemInfo getItemInfo() {
-        ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key);
+        final Task task = getTask();
+        ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
         WorkspaceItemInfo stubInfo = new WorkspaceItemInfo();
         stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
         stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
         stubInfo.user = componentKey.user;
         stubInfo.intent = new Intent().setComponent(componentKey.componentName);
-        stubInfo.title = TaskUtils.getTitle(getContext(), getTask());
+        stubInfo.title = task.title;
         stubInfo.screenId = getRecentsView().indexOfChild(this);
         return stubInfo;
     }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index c5863c1..cc97f4b 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -310,6 +310,10 @@
             assertTrue("The second app we should have quick switched to is not running",
                     isTestActivityRunning(2));
         }
+        background = getAndAssertBackground();
+        background.quickSwitchToPreviousAppSwipeLeft();
+        assertTrue("The 2nd app we should have quick switched to is not running",
+                isTestActivityRunning(3));
         getAndAssertBackground();
     }
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index ff53b5f..1d88e83 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.Utilities.getPointString;
 import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 
@@ -215,6 +216,9 @@
     }
 
     public static String getCurrentGridName(Context context) {
+        if (ENABLE_FOUR_COLUMNS.get()) {
+            return ENABLE_FOUR_COLUMNS.key;
+        }
         return Utilities.isGridOptionsEnabled(context)
                 ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) : null;
     }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ea98157..e099d85 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -961,8 +961,6 @@
         DiscoveryBounce.showForHomeIfNeeded(this);
     }
 
-    protected void handlePendingActivityRequest() { }
-
     private void logStopAndResume(boolean isResume) {
         if (mPendingExecutor != null) return;
         int pageIndex = mWorkspace.isOverlayShown() ? -1 : mWorkspace.getCurrentPage();
@@ -1053,7 +1051,7 @@
 
     @Override
     public void onStateSetEnd(LauncherState state) {
-        super.onStateSetStart(state);
+        super.onStateSetEnd(state);
         getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
         getWorkspace().setClipChildren(!state.hasFlag(FLAG_MULTI_PAGE));
 
@@ -1479,8 +1477,7 @@
                 if (!isInState(NORMAL)) {
                     // Only change state, if not already the same. This prevents cancelling any
                     // animations running as part of resume
-                    mStateManager.goToState(NORMAL, mStateManager.shouldAnimateStateChange(),
-                            this::handlePendingActivityRequest);
+                    mStateManager.goToState(NORMAL, mStateManager.shouldAnimateStateChange());
                 }
 
                 // Reset the apps view
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 8f63c09..883eab0 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -190,6 +190,10 @@
             "EXPANDED_SMARTSPACE", false, "Expands smartspace height to two rows. "
               + "Any apps occupying the first row will be removed from workspace.");
 
+    public static final BooleanFlag ENABLE_FOUR_COLUMNS = new DeviceFlag(
+            "ENABLE_FOUR_COLUMNS", false, "Uses 4 columns in launcher grid."
+            + "Warning: This will permanently alter your home screen items and is not reversible.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index b2d0081..218172b 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -103,7 +103,6 @@
     public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
 
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
-    public static final String PAUSE_NOT_DETECTED = "b/139891609";
     public static final String OVERIEW_NOT_ALLAPPS = "b/156095088";
     public static final String NO_SWIPE_TO_HOME = "b/158017601";
     public static final String WORK_PROFILE_REMOVED = "b/159671700";
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index a9d0e61..23baaf0 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -172,9 +172,6 @@
 
     @Override
     public final boolean onControllerTouchEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "onControllerTouchEvent");
-        }
         return mDetector.onTouchEvent(ev);
     }
 
@@ -396,6 +393,12 @@
 
     @Override
     public void onDragEnd(float velocity) {
+        if (mCurrentAnimation == null) {
+            // Unlikely, but we may have been canceled just before onDragEnd(). We assume whoever
+            // canceled us will handle a new state transition to clean up.
+            return;
+        }
+
         boolean fling = mDetector.isFling(velocity);
 
         boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
index 01b33d8..1276ece 100644
--- a/src/com/android/launcher3/touch/BaseSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -26,8 +26,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.testing.TestProtocol;
-
 import java.util.LinkedList;
 import java.util.Queue;
 
@@ -175,9 +173,6 @@
                 if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) {
                     setState(ScrollState.DRAGGING);
                 }
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.PAUSE_NOT_DETECTED, "before report dragging");
-                }
                 if (mState == ScrollState.DRAGGING) {
                     reportDragging(ev);
                 }
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 1d7f747..f05f15e 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -75,11 +75,6 @@
     }
 
     @Override
-    public boolean isGoingUp(float displacement, boolean isRtl) {
-        return isRtl ? displacement < 0 : displacement > 0;
-    }
-
-    @Override
     public boolean isLayoutNaturalToLauncher() {
         return false;
     }
@@ -215,11 +210,6 @@
     }
 
     @Override
-    public SingleAxisSwipeDetector.Direction getOppositeSwipeDirection() {
-        return HORIZONTAL;
-    }
-
-    @Override
     public int getPrimaryTranslationDirectionFactor() {
         return -1;
     }
@@ -229,11 +219,6 @@
     }
 
     @Override
-    public int getTaskDragDisplacementFactor(boolean isRtl) {
-        return isRtl ? 1 : -1;
-    }
-
-    @Override
     public float getTaskMenuX(float x, View thumbnailView) {
         return thumbnailView.getMeasuredWidth() + x;
     }
@@ -261,6 +246,31 @@
         lp.weight = 1;
     }
 
+    /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+
+    @Override
+    public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
+        return HORIZONTAL;
+    }
+
+    @Override
+    public int getUpDirection(boolean isRtl) {
+        return isRtl ? SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+                : SingleAxisSwipeDetector.DIRECTION_POSITIVE;
+    }
+
+    @Override
+    public boolean isGoingUp(float displacement, boolean isRtl) {
+        return isRtl ? displacement < 0 : displacement > 0;
+    }
+
+    @Override
+    public int getTaskDragDisplacementFactor(boolean isRtl) {
+        return isRtl ? 1 : -1;
+    }
+
+    /* -------------------- */
+
     @Override
     public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
         boolean layoutChild) {
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index a9c50cd..b9acfa3 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -75,10 +75,8 @@
     int getCenterForPage(View view, Rect insets);
     int getScrollOffsetStart(View view, Rect insets);
     int getScrollOffsetEnd(View view, Rect insets);
-    SingleAxisSwipeDetector.Direction getOppositeSwipeDirection();
     int getPrimaryTranslationDirectionFactor();
     int getSecondaryTranslationDirectionFactor();
-    int getTaskDragDisplacementFactor(boolean isRtl);
     ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
     void setMaxScroll(AccessibilityEvent event, int maxScroll);
     boolean getRecentsRtlSetting(Resources resources);
@@ -92,7 +90,6 @@
     void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y);
     void scrollerStartScroll(OverScroller scroller, int newPosition);
     void getCurveProperties(PagedView view, Rect insets, CurveProperties out);
-    boolean isGoingUp(float displacement, boolean isRtl);
     boolean isLayoutNaturalToLauncher();
     float getTaskMenuX(float x, View thumbnailView);
     float getTaskMenuY(float y, View thumbnailView);
@@ -101,6 +98,16 @@
     void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp);
     int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
 
+    // The following are only used by TaskViewTouchHandler.
+    /** @return Either VERTICAL or HORIZONTAL. */
+    SingleAxisSwipeDetector.Direction getUpDownSwipeDirection();
+    /** @return Given {@link #getUpDownSwipeDirection()}, whether POSITIVE or NEGATIVE is up. */
+    int getUpDirection(boolean isRtl);
+    /** @return Whether the displacement is going towards the top of the screen. */
+    boolean isGoingUp(float displacement, boolean isRtl);
+    /** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
+    int getTaskDragDisplacementFactor(boolean isRtl);
+
     /**
      * Maps the velocity from the coordinate plane of the foreground app to that
      * of Launcher's (which now will always be portrait)
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 587e35a..3663b5f 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -73,12 +73,6 @@
     }
 
     @Override
-    public boolean isGoingUp(float displacement, boolean isRtl) {
-        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
-        return displacement < 0;
-    }
-
-    @Override
     public boolean isLayoutNaturalToLauncher() {
         return true;
     }
@@ -212,11 +206,6 @@
     }
 
     @Override
-    public SingleAxisSwipeDetector.Direction getOppositeSwipeDirection() {
-        return VERTICAL;
-    }
-
-    @Override
     public int getPrimaryTranslationDirectionFactor() {
         return 1;
     }
@@ -226,12 +215,6 @@
     }
 
     @Override
-    public int getTaskDragDisplacementFactor(boolean isRtl) {
-        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
-        return 1;
-    }
-
-    @Override
     public float getTaskMenuX(float x, View thumbnailView) {
         return x;
     }
@@ -257,6 +240,33 @@
         // no-op, defaults are fine
     }
 
+    /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+
+    @Override
+    public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
+        return VERTICAL;
+    }
+
+    @Override
+    public int getUpDirection(boolean isRtl) {
+        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+        return SingleAxisSwipeDetector.DIRECTION_POSITIVE;
+    }
+
+    @Override
+    public boolean isGoingUp(float displacement, boolean isRtl) {
+        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+        return displacement < 0;
+    }
+
+    @Override
+    public int getTaskDragDisplacementFactor(boolean isRtl) {
+        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+        return 1;
+    }
+
+    /* -------------------- */
+
     @Override
     public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
         boolean layoutChild) {
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 60d19d9..54af029 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.touch;
 
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+
 import android.content.res.Resources;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -33,11 +35,6 @@
     }
 
     @Override
-    public int getTaskDragDisplacementFactor(boolean isRtl) {
-        return isRtl ? -1 : 1;
-    }
-
-    @Override
     public boolean getRecentsRtlSetting(Resources resources) {
         return Utilities.isRtl(resources);
     }
@@ -53,11 +50,6 @@
     }
 
     @Override
-    public boolean isGoingUp(float displacement, boolean isRtl) {
-        return isRtl ? displacement > 0 : displacement < 0;
-    }
-
-    @Override
     public void adjustFloatingIconStartVelocity(PointF velocity) {
         float oldX = velocity.x;
         float oldY = velocity.y;
@@ -78,4 +70,29 @@
     public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
         return dp.widthPx - rect.right;
     }
+
+    /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+
+    @Override
+    public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
+        return HORIZONTAL;
+    }
+
+    @Override
+    public int getUpDirection(boolean isRtl) {
+        return isRtl ? SingleAxisSwipeDetector.DIRECTION_POSITIVE
+                : SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
+    }
+
+    @Override
+    public boolean isGoingUp(float displacement, boolean isRtl) {
+        return isRtl ? displacement > 0 : displacement < 0;
+    }
+
+    @Override
+    public int getTaskDragDisplacementFactor(boolean isRtl) {
+        return isRtl ? -1 : 1;
+    }
+
+    /* -------------------- */
 }
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
index 875eefb..8c3c115 100644
--- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -17,7 +17,6 @@
 
 import android.content.Context;
 import android.graphics.PointF;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
@@ -25,7 +24,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
 
 /**
  * One dimensional scroll/drag/swipe gesture detector (either HORIZONTAL or VERTICAL).
@@ -60,6 +58,11 @@
             return direction.x;
         }
 
+        @NonNull
+        @Override
+        public String toString() {
+            return "VERTICAL";
+        }
     };
 
     public static final Direction HORIZONTAL = new Direction() {
@@ -86,6 +89,11 @@
             return direction.y;
         }
 
+        @NonNull
+        @Override
+        public String toString() {
+            return "HORIZONTAL";
+        }
     };
 
     private final Direction mDir;
@@ -105,11 +113,6 @@
         super(config, isRtl);
         mListener = l;
         mDir = dir;
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "SingleAxisSwipeDetector.ctor "
-                    + l.getClass().getSimpleName()
-                    + " @ " + android.util.Log.getStackTraceString(new Throwable()));
-        }
     }
 
     public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
@@ -117,10 +120,6 @@
         mIgnoreSlopWhenSettling = ignoreSlop;
     }
 
-    public int getScrollDirections() {
-        return mScrollDirections;
-    }
-
     /**
      * Returns if the start drag was towards the positive direction or negative.
      *
@@ -161,10 +160,6 @@
 
     @Override
     protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "SingleAxisSwipeDetector "
-                    + mListener.getClass().getSimpleName());
-        }
         mListener.onDrag(mDir.extractDirection(displacement),
                 mDir.extractOrthogonalDirection(displacement), event);
     }
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 2be827b..5464dd8 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -33,7 +33,6 @@
 import android.graphics.RectF;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.Property;
 import android.view.MotionEvent;
 import android.view.View;
@@ -49,7 +48,6 @@
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -191,12 +189,6 @@
     }
 
     private TouchController findControllerToHandleTouch(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "findControllerToHandleTouch ev=" + ev
-                    + ", isEventInLauncher=" + isEventInLauncher(ev)
-                    + ", topOpenView=" + AbstractFloatingView.getTopOpenView(mActivity));
-        }
-
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
         if (topView != null
                 && (isEventInLauncher(ev) || topView.canInterceptEventsInSystemGestureRegion())
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index d9a14e9..1857c5a 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -55,7 +55,6 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.testing.TestProtocol;
 
 /**
  * A view that is created to look like another view with the purpose of creating fluid animations.
@@ -534,11 +533,6 @@
         view.setVisibility(INVISIBLE);
         parent.addView(view);
         dragLayer.addView(view.mListenerView);
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "getFloatingIconView. listenerView "
-                    + "added to dragLayer. listenerView=" + view.mListenerView + ", fiv=" + view,
-                    new Exception());
-        }
         view.mListenerView.setListener(view::fastFinish);
 
         view.mEndRunnable = () -> {
@@ -578,10 +572,6 @@
     private void finish(DragLayer dragLayer) {
         ((ViewGroup) dragLayer.getParent()).removeView(this);
         dragLayer.removeView(mListenerView);
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "listenerView removed from dragLayer. "
-                    + "listenerView=" + mListenerView + ", fiv=" + this, new Exception());
-        }
         recycle();
         mLauncher.getViewCache().recycleView(R.layout.floating_icon_view, this);
     }
diff --git a/src/com/android/launcher3/views/ListenerView.java b/src/com/android/launcher3/views/ListenerView.java
index 6e3f0ce..b2df0ee 100644
--- a/src/com/android/launcher3/views/ListenerView.java
+++ b/src/com/android/launcher3/views/ListenerView.java
@@ -17,13 +17,11 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.testing.TestProtocol;
 
 /**
  * An invisible AbstractFloatingView that can run a callback when it is being closed.
@@ -38,20 +36,12 @@
     }
 
     public void setListener(Runnable listener) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView setListener lv=" + this
-                    + ", listener=" + listener, new Exception());
-        }
         mCloseListener = listener;
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView onAttachedToWindow lv=" + this,
-                    new Exception());
-        }
         mIsOpen = true;
     }
 
@@ -59,19 +49,10 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mIsOpen = false;
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView onDetachedFromView lv=" + this,
-                    new Exception());
-        }
     }
 
     @Override
     protected void handleClose(boolean animate) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView handeClose lv=" + this
-                    + ", mIsOpen=" + mIsOpen + ", mCloseListener=" + mCloseListener
-                    + ", getParent()=" + getParent(), new Exception());
-        }
         if (mIsOpen) {
             if (mCloseListener != null) {
                 mCloseListener.run();
@@ -91,10 +72,6 @@
 
     @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "ListenerView touchEvent lv=" + this
-                    + ", ev=" + ev, new Exception());
-        }
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             handleClose(false);
         }
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
index d6737db..03d3026 100644
--- a/src/com/android/launcher3/views/WorkEduView.java
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -60,7 +60,6 @@
     private View mViewWrapper;
     private Button mProceedButton;
     private TextView mContentText;
-    private AllAppsPagedView mAllAppsPagedView;
 
     private int mNextWorkEduStep = WORK_EDU_PERSONAL_APPS;
 
@@ -101,13 +100,10 @@
 
         // make sure layout does not shrink when we change the text
         mContentText.post(() -> mContentText.setMinLines(mContentText.getLineCount()));
-        if (mLauncher.getAppsView().getContentView() instanceof AllAppsPagedView) {
-            mAllAppsPagedView = (AllAppsPagedView) mLauncher.getAppsView().getContentView();
-        }
 
         mProceedButton.setOnClickListener(view -> {
-            if (mAllAppsPagedView != null) {
-                mAllAppsPagedView.snapToPage(AllAppsContainerView.AdapterHolder.WORK);
+            if (getAllAppsPagedView() != null) {
+                getAllAppsPagedView().snapToPage(AllAppsContainerView.AdapterHolder.WORK);
             }
             goToWorkTab(true);
         });
@@ -155,8 +151,8 @@
     }
 
     private void goToFirstPage() {
-        if (mAllAppsPagedView != null) {
-            mAllAppsPagedView.snapToPageImmediately(AllAppsContainerView.AdapterHolder.MAIN);
+        if (getAllAppsPagedView() != null) {
+            getAllAppsPagedView().snapToPageImmediately(AllAppsContainerView.AdapterHolder.MAIN);
         }
     }
 
@@ -171,6 +167,11 @@
         mOpenCloseAnimator.start();
     }
 
+    private AllAppsPagedView getAllAppsPagedView() {
+        View v =  mLauncher.getAppsView().getContentView();
+        return  (v instanceof AllAppsPagedView)  ? (AllAppsPagedView) v : null;
+    }
+
     /**
      * Checks if user has not seen onboarding UI yet and shows it when user navigates to all apps
      */
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 1e1cf04..ac0d355 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -82,6 +82,7 @@
         mDevice.pressHome();
         waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+        waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
         waitForLauncherCondition("Personal tab is missing",
                 launcher -> launcher.getAppsView().isPersonalTabVisible(), 60000);
         waitForLauncherCondition("Work tab is missing",
@@ -180,6 +181,10 @@
         // open work tab
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
         waitForState("Launcher did not switch to all apps", () -> ALL_APPS);
+        waitForLauncherCondition("Work tab not setup",
+                launcher -> launcher.getAppsView().getContentView() instanceof AllAppsPagedView,
+                60000);
+
         executeOnLauncher(launcher -> {
             AllAppsPagedView pagedView = (AllAppsPagedView) launcher.getAppsView().getContentView();
             pagedView.setCurrentPage(WORK_PAGE);
@@ -199,7 +204,7 @@
             DragLayer dragLayer = l.getDragLayer();
             return dragLayer.getChildCount() > 0 && dragLayer.getChildAt(
                     dragLayer.getChildCount() - 1) instanceof WorkEduView;
-        });
+        }, 6000);
         return getFromLauncher(launcher -> (WorkEduView) launcher.getDragLayer().getChildAt(
                 launcher.getDragLayer().getChildCount() - 1));
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 153b3ce..d317783 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -144,14 +144,25 @@
     private void expectSwitchToOverviewEvents() {
     }
 
-    /**
-     * Swipes right or double presses the square button to switch to the previous app.
-     */
     @NonNull
     public Background quickSwitchToPreviousApp() {
+        boolean toRight = true;
+        quickSwitch(toRight);
+        return new Background(mLauncher);
+    }
+
+    @NonNull
+    public Background quickSwitchToPreviousAppSwipeLeft() {
+        boolean toRight = false;
+        quickSwitch(toRight);
+        return new Background(mLauncher);
+    }
+
+    @NonNull
+    private void quickSwitch(boolean toRight) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
-             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                     "want to quick switch to the previous app")) {
+            LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                    "want to quick switch to the previous app")) {
             verifyActiveContainer();
             final boolean launcherWasVisible = mLauncher.isLauncherVisible();
             boolean transposeInLandscape = false;
@@ -164,19 +175,36 @@
                     final int startY;
                     final int endX;
                     final int endY;
-                    if (mLauncher.getDevice().isNaturalOrientation() || !transposeInLandscape) {
-                        // Swipe from the bottom left to the bottom right of the screen.
-                        startX = 0;
-                        startY = getSwipeStartY();
-                        endX = mLauncher.getDevice().getDisplayWidth();
-                        endY = startY;
+                    if (toRight) {
+                        if (mLauncher.getDevice().isNaturalOrientation() || !transposeInLandscape) {
+                            // Swipe from the bottom left to the bottom right of the screen.
+                            startX = 0;
+                            startY = getSwipeStartY();
+                            endX = mLauncher.getDevice().getDisplayWidth();
+                            endY = startY;
+                        } else {
+                            // Swipe from the bottom right to the top right of the screen.
+                            startX = getSwipeStartX();
+                            startY = mLauncher.getRealDisplaySize().y - 1;
+                            endX = startX;
+                            endY = 0;
+                        }
                     } else {
-                        // Swipe from the bottom right to the top right of the screen.
-                        startX = getSwipeStartX();
-                        startY = mLauncher.getRealDisplaySize().y - 1;
-                        endX = startX;
-                        endY = 0;
+                        if (mLauncher.getDevice().isNaturalOrientation() || !transposeInLandscape) {
+                            // Swipe from the bottom right to the bottom left of the screen.
+                            startX = mLauncher.getDevice().getDisplayWidth();
+                            startY = getSwipeStartY();
+                            endX = 0;
+                            endY = startY;
+                        } else {
+                            // Swipe from the bottom left to the top left of the screen.
+                            startX = getSwipeStartX();
+                            startY = 0;
+                            endX = startX;
+                            endY = mLauncher.getRealDisplaySize().y - 1;
+                        }
                     }
+
                     final boolean isZeroButton = mLauncher.getNavigationModel()
                             == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
                     LauncherInstrumentation.GestureScope gestureScope =
@@ -205,7 +233,7 @@
                     break;
             }
             mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
-            return new Background(mLauncher);
+            return;
         }
     }