Loading packages/SettingsProvider/Android.bp +17 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ android_app { "unsupportedappusage", ], static_libs: [ "device_config_service_flags_java", "junit", "SettingsLibDeviceStateRotationLock", "SettingsLibDisplayUtils", Loading @@ -56,7 +57,10 @@ android_test { ], static_libs: [ "androidx.test.rules", "device_config_service_flags_java", "flag-junit", "mockito-target-minus-junit4", "platform-test-annotations", "SettingsLibDeviceStateRotationLock", "SettingsLibDisplayUtils", "platform-test-annotations", Loading @@ -79,3 +83,16 @@ android_test { 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", } packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +88 −15 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.providers.settings; 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; Loading @@ -42,10 +43,8 @@ import com.android.internal.util.FastPrintWriter; 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; Loading @@ -69,6 +68,11 @@ public final class DeviceConfigService extends Binder { "/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) { Loading Loading @@ -171,6 +175,8 @@ public final class DeviceConfigService extends Binder { enum CommandVerb { GET, PUT, OVERRIDE, CLEAR_OVERRIDE, DELETE, LIST, LIST_NAMESPACES, Loading Loading @@ -244,6 +250,10 @@ public final class DeviceConfigService extends Binder { 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)) { Loading Loading @@ -330,7 +340,7 @@ public final class DeviceConfigService extends Binder { 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) { Loading @@ -342,9 +352,12 @@ public final class DeviceConfigService extends Binder { } } } 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; Loading @@ -355,9 +368,11 @@ public final class DeviceConfigService extends Binder { } } } 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)) { Loading Loading @@ -387,14 +402,71 @@ public final class DeviceConfigService extends Binder { 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 (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 { if (namespace != null) { DeviceConfig.Properties properties = DeviceConfig.getProperties(namespace); DeviceConfig.Properties properties = DeviceConfig.getProperties(namespace); List<String> keys = new ArrayList<>(properties.getKeyset()); Collections.sort(keys); for (String name : keys) { Loading @@ -405,6 +477,7 @@ public final class DeviceConfigService extends Binder { pout.println(line); } } } break; case LIST_NAMESPACES: List<String> namespaces; Loading packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig 0 → 100644 +8 −0 Original line number Diff line number Diff line 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" } packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java +52 −0 Original line number Diff line number Diff line Loading @@ -22,21 +22,30 @@ import static junit.framework.Assert.assertNull; 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}. Loading @@ -49,6 +58,10 @@ public class DeviceConfigServiceTest { private ContentResolver mContentResolver; @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Before public void setUp() { mContentResolver = InstrumentationRegistry.getContext().getContentResolver(); Loading @@ -59,6 +72,39 @@ public class DeviceConfigServiceTest { 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"; Loading Loading @@ -165,6 +211,12 @@ public class DeviceConfigServiceTest { 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); Loading Loading
packages/SettingsProvider/Android.bp +17 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ android_app { "unsupportedappusage", ], static_libs: [ "device_config_service_flags_java", "junit", "SettingsLibDeviceStateRotationLock", "SettingsLibDisplayUtils", Loading @@ -56,7 +57,10 @@ android_test { ], static_libs: [ "androidx.test.rules", "device_config_service_flags_java", "flag-junit", "mockito-target-minus-junit4", "platform-test-annotations", "SettingsLibDeviceStateRotationLock", "SettingsLibDisplayUtils", "platform-test-annotations", Loading @@ -79,3 +83,16 @@ android_test { 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", }
packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +88 −15 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.providers.settings; 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; Loading @@ -42,10 +43,8 @@ import com.android.internal.util.FastPrintWriter; 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; Loading @@ -69,6 +68,11 @@ public final class DeviceConfigService extends Binder { "/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) { Loading Loading @@ -171,6 +175,8 @@ public final class DeviceConfigService extends Binder { enum CommandVerb { GET, PUT, OVERRIDE, CLEAR_OVERRIDE, DELETE, LIST, LIST_NAMESPACES, Loading Loading @@ -244,6 +250,10 @@ public final class DeviceConfigService extends Binder { 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)) { Loading Loading @@ -330,7 +340,7 @@ public final class DeviceConfigService extends Binder { 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) { Loading @@ -342,9 +352,12 @@ public final class DeviceConfigService extends Binder { } } } 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; Loading @@ -355,9 +368,11 @@ public final class DeviceConfigService extends Binder { } } } 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)) { Loading Loading @@ -387,14 +402,71 @@ public final class DeviceConfigService extends Binder { 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 (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 { if (namespace != null) { DeviceConfig.Properties properties = DeviceConfig.getProperties(namespace); DeviceConfig.Properties properties = DeviceConfig.getProperties(namespace); List<String> keys = new ArrayList<>(properties.getKeyset()); Collections.sort(keys); for (String name : keys) { Loading @@ -405,6 +477,7 @@ public final class DeviceConfigService extends Binder { pout.println(line); } } } break; case LIST_NAMESPACES: List<String> namespaces; Loading
packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig 0 → 100644 +8 −0 Original line number Diff line number Diff line 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" }
packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java +52 −0 Original line number Diff line number Diff line Loading @@ -22,21 +22,30 @@ import static junit.framework.Assert.assertNull; 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}. Loading @@ -49,6 +58,10 @@ public class DeviceConfigServiceTest { private ContentResolver mContentResolver; @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Before public void setUp() { mContentResolver = InstrumentationRegistry.getContext().getContentResolver(); Loading @@ -59,6 +72,39 @@ public class DeviceConfigServiceTest { 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"; Loading Loading @@ -165,6 +211,12 @@ public class DeviceConfigServiceTest { 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); Loading