Loading api/system-current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -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); Loading core/java/android/provider/DeviceConfig.java +30 −2 Original line number Diff line number Diff line Loading @@ -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))); } /** Loading core/java/android/provider/Settings.java +179 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -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; } Loading Loading @@ -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); } } Loading @@ -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) { Loading Loading @@ -13499,6 +13643,7 @@ public final class Settings { DeviceConfig.CONTENT_URI, CALL_METHOD_GET_CONFIG, CALL_METHOD_PUT_CONFIG, CALL_METHOD_LIST_CONFIG, sProviderHolder); /** Loading @@ -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> core/tests/coretests/src/android/provider/DeviceConfigTest.java +176 −63 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +10 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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 Loading
api/system-current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
core/java/android/provider/DeviceConfig.java +30 −2 Original line number Diff line number Diff line Loading @@ -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))); } /** Loading
core/java/android/provider/Settings.java +179 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -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; } Loading Loading @@ -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); } } Loading @@ -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) { Loading Loading @@ -13499,6 +13643,7 @@ public final class Settings { DeviceConfig.CONTENT_URI, CALL_METHOD_GET_CONFIG, CALL_METHOD_PUT_CONFIG, CALL_METHOD_LIST_CONFIG, sProviderHolder); /** Loading @@ -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>
core/tests/coretests/src/android/provider/DeviceConfigTest.java +176 −63 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +10 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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