Enable local DeviceConfig overriding from adb.
Test: new unit test
Bug: 298392357
Change-Id: I4b3736f6742c20e2c6ae39b1536c8c44707c9c4c
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 346462d..92ebe09 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -31,6 +31,7 @@
"unsupportedappusage",
],
static_libs: [
+ "device_config_service_flags_java",
"junit",
"SettingsLibDeviceStateRotationLock",
"SettingsLibDisplayUtils",
@@ -56,7 +57,10 @@
],
static_libs: [
"androidx.test.rules",
+ "device_config_service_flags_java",
+ "flag-junit",
"mockito-target-minus-junit4",
+ "platform-test-annotations",
"SettingsLibDeviceStateRotationLock",
"SettingsLibDisplayUtils",
"platform-test-annotations",
@@ -79,3 +83,16 @@
manifest: "test/AndroidManifest.xml",
test_config: "test/AndroidTest.xml",
}
+
+aconfig_declarations {
+ name: "device_config_service_flags",
+ package: "com.android.providers.settings",
+ srcs: [
+ "src/com/android/providers/settings/device_config_service.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "device_config_service_flags_java",
+ aconfig_declarations: "device_config_service_flags",
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index ffaebf4..b57f6ca 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE;
import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
+import static com.android.providers.settings.Flags.supportOverrides;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -42,10 +43,8 @@
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
-import java.io.InputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
@@ -69,6 +68,11 @@
"/system_ext/etc/aconfig_flags.textproto",
"/vendor/etc/aconfig_flags.textproto");
+ private static final List<String> PRIVATE_NAMESPACES = List.of(
+ "device_config_overrides",
+ "staged",
+ "token_staged");
+
final SettingsProvider mProvider;
public DeviceConfigService(SettingsProvider provider) {
@@ -171,6 +175,8 @@
enum CommandVerb {
GET,
PUT,
+ OVERRIDE,
+ CLEAR_OVERRIDE,
DELETE,
LIST,
LIST_NAMESPACES,
@@ -244,6 +250,10 @@
verb = CommandVerb.GET;
} else if ("put".equalsIgnoreCase(cmd)) {
verb = CommandVerb.PUT;
+ } else if (supportOverrides() && "override".equalsIgnoreCase(cmd)) {
+ verb = CommandVerb.OVERRIDE;
+ } else if (supportOverrides() && "clear_override".equalsIgnoreCase(cmd)) {
+ verb = CommandVerb.CLEAR_OVERRIDE;
} else if ("delete".equalsIgnoreCase(cmd)) {
verb = CommandVerb.DELETE;
} else if ("list".equalsIgnoreCase(cmd)) {
@@ -330,7 +340,7 @@
publicOnly = true;
}
} else if (namespace == null) {
- // GET, PUT, DELETE, LIST 1st arg
+ // GET, PUT, OVERRIDE, DELETE, LIST 1st arg
namespace = arg;
if (verb == CommandVerb.LIST) {
if (peekNextArg() == null) {
@@ -342,9 +352,12 @@
}
}
} else if (key == null) {
- // GET, PUT, DELETE 2nd arg
+ // GET, PUT, OVERRIDE, DELETE 2nd arg
key = arg;
- if ((verb == CommandVerb.GET || verb == CommandVerb.DELETE)) {
+ boolean validVerb = verb == CommandVerb.GET
+ || verb == CommandVerb.DELETE
+ || verb == CommandVerb.CLEAR_OVERRIDE;
+ if (validVerb) {
// GET, DELETE only have 2 args
if (peekNextArg() == null) {
isValid = true;
@@ -355,9 +368,11 @@
}
}
} else if (value == null) {
- // PUT 3rd arg (required)
+ // PUT, OVERRIDE 3rd arg (required)
value = arg;
- if (verb == CommandVerb.PUT && peekNextArg() == null) {
+ boolean validVerb = verb == CommandVerb.PUT
+ || verb == CommandVerb.OVERRIDE;
+ if (validVerb && peekNextArg() == null) {
isValid = true;
}
} else if ("default".equalsIgnoreCase(arg)) {
@@ -387,22 +402,80 @@
case PUT:
DeviceConfig.setProperty(namespace, key, value, makeDefault);
break;
+ case OVERRIDE:
+ if (supportOverrides()) {
+ DeviceConfig.setLocalOverride(namespace, key, value);
+ }
+ break;
+ case CLEAR_OVERRIDE:
+ if (supportOverrides()) {
+ DeviceConfig.clearLocalOverride(namespace, key);
+ }
+ break;
case DELETE:
pout.println(delete(iprovider, namespace, key)
? "Successfully deleted " + key + " from " + namespace
: "Failed to delete " + key + " from " + namespace);
break;
case LIST:
- if (namespace != null) {
- DeviceConfig.Properties properties = DeviceConfig.getProperties(namespace);
- List<String> keys = new ArrayList<>(properties.getKeyset());
- Collections.sort(keys);
- for (String name : keys) {
- pout.println(name + "=" + properties.getString(name, null));
+ if (supportOverrides()) {
+ pout.println("Server overrides:");
+
+ Map<String, Map<String, String>> underlyingValues =
+ DeviceConfig.getUnderlyingValuesForOverriddenFlags();
+
+ if (namespace != null) {
+ DeviceConfig.Properties properties =
+ DeviceConfig.getProperties(namespace);
+ List<String> keys = new ArrayList<>(properties.getKeyset());
+ Collections.sort(keys);
+ for (String name : keys) {
+ String valueReadFromDeviceConfig = properties.getString(name, null);
+ String underlyingValue = underlyingValues.get(namespace).get(name);
+ String printValue = underlyingValue != null
+ ? underlyingValue
+ : valueReadFromDeviceConfig;
+ pout.println(name + "=" + printValue);
+ }
+ } else {
+ for (String line : listAll(iprovider)) {
+ boolean isPrivateNamespace = false;
+ for (String privateNamespace : PRIVATE_NAMESPACES) {
+ if (line.startsWith(privateNamespace)) {
+ isPrivateNamespace = true;
+ }
+ }
+ if (!isPrivateNamespace) {
+ pout.println(line);
+ }
+ }
+ }
+
+ pout.println("");
+ pout.println("Local overrides (these take precedence):");
+ for (String overrideNamespace : underlyingValues.keySet()) {
+ Map<String, String> flagToValue =
+ underlyingValues.get(overrideNamespace);
+ for (String flag : flagToValue.keySet()) {
+ String flagText = overrideNamespace + "/" + flag;
+ String valueText =
+ DeviceConfig.getProperty(overrideNamespace, flag);
+ pout.println(flagText + "=" + valueText);
+ }
}
} else {
- for (String line : listAll(iprovider)) {
- pout.println(line);
+ if (namespace != null) {
+ DeviceConfig.Properties properties =
+ DeviceConfig.getProperties(namespace);
+ List<String> keys = new ArrayList<>(properties.getKeyset());
+ Collections.sort(keys);
+ for (String name : keys) {
+ pout.println(name + "=" + properties.getString(name, null));
+ }
+ } else {
+ for (String line : listAll(iprovider)) {
+ pout.println(line);
+ }
}
}
break;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
new file mode 100644
index 0000000..bb8bb00
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.providers.settings"
+
+flag {
+ name: "support_overrides"
+ namespace: "core_experiments_team_internal"
+ description: "When enabled, allows setting and displaying local overrides via adb."
+ bug: "b/298392357"
+}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
index 753378b..8dd51b2 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
@@ -22,21 +22,30 @@
import android.content.ContentResolver;
import android.os.Bundle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.google.common.io.CharStreams;
+
import libcore.io.Streams;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
/**
* Tests for {@link DeviceConfigService}.
@@ -49,6 +58,10 @@
private ContentResolver mContentResolver;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
mContentResolver = InstrumentationRegistry.getContext().getContentResolver();
@@ -59,6 +72,39 @@
deleteFromContentProvider(mContentResolver, sNamespace, sKey);
}
+ /**
+ * Test that setting overrides are properly disabled when the flag is off.
+ */
+ @Test
+ @RequiresFlagsDisabled("com.android.providers.settings.support_overrides")
+ public void testOverrideDisabled() throws IOException {
+ final String newValue = "value2";
+
+ executeShellCommand("device_config put " + sNamespace + " " + sKey + " " + sValue);
+ executeShellCommand("device_config override " + sNamespace + " " + sKey + " " + newValue);
+ String result = readShellCommandOutput("device_config get " + sNamespace + " " + sKey);
+ assertEquals(sValue + "\n", result);
+ }
+
+ /**
+ * Test that overrides are readable and can be cleared.
+ */
+ @Test
+ @RequiresFlagsEnabled("com.android.providers.settings.support_overrides")
+ public void testOverride() throws IOException {
+ final String newValue = "value2";
+
+ executeShellCommand("device_config put " + sNamespace + " " + sKey + " " + sValue);
+ executeShellCommand("device_config override " + sNamespace + " " + sKey + " " + newValue);
+
+ String result = readShellCommandOutput("device_config get " + sNamespace + " " + sKey);
+ assertEquals(newValue + "\n", result);
+
+ executeShellCommand("device_config clear_override " + sNamespace + " " + sKey);
+ result = readShellCommandOutput("device_config get " + sNamespace + " " + sKey);
+ assertEquals(sValue + "\n", result);
+ }
+
@Test
public void testPut() throws Exception {
final String newNamespace = "namespace2";
@@ -165,6 +211,12 @@
Streams.readFully(is);
}
+ private static String readShellCommandOutput(String command) throws IOException {
+ InputStream is = new FileInputStream(InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().executeShellCommand(command).getFileDescriptor());
+ return CharStreams.toString(new InputStreamReader(is, StandardCharsets.UTF_8));
+ }
+
private static void putWithContentProvider(ContentResolver resolver, String namespace,
String key, String value) {
putWithContentProvider(resolver, namespace, key, value, false);