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,
+ )
+ }
+}