Modify input points to register in correct swipe touch regions

While the recents task list is frozen, launcher
will continue to listen for touch regions in
all orientations of apps it has seen while frozen.
Ex. Start w/ portrait app, then quick switch to
90-deg landscape app, touches in the navbars of both
those orientations will be treated as valid.
Once the task list unfreezes, only the orientation
that is currently displayed will have a valid navbar
touch region

fixes: 140116135
Test: tested manually in all 4 orientations with
quickswitch, swipe to recents and swipe to home
Attempted to write unit tests, but only basic ones
since there are some limitations on the MotionEvent
API for mocking

Change-Id: I8d3751571a939497b44e0dd249a0248299ba5ceb
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 646d01f..e0b8a37 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -770,6 +770,16 @@
             }
         }
 
+        if (endTarget == NEW_TASK) {
+            SystemUiProxy.INSTANCE.get(mContext).onQuickSwitchToNewTask();
+        }
+
+        if (endTarget == RECENTS || endTarget == HOME) {
+            // Since we're now done quickStepping, we want to only listen for touch events
+            // for the main orientation's nav bar, instead of multiple
+            mDeviceState.enableMultipleRegions(false);
+        }
+
         if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) {
             return LAST_TASK;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 28e8fb6..e1b5df0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -449,6 +449,8 @@
 
         Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
                 TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
+        mDeviceState.setOrientationTransformIfNeeded(event);
+
         if (event.getAction() == ACTION_DOWN) {
             GestureState newGestureState = new GestureState(mOverviewComponentObserver,
                     ActiveGestureLog.INSTANCE.generateAndSetLogId());
@@ -502,7 +504,9 @@
                 || previousGestureState.isRecentsAnimationRunning()
                         ? newBaseConsumer(previousGestureState, newGestureState, event)
                         : mResetGestureInputConsumer;
+        // TODO(b/149880412): 2 button landscape mode is wrecked. Fixit!
         if (mDeviceState.isFullyGesturalNavMode()) {
+            handleOrientationSetup(base);
             if (mDeviceState.canTriggerAssistantAction(event)) {
                 base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat);
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 8e7074d..2f8682f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -364,7 +364,6 @@
                         : mNavBarPosition.isLeftEdge()
                                 ? -velocityX
                                 : velocityY;
-
                 mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
                 mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY),
                         mDownPos);
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
new file mode 100644
index 0000000..53f37c1
--- /dev/null
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.quickstep;
+
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.util.DefaultDisplay;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class OrientationTouchTransformerTest {
+    private static final int SIZE_WIDTH = 1080;
+    private static final int SIZE_HEIGHT = 2280;
+    private static final float DENSITY_DISPLAY_METRICS = 3.0f;
+
+    private OrientationTouchTransformer mTouchTransformer;
+
+    Resources mResources;
+    private DefaultDisplay.Info mInfo;
+
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mResources = mock(Resources.class);
+        when(mResources.getBoolean(anyInt())).thenReturn(true);
+        when(mResources.getDimension(anyInt())).thenReturn(10.0f);
+        DisplayMetrics mockDisplayMetrics = new DisplayMetrics();
+        mockDisplayMetrics.density = DENSITY_DISPLAY_METRICS;
+        when(mResources.getDisplayMetrics()).thenReturn(mockDisplayMetrics);
+        mInfo = createDisplayInfo(Surface.ROTATION_0);
+        mTouchTransformer = new OrientationTouchTransformer(mResources, NO_BUTTON, () -> 0);
+    }
+
+    @Test
+    public void disabledMultipeRegions_shouldOverrideFirstRegion() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90);
+        mTouchTransformer.createOrAddTouchRegion(info2);
+
+        float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        MotionEvent inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inOldRegion);
+        assertFalse(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY()));
+
+        // Override region
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inOldRegion);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY()));
+    }
+
+    @Test
+    public void allowMultipeRegions_shouldOverrideFirstRegion() {
+        DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90);
+        mTouchTransformer.createOrAddTouchRegion(info2);
+        // We have to add 0 rotation second so that gets set as the current rotation, otherwise
+        // matrix transform will fail (tests only work in Portrait at the moment)
+        mTouchTransformer.enableMultipleRegions(true, mInfo);
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+
+        float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        MotionEvent inNewRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inNewRegion);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inNewRegion.getX(), inNewRegion.getY()));
+    }
+
+    @Test
+    public void applyTransform_taskNotFrozen_notInRegion() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100);
+        mTouchTransformer.transform(outOfRegion);
+        assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY()));
+    }
+
+    @Test
+    public void applyTransform_taskFrozen_noRotate_outOfRegion() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        mTouchTransformer.enableMultipleRegions(true, mInfo);
+        MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100);
+        mTouchTransformer.transform(outOfRegion);
+        assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY()));
+    }
+
+    @Test
+    public void applyTransform_taskFrozen_noRotate_inRegion() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        mTouchTransformer.enableMultipleRegions(true, mInfo);
+        float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inRegion);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+    }
+
+    @Test
+    public void applyTransform_taskNotFrozen_noRotate_inDefaultRegion() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inRegion);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+    }
+
+    @Test
+    public void applyTransform_taskNotFrozen_90Rotate_inRegion() {
+        mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+        float y = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+        MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inRegion);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+    }
+
+    @Test
+    @Ignore("There's too much that goes into needing to mock a real motion event so the "
+            + "transforms in native code get applied correctly. Once that happens then maybe we can"
+            + " write slightly more complex unit tests")
+    public void applyTransform_taskNotFrozen_90Rotate_inTwoRegions() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        mTouchTransformer.enableMultipleRegions(true, mInfo);
+        mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+        // Landscape point
+        float y1 = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+        MotionEvent inRegion1_down = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, y1);
+        MotionEvent inRegion1_up = generateMotionEvent(MotionEvent.ACTION_UP, 10, y1);
+        // Portrait point in landscape orientation axis
+        MotionEvent inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 10);
+        mTouchTransformer.transform(inRegion1_down);
+        mTouchTransformer.transform(inRegion2);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion1_down.getX(), inRegion1_down.getY()));
+        // We only process one gesture region until we see a MotionEvent.ACTION_UP
+        assertFalse(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
+
+        mTouchTransformer.transform(inRegion1_up);
+
+        // Set the new region with this MotionEvent.ACTION_DOWN
+        inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 370);
+        mTouchTransformer.transform(inRegion2);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
+    }
+
+    private DefaultDisplay.Info createDisplayInfo(int rotation) {
+        Point p = new Point(SIZE_WIDTH, SIZE_HEIGHT);
+        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+            p = new Point(SIZE_HEIGHT, SIZE_WIDTH);
+        }
+        return new DefaultDisplay.Info(0, rotation, 0, p, p, p, null);
+    }
+
+    private float generateTouchRegionHeight(int rotation) {
+        float height = SIZE_HEIGHT;
+        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+            height = SIZE_WIDTH;
+        }
+        return height - ResourceUtils.DEFAULT_NAVBAR_VALUE * DENSITY_DISPLAY_METRICS;
+    }
+
+    private MotionEvent generateMotionEvent(int motionAction, float x, float y) {
+        return MotionEvent.obtain(0, 0, motionAction, x, y, 0);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
new file mode 100644
index 0000000..3dae510
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.util.DefaultDisplay;
+
+/**
+ * Maintains state for supporting nav bars and tracking their gestures in multiple orientations.
+ * See {@link OrientationRectF#applyTransform(MotionEvent, boolean)} for transformation of
+ * MotionEvents from one orientation's coordinate space to another's.
+ *
+ * This class only supports single touch/pointer gesture tracking for touches started in a supported
+ * nav bar region.
+ */
+class OrientationTouchTransformer {
+
+    private static final String TAG = "OrientationTouchTransformer";
+    private static final boolean DEBUG = true;
+    private static final int MAX_ORIENTATIONS = 4;
+
+    private SparseArray<OrientationRectF> mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS);
+    private final RectF mAssistantLeftRegion = new RectF();
+    private final RectF mAssistantRightRegion = new RectF();
+    private int mCurrentRotation;
+    private boolean mEnableMultipleRegions;
+    private Resources mResources;
+    private OrientationRectF mLastRectTouched;
+    private SysUINavigationMode.Mode mMode;
+    private QuickStepContractInfo mContractInfo;
+    private int mQuickStepStartingRotation = -1;
+
+    /** For testability */
+    interface QuickStepContractInfo {
+        float getWindowCornerRadius();
+    }
+
+    OrientationTouchTransformer(Resources resources, SysUINavigationMode.Mode mode,
+            QuickStepContractInfo contractInfo) {
+        mResources = resources;
+        mMode = mode;
+        mContractInfo = contractInfo;
+    }
+
+    void setNavigationMode(SysUINavigationMode.Mode newMode) {
+        this.mMode = newMode;
+    }
+
+    /**
+     * Sets the current nav bar region to listen to events for as determined by
+     * {@param info}. If multiple nav bar regions are enabled, then this region will be added
+     * alongside other regions.
+     * Ok to call multiple times
+     *
+     * @see #enableMultipleRegions(boolean, DefaultDisplay.Info)
+     */
+    void createOrAddTouchRegion(DefaultDisplay.Info info) {
+        mCurrentRotation = info.rotation;
+        if (mQuickStepStartingRotation > -1 && mCurrentRotation == mQuickStepStartingRotation) {
+            // Ignore nav bars in other rotations except for the one we started out in
+            resetSwipeRegions(info);
+            return;
+        }
+
+        OrientationRectF region = mSwipeTouchRegions.get(mCurrentRotation);
+        if (region != null) {
+            return;
+        }
+
+        if (mEnableMultipleRegions) {
+            mSwipeTouchRegions.put(mCurrentRotation, createRegionForDisplay(info));
+        } else {
+            resetSwipeRegions(info);
+        }
+    }
+
+    /**
+     * Call when we want to start tracking nav bar touch regions in multiple orientations.
+     * ALSO, you BETTER call this with {@param enableMultipleRegions} set to false once you're done.
+     *
+     * @param enableMultipleRegions Set to true to start tracking multiple nav bar regions
+     * @param info The current displayInfo
+     */
+    void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) {
+        mEnableMultipleRegions = enableMultipleRegions;
+        if (!enableMultipleRegions) {
+            mQuickStepStartingRotation = -1;
+            resetSwipeRegions(info);
+        } else {
+            if (mQuickStepStartingRotation < 0) {
+                mQuickStepStartingRotation = mLastRectTouched.mRotation;
+            }
+        }
+    }
+
+    /**
+     * Only saves the swipe region represented by {@param region}, clears the
+     * rest from {@link #mSwipeTouchRegions}
+     * To be called whenever we want to stop tracking more than one swipe region.
+     * Ok to call multiple times.
+     */
+    private void resetSwipeRegions(DefaultDisplay.Info region) {
+        if (DEBUG) {
+            Log.d(TAG, "clearing all regions except rotation: " + mCurrentRotation);
+        }
+
+        mCurrentRotation = region.rotation;
+        OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentRotation);
+        mSwipeTouchRegions.clear();
+        mSwipeTouchRegions.put(mCurrentRotation,
+                regionToKeep != null ? regionToKeep : createRegionForDisplay(region));
+    }
+
+    private OrientationRectF createRegionForDisplay(DefaultDisplay.Info display) {
+        if (DEBUG) {
+            Log.d(TAG, "creating rotation region for: " + mCurrentRotation);
+        }
+
+        Point size = display.realSize;
+        int rotation = display.rotation;
+        OrientationRectF orientationRectF =
+                new OrientationRectF(0, 0, size.x, size.y, rotation,
+                        size.y, size.x);
+        if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
+            int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+            orientationRectF.top = orientationRectF.bottom - touchHeight;
+
+            final int assistantWidth = mResources
+                    .getDimensionPixelSize(R.dimen.gestures_assistant_width);
+            final float assistantHeight = Math.max(touchHeight,
+                    mContractInfo.getWindowCornerRadius());
+            mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = orientationRectF.bottom;
+            mAssistantLeftRegion.top = mAssistantRightRegion.top =
+                    orientationRectF.bottom - assistantHeight;
+
+            mAssistantLeftRegion.left = 0;
+            mAssistantLeftRegion.right = assistantWidth;
+
+            mAssistantRightRegion.right = orientationRectF.right;
+            mAssistantRightRegion.left = orientationRectF.right - assistantWidth;
+        } else {
+            mAssistantLeftRegion.setEmpty();
+            mAssistantRightRegion.setEmpty();
+            switch (rotation) {
+                case Surface.ROTATION_90:
+                    orientationRectF.left = orientationRectF.right
+                            - getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
+                    break;
+                case Surface.ROTATION_270:
+                    orientationRectF.right = orientationRectF.left
+                            + getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
+                    break;
+                default:
+                    orientationRectF.top = orientationRectF.bottom
+                            - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+            }
+        }
+
+        return orientationRectF;
+    }
+
+    boolean touchInAssistantRegion(MotionEvent ev) {
+        return mAssistantLeftRegion.contains(ev.getX(), ev.getY())
+                || mAssistantRightRegion.contains(ev.getX(), ev.getY());
+
+    }
+
+    private int getNavbarSize(String resName) {
+        return ResourceUtils.getNavbarSize(resName, mResources);
+    }
+
+    boolean touchInValidSwipeRegions(float x, float y) {
+        if (mLastRectTouched != null) {
+            return mLastRectTouched.contains(x, y);
+        }
+        return false;
+    }
+
+    public void transform(MotionEvent event) {
+        int eventAction = event.getActionMasked();
+        switch (eventAction) {
+            case ACTION_MOVE: {
+                if (mLastRectTouched == null) {
+                    return;
+                }
+                mLastRectTouched.applyTransform(event, true);
+                break;
+            }
+            case ACTION_CANCEL:
+            case ACTION_UP: {
+                if (mLastRectTouched == null) {
+                    return;
+                }
+                mLastRectTouched.applyTransform(event, true);
+                mLastRectTouched = null;
+                break;
+            }
+            case ACTION_POINTER_DOWN:
+            case ACTION_DOWN: {
+                if (mLastRectTouched != null) {
+                    return;
+                }
+
+                for (int i = 0; i < MAX_ORIENTATIONS; i++) {
+                    OrientationRectF rect = mSwipeTouchRegions.get(i);
+                    if (rect == null) {
+                        continue;
+                    }
+                    if (rect.applyTransform(event, false)) {
+                        mLastRectTouched = rect;
+                        if (DEBUG) {
+                            Log.d(TAG, "set active region: " + rect);
+                        }
+                        return;
+                    }
+                }
+                break;
+            }
+        }
+    }
+
+    private class OrientationRectF extends RectF {
+
+        /**
+         * Delta to subtract width and height by because if we report the translated touch
+         * bounds as the width and height, calling {@link RectF#contains(float, float)} will
+         * be false
+         */
+        private float maxDelta = 0.001f;
+
+        private int mRotation;
+        private float mHeight;
+        private float mWidth;
+
+        OrientationRectF(float left, float top, float right, float bottom, int rotation,
+                float height, float width) {
+            super(left, top, right, bottom);
+            this.mRotation = rotation;
+            mHeight = height - maxDelta;
+            mWidth = width - maxDelta;
+        }
+
+        @Override
+        public String toString() {
+            String s = super.toString();
+            s += " rotation: " + mRotation;
+            return s;
+        }
+
+        boolean applyTransform(MotionEvent event, boolean forceTransform) {
+            MotionEvent tmp = MotionEvent.obtain(event);
+            Matrix outMatrix = new Matrix();
+            int delta = deltaRotation(mCurrentRotation, mRotation);
+            switch (delta) {
+                case Surface.ROTATION_0:
+                    outMatrix.reset();
+                    break;
+                case Surface.ROTATION_90:
+                    outMatrix.setRotate(270);
+                    outMatrix.postTranslate(0, mHeight);
+                    break;
+                case Surface.ROTATION_180:
+                    outMatrix.setRotate(180);
+                    outMatrix.postTranslate(mHeight, mWidth);
+                    break;
+                case Surface.ROTATION_270:
+                    outMatrix.setRotate(90);
+                    outMatrix.postTranslate(mWidth, 0);
+                    break;
+            }
+
+            tmp.transform(outMatrix);
+            if (DEBUG) {
+                Log.d(TAG, "original: " + event.getX() + ", " + event.getY()
+                                + " new: " + tmp.getX() + ", " + tmp.getY()
+                                + " rect: " + this + " forceTransform: " + forceTransform
+                                + " contains: " + contains(tmp.getX(), tmp.getY()));
+            }
+
+            if (forceTransform || contains(tmp.getX(), tmp.getY())) {
+                event.transform(outMatrix);
+                tmp.recycle();
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
+         * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
+         * A value of 0 means no rotation has been applied
+         */
+        private int deltaRotation(int oldRotation, int newRotation) {
+            int delta = newRotation - oldRotation;
+            if (delta < 0) delta += 4;
+            return delta;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index abe1592..259d1dd 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -45,15 +45,16 @@
 import android.graphics.Region;
 import android.os.Process;
 import android.os.UserManager;
+import android.graphics.Region;
+import android.os.Process;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.MotionEvent;
-import android.view.Surface;
 
 import androidx.annotation.BinderThread;
 
+import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DefaultDisplay;
@@ -63,6 +64,7 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
+import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -87,10 +89,7 @@
     private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
     private NavBarPosition mNavBarPosition;
 
-    private final RectF mSwipeUpTouchRegion = new RectF();
     private final Region mDeferredGestureRegion = new Region();
-    private final RectF mAssistantLeftRegion = new RectF();
-    private final RectF mAssistantRightRegion = new RectF();
     private boolean mAssistantAvailable;
     private float mAssistantVisibility;
 
@@ -106,10 +105,13 @@
         }
     };
 
+    private OrientationTouchTransformer mOrientationTouchTransformer;
+
     private Region mExclusionRegion;
     private SystemGestureExclusionListenerCompat mExclusionListener;
 
     private final List<ComponentName> mGestureBlockedActivities;
+    private TaskStackChangeListener mFrozenTaskListener;
 
     public RecentsAnimationDeviceState(Context context) {
         final ContentResolver resolver = context.getContentResolver();
@@ -139,6 +141,8 @@
         };
         runOnDestroy(mExclusionListener::unregister);
 
+        setupOrientationSwipeHandler(context);
+
         // Register for navigation mode changes
         onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
@@ -160,6 +164,26 @@
         }
     }
 
+    private void setupOrientationSwipeHandler(Context context) {
+        final Resources resources = context.getResources();
+        mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
+                () -> QuickStepContract.getWindowCornerRadius(resources));
+
+        if (!PagedView.sFlagForcedRotation) {
+            return;
+        }
+
+        mFrozenTaskListener = new TaskStackChangeListener() {
+            @Override
+            public void onRecentTaskListFrozenChanged(boolean frozen) {
+                mOrientationTouchTransformer.enableMultipleRegions(frozen, mDefaultDisplay.getInfo());
+            }
+        };
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
+        runOnDestroy(() -> ActivityManagerWrapper.getInstance()
+                .unregisterTaskStackListener(mFrozenTaskListener));
+    }
+
     private void runOnDestroy(Runnable action) {
         mOnDestroyActions.add(action);
     }
@@ -198,7 +222,10 @@
             mExclusionListener.unregister();
         }
         mMode = newMode;
+
         mNavBarPosition = new NavBarPosition(mMode, mDefaultDisplay.getInfo());
+
+        mOrientationTouchTransformer.setNavigationMode(mMode);
     }
 
     @Override
@@ -209,6 +236,7 @@
 
         mNavBarPosition = new NavBarPosition(mMode, info);
         updateGestureTouchRegions();
+        mOrientationTouchTransformer.createOrAddTouchRegion(info);
     }
 
     /**
@@ -380,50 +408,14 @@
             return;
         }
 
-        Resources res = mContext.getResources();
-        DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo();
-        Point realSize = new Point(displayInfo.realSize);
-        mSwipeUpTouchRegion.set(0, 0, realSize.x, realSize.y);
-        if (mMode == NO_BUTTON) {
-            int touchHeight = ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE, res);
-            mSwipeUpTouchRegion.top = mSwipeUpTouchRegion.bottom - touchHeight;
-
-            final int assistantWidth = res.getDimensionPixelSize(R.dimen.gestures_assistant_width);
-            final float assistantHeight = Math.max(touchHeight,
-                    QuickStepContract.getWindowCornerRadius(res));
-            mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = mSwipeUpTouchRegion.bottom;
-            mAssistantLeftRegion.top = mAssistantRightRegion.top =
-                    mSwipeUpTouchRegion.bottom - assistantHeight;
-
-            mAssistantLeftRegion.left = 0;
-            mAssistantLeftRegion.right = assistantWidth;
-
-            mAssistantRightRegion.right = mSwipeUpTouchRegion.right;
-            mAssistantRightRegion.left = mSwipeUpTouchRegion.right - assistantWidth;
-        } else {
-            mAssistantLeftRegion.setEmpty();
-            mAssistantRightRegion.setEmpty();
-            switch (displayInfo.rotation) {
-                case Surface.ROTATION_90:
-                    mSwipeUpTouchRegion.left = mSwipeUpTouchRegion.right
-                            - ResourceUtils.getNavbarSize(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, res);
-                    break;
-                case Surface.ROTATION_270:
-                    mSwipeUpTouchRegion.right = mSwipeUpTouchRegion.left
-                            + ResourceUtils.getNavbarSize(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, res);
-                    break;
-                default:
-                    mSwipeUpTouchRegion.top = mSwipeUpTouchRegion.bottom
-                            - ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE, res);
-            }
-        }
+        mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
     }
 
     /**
      * @return whether the coordinates of the {@param event} is in the swipe up gesture region.
      */
     public boolean isInSwipeUpTouchRegion(MotionEvent event) {
-        return mSwipeUpTouchRegion.contains(event.getX(), event.getY());
+        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
     }
 
     /**
@@ -431,7 +423,8 @@
      *         is in the swipe up gesture region.
      */
     public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
-        return mSwipeUpTouchRegion.contains(event.getX(pointerIndex), event.getY(pointerIndex));
+        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
+                event.getY(pointerIndex));
     }
 
     /**
@@ -490,11 +483,22 @@
     public boolean canTriggerAssistantAction(MotionEvent ev) {
         return mAssistantAvailable
                 && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
-                && (mAssistantLeftRegion.contains(ev.getX(), ev.getY())
-                        || mAssistantRightRegion.contains(ev.getX(), ev.getY()))
+                && mOrientationTouchTransformer.touchInAssistantRegion(ev)
                 && !isLockToAppActive();
     }
 
+    /**
+     * *May* apply a transform on the motion event if it lies in the nav bar region for another
+     * orientation that is currently being tracked as a part of quickstep
+     */
+    public void setOrientationTransformIfNeeded(MotionEvent event) {
+        // negative coordinates bug b/143901881
+        if (event.getX() < 0 || event.getY() < 0) {
+            event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
+        }
+        mOrientationTouchTransformer.transform(event);
+    }
+
     public void dump(PrintWriter pw) {
         pw.println("DeviceState:");
         pw.println("  canStartSystemGesture=" + canStartSystemGesture());
@@ -505,4 +509,8 @@
         pw.println("  assistantDisabled="
                 + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
     }
+
+    public void enableMultipleRegions(boolean enable) {
+        mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index eb60601..908747c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -333,4 +333,26 @@
             }
         }
     }
+
+    @Override
+    public void setSplitScreenMinimized(boolean minimized) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.setSplitScreenMinimized(minimized);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call stopScreenPinning", e);
+            }
+        }
+    }
+
+    @Override
+    public void onQuickSwitchToNewTask() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onQuickSwitchToNewTask();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onQuickstepStarted", e);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index 7f327a5..403d779 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -21,12 +21,12 @@
 import android.util.TypedValue;
 
 public class ResourceUtils {
+    public static final int DEFAULT_NAVBAR_VALUE = 48;
     public static final String NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE = "navigation_bar_width";
     public static final String NAVBAR_BOTTOM_GESTURE_SIZE = "navigation_bar_gesture_height";
 
-
     public static int getNavbarSize(String resName, Resources res) {
-        return getDimenByName(resName, res, 48);
+        return getDimenByName(resName, res, DEFAULT_NAVBAR_VALUE);
     }
 
     public static int getDimenByName(String resName, Resources res, int defaultValue) {
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
index d3dac04..f18e411 100644
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -28,6 +28,8 @@
 import android.view.Display;
 import android.view.WindowManager;
 
+import androidx.annotation.VisibleForTesting;
+
 import java.util.ArrayList;
 
 /**
@@ -127,6 +129,18 @@
 
         public final DisplayMetrics metrics;
 
+        @VisibleForTesting
+        public Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize,
+                Point largestSize, DisplayMetrics metrics) {
+            this.id = id;
+            this.rotation = rotation;
+            this.singleFrameMs = singleFrameMs;
+            this.realSize = realSize;
+            this.smallestSize = smallestSize;
+            this.largestSize = largestSize;
+            this.metrics = metrics;
+        }
+
         private Info(Context context) {
             this(context.getSystemService(WindowManager.class).getDefaultDisplay());
         }