diff options
4 files changed, 295 insertions, 2 deletions
diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java new file mode 100644 index 000000000000..0d1bb77ae8a2 --- /dev/null +++ b/core/java/android/window/DesktopExperienceFlags.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2024 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 android.window; + +import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement; + +import android.annotation.Nullable; +import android.os.SystemProperties; +import android.util.Log; + +import com.android.window.flags.Flags; + +import java.util.function.BooleanSupplier; + +/** + * Checks Desktop Experience flag state. + * + * <p>This enum provides a centralized way to control the behavior of flags related to desktop + * experience features which are aiming for developer preview before their release. It allows + * developer option to override the default behavior of these flags. + * + * <p>The flags here will be controlled by the {@code + * persist.wm.debug.desktop_experience_devopts} system property. + * + * <p>NOTE: Flags should only be added to this enum when they have received Product and UX alignment + * that the feature is ready for developer preview, otherwise just do a flag check. + * + * @hide + */ +public enum DesktopExperienceFlags { + ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT(() -> enableDisplayContentModeManagement(), true); + + /** + * Flag class, to be used in case the enum cannot be used because the flag is not accessible. + * + * <p>This class will still use the process-wide cache. + */ + public static class DesktopExperienceFlag { + // Function called to obtain aconfig flag value. + private final BooleanSupplier mFlagFunction; + // Whether the flag state should be affected by developer option. + private final boolean mShouldOverrideByDevOption; + + public DesktopExperienceFlag(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { + this.mFlagFunction = flagFunction; + this.mShouldOverrideByDevOption = shouldOverrideByDevOption; + } + + /** + * Determines state of flag based on the actual flag and desktop experience developer option + * overrides. + */ + public boolean isTrue() { + return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); + } + } + + private static final String TAG = "DesktopExperienceFlags"; + // Function called to obtain aconfig flag value. + private final BooleanSupplier mFlagFunction; + // Whether the flag state should be affected by developer option. + private final boolean mShouldOverrideByDevOption; + + // Local cache for toggle override, which is initialized once on its first access. It needs to + // be refreshed only on reboots as overridden state is expected to take effect on reboots. + @Nullable private static Boolean sCachedToggleOverride; + + public static final String SYSTEM_PROPERTY_NAME = "persist.wm.debug.desktop_experience_devopts"; + + DesktopExperienceFlags(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { + this.mFlagFunction = flagFunction; + this.mShouldOverrideByDevOption = shouldOverrideByDevOption; + } + + /** + * Determines state of flag based on the actual flag and desktop experience developer option + * overrides. + */ + public boolean isTrue() { + return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); + } + + private static boolean isFlagTrue( + BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { + if (shouldOverrideByDevOption + && Flags.showDesktopExperienceDevOption() + && getToggleOverride()) { + return true; + } + return flagFunction.getAsBoolean(); + } + + private static boolean getToggleOverride() { + // If cached, return it + if (sCachedToggleOverride != null) { + return sCachedToggleOverride; + } + + // Otherwise, fetch and cache it + boolean override = getToggleOverrideFromSystem(); + sCachedToggleOverride = override; + Log.d(TAG, "Toggle override initialized to: " + override); + return override; + } + + /** Returns the {@link ToggleOverride} from the system property.. */ + private static boolean getToggleOverrideFromSystem() { + return SystemProperties.getBoolean(SYSTEM_PROPERTY_NAME, false); + } +} diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index cebfb381211d..e51ef4f6d04c 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -35,6 +35,10 @@ import java.util.function.BooleanSupplier; * windowing features which are aiming for developer preview before their release. It allows * developer option to override the default behavior of these flags. * + * <p> The flags here will be controlled by either {@link + * Settings.Global#DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES} or the {@code + * persyst.wm.debug.desktop_experience_devopts} system property. + * * <p>NOTE: Flags should only be added to this enum when they have received Product and UX * alignment that the feature is ready for developer preview, otherwise just do a flag check. * @@ -110,7 +114,7 @@ public enum DesktopModeFlags { /** * Determines state of flag based on the actual flag and desktop mode developer option - * overrides. + * or desktop experience developer option overrides. */ public boolean isTrue() { return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); diff --git a/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java b/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java new file mode 100644 index 000000000000..cc06f3d21332 --- /dev/null +++ b/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2024 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 android.window; + +import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assume.assumeTrue; +import static org.junit.Assume.assumeFalse; + +import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.FlagsParameterization; +import android.platform.test.flag.junit.SetFlagsRule; +import android.support.test.uiautomator.UiDevice; +import android.window.DesktopExperienceFlags.DesktopExperienceFlag; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.window.flags.Flags; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * Test class for {@link android.window.DesktopExperienceFlags} + * + * <p>Build/Install/Run: atest FrameworksCoreTests:DesktopExperienceFlagsTest + */ +@SmallTest +@Presubmit +@RunWith(ParameterizedAndroidJunit4.class) +public class DesktopExperienceFlagsTest { + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf(FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION); + } + + @Rule public SetFlagsRule mSetFlagsRule; + + private UiDevice mUiDevice; + private Context mContext; + private boolean mLocalFlagValue = false; + private final DesktopExperienceFlag mOverriddenLocalFlag = + new DesktopExperienceFlag(() -> mLocalFlagValue, true); + private final DesktopExperienceFlag mNotOverriddenLocalFlag = + new DesktopExperienceFlag(() -> mLocalFlagValue, false); + + private static final String OVERRIDE_OFF_SETTING = "0"; + private static final String OVERRIDE_ON_SETTING = "1"; + private static final String OVERRIDE_INVALID_SETTING = "garbage"; + + public DesktopExperienceFlagsTest(FlagsParameterization flags) { + mSetFlagsRule = new SetFlagsRule(flags); + } + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + setSysProp(null); + } + + @After + public void tearDown() throws Exception { + resetCache(); + setSysProp(null); + } + + @Test + public void isTrue_overrideOff_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; + setSysProp(OVERRIDE_OFF_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); + } + + @Test + public void isTrue_overrideOn_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; + setSysProp(OVERRIDE_ON_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); + } + + @Test + public void isTrue_overrideOff_featureFlagOff_returnsFalse() throws Exception { + mLocalFlagValue = false; + setSysProp(OVERRIDE_OFF_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); + } + + @Test + public void isTrue_devOptionEnabled_overrideOn_featureFlagOff() throws Exception { + assumeTrue(Flags.showDesktopExperienceDevOption()); + mLocalFlagValue = false; + setSysProp(OVERRIDE_ON_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); + } + + @Test + public void isTrue_devOptionDisabled_overrideOn_featureFlagOff_returnsFalse() throws Exception { + assumeFalse(Flags.showDesktopExperienceDevOption()); + mLocalFlagValue = false; + setSysProp(OVERRIDE_ON_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); + } + + private void setSysProp(String value) throws Exception { + if (value == null) { + resetSysProp(); + } else { + mUiDevice.executeShellCommand( + "setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " " + value); + } + } + + private void resetSysProp() throws Exception { + mUiDevice.executeShellCommand("setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " ''"); + } + + private void resetCache() throws Exception { + Field cachedToggleOverride = + DesktopExperienceFlags.class.getDeclaredField("sCachedToggleOverride"); + cachedToggleOverride.setAccessible(true); + cachedToggleOverride.set(null, null); + } +} diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index d435144b28c6..b9ce8c93dbde 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -21,6 +21,7 @@ import android.os.Build; import android.os.SystemProperties; import android.text.TextUtils; import android.util.Slog; +import android.window.DesktopExperienceFlags; import com.android.server.display.feature.flags.Flags; import com.android.server.display.utils.DebugUtils; @@ -250,7 +251,7 @@ public class DisplayManagerFlags { ); private final FlagState mEnableDisplayContentModeManagementFlagState = new FlagState( Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT, - Flags::enableDisplayContentModeManagement + DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT::isTrue ); private final FlagState mSubscribeGranularDisplayEvents = new FlagState( |