Loading packages/SystemUI/res/values/flags.xml 0 → 100644 +26 −0 Original line number Original line Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2021 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. --> <resources> <bool name="are_flags_overrideable">true</bool> <bool name="flag_notification_pipeline2">false</bool> <bool name="flag_notification_pipeline2_rendering">false</bool> <!-- b/171917882 --> <bool name="flag_notification_twocolumn">false</bool> </resources> packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java 0 → 100644 +165 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2021 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.systemui.flags; import android.annotation.NonNull; import android.content.res.Resources; import android.provider.DeviceConfig; import android.util.SparseArray; import androidx.annotation.BoolRes; import androidx.annotation.Nullable; import com.android.systemui.R; import com.android.systemui.assist.DeviceConfigHelper; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.util.wrapper.BuildInfo; import java.util.concurrent.Executor; import javax.inject.Inject; /** * Reads and caches feature flags for quick access * * Feature flags must be defined as boolean resources. For example: * * {@code * <bool name="flag_foo_bar_baz">false</bool> * } * * It is strongly recommended that the name of the resource begin with "flag_". * * Flags can be overridden via adb on development builds. For example, to override the flag from the * previous example, do the following: * * {@code * $ adb shell device_config put systemui flag_foo_bar_baz true * } * * Note that all storage keys begin with "flag_", even if their associated resId does not. * * Calls to this class should probably be wrapped by {@link FeatureFlags}. */ @SysUISingleton public class FeatureFlagReader { private final Resources mResources; private final DeviceConfigHelper mDeviceConfig; private final boolean mAreFlagsOverrideable; private final SparseArray<CachedFlag> mCachedFlags = new SparseArray<>(); @Inject public FeatureFlagReader( @Main Resources resources, BuildInfo build, DeviceConfigHelper deviceConfig, @Background Executor executor) { mResources = resources; mDeviceConfig = deviceConfig; mAreFlagsOverrideable = build.isDebuggable() && mResources.getBoolean(R.bool.are_flags_overrideable); mDeviceConfig.addOnPropertiesChangedListener(executor, this::onPropertiesChanged); } /** * Returns true if the specified feature flag has been enabled. * * @param resId The backing boolean resource that determines the value of the flag. This value * can be overridden via DeviceConfig on development builds. */ public boolean isEnabled(@BoolRes int resId) { synchronized (mCachedFlags) { CachedFlag cachedFlag = mCachedFlags.get(resId); if (cachedFlag == null) { String name = resourceIdToFlagName(resId); boolean value = mResources.getBoolean(resId); if (mAreFlagsOverrideable) { value = mDeviceConfig.getBoolean(flagNameToStorageKey(name), value); } cachedFlag = new CachedFlag(name, value); mCachedFlags.put(resId, cachedFlag); } return cachedFlag.value; } } private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { synchronized (mCachedFlags) { for (String key : properties.getKeyset()) { String flagName = storageKeyToFlagName(key); if (flagName != null) { clearCachedFlag(flagName); } } } } private void clearCachedFlag(String flagName) { for (int i = 0; i < mCachedFlags.size(); i++) { CachedFlag flag = mCachedFlags.valueAt(i); if (flag.name.equals(flagName)) { mCachedFlags.removeAt(i); break; } } } private String resourceIdToFlagName(@BoolRes int resId) { String resName = mResources.getResourceEntryName(resId); if (resName.startsWith(RESNAME_PREFIX)) { resName = resName.substring(RESNAME_PREFIX.length()); } return resName; } private String flagNameToStorageKey(String flagName) { if (flagName.startsWith(STORAGE_KEY_PREFIX)) { return flagName; } else { return STORAGE_KEY_PREFIX + flagName; } } @Nullable private String storageKeyToFlagName(String configName) { if (configName.startsWith(STORAGE_KEY_PREFIX)) { return configName.substring(STORAGE_KEY_PREFIX.length()); } else { return null; } } private static class CachedFlag { public final String name; public final boolean value; private CachedFlag(String name, boolean value) { this.name = name; this.value = value; } } private static final String STORAGE_KEY_PREFIX = "flag_"; private static final String RESNAME_PREFIX = "flag_"; } packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +10 −52 Original line number Original line Diff line number Diff line Loading @@ -16,78 +16,36 @@ package com.android.systemui.statusbar; package com.android.systemui.statusbar; import android.annotation.NonNull; import com.android.systemui.R; import android.provider.DeviceConfig; import android.util.ArrayMap; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.flags.FeatureFlagReader; import java.util.Map; import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Inject; /** /** * Class to manage simple DeviceConfig-based feature flags. * Class to manage simple DeviceConfig-based feature flags. * * * To enable or disable a flag, run: * See {@link FeatureFlagReader} for instructions on defining and flipping flags. * * {@code * $ adb shell device_config put systemui <key> <true|false> * } * * You will probably need to restart systemui for the changes to be picked up: * * {@code * $ adb shell am restart com.android.systemui * } */ */ @SysUISingleton @SysUISingleton public class FeatureFlags { public class FeatureFlags { private final Map<String, Boolean> mCachedDeviceConfigFlags = new ArrayMap<>(); private final FeatureFlagReader mFlagReader; @Inject @Inject public FeatureFlags(@Background Executor executor) { public FeatureFlags(FeatureFlagReader flagReader) { DeviceConfig.addOnPropertiesChangedListener( mFlagReader = flagReader; /* namespace= */ "systemui", executor, this::onPropertiesChanged); } } public boolean isNewNotifPipelineEnabled() { public boolean isNewNotifPipelineEnabled() { return getDeviceConfigFlag("notification.newpipeline.enabled", /* defaultValue= */ true); return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2); } } public boolean isNewNotifPipelineRenderingEnabled() { public boolean isNewNotifPipelineRenderingEnabled() { return isNewNotifPipelineEnabled() return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2_rendering); && getDeviceConfigFlag("notification.newpipeline.rendering", /* defaultValue= */ false); } } /** /** b/171917882 */ * Flag used for guarding development of b/171917882. */ public boolean isTwoColumnNotificationShadeEnabled() { public boolean isTwoColumnNotificationShadeEnabled() { return getDeviceConfigFlag("notification.twocolumn", /* defaultValue= */ false); return mFlagReader.isEnabled(R.bool.flag_notification_twocolumn); } private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { synchronized (mCachedDeviceConfigFlags) { for (String key : properties.getKeyset()) { mCachedDeviceConfigFlags.remove(key); } } } private boolean getDeviceConfigFlag(String key, boolean defaultValue) { synchronized (mCachedDeviceConfigFlags) { Boolean flag = mCachedDeviceConfigFlags.get(key); if (flag == null) { flag = DeviceConfig.getBoolean(/* namespace= */ "systemui", key, defaultValue); mCachedDeviceConfigFlags.put(key, flag); } return flag; } } } } } packages/SystemUI/src/com/android/systemui/util/wrapper/BuildInfo.java 0 → 100644 +37 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2021 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.systemui.util.wrapper; import android.os.Build; import javax.inject.Inject; import javax.inject.Singleton; /** * Testable wrapper around {@link Build}. */ @Singleton public class BuildInfo { @Inject public BuildInfo() { } /** @see Build#IS_DEBUGGABLE */ public boolean isDebuggable() { return Build.IS_DEBUGGABLE; } } packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java 0 → 100644 +175 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2021 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.systemui.flags; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Resources; import android.provider.DeviceConfig; import androidx.annotation.BoolRes; import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.DeviceConfigHelper; import com.android.systemui.util.wrapper.BuildInfo; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; @SmallTest public class FeatureFlagReaderTest extends SysuiTestCase { @Mock private Resources mResources; @Mock private BuildInfo mBuildInfo; @Mock private DeviceConfigHelper mDeviceConfig; @Mock private Executor mBackgroundExecutor; private FeatureFlagReader mReader; @Captor private ArgumentCaptor<DeviceConfig.OnPropertiesChangedListener> mListenerCaptor; private DeviceConfig.OnPropertiesChangedListener mPropChangeListener; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mDeviceConfig.getBoolean(anyString(), anyBoolean())) .thenAnswer(invocation -> invocation.getArgument(1)); defineFlag(FLAG_RESID_0, false); defineFlag(FLAG_RESID_1, true); initialize(true, true); verify(mDeviceConfig).addOnPropertiesChangedListener(any(), mListenerCaptor.capture()); mPropChangeListener = mListenerCaptor.getValue(); } private void initialize(boolean isDebuggable, boolean isOverrideable) { when(mBuildInfo.isDebuggable()).thenReturn(isDebuggable); when(mResources.getBoolean(R.bool.are_flags_overrideable)).thenReturn(isOverrideable); mReader = new FeatureFlagReader(mResources, mBuildInfo, mDeviceConfig, mBackgroundExecutor); } @Test public void testCantOverrideIfNotDebuggable() { // GIVEN that the build is not debuggable initialize(false, true); // GIVEN that a flag has been overridden to true overrideFlag(FLAG_RESID_0, true); // THEN the flag is still false assertFalse(mReader.isEnabled(FLAG_RESID_0)); } @Test public void testCantOverrideIfNotOverrideable() { // GIVEN that flags are not overrideable initialize(true, false); // GIVEN that a flag has been overridden to true overrideFlag(FLAG_RESID_0, true); // THEN the flag is still false assertFalse(mReader.isEnabled(FLAG_RESID_0)); } @Test public void testReadFlags() { assertFalse(mReader.isEnabled(FLAG_RESID_0)); assertTrue(mReader.isEnabled(FLAG_RESID_1)); } @Test public void testOverrideFlags() { // GIVEN that flags are overridden overrideFlag(FLAG_RESID_0, true); overrideFlag(FLAG_RESID_1, false); // THEN the reader returns the overridden values assertTrue(mReader.isEnabled(FLAG_RESID_0)); assertFalse(mReader.isEnabled(FLAG_RESID_1)); } @Test public void testThatFlagReadsAreCached() { // GIVEN that a flag is overridden overrideFlag(FLAG_RESID_0, true); // WHEN the flag is queried many times mReader.isEnabled(FLAG_RESID_0); mReader.isEnabled(FLAG_RESID_0); mReader.isEnabled(FLAG_RESID_0); mReader.isEnabled(FLAG_RESID_0); // THEN the underlying resource and override are only queried once verify(mResources, times(1)).getBoolean(FLAG_RESID_0); verify(mDeviceConfig, times(1)).getBoolean(fakeStorageKey(FLAG_RESID_0), false); } @Test public void testCachesAreClearedAfterPropsChange() { // GIVEN a flag whose value has already been queried assertFalse(mReader.isEnabled(FLAG_RESID_0)); // WHEN the value of the flag changes overrideFlag(FLAG_RESID_0, true); Map<String, String> changedMap = new HashMap<>(); changedMap.put(fakeStorageKey(FLAG_RESID_0), "true"); DeviceConfig.Properties properties = new DeviceConfig.Properties("systemui", changedMap); mPropChangeListener.onPropertiesChanged(properties); // THEN the new value is provided assertTrue(mReader.isEnabled(FLAG_RESID_0)); } private void defineFlag(int resId, boolean value) { when(mResources.getBoolean(resId)).thenReturn(value); when(mResources.getResourceEntryName(resId)).thenReturn(fakeStorageKey(resId)); } private void overrideFlag(int resId, boolean value) { when(mDeviceConfig.getBoolean(eq(fakeStorageKey(resId)), anyBoolean())) .thenReturn(value); } private String fakeStorageKey(@BoolRes int resId) { return "flag_testname_" + resId; } private static final int FLAG_RESID_0 = 47; private static final int FLAG_RESID_1 = 48; } Loading
packages/SystemUI/res/values/flags.xml 0 → 100644 +26 −0 Original line number Original line Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2021 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. --> <resources> <bool name="are_flags_overrideable">true</bool> <bool name="flag_notification_pipeline2">false</bool> <bool name="flag_notification_pipeline2_rendering">false</bool> <!-- b/171917882 --> <bool name="flag_notification_twocolumn">false</bool> </resources>
packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java 0 → 100644 +165 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2021 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.systemui.flags; import android.annotation.NonNull; import android.content.res.Resources; import android.provider.DeviceConfig; import android.util.SparseArray; import androidx.annotation.BoolRes; import androidx.annotation.Nullable; import com.android.systemui.R; import com.android.systemui.assist.DeviceConfigHelper; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.util.wrapper.BuildInfo; import java.util.concurrent.Executor; import javax.inject.Inject; /** * Reads and caches feature flags for quick access * * Feature flags must be defined as boolean resources. For example: * * {@code * <bool name="flag_foo_bar_baz">false</bool> * } * * It is strongly recommended that the name of the resource begin with "flag_". * * Flags can be overridden via adb on development builds. For example, to override the flag from the * previous example, do the following: * * {@code * $ adb shell device_config put systemui flag_foo_bar_baz true * } * * Note that all storage keys begin with "flag_", even if their associated resId does not. * * Calls to this class should probably be wrapped by {@link FeatureFlags}. */ @SysUISingleton public class FeatureFlagReader { private final Resources mResources; private final DeviceConfigHelper mDeviceConfig; private final boolean mAreFlagsOverrideable; private final SparseArray<CachedFlag> mCachedFlags = new SparseArray<>(); @Inject public FeatureFlagReader( @Main Resources resources, BuildInfo build, DeviceConfigHelper deviceConfig, @Background Executor executor) { mResources = resources; mDeviceConfig = deviceConfig; mAreFlagsOverrideable = build.isDebuggable() && mResources.getBoolean(R.bool.are_flags_overrideable); mDeviceConfig.addOnPropertiesChangedListener(executor, this::onPropertiesChanged); } /** * Returns true if the specified feature flag has been enabled. * * @param resId The backing boolean resource that determines the value of the flag. This value * can be overridden via DeviceConfig on development builds. */ public boolean isEnabled(@BoolRes int resId) { synchronized (mCachedFlags) { CachedFlag cachedFlag = mCachedFlags.get(resId); if (cachedFlag == null) { String name = resourceIdToFlagName(resId); boolean value = mResources.getBoolean(resId); if (mAreFlagsOverrideable) { value = mDeviceConfig.getBoolean(flagNameToStorageKey(name), value); } cachedFlag = new CachedFlag(name, value); mCachedFlags.put(resId, cachedFlag); } return cachedFlag.value; } } private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { synchronized (mCachedFlags) { for (String key : properties.getKeyset()) { String flagName = storageKeyToFlagName(key); if (flagName != null) { clearCachedFlag(flagName); } } } } private void clearCachedFlag(String flagName) { for (int i = 0; i < mCachedFlags.size(); i++) { CachedFlag flag = mCachedFlags.valueAt(i); if (flag.name.equals(flagName)) { mCachedFlags.removeAt(i); break; } } } private String resourceIdToFlagName(@BoolRes int resId) { String resName = mResources.getResourceEntryName(resId); if (resName.startsWith(RESNAME_PREFIX)) { resName = resName.substring(RESNAME_PREFIX.length()); } return resName; } private String flagNameToStorageKey(String flagName) { if (flagName.startsWith(STORAGE_KEY_PREFIX)) { return flagName; } else { return STORAGE_KEY_PREFIX + flagName; } } @Nullable private String storageKeyToFlagName(String configName) { if (configName.startsWith(STORAGE_KEY_PREFIX)) { return configName.substring(STORAGE_KEY_PREFIX.length()); } else { return null; } } private static class CachedFlag { public final String name; public final boolean value; private CachedFlag(String name, boolean value) { this.name = name; this.value = value; } } private static final String STORAGE_KEY_PREFIX = "flag_"; private static final String RESNAME_PREFIX = "flag_"; }
packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +10 −52 Original line number Original line Diff line number Diff line Loading @@ -16,78 +16,36 @@ package com.android.systemui.statusbar; package com.android.systemui.statusbar; import android.annotation.NonNull; import com.android.systemui.R; import android.provider.DeviceConfig; import android.util.ArrayMap; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.flags.FeatureFlagReader; import java.util.Map; import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Inject; /** /** * Class to manage simple DeviceConfig-based feature flags. * Class to manage simple DeviceConfig-based feature flags. * * * To enable or disable a flag, run: * See {@link FeatureFlagReader} for instructions on defining and flipping flags. * * {@code * $ adb shell device_config put systemui <key> <true|false> * } * * You will probably need to restart systemui for the changes to be picked up: * * {@code * $ adb shell am restart com.android.systemui * } */ */ @SysUISingleton @SysUISingleton public class FeatureFlags { public class FeatureFlags { private final Map<String, Boolean> mCachedDeviceConfigFlags = new ArrayMap<>(); private final FeatureFlagReader mFlagReader; @Inject @Inject public FeatureFlags(@Background Executor executor) { public FeatureFlags(FeatureFlagReader flagReader) { DeviceConfig.addOnPropertiesChangedListener( mFlagReader = flagReader; /* namespace= */ "systemui", executor, this::onPropertiesChanged); } } public boolean isNewNotifPipelineEnabled() { public boolean isNewNotifPipelineEnabled() { return getDeviceConfigFlag("notification.newpipeline.enabled", /* defaultValue= */ true); return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2); } } public boolean isNewNotifPipelineRenderingEnabled() { public boolean isNewNotifPipelineRenderingEnabled() { return isNewNotifPipelineEnabled() return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2_rendering); && getDeviceConfigFlag("notification.newpipeline.rendering", /* defaultValue= */ false); } } /** /** b/171917882 */ * Flag used for guarding development of b/171917882. */ public boolean isTwoColumnNotificationShadeEnabled() { public boolean isTwoColumnNotificationShadeEnabled() { return getDeviceConfigFlag("notification.twocolumn", /* defaultValue= */ false); return mFlagReader.isEnabled(R.bool.flag_notification_twocolumn); } private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { synchronized (mCachedDeviceConfigFlags) { for (String key : properties.getKeyset()) { mCachedDeviceConfigFlags.remove(key); } } } private boolean getDeviceConfigFlag(String key, boolean defaultValue) { synchronized (mCachedDeviceConfigFlags) { Boolean flag = mCachedDeviceConfigFlags.get(key); if (flag == null) { flag = DeviceConfig.getBoolean(/* namespace= */ "systemui", key, defaultValue); mCachedDeviceConfigFlags.put(key, flag); } return flag; } } } } }
packages/SystemUI/src/com/android/systemui/util/wrapper/BuildInfo.java 0 → 100644 +37 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2021 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.systemui.util.wrapper; import android.os.Build; import javax.inject.Inject; import javax.inject.Singleton; /** * Testable wrapper around {@link Build}. */ @Singleton public class BuildInfo { @Inject public BuildInfo() { } /** @see Build#IS_DEBUGGABLE */ public boolean isDebuggable() { return Build.IS_DEBUGGABLE; } }
packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java 0 → 100644 +175 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2021 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.systemui.flags; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Resources; import android.provider.DeviceConfig; import androidx.annotation.BoolRes; import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.DeviceConfigHelper; import com.android.systemui.util.wrapper.BuildInfo; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; @SmallTest public class FeatureFlagReaderTest extends SysuiTestCase { @Mock private Resources mResources; @Mock private BuildInfo mBuildInfo; @Mock private DeviceConfigHelper mDeviceConfig; @Mock private Executor mBackgroundExecutor; private FeatureFlagReader mReader; @Captor private ArgumentCaptor<DeviceConfig.OnPropertiesChangedListener> mListenerCaptor; private DeviceConfig.OnPropertiesChangedListener mPropChangeListener; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mDeviceConfig.getBoolean(anyString(), anyBoolean())) .thenAnswer(invocation -> invocation.getArgument(1)); defineFlag(FLAG_RESID_0, false); defineFlag(FLAG_RESID_1, true); initialize(true, true); verify(mDeviceConfig).addOnPropertiesChangedListener(any(), mListenerCaptor.capture()); mPropChangeListener = mListenerCaptor.getValue(); } private void initialize(boolean isDebuggable, boolean isOverrideable) { when(mBuildInfo.isDebuggable()).thenReturn(isDebuggable); when(mResources.getBoolean(R.bool.are_flags_overrideable)).thenReturn(isOverrideable); mReader = new FeatureFlagReader(mResources, mBuildInfo, mDeviceConfig, mBackgroundExecutor); } @Test public void testCantOverrideIfNotDebuggable() { // GIVEN that the build is not debuggable initialize(false, true); // GIVEN that a flag has been overridden to true overrideFlag(FLAG_RESID_0, true); // THEN the flag is still false assertFalse(mReader.isEnabled(FLAG_RESID_0)); } @Test public void testCantOverrideIfNotOverrideable() { // GIVEN that flags are not overrideable initialize(true, false); // GIVEN that a flag has been overridden to true overrideFlag(FLAG_RESID_0, true); // THEN the flag is still false assertFalse(mReader.isEnabled(FLAG_RESID_0)); } @Test public void testReadFlags() { assertFalse(mReader.isEnabled(FLAG_RESID_0)); assertTrue(mReader.isEnabled(FLAG_RESID_1)); } @Test public void testOverrideFlags() { // GIVEN that flags are overridden overrideFlag(FLAG_RESID_0, true); overrideFlag(FLAG_RESID_1, false); // THEN the reader returns the overridden values assertTrue(mReader.isEnabled(FLAG_RESID_0)); assertFalse(mReader.isEnabled(FLAG_RESID_1)); } @Test public void testThatFlagReadsAreCached() { // GIVEN that a flag is overridden overrideFlag(FLAG_RESID_0, true); // WHEN the flag is queried many times mReader.isEnabled(FLAG_RESID_0); mReader.isEnabled(FLAG_RESID_0); mReader.isEnabled(FLAG_RESID_0); mReader.isEnabled(FLAG_RESID_0); // THEN the underlying resource and override are only queried once verify(mResources, times(1)).getBoolean(FLAG_RESID_0); verify(mDeviceConfig, times(1)).getBoolean(fakeStorageKey(FLAG_RESID_0), false); } @Test public void testCachesAreClearedAfterPropsChange() { // GIVEN a flag whose value has already been queried assertFalse(mReader.isEnabled(FLAG_RESID_0)); // WHEN the value of the flag changes overrideFlag(FLAG_RESID_0, true); Map<String, String> changedMap = new HashMap<>(); changedMap.put(fakeStorageKey(FLAG_RESID_0), "true"); DeviceConfig.Properties properties = new DeviceConfig.Properties("systemui", changedMap); mPropChangeListener.onPropertiesChanged(properties); // THEN the new value is provided assertTrue(mReader.isEnabled(FLAG_RESID_0)); } private void defineFlag(int resId, boolean value) { when(mResources.getBoolean(resId)).thenReturn(value); when(mResources.getResourceEntryName(resId)).thenReturn(fakeStorageKey(resId)); } private void overrideFlag(int resId, boolean value) { when(mDeviceConfig.getBoolean(eq(fakeStorageKey(resId)), anyBoolean())) .thenReturn(value); } private String fakeStorageKey(@BoolRes int resId) { return "flag_testname_" + resId; } private static final int FLAG_RESID_0 = 47; private static final int FLAG_RESID_1 = 48; }