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

Commit c4fff182 authored by Ted Bauer's avatar Ted Bauer
Browse files

Enable local DeviceConfig overriding from adb.

Test: new unit test
Bug: 298392357
Change-Id: I4b3736f6742c20e2c6ae39b1536c8c44707c9c4c
parent 4c7eb2c5
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ android_app {
        "unsupportedappusage",
    ],
    static_libs: [
        "device_config_service_flags_java",
        "junit",
        "SettingsLibDeviceStateRotationLock",
        "SettingsLibDisplayUtils",
@@ -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",
@@ -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",
}
+88 −15
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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) {
@@ -171,6 +175,8 @@ public final class DeviceConfigService extends Binder {
        enum CommandVerb {
            GET,
            PUT,
            OVERRIDE,
            CLEAR_OVERRIDE,
            DELETE,
            LIST,
            LIST_NAMESPACES,
@@ -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)) {
@@ -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) {
@@ -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;
@@ -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)) {
@@ -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) {
@@ -405,6 +477,7 @@ public final class DeviceConfigService extends Binder {
                                pout.println(line);
                            }
                        }
                    }
                    break;
                case LIST_NAMESPACES:
                    List<String> namespaces;
+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"
}
+52 −0
Original line number Diff line number Diff line
@@ -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}.
@@ -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();
@@ -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";
@@ -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);