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

Commit 5644b9be authored by Pierre Barbier de Reuille's avatar Pierre Barbier de Reuille Committed by Android (Google) Code Review
Browse files

Merge "Add a new enum for desktop experience flags" into main

parents 9e363ed9 232f51ac
Loading
Loading
Loading
Loading
+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);
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -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);
+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);
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -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(