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

Commit 32bbdbdf authored by Biswarup Pal's avatar Biswarup Pal
Browse files

Make SettingsProvider device-aware

Allow system_server to override settings values for a
virtual device. Since virtual devices are ephemeral,
there is no need to persist settings specific to virtual
devices. Note that device-aware settings are applicable
only for secure and system settings, not for global,
config or ssaid settings. For more details, please refer
to go/device-aware-settings.

Test: atest SettingsProviderTest (module)
Test: atest android.provider.cts.settings.Settings_ConfigTest
Test: atest android.provider.cts.settings.Settings_SecureTest
Test: atest android.provider.cts.settings.Settings_SystemTest
Test: atest android.provider.cts.settings.SettingsTest
Test: atest android.provider.cts.settings.Settings_MemoryUsageTest
Test: atest android.appsecurity.cts.ReadableSettingsFieldsTest
Test: atest android.appsecurity.cts.SettingsProviderInvalidKeyTest
Flag: android.companion.virtualdevice.flags.device_aware_settings_override
Bug: 371801645
Change-Id: I3584ccb5911a8c44c2fbbfbfd4611b4e4331bb86
parent fb4bcf40
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -1207,6 +1207,22 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
        return null;
    }

    /**
     * Returns the device id associated with the {@link Context} of the caller.
     *
     * @see Context#getDeviceId()
     * @hide
     */
    public final int getCallingDeviceId() {
        if (android.permission.flags.Flags.deviceAwarePermissionApisEnabled()) {
            final AttributionSource attributionSource = mCallingAttributionSource.get();
            if (attributionSource != null) {
                return attributionSource.getDeviceId();
            }
        }
        return Context.DEVICE_ID_DEFAULT;
    }

    /**
     * @removed
     */
+69 −34
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import android.app.NotificationManager;
import android.app.SearchManager;
import android.app.WallpaperManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -3369,15 +3370,15 @@ public final class Settings {
    }
    private static final class GenerationTracker {
        @NonNull private final String mName;
        @NonNull private final Key mKey;
        @NonNull private final MemoryIntArray mArray;
        @NonNull private final Consumer<String> mErrorHandler;
        @NonNull private final Consumer<Key> mErrorHandler;
        private final int mIndex;
        private int mCurrentGeneration;
        GenerationTracker(@NonNull String name, @NonNull MemoryIntArray array, int index,
                int generation, Consumer<String> errorHandler) {
            mName = name;
        GenerationTracker(@NonNull Key key, @NonNull MemoryIntArray array, int index,
                int generation, @NonNull Consumer<Key> errorHandler) {
            mKey = key;
            mArray = array;
            mIndex = index;
            mErrorHandler = errorHandler;
@@ -3405,7 +3406,7 @@ public final class Settings {
                return mArray.get(mIndex);
            } catch (IOException e) {
                Log.e(TAG, "Error getting current generation", e);
                mErrorHandler.accept(mName);
                mErrorHandler.accept(mKey);
            }
            return -1;
        }
@@ -3422,6 +3423,28 @@ public final class Settings {
                super.finalize();
            }
        }
        private static final class Key {
            private final String mName;
            private final int mDeviceId;
            Key(String name, int deviceId) {
                mName = name;
                mDeviceId = deviceId;
            }
            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (!(o instanceof Key key)) return false;
                return mDeviceId == key.mDeviceId && Objects.equals(mName, key.mName);
            }
            @Override
            public int hashCode() {
                return Objects.hash(mName, mDeviceId);
            }
        }
    }
    private static void maybeCloseGenerationArray(@Nullable MemoryIntArray array) {
@@ -3479,9 +3502,9 @@ public final class Settings {
        private static final String NAME_EQ_PLACEHOLDER = "name=?";
        // Cached values of queried settings.
        // Key is the setting's name, value is the setting's value.
        // Key is composed by the setting's name and deviceId, value is the setting's value.
        // Must synchronize on 'this' to access mValues and mValuesVersion.
        private final ArrayMap<String, String> mValues = new ArrayMap<>();
        private final ArrayMap<GenerationTracker.Key, String> mValues = new ArrayMap<>();
        // Cached values for queried prefixes.
        // Key is the prefix, value is all of the settings under the prefix, mapped from a setting's
@@ -3505,19 +3528,22 @@ public final class Settings {
        private final ArraySet<String> mAllFields;
        private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk;
        // Mapping from the name of a setting (or the prefix of a namespace) to a generation tracker
        // Mapping of key to generation trackers for queried settings.
        // Key is composed by the setting's name and deviceId, value is the generation tracker.
        // Must synchronize on 'this' to access mGenerationTrackers.
        @GuardedBy("this")
        private ArrayMap<String, GenerationTracker> mGenerationTrackers = new ArrayMap<>();
        private final ArrayMap<GenerationTracker.Key, GenerationTracker> mGenerationTrackers =
                new ArrayMap<>();
        private Consumer<String> mGenerationTrackerErrorHandler = (String name) -> {
        private final Consumer<GenerationTracker.Key> mGenerationTrackerErrorHandler = (key) -> {
            synchronized (NameValueCache.this) {
                Log.e(TAG, "Error accessing generation tracker - removing");
                final GenerationTracker tracker = mGenerationTrackers.get(name);
                final GenerationTracker tracker = mGenerationTrackers.get(key);
                if (tracker != null) {
                    tracker.destroy();
                    mGenerationTrackers.remove(name);
                    mGenerationTrackers.remove(key);
                }
                mValues.remove(name);
                mValues.remove(key);
            }
        };
@@ -3621,11 +3647,18 @@ public final class Settings {
        @UnsupportedAppUsage
        public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
            final boolean isSelf = (userHandle == UserHandle.myUserId());
            final AttributionSource attributionSource = cr.getAttributionSource();
            final int deviceId =
                    android.companion.virtualdevice.flags.Flags.deviceAwareSettingsOverride()
                            && android.permission.flags.Flags.deviceAwarePermissionApisEnabled()
                            && attributionSource != null
                            ? attributionSource.getDeviceId() : Context.DEVICE_ID_DEFAULT;
            final GenerationTracker.Key key = new GenerationTracker.Key(name, deviceId);
            final boolean useCache = isSelf && !isInSystemServer();
            boolean needsGenerationTracker = false;
            if (useCache) {
                synchronized (NameValueCache.this) {
                    final GenerationTracker generationTracker = mGenerationTrackers.get(name);
                    final GenerationTracker generationTracker = mGenerationTrackers.get(key);
                    if (generationTracker != null) {
                        if (generationTracker.isGenerationChanged()) {
                            if (DEBUG) {
@@ -3636,14 +3669,14 @@ public final class Settings {
                            }
                            // When a generation number changes, remove cached value, remove the old
                            // generation tracker and request a new one
                            mValues.remove(name);
                            mValues.remove(key);
                            generationTracker.destroy();
                            mGenerationTrackers.remove(name);
                        } else if (mValues.containsKey(name)) {
                            mGenerationTrackers.remove(key);
                        } else if (mValues.containsKey(key)) {
                            if (DEBUG) {
                                Log.i(TAG, "Cache hit for setting:" + name);
                            }
                            return mValues.get(name);
                            return mValues.get(key);
                        }
                    }
                }
@@ -3761,23 +3794,23 @@ public final class Settings {
                                        }
                                        // Always make sure to close any pre-existing tracker before
                                        // replacing it, to prevent memory leaks
                                        var oldTracker = mGenerationTrackers.get(name);
                                        var oldTracker = mGenerationTrackers.get(key);
                                        if (oldTracker != null) {
                                            oldTracker.destroy();
                                        }
                                        mGenerationTrackers.put(name, new GenerationTracker(name,
                                        mGenerationTrackers.put(key, new GenerationTracker(key,
                                                array, index, generation,
                                                mGenerationTrackerErrorHandler));
                                    } else {
                                        maybeCloseGenerationArray(array);
                                    }
                                }
                                if (mGenerationTrackers.get(name) != null
                                        && !mGenerationTrackers.get(name).isGenerationChanged()) {
                                GenerationTracker tracker = mGenerationTrackers.get(key);
                                if (tracker != null && !tracker.isGenerationChanged()) {
                                    if (DEBUG) {
                                        Log.i(TAG, "Updating cache for setting:" + name);
                                    }
                                    mValues.put(name, value);
                                    mValues.put(key, value);
                                }
                            }
                        } else {
@@ -3822,12 +3855,12 @@ public final class Settings {
                String value = c.moveToNext() ? c.getString(0) : null;
                synchronized (NameValueCache.this) {
                    if (mGenerationTrackers.get(name) != null
                            && !mGenerationTrackers.get(name).isGenerationChanged()) {
                    GenerationTracker tracker = mGenerationTrackers.get(key);
                    if (tracker != null && !tracker.isGenerationChanged()) {
                        if (DEBUG) {
                            Log.i(TAG, "Updating cache for setting:" + name + " using query");
                        }
                        mValues.put(name, value);
                        mValues.put(key, value);
                    }
                }
                return value;
@@ -3859,13 +3892,15 @@ public final class Settings {
        private Map<String, String> getStringsForPrefixStripPrefix(
                ContentResolver cr, String prefix, List<String> names) {
            final GenerationTracker.Key trackerKey = new GenerationTracker.Key(prefix,
                    Context.DEVICE_ID_DEFAULT);
            String namespace = prefix.substring(0, prefix.length() - 1);
            ArrayMap<String, String> keyValues = new ArrayMap<>();
            int substringLength = prefix.length();
            int currentGeneration = -1;
            boolean needsGenerationTracker = false;
            synchronized (NameValueCache.this) {
                final GenerationTracker generationTracker = mGenerationTrackers.get(prefix);
                final GenerationTracker generationTracker = mGenerationTrackers.get(trackerKey);
                if (generationTracker != null) {
                    if (generationTracker.isGenerationChanged()) {
                        if (DEBUG) {
@@ -3876,7 +3911,7 @@ public final class Settings {
                        // When a generation number changes, remove cached values, remove the old
                        // generation tracker and request a new one
                        generationTracker.destroy();
                        mGenerationTrackers.remove(prefix);
                        mGenerationTrackers.remove(trackerKey);
                        mPrefixToValues.remove(prefix);
                        needsGenerationTracker = true;
                    } else {
@@ -3993,20 +4028,20 @@ public final class Settings {
                            }
                            // Always make sure to close any pre-existing tracker before
                            // replacing it, to prevent memory leaks
                            var oldTracker = mGenerationTrackers.get(prefix);
                            var oldTracker = mGenerationTrackers.get(trackerKey);
                            if (oldTracker != null) {
                                oldTracker.destroy();
                            }
                            mGenerationTrackers.put(prefix,
                                    new GenerationTracker(prefix, array, index, generation,
                            mGenerationTrackers.put(trackerKey,
                                    new GenerationTracker(trackerKey, array, index, generation,
                                            mGenerationTrackerErrorHandler));
                            currentGeneration = generation;
                        } else {
                            maybeCloseGenerationArray(array);
                        }
                    }
                    if (mGenerationTrackers.get(prefix) != null && currentGeneration
                            == mGenerationTrackers.get(prefix).getCurrentGeneration()) {
                    GenerationTracker tracker = mGenerationTrackers.get(trackerKey);
                    if (tracker != null && currentGeneration == tracker.getCurrentGeneration()) {
                        if (DEBUG) {
                            Log.i(TAG, "Updating cache for prefix:" + prefix);
                        }
+1 −1
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ message GenerationRegistryProto {
}

message BackingStoreProto {
  optional int32 key = 1;
  optional int64 key = 1;
  optional int32 backing_store_size = 2;
  optional int32 num_cached_entries = 3;
  repeated CacheEntryProto cache_entries = 4;
+3 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ android_library {
        "aconfig_new_storage_flags_lib",
        "aconfigd_java_utils",
        "aconfig_demo_flags_java_lib",
        "android.companion.virtualdevice.flags-aconfig-java",
        "configinfra_framework_flags_java_lib",
        "device_config_service_flags_java",
        "libaconfig_java_proto_lite",
@@ -62,6 +63,7 @@ android_test {
        "test/**/*.java",
    ],
    static_libs: [
        "CtsVirtualDeviceCommonLib",
        // Note we statically link SettingsProviderLib to do some unit tests.  It's not accessible otherwise
        // because this test is not an instrumentation test. (because the target runs in the system process.)
        "SettingsProviderLib",
@@ -70,6 +72,7 @@ android_test {
        "device_config_service_flags_java",
        "flag-junit",
        "junit",
        "junit-params",
        "libaconfig_java_proto_lite",
        "mockito-target-minus-junit4",
        "platform-test-annotations",
+42 −36
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.providers.settings;

import android.annotation.NonNull;
import android.content.Context;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
@@ -24,6 +25,7 @@ import android.providers.settings.BackingStoreProto;
import android.providers.settings.CacheEntryProto;
import android.providers.settings.GenerationRegistryProto;
import android.util.ArrayMap;
import android.util.LongSparseArray;
import android.util.MemoryIntArray;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -50,11 +52,12 @@ final class GenerationRegistry {

    // Key -> backingStore mapping
    @GuardedBy("mLock")
    private final ArrayMap<Integer, MemoryIntArray> mKeyToBackingStoreMap = new ArrayMap();
    private final LongSparseArray<MemoryIntArray> mKeyToBackingStoreMap = new LongSparseArray<>();

    // Key -> (String->Index map) mapping
    @GuardedBy("mLock")
    private final ArrayMap<Integer, ArrayMap<String, Integer>> mKeyToIndexMapMap = new ArrayMap<>();
    private final LongSparseArray<ArrayMap<String, Integer>> mKeyToIndexMapMap =
            new LongSparseArray<>();

    @GuardedBy("mLock")
    private int mNumBackingStore = 0;
@@ -90,18 +93,19 @@ final class GenerationRegistry {
     *  Increment the generation number if the setting is already cached in the backing stores.
     *  Otherwise, do nothing.
     */
    public void incrementGeneration(int key, String name) {
        final boolean isConfig =
                (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
    public void incrementGeneration(long key, String name) {
        final boolean isConfig = SettingsState.isConfigSettingsKey(key);
        // Only store the prefix if the mutated setting is a config
        final String indexMapKey = isConfig ? (name.split("/")[0] + "/") : name;
        incrementGenerationInternal(key, indexMapKey);
    }

    private void incrementGenerationInternal(int key, @NonNull String indexMapKey) {
    private void incrementGenerationInternal(long key, @NonNull String indexMapKey) {
        if (SettingsState.isGlobalSettingsKey(key)) {
            // Global settings are shared across users, so ignore the userId in the key
            key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
            // Global settings are shared across users and devices, so ignore the userId and the
            // deviceId in the key
            key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM,
                    Context.DEVICE_ID_DEFAULT);
        }
        synchronized (mLock) {
            final MemoryIntArray backingStore = getBackingStoreLocked(key,
@@ -132,9 +136,8 @@ final class GenerationRegistry {

    // A new, non-predefined setting has been inserted, increment the tracking number for all unset
    // settings
    public void incrementGenerationForUnsetSettings(int key) {
        final boolean isConfig =
                (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
    public void incrementGenerationForUnsetSettings(long key) {
        final boolean isConfig = SettingsState.isConfigSettingsKey(key);
        if (isConfig) {
            // No need to track new settings for configs
            return;
@@ -147,10 +150,12 @@ final class GenerationRegistry {
     *  of a cached setting. If it was not in the backing store, first create the entry in it before
     *  returning the result.
     */
    public void addGenerationData(Bundle bundle, int key, String indexMapKey) {
    public void addGenerationData(Bundle bundle, long key, String indexMapKey) {
        if (SettingsState.isGlobalSettingsKey(key)) {
            // Global settings are shared across users, so ignore the userId in the key
            key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
            // Global settings are shared across users and devices, so ignore the userId
            // and the deviceId in the key
            key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM,
                    Context.DEVICE_ID_DEFAULT);
        }
        synchronized (mLock) {
            final MemoryIntArray backingStore = getBackingStoreLocked(key,
@@ -181,22 +186,22 @@ final class GenerationRegistry {
        }
    }

    public void addGenerationDataForUnsetSettings(Bundle bundle, int key) {
    public void addGenerationDataForUnsetSettings(Bundle bundle, long key) {
        addGenerationData(bundle, key, /* indexMapKey= */ DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS);
    }

    public void onUserRemoved(int userId) {
        final int secureKey = SettingsState.makeKey(
                SettingsState.SETTINGS_TYPE_SECURE, userId);
        final int systemKey = SettingsState.makeKey(
                SettingsState.SETTINGS_TYPE_SYSTEM, userId);
    public void onUserAndDeviceRemoved(int userId, int deviceId) {
        final long secureKey = SettingsState.makeKey(
                SettingsState.SETTINGS_TYPE_SECURE, userId, deviceId);
        final long systemKey = SettingsState.makeKey(
                SettingsState.SETTINGS_TYPE_SYSTEM, userId, deviceId);
        synchronized (mLock) {
            if (mKeyToIndexMapMap.containsKey(secureKey)) {
            if (mKeyToIndexMapMap.get(secureKey) != null) {
                destroyBackingStoreLocked(secureKey);
                mKeyToIndexMapMap.remove(secureKey);
                mNumBackingStore = mNumBackingStore - 1;
            }
            if (mKeyToIndexMapMap.containsKey(systemKey)) {
            if (mKeyToIndexMapMap.get(systemKey) != null) {
                destroyBackingStoreLocked(systemKey);
                mKeyToIndexMapMap.remove(systemKey);
                mNumBackingStore = mNumBackingStore - 1;
@@ -205,7 +210,7 @@ final class GenerationRegistry {
    }

    @GuardedBy("mLock")
    private MemoryIntArray getBackingStoreLocked(int key, boolean createIfNotExist) {
    private MemoryIntArray getBackingStoreLocked(long key, boolean createIfNotExist) {
        MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
        if (!createIfNotExist) {
            return backingStore;
@@ -222,9 +227,8 @@ final class GenerationRegistry {
                mKeyToBackingStoreMap.put(key, backingStore);
                mNumBackingStore += 1;
                if (DEBUG) {
                    Slog.e(LOG_TAG, "Created backing store for "
                            + SettingsState.keyToString(key) + " on user: "
                            + SettingsState.getUserIdFromKey(key));
                    Slog.e(LOG_TAG, "Created backing store for key "
                            + SettingsState.keyToString(key));
                }
            } catch (IOException e) {
                Slog.e(LOG_TAG, "Error creating generation tracker", e);
@@ -234,7 +238,7 @@ final class GenerationRegistry {
    }

    @GuardedBy("mLock")
    private void destroyBackingStoreLocked(int key) {
    private void destroyBackingStoreLocked(long key) {
        MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
        if (backingStore != null) {
            try {
@@ -249,8 +253,8 @@ final class GenerationRegistry {
        }
    }

    private static int getKeyIndexLocked(int key, String indexMapKey,
            ArrayMap<Integer, ArrayMap<String, Integer>> keyToIndexMapMap,
    private static int getKeyIndexLocked(long key, String indexMapKey,
            LongSparseArray<ArrayMap<String, Integer>> keyToIndexMapMap,
            MemoryIntArray backingStore, boolean createIfNotExist) throws IOException {
        ArrayMap<String, Integer> nameToIndexMap = keyToIndexMapMap.get(key);
        if (nameToIndexMap == null) {
@@ -271,8 +275,10 @@ final class GenerationRegistry {
                nameToIndexMap.put(indexMapKey, index);
                if (DEBUG) {
                    Slog.i(LOG_TAG, "Allocated index:" + index + " for setting:" + indexMapKey
                            + " of type:" + SettingsState.keyToString(key)
                            + " on user:" + SettingsState.getUserIdFromKey(key));
                            + " of type:"
                            + SettingsState.settingTypeToString(SettingsState.getTypeFromKey(key))
                            + " on user:" + SettingsState.getUserIdFromKey(key)
                            + " on device:" + SettingsState.getDeviceIdFromKey(key));
                }
            } else {
                if (DEBUG) {
@@ -306,10 +312,10 @@ final class GenerationRegistry {

            for (int i = 0; i < numBackingStores; i++) {
                final long token = proto.start(GenerationRegistryProto.BACKING_STORES);
                final int key = mKeyToBackingStoreMap.keyAt(i);
                final long key = mKeyToBackingStoreMap.keyAt(i);
                proto.write(BackingStoreProto.KEY, key);
                proto.write(BackingStoreProto.BACKING_STORE_SIZE,
                        mKeyToBackingStoreMap.valueAt(i).size());
                        mKeyToBackingStoreMap.get(key).size());
                proto.write(BackingStoreProto.NUM_CACHED_ENTRIES,
                        mKeyToIndexMapMap.get(key).size());
                final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key);
@@ -339,7 +345,6 @@ final class GenerationRegistry {
                }
                proto.end(token);
            }

        }
    }

@@ -350,11 +355,12 @@ final class GenerationRegistry {
            final int numBackingStores = mKeyToBackingStoreMap.size();
            pw.println("Number of backing stores:" + numBackingStores);
            for (int i = 0; i < numBackingStores; i++) {
                final int key = mKeyToBackingStoreMap.keyAt(i);
                final long key = mKeyToBackingStoreMap.keyAt(i);
                pw.print("_Backing store for type:"); pw.print(SettingsState.settingTypeToString(
                        SettingsState.getTypeFromKey(key)));
                pw.print(" user:"); pw.print(SettingsState.getUserIdFromKey(key));
                pw.print(" size:" + mKeyToBackingStoreMap.valueAt(i).size());
                pw.print(" deviceId:"); pw.print(SettingsState.getDeviceIdFromKey(key));
                pw.print(" size:" + mKeyToBackingStoreMap.get(key).size());
                pw.println(" cachedEntries:" + mKeyToIndexMapMap.get(key).size());
                final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key);
                final MemoryIntArray backingStore = getBackingStoreLocked(key,
Loading