Add SystemUiSystemPropertiesFlags

Bug: 267338705
Test: atest FrameworksCoreTests:SystemUiSystemPropertiesFlagsTest
Ignore-AOSP-First: folder is new in this branch
Change-Id: Iee2cac6f322b98254d709560b0a84f0549e91972
This commit is contained in:
Jeff DeCew
2023-01-30 22:13:08 +00:00
parent ef056c366d
commit 6fe1debe13
3 changed files with 294 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
/**
* Copyright (C) 2023 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.internal.config.sysui;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
import android.os.SystemProperties;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
/**
* Provides a central definition of debug SystemUI's SystemProperties flags, and their defaults.
*
* The main feature of this class is that it encodes a system-wide default for each flag which can
* be updated by engineers with a single-line CL.
*
* NOTE: Because flag values returned by this class are not cached, it is important that developers
* understand the intricacies of changing values and how that applies to their own code.
* Generally, the best practice is to set the property, and then restart the device so that any
* processes with stale state can be updated. However, if your code has no state derived from the
* flag value and queries it any time behavior is relevant, then it may be safe to change the flag
* and not immediately reboot.
*
* To enable flags in debuggable builds, use the following commands:
*
* $ adb shell setprop persist.sysui.whatever_the_flag true
* $ adb reboot
*
* @hide
*/
public class SystemUiSystemPropertiesFlags {
/** The interface used for resolving SystemUI SystemProperties Flags to booleans. */
public interface FlagResolver {
/** Is the flag enabled? */
boolean isEnabled(Flag flag);
}
/** The primary, immutable resolver returned by getResolver() */
private static final FlagResolver
MAIN_RESOLVER =
Build.IS_DEBUGGABLE ? new DebugResolver() : new ProdResolver();
/**
* On debuggable builds, this can be set to override the resolver returned by getResolver().
* This can be useful to override flags when testing components that do not allow injecting the
* SystemUiPropertiesFlags resolver they use.
* Always set this to null when tests tear down.
*/
@VisibleForTesting
public static FlagResolver TEST_RESOLVER = null;
/** Get the resolver for this device configuration. */
public static FlagResolver getResolver() {
if (Build.IS_DEBUGGABLE && TEST_RESOLVER != null) {
Log.i("SystemUiSystemPropertiesFlags", "Returning debug resolver " + TEST_RESOLVER);
return TEST_RESOLVER;
}
return MAIN_RESOLVER;
}
/** The teamfood flag allows multiple features to be opted into at once. */
public static final Flag TEAMFOOD = devFlag("persist.sysui.teamfood");
/**
* Flags related to notification features
*/
public static final class NotificationFlags {
/**
* FOR DEVELOPMENT / TESTING ONLY!!!
* Forcibly demote *ALL* FSI notifications as if no apps have the app op permission.
*/
public static final Flag FSI_FORCE_DEMOTE =
devFlag("persist.sysui.notification.fsi_force_demote");
/** Gating the ability for users to dismiss ongoing event notifications */
public static final Flag ALLOW_DISMISS_ONGOING =
devFlag("persist.sysui.notification.ongoing_dismissal");
/** Gating the redaction of OTP notifications on the lockscreen */
public static final Flag OTP_REDACTION =
devFlag("persist.sysui.notification.otp_redaction");
}
//// == Everything below this line is the implementation == ////
/**
* Creates a flag that is enabled by default in debuggable builds.
* It can be enabled by setting this flag's SystemProperty to 1.
*
* This flag is ALWAYS disabled in release builds.
*/
@VisibleForTesting
public static Flag devFlag(String name) {
return new Flag(name, false, null);
}
/**
* Creates a flag that is disabled by default in debuggable builds.
* It can be enabled or force-disabled by setting this flag's SystemProperty to 1 or 0.
* If this flag's SystemProperty is not set, the flag can be enabled by setting the
* TEAMFOOD flag's SystemProperty to 1.
*
* This flag is ALWAYS disabled in release builds.
*/
@VisibleForTesting
public static Flag teamfoodFlag(String name) {
return new Flag(name, false, TEAMFOOD);
}
/**
* Creates a flag that is enabled by default in debuggable builds.
* It can be enabled by setting this flag's SystemProperty to 0.
*
* This flag is ALWAYS enabled in release builds.
*/
@VisibleForTesting
public static Flag releasedFlag(String name) {
return new Flag(name, true, null);
}
/** Represents a developer-switchable gate for a feature. */
public static final class Flag {
public final String mSysPropKey;
public final boolean mDefaultValue;
@Nullable
public final Flag mDebugDefault;
/** constructs a new flag. only visible for testing the class */
@VisibleForTesting
public Flag(@NonNull String sysPropKey, boolean defaultValue, @Nullable Flag debugDefault) {
mSysPropKey = sysPropKey;
mDefaultValue = defaultValue;
mDebugDefault = debugDefault;
}
}
/** Implementation of the interface used in release builds. */
@VisibleForTesting
public static final class ProdResolver implements
FlagResolver {
@Override
public boolean isEnabled(Flag flag) {
return flag.mDefaultValue;
}
}
/** Implementation of the interface used in debuggable builds. */
@VisibleForTesting
public static class DebugResolver implements FlagResolver {
@Override
public final boolean isEnabled(Flag flag) {
if (flag.mDebugDefault == null) {
return getBoolean(flag.mSysPropKey, flag.mDefaultValue);
}
return getBoolean(flag.mSysPropKey, isEnabled(flag.mDebugDefault));
}
/** Look up the value; overridable for tests to avoid needing to set SystemProperties */
@VisibleForTesting
public boolean getBoolean(String key, boolean defaultValue) {
return SystemProperties.getBoolean(key, defaultValue);
}
}
}

View File

@@ -0,0 +1 @@
include /packages/SystemUI/OWNERS

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2023 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.internal.config.sysui;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.filters.SmallTest;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver;
import junit.framework.TestCase;
import java.util.HashMap;
import java.util.Map;
@SmallTest
public class SystemUiSystemPropertiesFlagsTest extends TestCase {
public class TestableDebugResolver extends SystemUiSystemPropertiesFlags.DebugResolver {
final Map<String, Boolean> mTestData = new HashMap<>();
@Override
public boolean getBoolean(String key, boolean defaultValue) {
Boolean testValue = mTestData.get(key);
return testValue == null ? defaultValue : testValue;
}
public void set(Flag flag, Boolean value) {
mTestData.put(flag.mSysPropKey, value);
}
}
private FlagResolver mProdResolver;
private TestableDebugResolver mDebugResolver;
private Flag mReleasedFlag;
private Flag mTeamfoodFlag;
private Flag mDevFlag;
public void setUp() {
mProdResolver = new SystemUiSystemPropertiesFlags.ProdResolver();
mDebugResolver = new TestableDebugResolver();
mReleasedFlag = SystemUiSystemPropertiesFlags.releasedFlag("mReleasedFlag");
mTeamfoodFlag = SystemUiSystemPropertiesFlags.teamfoodFlag("mTeamfoodFlag");
mDevFlag = SystemUiSystemPropertiesFlags.devFlag("mDevFlag");
}
public void tearDown() {
SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
}
public void testProdResolverReturnsDefault() {
assertThat(mProdResolver.isEnabled(mReleasedFlag)).isTrue();
assertThat(mProdResolver.isEnabled(mTeamfoodFlag)).isFalse();
assertThat(mProdResolver.isEnabled(mDevFlag)).isFalse();
}
public void testDebugResolverAndReleasedFlag() {
assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isTrue();
mDebugResolver.set(mReleasedFlag, false);
assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isFalse();
mDebugResolver.set(mReleasedFlag, true);
assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isTrue();
}
private void assertTeamfoodFlag(Boolean flagValue, Boolean teamfood, boolean expected) {
mDebugResolver.set(mTeamfoodFlag, flagValue);
mDebugResolver.set(SystemUiSystemPropertiesFlags.TEAMFOOD, teamfood);
assertThat(mDebugResolver.isEnabled(mTeamfoodFlag)).isEqualTo(expected);
}
public void testDebugResolverAndTeamfoodFlag() {
assertTeamfoodFlag(null, null, false);
assertTeamfoodFlag(true, null, true);
assertTeamfoodFlag(false, null, false);
assertTeamfoodFlag(null, true, true);
assertTeamfoodFlag(true, true, true);
assertTeamfoodFlag(false, true, false);
assertTeamfoodFlag(null, false, false);
assertTeamfoodFlag(true, false, true);
assertTeamfoodFlag(false, false, false);
}
public void testDebugResolverAndDevFlag() {
assertThat(mDebugResolver.isEnabled(mDevFlag)).isFalse();
mDebugResolver.set(mDevFlag, true);
assertThat(mDebugResolver.isEnabled(mDevFlag)).isTrue();
mDebugResolver.set(mDevFlag, false);
assertThat(mDebugResolver.isEnabled(mDevFlag)).isFalse();
}
}