Loading core/java/android/window/DesktopExperienceFlags.java 0 → 100644 +124 −0 Original line number Diff line number Diff line /* * 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); } } core/java/android/window/DesktopModeFlags.java +5 −1 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -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); Loading core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * 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); } } services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading Loading
core/java/android/window/DesktopExperienceFlags.java 0 → 100644 +124 −0 Original line number Diff line number Diff line /* * 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); } }
core/java/android/window/DesktopModeFlags.java +5 −1 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -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); Loading
core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * 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); } }
services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading