Fixing jump when swiping up in landscape and in waterfall cutout

> Adding tests for TaskViewSimulator to ensure proper calculations
> Disabling orientation listener while user is interacting with quickstep

Bug: 158781568
Bug: 156891776
Change-Id: I299c3b1243ac0dbf28faee1b8566c77ea3954e33
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index f5c5874..9db6576 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -121,6 +121,10 @@
         });
 
         mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+            // Wait until the first scroll event before applying scroll to taskViewSimulator.
+            // Since, by default the current/running task already centered, this ensures that we
+            // do not move the running task, in case RecentsView has not yet laid out completely.
+            mRecentsViewScrollLinked = true;
             if (moveWindowWithRecentsScroll()) {
                 updateFinalShift();
             }
@@ -128,7 +132,6 @@
         runOnRecentsAnimationStart(() ->
                 mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
                         mRecentsAnimationTargets));
-        mRecentsViewScrollLinked = true;
     }
 
     protected void startNewTask(Consumer<Boolean> resultCallback) {
@@ -205,26 +208,30 @@
         mRecentsAnimationController = recentsAnimationController;
         mRecentsAnimationTargets = targets;
         mTransformParams.setTargetSet(mRecentsAnimationTargets);
-        DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
         RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
                 mGestureState.getRunningTaskId());
 
-        if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
-            Rect overviewStackBounds = mActivityInterface
-                    .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
-            dp = dp.getMultiWindowProfile(mContext,
-                    new WindowBounds(overviewStackBounds, targets.homeContentInsets));
-        } else {
-            // If we are not in multi-window mode, home insets should be same as system insets.
-            dp = dp.copy(mContext);
-        }
-        dp.updateInsets(targets.homeContentInsets);
-        dp.updateIsSeascape(mContext);
         if (runningTaskTarget != null) {
             mTaskViewSimulator.setPreview(runningTaskTarget);
         }
 
-        initTransitionEndpoints(dp);
+        // Only initialize the device profile, if it has not been initialized before, as in some
+        // configurations targets.homeContentInsets may not be correct.
+        if (mActivity == null) {
+            DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
+            if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+                Rect overviewStackBounds = mActivityInterface
+                        .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
+                dp = dp.getMultiWindowProfile(mContext,
+                        new WindowBounds(overviewStackBounds, targets.homeContentInsets));
+            } else {
+                // If we are not in multi-window mode, home insets should be same as system insets.
+                dp = dp.copy(mContext);
+            }
+            dp.updateInsets(targets.homeContentInsets);
+            dp.updateIsSeascape(mContext);
+            initTransitionEndpoints(dp);
+        }
 
         // Notify when the animation starts
         if (!mRecentsAnimationStartCallbacks.isEmpty()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
index 196a7c4..f62218f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -255,8 +255,8 @@
         float taskHeight = mTaskRect.height();
 
         mMatrix.set(mPositionHelper.getMatrix());
-        mMatrix.postScale(scale, scale);
         mMatrix.postTranslate(insets.left, insets.top);
+        mMatrix.postScale(scale, scale);
 
         // Apply TaskView matrix: translate, scale, scroll
         mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 99afe39..3977598 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -998,6 +998,7 @@
         mDwbToastShown = false;
         mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
         LayoutUtils.setViewEnabled(mActionsView, true);
+        mOrientationState.setGestureActive(false);
     }
 
     public @Nullable TaskView getRunningTaskView() {
@@ -1035,6 +1036,7 @@
      */
     public void onGestureAnimationStart(int runningTaskId) {
         // This needs to be called before the other states are set since it can create the task view
+        mOrientationState.setGestureActive(true);
         showCurrentTask(runningTaskId);
         setEnableFreeScroll(false);
         setEnableDrawingLiveTile(false);
@@ -1097,6 +1099,8 @@
      * Called when a gesture from an app has finished.
      */
     public void onGestureAnimationEnd() {
+        mOrientationState.setGestureActive(false);
+
         setOnScrollChangeListener(null);
         setEnableFreeScroll(true);
         setEnableDrawingLiveTile(true);
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
new file mode 100644
index 0000000..a31ba21
--- /dev/null
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.shadows.LShadowDisplay;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.LauncherActivityInterface;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowDisplayManager;
+
+@RunWith(RobolectricTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class TaskViewSimulatorTest {
+
+    @Test
+    public void taskProperlyScaled_portrait_noRotation_sameInsets1() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(1200, 2450)
+                .withInsets(new Rect(0, 80, 0, 120))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_portrait_noRotation_sameInsets2() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(1200, 2450)
+                .withInsets(new Rect(55, 80, 55, 120))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_noRotation_sameInsets1() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(2450, 1250)
+                .withInsets(new Rect(0, 80, 0, 40))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_noRotation_sameInsets2() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(2450, 1250)
+                .withInsets(new Rect(0, 80, 120, 0))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_noRotation_sameInsets3() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(2450, 1250)
+                .withInsets(new Rect(55, 80, 55, 120))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_rotated() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(1200, 2450)
+                .withInsets(new Rect(0, 80, 0, 120))
+                .withAppBounds(
+                        new Rect(0, 0, 2450, 1200),
+                        new Rect(0, 80, 0, 120),
+                        Surface.ROTATION_90)
+                .verifyNoTransforms();
+    }
+
+    private static class TaskMatrixVerifier extends TransformParams {
+
+        private final Context mContext = RuntimeEnvironment.application;
+
+        private Rect mAppBounds = new Rect();
+        private Rect mLauncherInsets = new Rect();
+
+        private Rect mAppInsets;
+
+        private int mAppRotation = -1;
+        private DeviceProfile mDeviceProfile;
+
+        TaskMatrixVerifier withLauncherSize(int width, int height) {
+            ShadowDisplayManager.changeDisplay(DEFAULT_DISPLAY,
+                    String.format("w%sdp-h%sdp-mdpi", width, height));
+            if (mAppBounds.isEmpty()) {
+                mAppBounds.set(0, 0, width, height);
+            }
+            return this;
+        }
+
+        TaskMatrixVerifier withInsets(Rect insets) {
+            LShadowDisplay shadowDisplay = Shadow.extract(
+                    mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
+            shadowDisplay.setInsets(insets);
+            mLauncherInsets.set(insets);
+            return this;
+        }
+
+        TaskMatrixVerifier withAppBounds(Rect bounds, Rect insets, int appRotation) {
+            mAppBounds.set(bounds);
+            mAppInsets = insets;
+            mAppRotation = appRotation;
+            return this;
+        }
+
+        void verifyNoTransforms() {
+            mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(mContext)
+                    .getDeviceProfile(mContext);
+            mDeviceProfile.updateInsets(mLauncherInsets);
+
+            TaskViewSimulator tvs = new TaskViewSimulator(mContext,
+                    LauncherActivityInterface.INSTANCE);
+            tvs.setDp(mDeviceProfile);
+
+            int launcherRotation = DefaultDisplay.INSTANCE.get(mContext).getInfo().rotation;
+            if (mAppRotation < 0) {
+                mAppRotation = launcherRotation;
+            }
+            tvs.setLayoutRotation(launcherRotation, mAppRotation);
+            if (mAppInsets == null) {
+                mAppInsets = new Rect(mLauncherInsets);
+            }
+            tvs.setPreviewBounds(mAppBounds, mAppInsets);
+
+            tvs.fullScreenProgress.value = 1;
+            tvs.recentsViewScale.value = tvs.getFullScreenScale();
+            tvs.apply(this);
+        }
+
+        @Override
+        public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
+            SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
+            proxy.onBuildTargetParams(builder, null, this);
+            return new SurfaceParams[] {builder.build()};
+        }
+
+        @Override
+        public void applySurfaceParams(SurfaceParams[] params) {
+            // Verify that the task position remains the same
+            RectF newAppBounds = new RectF(mAppBounds);
+            params[0].matrix.mapRect(newAppBounds);
+            Assert.assertThat(newAppBounds, new AlmostSame(mAppBounds));
+
+            System.err.println("Bounds mapped: " + mAppBounds + " => " + newAppBounds);
+        }
+    }
+
+    private static class AlmostSame extends TypeSafeMatcher<RectF>  {
+
+        // Allow 1px error margin to account for float to int conversions
+        private final float mError = 1f;
+        private final Rect mExpected;
+
+        AlmostSame(Rect expected) {
+            mExpected = expected;
+        }
+
+        @Override
+        protected boolean matchesSafely(RectF item) {
+            return Math.abs(item.left - mExpected.left) < mError
+                    && Math.abs(item.top - mExpected.top) < mError
+                    && Math.abs(item.right - mExpected.right) < mError
+                    && Math.abs(item.bottom - mExpected.bottom) < mError;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendValue(mExpected);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 68636cb..90ee18f 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -105,6 +105,9 @@
     private static final int FLAG_ROTATION_WATCHER_ENABLED = 1 << 6;
     // Enable home rotation for UI tests, ignoring home rotation value from prefs
     private static final int FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING = 1 << 7;
+    // Whether the swipe gesture is running, so the recents would stay locked in the
+    // current orientation
+    private static final int FLAG_SWIPE_UP_NOT_RUNNING = 1 << 8;
 
     private static final int MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE =
             FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY
@@ -114,7 +117,8 @@
     // multi-window is enabled as in that case, activity itself rotates.
     private static final int VALUE_ROTATION_WATCHER_ENABLED =
             MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE | FLAG_SYSTEM_ROTATION_ALLOWED
-                    | FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED;
+                    | FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED
+                    | FLAG_SWIPE_UP_NOT_RUNNING;
 
     private final Context mContext;
     private final ContentResolver mContentResolver;
@@ -156,6 +160,7 @@
         if (originalSmallestWidth < 600) {
             mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
         }
+        mFlags |= FLAG_SWIPE_UP_NOT_RUNNING;
         initFlags();
     }
 
@@ -167,6 +172,13 @@
     }
 
     /**
+     * Sets if the swipe up gesture is currently running or not
+     */
+    public void setGestureActive(boolean isGestureActive) {
+        setFlag(FLAG_SWIPE_UP_NOT_RUNNING, !isGestureActive);
+    }
+
+    /**
      * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
      * @param touchRotation The rotation the nav bar region that is touched is in
      * @param displayRotation Rotation of the display/device
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties
index b171712..a8e0cb3 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/config/robolectric.properties
@@ -5,6 +5,7 @@
     com.android.launcher3.shadows.LShadowAppWidgetManager \
     com.android.launcher3.shadows.LShadowBackupManager \
     com.android.launcher3.shadows.LShadowBitmap \
+    com.android.launcher3.shadows.LShadowDisplay \
     com.android.launcher3.shadows.LShadowLauncherApps \
     com.android.launcher3.shadows.LShadowTypeface \
     com.android.launcher3.shadows.LShadowUserManager \
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
new file mode 100644
index 0000000..3813fa1
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 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.launcher3.shadows;
+
+import static org.robolectric.shadow.api.Shadow.directlyOn;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Display;
+
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.shadows.ShadowDisplay;
+
+/**
+ * Extension of {@link ShadowDisplay} with missing shadow methods
+ */
+@Implements(value = Display.class)
+public class LShadowDisplay extends ShadowDisplay {
+
+    private final Rect mInsets = new Rect();
+
+    @RealObject Display realObject;
+
+    /**
+     * Sets the insets for the display
+     */
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+    }
+
+    @Override
+    protected void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) {
+        directlyOn(realObject, Display.class).getCurrentSizeRange(outSmallestSize, outLargestSize);
+        outSmallestSize.x -= mInsets.left + mInsets.right;
+        outLargestSize.x -= mInsets.left + mInsets.right;
+
+        outSmallestSize.y -= mInsets.top + mInsets.bottom;
+        outLargestSize.y -= mInsets.top + mInsets.bottom;
+    }
+}