Sync landscape rotation with Overview rotation

Bug: 390487996
Test: NA
Flag: com.android.launcher3.one_grid_specs
Change-Id: Ib564c21befd06edd5c720fe33f36780761035fee
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 8954d80..9cdde01 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -24,6 +24,7 @@
 
 import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays;
 import static com.android.launcher3.LauncherPrefs.ALLOW_ROTATION;
+import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
 import static com.android.quickstep.BaseActivityInterface.getTaskDimension;
@@ -44,6 +45,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherPrefChangeListener;
 import com.android.launcher3.LauncherPrefs;
@@ -107,9 +109,12 @@
     // Ignore shared prefs for home rotation rotation, allowing it in if the activity supports it
     private static final int FLAG_IGNORE_ALLOW_HOME_ROTATION_PREF = 1 << 9;
 
+    // Shared prefs for fixed 90 degree rotation, activities should rotate if they support it
+    private static final int FLAG_HOME_FIXED_LANDSCAPE_PREFS = 1 << 10;
+
     private static final int MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE =
             FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY
-            | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
+                    | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
 
     // State for which rotation watcher will be enabled. We skip it when home rotation or
     // multi-window is enabled as in that case, activity itself rotates.
@@ -227,7 +232,7 @@
 
     private boolean updateHandler() {
         mRecentsActivityRotation = inferRecentsActivityRotation(mDisplayRotation);
-        if (mRecentsActivityRotation == mTouchRotation || isRecentsActivityRotationAllowed()) {
+        if (mRecentsActivityRotation == mTouchRotation || shouldUseRealOrientation()) {
             mOrientationHandler = RecentsPagedOrientationHandler.PORTRAIT;
         } else if (mTouchRotation == ROTATION_90) {
             mOrientationHandler = RecentsPagedOrientationHandler.LANDSCAPE;
@@ -249,9 +254,13 @@
         return mStateId != oldStateId;
     }
 
+    private boolean shouldUseRealOrientation() {
+        return isRecentsActivityRotationAllowed() || isLauncherFixedLandscape();
+    }
+
     @SurfaceRotation
     private int inferRecentsActivityRotation(@SurfaceRotation int displayRotation) {
-        if (isRecentsActivityRotationAllowed()) {
+        if (shouldUseRealOrientation()) {
             return mRecentsRotation < 0 ? displayRotation : mRecentsRotation;
         } else {
             return ROTATION_0;
@@ -288,6 +297,9 @@
         if (LauncherPrefs.ALLOW_ROTATION.getSharedPrefKey().equals(s)) {
             updateHomeRotationSetting();
         }
+        if (LauncherPrefs.FIXED_LANDSCAPE_MODE.getSharedPrefKey().equals(s)) {
+            updateFixedLandscapeSetting();
+        }
     }
 
     private void updateAutoRotateSetting() {
@@ -295,6 +307,15 @@
                 mSettingsCache.getValue(ROTATION_SETTING_URI, 1));
     }
 
+    private void updateFixedLandscapeSetting() {
+        if (Flags.oneGridSpecs()) {
+            setFlag(
+                    FLAG_HOME_FIXED_LANDSCAPE_PREFS,
+                    LauncherPrefs.get(mContext).get(FIXED_LANDSCAPE_MODE)
+            );
+        }
+    }
+
     private void updateHomeRotationSetting() {
         boolean homeRotationEnabled = LauncherPrefs.get(mContext).get(ALLOW_ROTATION);
         setFlag(FLAG_HOME_ROTATION_ALLOWED_IN_PREFS, homeRotationEnabled);
@@ -307,6 +328,7 @@
         // initialize external flags
         updateAutoRotateSetting();
         updateHomeRotationSetting();
+        updateFixedLandscapeSetting();
     }
 
     private void initMultipleOrientationListeners() {
@@ -383,15 +405,19 @@
         setFlag(FLAG_IGNORE_ALLOW_HOME_ROTATION_PREF, true);
     }
 
+    public boolean isLauncherFixedLandscape() {
+        return (mFlags & FLAG_HOME_FIXED_LANDSCAPE_PREFS) == FLAG_HOME_FIXED_LANDSCAPE_PREFS;
+    }
+
     public boolean isRecentsActivityRotationAllowed() {
         // Activity rotation is allowed if the multi-simulated-rotation is not supported
         // (fallback recents or tablets) or activity rotation is enabled by various settings.
         return ((mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
                 != MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
                 || (mFlags & (FLAG_IGNORE_ALLOW_HOME_ROTATION_PREF
-                        | FLAG_HOME_ROTATION_ALLOWED_IN_PREFS
-                        | FLAG_MULTIWINDOW_ROTATION_ALLOWED
-                        | FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING)) != 0;
+                | FLAG_HOME_ROTATION_ALLOWED_IN_PREFS
+                | FLAG_MULTIWINDOW_ROTATION_ALLOWED
+                | FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING)) != 0;
     }
 
     /**
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt
new file mode 100644
index 0000000..3cdf608
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2025 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 android.view.Surface
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_180
+import android.view.Surface.ROTATION_90
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.quickstep.FallbackActivityInterface
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler.Companion.PORTRAIT
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+/**
+ * Test all possible inputs to RecentsOrientedState.updateHandler. It tests all possible
+ * combinations of rotations and relevant methods (two methods that return boolean values) but it
+ * only provides the expected result when the final rotation is different from ROTATION_0 for
+ * simplicity. So any case not shown in resultMap you can assume results in ROTATION_0.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RecentOrientedStateHandlerTests {
+
+    data class TestCase(
+        val recentsRotation: Int,
+        val displayRotation: Int,
+        val touchRotation: Int,
+        val isRotationAllowed: Boolean,
+        val isFixedLandscape: Boolean,
+    ) {
+        override fun toString(): String {
+            return "TestCase(recentsRotation=${Surface.rotationToString(recentsRotation)}, " +
+                "displayRotation=${Surface.rotationToString(displayRotation)}, " +
+                "touchRotation=${Surface.rotationToString(touchRotation)}, " +
+                "isRotationAllowed=$isRotationAllowed, " +
+                "isFixedLandscape=$isFixedLandscape)"
+        }
+    }
+
+    private fun runTestCase(testCase: TestCase, expectedHandler: RecentsPagedOrientationHandler) {
+        val recentOrientedState =
+            spy(
+                RecentsOrientedState(
+                    ApplicationProvider.getApplicationContext(),
+                    FallbackActivityInterface.INSTANCE,
+                ) {}
+            )
+        whenever(recentOrientedState.isRecentsActivityRotationAllowed).thenAnswer {
+            testCase.isRotationAllowed
+        }
+        whenever(recentOrientedState.isLauncherFixedLandscape).thenAnswer {
+            testCase.isFixedLandscape
+        }
+
+        recentOrientedState.update(testCase.displayRotation, testCase.touchRotation)
+        val rotation = recentOrientedState.orientationHandler.rotation
+        assertWithMessage("$testCase to ${Surface.rotationToString(rotation)},")
+            .that(rotation)
+            .isEqualTo(expectedHandler.rotation)
+    }
+
+    @Test
+    fun `test fixed landscape when device is portrait`() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_0,
+                displayRotation = -1,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+
+    @Test
+    fun `test fixed landscape when device is landscape`() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_90,
+                displayRotation = -1,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+
+    @Test
+    fun `test fixed landscape when device is seascape`() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_180,
+                displayRotation = -1,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+
+    @Test
+    fun `test fixed landscape when device is portrait and display rotation is portrait`() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_0,
+                displayRotation = ROTATION_0,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+
+    @Test
+    fun `test fixed landscape when device is landscape and display rotation is landscape `() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_90,
+                displayRotation = ROTATION_90,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+
+    @Test
+    fun `test fixed landscape when device is seascape and display rotation is seascape`() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_180,
+                displayRotation = ROTATION_180,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+}