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

Commit 793b15c1 authored by Matt Pape's avatar Matt Pape
Browse files

Adds DeviceConfig.getProperties method for atomic reading.

This fetches multiple flags atomically, either the entire contents of
the namespace, or just a list of flags specified by the caller. This
update also enables the local Settings class to fetch and cache the entire namespace
whenever any flags from that namespace are read so that we only incur the cost of the
IPC once.

Test: atest FrameworksCoreTests:DeviceConfigTest
      atest FrameworksCoreTests:SettingsProviderTest
      atest SettingsProviderTest:DeviceConfigServiceTest
Bug: 136135417
Change-Id: I0be7c4b51c37590f5001e53b074b06246851a198
parent 07795adc
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -5875,6 +5875,7 @@ package android.provider {
    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float);
    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static int getInt(@NonNull String, @NonNull String, int);
    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static long getLong(@NonNull String, @NonNull String, long);
    method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getProperty(@NonNull String, @NonNull String);
    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
    method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
+30 −2
Original line number Diff line number Diff line
@@ -403,9 +403,37 @@ public final class DeviceConfig {
    @TestApi
    @RequiresPermission(READ_DEVICE_CONFIG)
    public static String getProperty(@NonNull String namespace, @NonNull String name) {
        // Fetch all properties for the namespace at once and cache them in the local process, so we
        // incur the cost of the IPC less often. Lookups happen much more frequently than updates,
        // and we want to optimize the former.
        return getProperties(namespace, name).getString(name, null);
    }

    /**
     * Look up the values of multiple properties for a particular namespace. The lookup is atomic,
     * such that the values of these properties cannot change between the time when the first is
     * fetched and the time when the last is fetched.
     *
     * TODO: reference setProperties when it is added.
     *
     * @param namespace The namespace containing the properties to look up.
     * @param names     The names of properties to look up, or empty to fetch all properties for the
     *                  given namespace.
     * @return {@link Properties} object containing the requested properties. This reflects the
     *     state of these properties at the time of the lookup, and is not updated to reflect any
     *     future changes. The keyset of this Properties object will contain only the intersection
     *     of properties already set and properties requested via the names parameter. Properties
     *     that are already set but were not requested will not be contained here. Properties that
     *     are not set, but were requested will not be contained here either.
     * @hide
     */
    @SystemApi
    @NonNull
    @RequiresPermission(READ_DEVICE_CONFIG)
    public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) {
        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
        String compositeName = createCompositeName(namespace, name);
        return Settings.Config.getString(contentResolver, compositeName);
        return new Properties(namespace,
                Settings.Config.getStrings(contentResolver, namespace, Arrays.asList(names)));
    }

    /**
+179 −3
Original line number Diff line number Diff line
@@ -84,8 +84,10 @@ import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -2248,7 +2250,7 @@ public final class Settings {
        private static final String NAME_EQ_PLACEHOLDER = "name=?";
        // Must synchronize on 'this' to access mValues and mValuesVersion.
        private final HashMap<String, String> mValues = new HashMap<>();
        private final ArrayMap<String, String> mValues = new ArrayMap<>();
        private final Uri mUri;
        @UnsupportedAppUsage
@@ -2258,15 +2260,22 @@ public final class Settings {
        // for the fast path of retrieving settings.
        private final String mCallGetCommand;
        private final String mCallSetCommand;
        private final String mCallListCommand;
        @GuardedBy("this")
        private GenerationTracker mGenerationTracker;
        public NameValueCache(Uri uri, String getCommand, String setCommand,
                ContentProviderHolder providerHolder) {
            this(uri, getCommand, setCommand, null, providerHolder);
        }
        NameValueCache(Uri uri, String getCommand, String setCommand, String listCommand,
                ContentProviderHolder providerHolder) {
            mUri = uri;
            mCallGetCommand = getCommand;
            mCallSetCommand = setCommand;
            mCallListCommand = listCommand;
            mProviderHolder = providerHolder;
        }
@@ -2448,8 +2457,8 @@ public final class Settings {
                String value = c.moveToNext() ? c.getString(0) : null;
                synchronized (NameValueCache.this) {
                    if(mGenerationTracker != null &&
                            currentGeneration == mGenerationTracker.getCurrentGeneration()) {
                    if (mGenerationTracker != null
                            && currentGeneration == mGenerationTracker.getCurrentGeneration()) {
                        mValues.put(name, value);
                    }
                }
@@ -2466,6 +2475,141 @@ public final class Settings {
            }
        }
        public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
                List<String> names) {
            ArrayMap<String, String> keyValues = new ArrayMap<>();
            int currentGeneration = -1;
            synchronized (NameValueCache.this) {
                if (mGenerationTracker != null) {
                    if (mGenerationTracker.isGenerationChanged()) {
                        if (DEBUG) {
                            Log.i(TAG, "Generation changed for type:" + mUri.getPath()
                                    + " in package:" + cr.getPackageName());
                        }
                        mValues.clear();
                    } else {
                        boolean prefixCached = false;
                        int size = mValues.size();
                        for (int i = 0; i < size; ++i) {
                            if (mValues.keyAt(i).startsWith(prefix + "/")) {
                                prefixCached = true;
                                break;
                            }
                        }
                        if (prefixCached) {
                            if (!names.isEmpty()) {
                                for (String name : names) {
                                    if (mValues.containsKey(name)) {
                                        keyValues.put(name, mValues.get(name));
                                    }
                                }
                            } else {
                                for (int i = 0; i < size; ++i) {
                                    String key = mValues.keyAt(i);
                                    if (key.startsWith(prefix + "/")) {
                                        keyValues.put(key, mValues.get(key));
                                    }
                                }
                            }
                            return keyValues;
                        }
                    }
                    if (mGenerationTracker != null) {
                        currentGeneration = mGenerationTracker.getCurrentGeneration();
                    }
                }
            }
            if (mCallListCommand == null) {
                // No list command specified, return empty map
                return keyValues;
            }
            IContentProvider cp = mProviderHolder.getProvider(cr);
            try {
                Bundle args = new Bundle();
                args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
                boolean needsGenerationTracker = false;
                synchronized (NameValueCache.this) {
                    if (mGenerationTracker == null) {
                        needsGenerationTracker = true;
                        args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
                        if (DEBUG) {
                            Log.i(TAG, "Requested generation tracker for type: "
                                    + mUri.getPath() + " in package:" + cr.getPackageName());
                        }
                    }
                }
                // Fetch all flags for the namespace at once for caching purposes
                Bundle b = cp.call(cr.getPackageName(), mProviderHolder.mUri.getAuthority(),
                        mCallListCommand, null, args);
                if (b == null) {
                    // Invalid response, return an empty map
                    return keyValues;
                }
                // All flags for the namespace
                Map<String, String> flagsToValues =
                        (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
                // Only the flags requested by the caller
                if (!names.isEmpty()) {
                    for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
                        if (names.contains(flag.getKey())) {
                            keyValues.put(flag.getKey(), flag.getValue());
                        }
                    }
                } else {
                    keyValues.putAll(flagsToValues);
                }
                synchronized (NameValueCache.this) {
                    if (needsGenerationTracker) {
                        MemoryIntArray array = b.getParcelable(
                                CALL_METHOD_TRACK_GENERATION_KEY);
                        final int index = b.getInt(
                                CALL_METHOD_GENERATION_INDEX_KEY, -1);
                        if (array != null && index >= 0) {
                            final int generation = b.getInt(
                                    CALL_METHOD_GENERATION_KEY, 0);
                            if (DEBUG) {
                                Log.i(TAG, "Received generation tracker for type:"
                                        + mUri.getPath() + " in package:"
                                        + cr.getPackageName() + " with index:" + index);
                            }
                            if (mGenerationTracker != null) {
                                mGenerationTracker.destroy();
                            }
                            mGenerationTracker = new GenerationTracker(array, index,
                                    generation, () -> {
                                synchronized (NameValueCache.this) {
                                    Log.e(TAG, "Error accessing generation tracker"
                                            + " - removing");
                                    if (mGenerationTracker != null) {
                                        GenerationTracker generationTracker =
                                                mGenerationTracker;
                                        mGenerationTracker = null;
                                        generationTracker.destroy();
                                        mValues.clear();
                                    }
                                }
                            });
                        }
                    }
                    if (mGenerationTracker != null && currentGeneration
                            == mGenerationTracker.getCurrentGeneration()) {
                        // cache the complete list of flags for the namespace
                        mValues.putAll(flagsToValues);
                    }
                }
                return keyValues;
            } catch (RemoteException e) {
                // Not supported by the remote side, return an empty map
                return keyValues;
            }
        }
        public void clearGenerationTrackerForTest() {
            synchronized (NameValueCache.this) {
                if (mGenerationTracker != null) {
@@ -13499,6 +13643,7 @@ public final class Settings {
                DeviceConfig.CONTENT_URI,
                CALL_METHOD_GET_CONFIG,
                CALL_METHOD_PUT_CONFIG,
                CALL_METHOD_LIST_CONFIG,
                sProviderHolder);
        /**
@@ -13514,6 +13659,37 @@ public final class Settings {
            return sNameValueCache.getStringForUser(resolver, name, resolver.getUserId());
        }
        /**
         * Look up a list of names in the database, based on a common prefix.
         *
         * @param resolver to access the database with
         * @param prefix to apply to all of the names which will be fetched
         * @param names to look up in the table
         * @return a non null, but possibly empty, map from name to value for any of the names that
         *         were found during lookup.
         *
         * @hide
         */
        @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
        static Map<String, String> getStrings(@NonNull ContentResolver resolver,
                @NonNull String prefix, @NonNull List<String> names) {
            List<String> concatenatedNames = new ArrayList<>(names.size());
            for (String name : names) {
                concatenatedNames.add(prefix + "/" + name);
            }
            ArrayMap<String, String> rawKeyValues = sNameValueCache.getStringsForPrefix(
                    resolver, prefix, concatenatedNames);
            int size = rawKeyValues.size();
            int substringLength = prefix.length() + 1;
            ArrayMap<String, String> keyValues = new ArrayMap<>(size);
            for (int i = 0; i < size; ++i) {
                keyValues.put(rawKeyValues.keyAt(i).substring(substringLength),
                        rawKeyValues.valueAt(i));
            }
            return keyValues;
        }
        /**
         * Store a name/value pair into the database.
         * <p>
+176 −63

File changed.

Preview size limit exceeded, changes collapsed.

+10 −7
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.providers.settings;

import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.ActivityManager;
import android.content.IContentProvider;
@@ -195,9 +194,16 @@ public final class DeviceConfigService extends Binder {
                            : "Failed to delete " + key + " from " + namespace);
                    break;
                case LIST:
                    for (String line : list(iprovider, namespace)) {
                    if (namespace != null) {
                        DeviceConfig.Properties properties = DeviceConfig.getProperties(namespace);
                        for (String name : properties.getKeyset()) {
                            pout.println(name + "=" + properties.getString(name, null));
                        }
                    } else {
                        for (String line : listAll(iprovider)) {
                            pout.println(line);
                        }
                    }
                    break;
                case RESET:
                    DeviceConfig.resetToDefaults(resetMode, namespace);
@@ -251,16 +257,13 @@ public final class DeviceConfigService extends Binder {
            return success;
        }

        private List<String> list(IContentProvider provider, @Nullable String namespace) {
        private List<String> listAll(IContentProvider provider) {
            final ArrayList<String> lines = new ArrayList<>();

            try {
                Bundle args = new Bundle();
                args.putInt(Settings.CALL_METHOD_USER_KEY,
                        ActivityManager.getService().getCurrentUser().id);
                if (namespace != null) {
                    args.putString(Settings.CALL_METHOD_PREFIX_KEY, namespace);
                }
                Bundle b = provider.call(resolveCallingPackage(), Settings.AUTHORITY,
                        Settings.CALL_METHOD_LIST_CONFIG, null, args);
                if (b != null) {
Loading