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

Commit ab40b5f2 authored by Lorenzo Colitti's avatar Lorenzo Colitti
Browse files

Make FakeSettingsProvider multiuser aware, take 2.

This allows reading and writing settings with the
{get,put}StringForUser methods.

Compared to the previous version of the CL, which was reverted:
- Always read and write Global settings as USER_SYSTEM, because
  that's what SettingsProvider does,
- Don't throw UOE when removing settings in tables that were
  never written to.
- Treat USER_NULL as the current user, since many tests seem to
  write settings before initializing their multiuser code.
- Use the current userId if the caller does not pass in a userId,
  since in-process reads never pass in a userId.

Bug: 397521796
Flag: TEST_ONLY
Test: new unit tests
Test: passes HSUM presubmit: https://android-build.corp.google.com/builds/abtd/run/L55100030013022217
Test: atest PowerServiceTests_server_power
Test: atest DisplayServiceTests_server_display
Change-Id: I4aba97c1b49830a0a353f391b0cc95ae3a0941d0
parent 68da9a90
Loading
Loading
Loading
Loading
+78 −7
Original line number Diff line number Diff line
@@ -16,10 +16,14 @@

package com.android.internal.util.test;

import static junit.framework.Assert.assertNull;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import android.content.ContentProvider;
import android.os.Process;
import android.os.UserHandle;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.Settings;
@@ -42,6 +46,10 @@ public class FakeSettingsProviderTest {
    @Rule
    public final RavenwoodRule mRavenwood = new RavenwoodRule();

    private static final String SYSTEM_SETTING = Settings.System.SCREEN_BRIGHTNESS;
    private static final String SECURE_SETTING = Settings.Secure.BLUETOOTH_NAME;
    private static final String GLOBAL_SETTING = Settings.Global.MOBILE_DATA_ALWAYS_ON;

    private MockContentResolver mCr;

    @Before
@@ -50,18 +58,81 @@ public class FakeSettingsProviderTest {
        mCr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
    }

    private void assertSystemSettingNotFound(String name) {
        try {
            Settings.System.getInt(mCr, name);
            fail("Setting " + name + " unexpectedly present.");
        } catch (Settings.SettingNotFoundException expected) {
            // Expected behaviour.
        }
    }

    @Test
    @SmallTest
    public void testBasicOperation() throws Exception {
        String settingName = Settings.System.SCREEN_BRIGHTNESS;
        assertSystemSettingNotFound(SYSTEM_SETTING);

        // Check that fake settings can be written and read back.
        Settings.System.putInt(mCr, SYSTEM_SETTING, 123);
        assertEquals(123, Settings.System.getInt(mCr, SYSTEM_SETTING));

        // Fake settings can be removed.
        Settings.System.putString(mCr, SYSTEM_SETTING, null);
        assertSystemSettingNotFound(SYSTEM_SETTING);

        // Removal is a no-op if the setting does not exist.
        Settings.System.putString(mCr, SYSTEM_SETTING, null);
        assertSystemSettingNotFound(SYSTEM_SETTING);

        // Corner case: removing a setting in a table that never existed.
        Settings.Secure.putString(mCr, SECURE_SETTING, null);
    }

    private void assertUserHandleUnsupported(UserHandle userHandle, String settingName) {
        try {
            Settings.System.getInt(mCr, settingName);
            fail("FakeSettingsProvider should start off empty.");
        } catch (Settings.SettingNotFoundException expected) {}
            Settings.Secure.putStringForUser(mCr, settingName, "currentUserSetting",
                    userHandle.getIdentifier());
            fail("UserHandle " + userHandle + " is unsupported and should throw");
        } catch (UnsupportedOperationException expected) {
            // Expected behaviour.
        }
    }

    @Test
    @SmallTest
    public void testMultiUserOperation() {
        Settings.Secure.putStringForUser(mCr, SECURE_SETTING, "user0Setting", 0);
        Settings.Secure.putStringForUser(mCr, SECURE_SETTING, "user1Setting", 1);
        assertEquals("user0Setting", Settings.Secure.getStringForUser(mCr, SECURE_SETTING, 0));
        assertEquals("user1Setting", Settings.Secure.getStringForUser(mCr, SECURE_SETTING, 1));
        assertNull(Settings.Secure.getStringForUser(mCr, SECURE_SETTING, 2));

        final int currentUserId = UserHandle.getUserId(Process.myUid());
        Settings.Secure.putStringForUser(mCr, SECURE_SETTING, "currentUserSetting", currentUserId);
        assertEquals("currentUserSetting", Settings.Secure.getString(mCr, SECURE_SETTING));

        Settings.Secure.putString(mCr, SECURE_SETTING, "newValue");
        assertEquals("newValue",
                Settings.Secure.getStringForUser(mCr, SECURE_SETTING, currentUserId));

        Settings.Secure.putString(mCr, SECURE_SETTING, "newValue2");
        assertEquals("newValue2",
                Settings.Secure.getStringForUser(mCr, SECURE_SETTING, UserHandle.USER_CURRENT));

        Settings.Secure.putStringForUser(mCr, SECURE_SETTING, "newValue3", UserHandle.USER_CURRENT);
        assertEquals("newValue3", Settings.Secure.getString(mCr, SECURE_SETTING));

        assertUserHandleUnsupported(UserHandle.ALL, SECURE_SETTING);
        assertUserHandleUnsupported(UserHandle.CURRENT_OR_SELF, SECURE_SETTING);

        Settings.Global.putStringForUser(mCr, GLOBAL_SETTING, "globalSetting", 42);
        assertEquals("globalSetting", Settings.Global.getStringForUser(mCr, GLOBAL_SETTING, 1));
        assertEquals("globalSetting", Settings.Global.getStringForUser(mCr, GLOBAL_SETTING,
                UserHandle.USER_CURRENT));
        assertEquals("globalSetting", Settings.Global.getString(mCr, GLOBAL_SETTING));

        Settings.Global.putString(mCr, GLOBAL_SETTING, "newGlobalSetting");
        assertEquals("newGlobalSetting", Settings.Global.getStringForUser(mCr, GLOBAL_SETTING, 42));

        // Check that fake settings can be written and read back.
        Settings.System.putInt(mCr, settingName, 123);
        assertEquals(123, Settings.System.getInt(mCr, settingName));
    }
}
+51 −14
Original line number Diff line number Diff line
@@ -18,11 +18,13 @@ package com.android.internal.util.test;

import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
import android.test.mock.MockContentProvider;
import android.util.ArrayMap;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;

/**
 * Fake for system settings.
@@ -69,12 +71,19 @@ public class FakeSettingsProvider extends MockContentProvider {
    private static final String TAG = FakeSettingsProvider.class.getSimpleName();
    private static final boolean DBG = false;
    private static final String[] TABLES = { "system", "secure", "global" };
    private static final Map<String, String> EMPTY_MAP = Map.of();

    private final HashMap<String, HashMap<String, String>> mTables = new HashMap<>();
    /**
     * Stores settings values. Keyed by:
     * - Table name (e.g., "system")
     * - User ID (e.g., USER_SYSTEM)
     * - Setting name (e.g., "screen_brightness")
     */
    private final Map<String, Map<Integer, Map<String, String>>> mDb = new ArrayMap<>();

    public FakeSettingsProvider() {
        for (int i = 0; i < TABLES.length; i++) {
            mTables.put(TABLES[i], new HashMap<String, String>());
            mDb.put(TABLES[i], new ArrayMap<>());
        }
    }

@@ -108,6 +117,24 @@ public class FakeSettingsProvider extends MockContentProvider {
        Settings.System.clearProviderForTest();
    }

    private int getUserId(String table, Bundle extras) {
        // Global settings always use USER_SYSTEM, see SettingsProvider#getGlobalSetting.
        if ("global".equals(table)) {
            return UserHandle.USER_SYSTEM;
        }
        // When running in process, Settings does not set the userId. Default to current userId.
        final int myUserId = UserHandle.getCallingUserId();
        int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, myUserId);
        // Many tests write settings before initializing users, so treat USER_NULL as current user.
        if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_NULL) {
            return myUserId;
        }
        if (userId < 0) {
            throw new UnsupportedOperationException("userId " + userId + " is not a real user");
        }
        return userId;
    }

    public Bundle call(String method, String arg, Bundle extras) {
        // Methods are "GET_system", "GET_global", "PUT_secure", etc.
        String[] commands = method.split("_", 2);
@@ -116,27 +143,37 @@ public class FakeSettingsProvider extends MockContentProvider {

        Bundle out = new Bundle();
        String value;
        final int userId = getUserId(table, extras);

        switch (op) {
            case "GET":
                value = mTables.get(table).get(arg);
                value = mDb.get(table).getOrDefault(userId, EMPTY_MAP).get(arg);
                if (value != null) {
                    if (DBG) {
                        Log.d(TAG, String.format("Returning fake setting %s.%s = %s",
                                table, arg, value));
                    }
                    out.putString(Settings.NameValueTable.VALUE, value);
                }
                if (DBG) {
                    Log.d(TAG, String.format("Returning fake setting %d:%s.%s = %s",
                            userId, table, arg, (value == null) ? "null" : "\"" + value + "\""));
                }
                break;
            case "PUT":
                value = extras.getString(Settings.NameValueTable.VALUE, null);
                final boolean changed;
                if (value != null) {
                    changed = !value.equals(mDb.get(table)
                            .computeIfAbsent(userId, (u) -> new ArrayMap<>())
                            .put(arg, value));
                    if (DBG) {
                    Log.d(TAG, String.format("Inserting fake setting %s.%s = %s",
                            table, arg, value));
                        Log.d(TAG, String.format("Inserting fake setting %d:%s.%s = \"%s\"",
                                userId, table, arg, value));
                    }
                if (value != null) {
                    mTables.get(table).put(arg, value);
                } else {
                    mTables.get(table).remove(arg);
                    final Map<String, String> perUserTable = mDb.get(table).get(userId);
                    changed = perUserTable != null && perUserTable.remove(arg) != null;
                    if (DBG) {
                        Log.d(TAG, String.format("Removing fake setting %d:%s.%s", userId, table,
                                arg));
                    }
                }
                break;
            default: