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

Commit 484668de authored by Lorenzo Colitti's avatar Lorenzo Colitti Committed by Android (Google) Code Review
Browse files

Merge "Make FakeSettingsProvider multiuser aware, take 2." into main

parents 7cd4858f ab40b5f2
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: