Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cd66718e authored by Pierre Barbier de Reuille's avatar Pierre Barbier de Reuille
Browse files

Change the config to show Desktop Mode Dev option

Introduces a new config overlay that controls which devices shows the
new Desktop Mode developer option.

Bug: 382238347
Flag: EXEMPT Bug fix
Test: Build and test on devices with only the dev option enabled.
Change-Id: Ie205ed84ee8be877e30d793fa4f48c5072b3a876
parent 7b8ccec4
Loading
Loading
Loading
Loading
+19 −17
Original line number Diff line number Diff line
@@ -144,28 +144,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.
@@ -176,14 +170,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;
@@ -192,8 +185,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(
@@ -201,6 +193,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,
+3 −0
Original line number Diff line number Diff line
@@ -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>

+3 −0
Original line number Diff line number Diff line
@@ -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"/>

+44 −8
Original line number Diff line number Diff line
@@ -77,11 +77,18 @@ public class DesktopModeStatus {
    private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean(
            "persist.wm.debug.desktop_use_rounded_corners", true);

    /**
     * Name of the system property used to set the device restriction.
     */
    @VisibleForTesting
    static final String ENFORCE_DEVICE_RESTRICTIONS_PROPERTY =
            "persist.wm.debug.desktop_mode_enforce_device_restrictions";

    /**
     * Flag to indicate whether to restrict desktop mode to supported devices.
     */
    private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
            "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
            ENFORCE_DEVICE_RESTRICTIONS_PROPERTY, true);

    private static final boolean USE_APP_TO_WEB_BUILD_TIME_GENERIC_LINKS =
            SystemProperties.getBoolean(
@@ -211,11 +218,19 @@ public class DesktopModeStatus {
        return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported);
    }

    /**
     * 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,19 +241,26 @@ 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 DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue();
        return (isDeviceEligibleForDesktopMode(context)
                && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue())
                || isDesktopModeEnabledByDevOption(context);
    }

    /**
     * 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);
    }
    /**
     * Returns whether the multiple desktops feature is enabled for this device (both backend and
     * frontend implementations).
@@ -298,7 +320,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.forceEnableDesktopModeWithDevOption() && 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);
    }

    /**
+202 −0
Original line number Diff line number Diff line
/*
 * 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.os.SystemProperties
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.modules.utils.testing.ExtendedMockitoRule
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.ArgumentMatchers.anyBoolean
import org.mockito.kotlin.doAnswer
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(order = 0)
    val mSetFlagsRule = SetFlagsRule();

    @get:Rule(order = 1)
    val extendedMockitoRule =
        ExtendedMockitoRule.Builder(this)
            .mockStatic(SystemProperties::class.java)
            .build()

    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)
    @Test
    fun canEnterDesktopMode_DWFlagDisabled_configsOff_returnsFalse() {
        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
    }

    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
    @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)
    @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)
    @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()
    }

    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
    @Test
    fun canEnterDesktopMode_DWFlagEnabled_configsOff_returnsFalse() {
        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
    }

    @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()
    }

    @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()
    }

    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
    @Test
    fun canEnterDesktopMode_DWFlagEnabled_configsOff_disableDeviceRestrictions_returnsTrue() {
        disableEnforceDeviceRestriction();

        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
    }

    @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()
    }

    private fun resetEnforceDeviceRestriction() {
        doAnswer { invocation -> invocation.getArgument<Boolean>(1) }.whenever(
            SystemProperties.getBoolean(
                DesktopModeStatus.ENFORCE_DEVICE_RESTRICTIONS_PROPERTY,
                anyBoolean()
            )
        )
    }

    private fun disableEnforceDeviceRestriction() {
        doReturn(false).whenever(
            SystemProperties.getBoolean(
                DesktopModeStatus.ENFORCE_DEVICE_RESTRICTIONS_PROPERTY,
                anyBoolean()
            )
        )
    }

    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
        )
    }
}
Loading