Merge "Fix swipe for notification default should be on instead of off"
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/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
index 8f31c22..080633a 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
@@ -52,8 +52,7 @@
     public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) {
         Bundle bundle = new Bundle();
         ArrayList<AppTargetEvent> events = new ArrayList<>();
-        ArrayList<ItemInfo> workspaceItems = new ArrayList<>(dataModel.workspaceItems);
-        workspaceItems.addAll(dataModel.appWidgets);
+        ArrayList<ItemInfo> workspaceItems = dataModel.getAllWorkspaceItems();
         for (ItemInfo item : workspaceItems) {
             AppTarget target = getAppTargetFromInfo(context, item);
             if (target != null && !isTrackedForPrediction(item)) continue;
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..7675a79 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;
         }
@@ -297,7 +304,8 @@
 
         mCurrentAnimation.setEndAction(this::clearState);
         mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
-                velocity, mEndDisplacement, animationDuration);
+                velocity * orientationHandler.getSecondaryTranslationDirectionFactor(),
+                mEndDisplacement, animationDuration);
     }
 
     private void clearState() {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 4ed2425..201dfe7 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -59,7 +59,6 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
@@ -80,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;
@@ -324,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();
@@ -732,6 +724,7 @@
         setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
         mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
         mGestureStarted = true;
+        mTaskViewSimulator.setDrawsBelowRecents(true);
     }
 
     /**
@@ -1746,6 +1739,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/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index ffb05df..901040d 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -294,8 +294,12 @@
             if (mSurfaceControl != null) {
                 currentRect.roundOut(mTempRect);
                 Transaction t = new Transaction();
-                t.setGeometry(mSurfaceControl, null, mTempRect, Surface.ROTATION_0);
-                t.apply();
+                try {
+                    t.setGeometry(mSurfaceControl, null, mTempRect, Surface.ROTATION_0);
+                    t.apply();
+                } catch (RuntimeException e) {
+                    // Ignore
+                }
             }
         }
 
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 4bb1bb5..f696efe 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -82,7 +82,6 @@
         mTaskViewSimulator.getOrientationState().update(
                 mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
                 mDeviceState.getRotationTouchHelper().getDisplayRotation());
-        mTaskViewSimulator.setDrawsBelowRecents(true);
 
         mMaxShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.max_shadow_radius);
         mTransformParams.setShadowRadius(mMaxShadowRadius);
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/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 87fee79..16ca43d 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -16,8 +16,9 @@
 
 package com.android.quickstep.util;
 
+import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_PIP;
+
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
@@ -26,7 +27,9 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 
 /**
  * An {@link Animator} that animates an Activity to PiP (picture-in-picture) window when
@@ -76,9 +79,27 @@
                 startBounds.right - sourceRectHint.right,
                 startBounds.bottom - sourceRectHint.bottom);
 
-        addListener(new AnimatorListenerAdapter() {
+        addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                InteractionJankMonitorWrapper.begin(CUJ_APP_CLOSE_TO_PIP);
+                super.onAnimationStart(animation);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                super.onAnimationCancel(animation);
+                InteractionJankMonitorWrapper.cancel(CUJ_APP_CLOSE_TO_PIP);
+            }
+
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                InteractionJankMonitorWrapper.end(CUJ_APP_CLOSE_TO_PIP);
+            }
+
             @Override
             public void onAnimationEnd(Animator animation) {
+                if (!mHasAnimationEnded) super.onAnimationEnd(animation);
                 SwipePipToHomeAnimator.this.onAnimationEnd();
             }
         });
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/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/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index aeed16a..e151777 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -1099,11 +1099,20 @@
      * @return the max _id in the provided table.
      */
     @Thunk static int getMaxId(SQLiteDatabase db, String query, Object... args) {
-        int max = (int) DatabaseUtils.longForQuery(db,
-                String.format(Locale.ENGLISH, query, args),
-                null);
-        if (max < 0) {
-            throw new RuntimeException("Error: could not query max id");
+        int max = 0;
+        try (SQLiteStatement prog = db.compileStatement(
+                String.format(Locale.ENGLISH, query, args))) {
+            max = (int) DatabaseUtils.longForQuery(prog, null);
+            if (max < 0) {
+                throw new RuntimeException("Error: could not query max id");
+            }
+        } catch (IllegalArgumentException exception) {
+            String message = exception.getMessage();
+            if (message.contains("re-open") && message.contains("already-closed")) {
+                // Don't crash trying to end a transaction an an already closed DB. See b/173162852.
+            } else {
+                throw exception;
+            }
         }
         return max;
     }
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index edaf51d..f6d1651 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -29,6 +29,8 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 
+import com.android.launcher3.Utilities;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -133,15 +135,20 @@
 
     /**
      * Starts playing the animation with the provided velocity optionally playing any
-     * physics based animations
+     * physics based animations.
+     * @param goingToEnd Whether we are going to the end (progress = 1) or not (progress = 0).
+     * @param velocityPxPerMs The velocity at which to start the animation, in pixels / millisecond.
+     * @param endDistance The distance (pixels) that the animation will travel from progress 0 to 1.
+     * @param animationDuration The duration of the non-physics based animation.
      */
     public void startWithVelocity(Context context, boolean goingToEnd,
-            float velocity, float scale, long animationDuration) {
-        float scaleInverse = 1 / Math.abs(scale);
-        float scaledVelocity = velocity * scaleInverse;
+            float velocityPxPerMs, float endDistance, long animationDuration) {
+        float distanceInverse = 1 / Math.abs(endDistance);
+        float velocityProgressPerMs = velocityPxPerMs * distanceInverse;
 
+        float oneFrameProgress = velocityProgressPerMs * getSingleFrameMs(context);
         float nextFrameProgress = boundToRange(getProgressFraction()
-                + scaledVelocity * getSingleFrameMs(context), 0f, 1f);
+                + oneFrameProgress, 0f, 1f);
 
         // Update setters for spring
         int springFlag = goingToEnd
@@ -154,8 +161,8 @@
                 SpringAnimationBuilder s = new SpringAnimationBuilder(context)
                         .setStartValue(mCurrentFraction)
                         .setEndValue(goingToEnd ? 1 : 0)
-                        .setStartVelocity(scaledVelocity)
-                        .setMinimumVisibleChange(scaleInverse)
+                        .setStartVelocity(velocityProgressPerMs)
+                        .setMinimumVisibleChange(distanceInverse)
                         .setDampingRatio(h.springProperty.mDampingRatio)
                         .setStiffness(h.springProperty.mStiffness)
                         .computeParams();
@@ -164,8 +171,18 @@
                 springDuration = Math.max(expectedDurationL, springDuration);
 
                 float expectedDuration = expectedDurationL;
-                h.mapper = (progress, globalEndProgress) ->
-                        mAnimationPlayer.getCurrentPlayTime() / expectedDuration;
+                h.mapper = (progress, globalEndProgress) -> {
+                    if (expectedDuration <= 0 || oneFrameProgress >= 1) {
+                        return 1;
+                    } else {
+                        // Start from one frame ahead of the current position.
+                        return Utilities.mapToRange(
+                                mAnimationPlayer.getCurrentPlayTime() / expectedDuration,
+                                0, 1,
+                                Math.abs(oneFrameProgress), 1,
+                                LINEAR);
+                    }
+                };
                 h.anim.setInterpolator(s::getInterpolatedValue);
             }
         }
@@ -174,7 +191,7 @@
 
         if (springDuration <= animationDuration) {
             mAnimationPlayer.setDuration(animationDuration);
-            mAnimationPlayer.setInterpolator(scrollInterpolatorForVelocity(velocity));
+            mAnimationPlayer.setInterpolator(scrollInterpolatorForVelocity(velocityPxPerMs));
         } else {
             // Since spring requires more time to run, we let the other animations play with
             // current time and interpolation and by clamping the duration.
@@ -182,7 +199,7 @@
 
             float cutOff = animationDuration / (float) springDuration;
             mAnimationPlayer.setInterpolator(
-                    clampToProgress(scrollInterpolatorForVelocity(velocity), 0, cutOff));
+                    clampToProgress(scrollInterpolatorForVelocity(velocityPxPerMs), 0, cutOff));
         }
         mAnimationPlayer.start();
     }
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/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index effb3a4..efc1201 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -629,11 +629,13 @@
         private WorkspaceResult(BgDataModel dataModel,
                 WidgetsModel widgetsModel,
                 Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
-            mWorkspaceItems = dataModel.workspaceItems;
-            mAppWidgets = dataModel.appWidgets;
-            mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
-            mWidgetsModel = widgetsModel;
-            mWidgetProvidersMap = widgetProviderInfoMap;
+            synchronized (dataModel) {
+                mWorkspaceItems = dataModel.workspaceItems;
+                mAppWidgets = dataModel.appWidgets;
+                mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
+                mWidgetsModel = widgetsModel;
+                mWidgetProvidersMap = widgetProviderInfoMap;
+            }
         }
     }
 }
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 49b40ed..c217a47 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -376,6 +376,16 @@
     }
 
     /**
+     * Returns a list containing all workspace items including widgets.
+     */
+    public synchronized ArrayList<ItemInfo> getAllWorkspaceItems() {
+        ArrayList<ItemInfo> items = new ArrayList<>(workspaceItems.size() + appWidgets.size());
+        items.addAll(workspaceItems);
+        items.addAll(appWidgets);
+        return items;
+    }
+
+    /**
      * Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted
      * items and dynamic/predicted items for the provided {@code userHandle}.
      * Note the call is not synchronized over the model, that should be handled by the called.
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index b108788..8e085ce 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -166,12 +166,7 @@
 
     private void sendFirstScreenActiveInstallsBroadcast() {
         ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
-
-        ArrayList<ItemInfo> allItems = new ArrayList<>();
-        synchronized (mBgDataModel) {
-            allItems.addAll(mBgDataModel.workspaceItems);
-            allItems.addAll(mBgDataModel.appWidgets);
-        }
+        ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
         // Screen set is never empty
         final int firstScreen = mBgDataModel.collectWorkspaceScreens().get(0);
 
@@ -858,10 +853,12 @@
                     .call(contentResolver,
                             LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
                     .getIntArray(LauncherSettings.Settings.EXTRA_VALUE);
-            for (int folderId : deletedFolderIds) {
-                mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
-                mBgDataModel.folders.remove(folderId);
-                mBgDataModel.itemsIdMap.remove(folderId);
+            synchronized (mBgDataModel) {
+                for (int folderId : deletedFolderIds) {
+                    mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
+                    mBgDataModel.folders.remove(folderId);
+                    mBgDataModel.itemsIdMap.remove(folderId);
+                }
             }
 
             // Remove any ghost widgets
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 7c02f3d..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);
     }
 
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/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index a39f215..1648bdd 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -299,7 +299,9 @@
                     launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
                             isOptionsPopupVisible(launcher)));
 
-            menu.getMenuItem(1).launch(getAppPackageName());
+            final AppIconMenuItem menuItem = menu.getMenuItem(1);
+            assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
+            menuItem.launch(getAppPackageName());
         } finally {
             allApps.unfreeze();
         }
@@ -342,6 +344,7 @@
                     openMenu().
                     getMenuItem(0);
             final String shortcutName = menuItem.getText();
+            assertEquals("Wrong menu item", "Shortcut 3", shortcutName);
 
             menuItem.dragToWorkspace(false, false);
             mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName());
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;
         }
     }