summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Pierre Barbier de Reuille <pbdr@google.com> 2025-01-29 12:39:43 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-01-29 12:39:43 -0800
commite364898e225a71d7dc5818129d72b5886a97d229 (patch)
treed20ddd8e486da886f56d45d3668e4b1944c0f295
parent9030ff14c8a20f7b51ef42669fdcdb73ec4f0a82 (diff)
parenta91be2535be774eaf4b88c5042a657d9a54a38c5 (diff)
Merge changes from topic "new-devopt-config" into main
* changes: Add flag to enable desktop mode on specific devices Change the config to show Desktop Mode Dev option
-rw-r--r--core/java/android/window/DesktopModeFlags.java36
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig10
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt235
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeHelper.java26
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java237
8 files changed, 566 insertions, 26 deletions
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 3f2aa1ccecd8..29186109f818 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -148,28 +148,22 @@ public enum DesktopModeFlags {
return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption);
}
+ public static boolean isDesktopModeForcedEnabled() {
+ return getToggleOverride() == ToggleOverride.OVERRIDE_ON;
+ }
+
private static boolean isFlagTrue(BooleanSupplier flagFunction,
boolean shouldOverrideByDevOption) {
if (!shouldOverrideByDevOption) return flagFunction.getAsBoolean();
if (Flags.showDesktopExperienceDevOption()) {
- return switch (getToggleOverride(null)) {
+ return switch (getToggleOverride()) {
case OVERRIDE_UNSET, OVERRIDE_OFF -> flagFunction.getAsBoolean();
case OVERRIDE_ON -> true;
};
}
if (Flags.showDesktopWindowingDevOption()) {
- Application application = ActivityThread.currentApplication();
- if (application == null) {
- Log.w(TAG, "Could not get the current application.");
- return flagFunction.getAsBoolean();
- }
- ContentResolver contentResolver = application.getContentResolver();
- if (contentResolver == null) {
- Log.w(TAG, "Could not get the content resolver for the application.");
- return flagFunction.getAsBoolean();
- }
boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode();
- return switch (getToggleOverride(contentResolver)) {
+ return switch (getToggleOverride()) {
case OVERRIDE_UNSET -> flagFunction.getAsBoolean();
// When toggle override matches its default state, don't override flags. This
// helps users reset their feature overrides.
@@ -180,14 +174,13 @@ public enum DesktopModeFlags {
return flagFunction.getAsBoolean();
}
- private static ToggleOverride getToggleOverride(@Nullable ContentResolver contentResolver) {
+ private static ToggleOverride getToggleOverride() {
// If cached, return it
if (sCachedToggleOverride != null) {
return sCachedToggleOverride;
}
-
// Otherwise, fetch and cache it
- ToggleOverride override = getToggleOverrideFromSystem(contentResolver);
+ ToggleOverride override = getToggleOverrideFromSystem();
sCachedToggleOverride = override;
Log.d(TAG, "Toggle override initialized to: " + override);
return override;
@@ -196,8 +189,7 @@ public enum DesktopModeFlags {
/**
* Returns {@link ToggleOverride} from Settings.Global set by toggle.
*/
- private static ToggleOverride getToggleOverrideFromSystem(
- @Nullable ContentResolver contentResolver) {
+ private static ToggleOverride getToggleOverrideFromSystem() {
int settingValue;
if (Flags.showDesktopExperienceDevOption()) {
settingValue = SystemProperties.getInt(
@@ -205,6 +197,16 @@ public enum DesktopModeFlags {
ToggleOverride.OVERRIDE_UNSET.getSetting()
);
} else {
+ final Application application = ActivityThread.currentApplication();
+ if (application == null) {
+ Log.w(TAG, "Could not get the current application.");
+ return ToggleOverride.OVERRIDE_UNSET;
+ }
+ final ContentResolver contentResolver = application.getContentResolver();
+ if (contentResolver == null) {
+ Log.w(TAG, "Could not get the content resolver for the application.");
+ return ToggleOverride.OVERRIDE_UNSET;
+ }
settingValue = Settings.Global.getInt(
contentResolver,
Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 51d488fdd76b..d20b06738f8c 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -600,3 +600,13 @@ flag {
description: "Enables split screen on non default displays"
bug: "384999213"
}
+
+flag {
+ name: "enable_desktop_mode_through_dev_option"
+ namespace: "lse_desktop_experience"
+ description: "Enables support for desktop mode through developer options for devices eligible for desktop mode."
+ bug: "382238347"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 586cafdd2b57..a49e03484192 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7245,6 +7245,9 @@
<!-- Whether desktop mode is supported on the current device -->
<bool name="config_isDesktopModeSupported">false</bool>
+ <!-- Whether the developer option for desktop mode is supported on the current device -->
+ <bool name="config_isDesktopModeDevOptionSupported">false</bool>
+
<!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
<integer name="config_maxDesktopWindowingActiveTasks">0</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 772a7413a4a7..aca9d30a2607 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5709,6 +5709,9 @@
<!-- Whether desktop mode is supported on the current device -->
<java-symbol type="bool" name="config_isDesktopModeSupported" />
+ <!-- Whether the developer option for desktop mode is supported on the current device -->
+ <java-symbol type="bool" name="config_isDesktopModeDevOptionSupported" />
+
<!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
<java-symbol type="integer" name="config_maxDesktopWindowingActiveTasks"/>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 1ee71ca78815..e196880aad0f 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -212,10 +212,18 @@ public class DesktopModeStatus {
}
/**
+ * Return {@code true} if the current device supports the developer option for desktop mode.
+ */
+ private static boolean isDesktopModeDevOptionSupported(@NonNull Context context) {
+ return context.getResources().getBoolean(R.bool.config_isDesktopModeDevOptionSupported);
+ }
+
+ /**
* Return {@code true} if desktop mode dev option should be shown on current device
*/
public static boolean canShowDesktopModeDevOption(@NonNull Context context) {
- return isDeviceEligibleForDesktopMode(context) && Flags.showDesktopWindowingDevOption();
+ return isDeviceEligibleForDesktopModeDevOption(context)
+ && Flags.showDesktopWindowingDevOption();
}
/**
@@ -226,17 +234,25 @@ public class DesktopModeStatus {
}
/** Returns if desktop mode dev option should be enabled if there is no user override. */
- public static boolean shouldDevOptionBeEnabledByDefault() {
- return Flags.enableDesktopWindowingMode();
+ public static boolean shouldDevOptionBeEnabledByDefault(Context context) {
+ return isDeviceEligibleForDesktopMode(context) && Flags.enableDesktopWindowingMode();
}
/**
* Return {@code true} if desktop mode is enabled and can be entered on the current device.
*/
public static boolean canEnterDesktopMode(@NonNull Context context) {
- if (!isDeviceEligibleForDesktopMode(context)) return false;
+ return (isDeviceEligibleForDesktopMode(context)
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue())
+ || isDesktopModeEnabledByDevOption(context);
+ }
- return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue();
+ /**
+ * Check if Desktop mode should be enabled because the dev option is shown and enabled.
+ */
+ private static boolean isDesktopModeEnabledByDevOption(@NonNull Context context) {
+ return DesktopModeFlags.isDesktopModeForcedEnabled()
+ && canShowDesktopModeDevOption(context);
}
/**
@@ -298,7 +314,21 @@ public class DesktopModeStatus {
* Return {@code true} if desktop mode is unrestricted and is supported in the device.
*/
public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
- return !enforceDeviceRestrictions() || isDesktopModeSupported(context);
+ return !enforceDeviceRestrictions() || isDesktopModeSupported(context) || (
+ Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionSupported(
+ context));
+ }
+
+ /**
+ * Return {@code true} if the developer option for desktop mode is unrestricted and is supported
+ * in the device.
+ *
+ * Note that, if {@link #isDeviceEligibleForDesktopMode(Context)} is true, then
+ * {@link #isDeviceEligibleForDesktopModeDevOption(Context)} is also true.
+ */
+ private static boolean isDeviceEligibleForDesktopModeDevOption(@NonNull Context context) {
+ return !enforceDeviceRestrictions() || isDesktopModeSupported(context)
+ || isDesktopModeDevOptionSupported(context);
}
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
new file mode 100644
index 000000000000..4dac99b14aaf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
@@ -0,0 +1,235 @@
+/*
+ * 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.wm.shell.shared.desktopmode
+
+import android.content.Context
+import android.content.res.Resources
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES
+import android.window.DesktopModeFlags
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@Presubmit
+@EnableFlags(Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+class DesktopModeStatusTest : ShellTestCase() {
+ @get:Rule
+ val mSetFlagsRule = SetFlagsRule()
+
+ private val mockContext = mock<Context>()
+ private val mockResources = mock<Resources>()
+
+ @Before
+ fun setUp() {
+ doReturn(mockResources).whenever(mockContext).getResources()
+ doReturn(false).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+ doReturn(false).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+ doReturn(context.contentResolver).whenever(mockContext).contentResolver
+ resetDesktopModeFlagsCache()
+ resetEnforceDeviceRestriction()
+ resetFlagOverride()
+ }
+
+ @After
+ fun tearDown() {
+ resetDesktopModeFlagsCache()
+ resetEnforceDeviceRestriction()
+ resetFlagOverride()
+ }
+
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+ )
+ @Test
+ fun canEnterDesktopMode_DWFlagDisabled_configsOff_returnsFalse() {
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+ }
+
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+ )
+ @Test
+ fun canEnterDesktopMode_DWFlagDisabled_configsOn_disableDeviceRestrictions_returnsFalse() {
+ doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+ disableEnforceDeviceRestriction()
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+ }
+
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+ )
+ @Test
+ fun canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_returnsFalse() {
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+ }
+
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+ )
+ @Test
+ fun canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+ setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON)
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ fun canEnterDesktopMode_DWFlagEnabled_configsOff_returnsFalse() {
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ fun canEnterDesktopMode_DWFlagEnabled_configDesktopModeOff_returnsFalse() {
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ fun canEnterDesktopMode_DWFlagEnabled_configDesktopModeOn_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ fun canEnterDesktopMode_DWFlagEnabled_configsOff_disableDeviceRestrictions_returnsTrue() {
+ disableEnforceDeviceRestriction()
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ fun canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+ setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON)
+
+ assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+ }
+
+ @Test
+ fun isDeviceEligibleForDesktopMode_configDEModeOn_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ fun isDeviceEligibleForDesktopMode_supportFlagOff_returnsFalse() {
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ fun isDeviceEligibleForDesktopMode_supportFlagOn_returnsFalse() {
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ fun isDeviceEligibleForDesktopMode_supportFlagOn_configDevOptModeOn_returnsTrue() {
+ doReturn(true).whenever(mockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ )
+
+ assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
+ }
+
+ private fun resetEnforceDeviceRestriction() {
+ setEnforceDeviceRestriction(true)
+ }
+
+ private fun disableEnforceDeviceRestriction() {
+ setEnforceDeviceRestriction(false)
+ }
+
+ private fun setEnforceDeviceRestriction(value: Boolean) {
+ val field = DesktopModeStatus::class.java.getDeclaredField("ENFORCE_DEVICE_RESTRICTIONS")
+ field.isAccessible = true
+ field.setBoolean(null, value)
+ }
+
+ private fun resetDesktopModeFlagsCache() {
+ val cachedToggleOverride =
+ DesktopModeFlags::class.java.getDeclaredField("sCachedToggleOverride")
+ cachedToggleOverride.isAccessible = true
+ cachedToggleOverride.set(null, null)
+ }
+
+ private fun resetFlagOverride() {
+ Settings.Global.putString(
+ context.contentResolver,
+ DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null
+ )
+ }
+
+ private fun setFlagOverride(override: DesktopModeFlags.ToggleOverride) {
+ Settings.Global.putInt(
+ context.contentResolver,
+ DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.setting
+ )
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index 6bf1c466aeb5..e3906f9119c2 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -23,6 +23,7 @@ import android.window.DesktopModeFlags;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
/**
* Constants for desktop mode feature
@@ -35,7 +36,7 @@ public final class DesktopModeHelper {
"persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
/** Whether desktop mode is enabled. */
- static boolean isDesktopModeEnabled() {
+ private static boolean isDesktopModeEnabled() {
return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue();
}
@@ -56,11 +57,30 @@ public final class DesktopModeHelper {
return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported);
}
+ static boolean isDesktopModeDevOptionsSupported(@NonNull Context context) {
+ return context.getResources().getBoolean(R.bool.config_isDesktopModeDevOptionSupported);
+ }
+
+ /**
+ * Check if Desktop mode should be enabled because the dev option is shown and enabled.
+ */
+ private static boolean isDesktopModeEnabledByDevOption(@NonNull Context context) {
+ return DesktopModeFlags.isDesktopModeForcedEnabled() && (isDesktopModeDevOptionsSupported(
+ context) || isDeviceEligibleForDesktopMode(context));
+ }
+
+ @VisibleForTesting
+ static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
+ return !shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context) || (
+ Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionsSupported(
+ context));
+ }
+
/**
* Return {@code true} if desktop mode can be entered on the current device.
*/
static boolean canEnterDesktopMode(@NonNull Context context) {
- return isDesktopModeEnabled()
- && (!shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context));
+ return (isDesktopModeEnabled() && isDeviceEligibleForDesktopMode(context))
+ || isDesktopModeEnabledByDevOption(context);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
new file mode 100644
index 000000000000..e0b700a4ffe3
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.server.wm;
+
+import static android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.window.DesktopModeFlags;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.R;
+import com.android.window.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+/**
+ * Test class for {@link DesktopModeHelper}.
+ */
+@SmallTest
+@Presubmit
+@EnableFlags(Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+public class DesktopModeHelperTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private Context mContext;
+ private Context mMockContext;
+ private Resources mMockResources;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mMockContext = mock(Context.class);
+ mMockResources = mock(Resources.class);
+
+ doReturn(mMockResources).when(mMockContext).getResources();
+ doReturn(false).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+ doReturn(false).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported));
+ doReturn(mContext.getContentResolver()).when(mMockContext).getContentResolver();
+ resetDesktopModeFlagsCache();
+ resetEnforceDeviceRestriction();
+ resetFlagOverride();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ resetDesktopModeFlagsCache();
+ resetEnforceDeviceRestriction();
+ resetFlagOverride();
+ }
+
+ @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+ @Test
+ public void canEnterDesktopMode_DWFlagDisabled_configsOff_returnsFalse() {
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+ }
+
+ @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+ @Test
+ public void canEnterDesktopMode_DWFlagDisabled_configsOn_disableDeviceCheck_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported));
+ disableEnforceDeviceRestriction();
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+ }
+
+ @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+ @Test
+ public void canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_returnsFalse() {
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported));
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+ }
+
+ @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+ @Test
+ public void canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_flagOverrideOn_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported));
+ setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON);
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ public void canEnterDesktopMode_DWFlagEnabled_configsOff_returnsFalse() {
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ public void canEnterDesktopMode_DWFlagEnabled_configDesktopModeOff_returnsFalse() {
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported));
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ public void canEnterDesktopMode_DWFlagEnabled_configDesktopModeOn_returnsTrue() {
+ doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ public void canEnterDesktopMode_DWFlagEnabled_configsOff_disableDeviceRestrictions_returnsTrue()
+ throws Exception {
+ disableEnforceDeviceRestriction();
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @Test
+ public void canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ );
+ setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON);
+
+ assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+ }
+
+ @Test
+ public void isDeviceEligibleForDesktopMode_configDEModeOn_returnsTrue() {
+ doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ public void isDeviceEligibleForDesktopMode_supportFlagOff_returnsFalse() {
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ public void isDeviceEligibleForDesktopMode_supportFlagOn_returnsFalse() {
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+ @Test
+ public void isDeviceEligibleForDesktopMode_supportFlagOn_configDevOptModeOn_returnsTrue() {
+ doReturn(true).when(mMockResources).getBoolean(
+ eq(R.bool.config_isDesktopModeDevOptionSupported)
+ );
+
+ assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
+ }
+
+ private void resetEnforceDeviceRestriction() throws Exception {
+ setEnforceDeviceRestriction(true);
+ }
+
+ private void disableEnforceDeviceRestriction() throws Exception {
+ setEnforceDeviceRestriction(false);
+ }
+
+ private void setEnforceDeviceRestriction(boolean value) throws Exception {
+ Field deviceRestriction = DesktopModeHelper.class.getDeclaredField(
+ "ENFORCE_DEVICE_RESTRICTIONS");
+ deviceRestriction.setAccessible(true);
+ deviceRestriction.setBoolean(/* obj= */ null, /* z= */ value);
+ }
+
+ private void resetDesktopModeFlagsCache() throws Exception {
+ Field cachedToggleOverride = DesktopModeFlags.class.getDeclaredField(
+ "sCachedToggleOverride");
+ cachedToggleOverride.setAccessible(true);
+ cachedToggleOverride.set(/* obj= */ null, /* value= */ null);
+ }
+
+ private void resetFlagOverride() {
+ Settings.Global.putString(mContext.getContentResolver(),
+ DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null);
+ }
+
+ private void setFlagOverride(DesktopModeFlags.ToggleOverride override) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.getSetting());
+ }
+}