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

Commit 9cb406e5 authored by Ted Bauer's avatar Ted Bauer Committed by Android (Google) Code Review
Browse files

Merge "Enable local DeviceConfig overriding from adb." into main

parents 850a7515 c4fff182
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);