Merge "Add reset and list for device config flags to SettingsProvider to support command line debug tool."
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 79e0708..f86296b8 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1680,6 +1680,11 @@
*/
public static final String CALL_METHOD_TAG_KEY = "_tag";
+ /**
+ * @hide - String argument extra to the fast-path call()-based requests
+ */
+ public static final String CALL_METHOD_PREFIX_KEY = "_prefix";
+
/** @hide - Private call() method to write to 'system' table */
public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
@@ -1701,15 +1706,18 @@
/** @hide - Private call() method to delete from the 'global' table */
public static final String CALL_METHOD_DELETE_GLOBAL = "DELETE_global";
+ /** @hide - Private call() method to reset to defaults the 'configuration' table */
+ public static final String CALL_METHOD_DELETE_CONFIG = "DELETE_config";
+
+ /** @hide - Private call() method to reset to defaults the 'secure' table */
+ public static final String CALL_METHOD_RESET_SECURE = "RESET_secure";
+
/** @hide - Private call() method to reset to defaults the 'global' table */
public static final String CALL_METHOD_RESET_GLOBAL = "RESET_global";
/** @hide - Private call() method to reset to defaults the 'configuration' table */
public static final String CALL_METHOD_RESET_CONFIG = "RESET_config";
- /** @hide - Private call() method to reset to defaults the 'secure' table */
- public static final String CALL_METHOD_RESET_SECURE = "RESET_secure";
-
/** @hide - Private call() method to query the 'system' table */
public static final String CALL_METHOD_LIST_SYSTEM = "LIST_system";
@@ -1719,6 +1727,9 @@
/** @hide - Private call() method to query the 'global' table */
public static final String CALL_METHOD_LIST_GLOBAL = "LIST_global";
+ /** @hide - Private call() method to reset to defaults the 'configuration' table */
+ public static final String CALL_METHOD_LIST_CONFIG = "LIST_config";
+
/**
* Activity Extra: Limit available options in launched activity based on the given authority.
* <p>
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 108585d..04e8802 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -16,6 +16,10 @@
package android.provider;
+import static org.hamcrest.Matchers.aMapWithSize;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.Assert.assertThat;
+
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -26,6 +30,7 @@
import android.content.pm.UserInfo;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.test.AndroidTestCase;
@@ -33,10 +38,19 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.Suppress;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/** Unit test for SettingsProvider. */
public class SettingsProviderTest extends AndroidTestCase {
+ /**
+ * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
+ * API.
+ */
+ private static final Uri CONFIG_CONTENT_URI =
+ Uri.parse("content://" + Settings.AUTHORITY + "/config");
+
@MediumTest
public void testNameValueCache() {
ContentResolver r = getContext().getContentResolver();
@@ -379,4 +393,109 @@
assertTrue(ssaid.equals(ssaid2));
}
+
+ @MediumTest
+ public void testCall_putAndGetConfig() {
+ ContentResolver r = getContext().getContentResolver();
+ String name = "key1";
+ String value = "value1";
+ String newValue = "value2";
+ Bundle args = new Bundle();
+ args.putString(Settings.NameValueTable.VALUE, value);
+
+ try {
+ // value is empty
+ Bundle results =
+ r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ assertNull(results.get(Settings.NameValueTable.VALUE));
+
+ // save value
+ results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ assertNull(results);
+
+ // value is no longer empty
+ results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ assertEquals(value, results.get(Settings.NameValueTable.VALUE));
+
+ // save new value
+ args.putString(Settings.NameValueTable.VALUE, newValue);
+ r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+
+ // new value is returned
+ results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ assertEquals(newValue, results.get(Settings.NameValueTable.VALUE));
+ } finally {
+ // clean up
+ r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ }
+ }
+
+ @MediumTest
+ public void testCall_deleteConfig() {
+ ContentResolver r = getContext().getContentResolver();
+ String name = "key1";
+ String value = "value1";
+ Bundle args = new Bundle();
+ args.putString(Settings.NameValueTable.VALUE, value);
+
+ try {
+ // save value
+ r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+
+ // get value
+ Bundle results =
+ r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ assertEquals(value, results.get(Settings.NameValueTable.VALUE));
+
+ // delete value
+ results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+
+ // value is empty now
+ results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ assertNull(results.get(Settings.NameValueTable.VALUE));
+ } finally {
+ // clean up
+ r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ }
+ }
+
+ @MediumTest
+ public void testCall_listConfig() {
+ ContentResolver r = getContext().getContentResolver();
+ String prefix = "foo";
+ String newPrefix = "bar";
+ String name = prefix + "/" + "key1";
+ String newName = newPrefix + "/" + "key1";
+ String value = "value1";
+ String newValue = "value2";
+ Bundle args = new Bundle();
+ args.putString(Settings.NameValueTable.VALUE, value);
+
+ try {
+ // save both values
+ r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ args.putString(Settings.NameValueTable.VALUE, newValue);
+ r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
+
+ // list all values
+ Bundle result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
+ null, null);
+ Map<String, String> keyValueMap =
+ (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
+ assertThat(keyValueMap.size(), greaterThanOrEqualTo(2));
+ assertEquals(value, keyValueMap.get(name));
+ assertEquals(newValue, keyValueMap.get(newName));
+
+ // list values for prefix
+ args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
+ result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
+ keyValueMap = (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
+ assertThat(keyValueMap, aMapWithSize(1));
+ assertEquals(value, keyValueMap.get(name));
+ } finally {
+ // clean up
+ r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
+ }
+ }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 00ea45c..140a5a3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -94,6 +94,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -147,6 +148,7 @@
private static final String TABLE_SYSTEM = "system";
private static final String TABLE_SECURE = "secure";
private static final String TABLE_GLOBAL = "global";
+ private static final String TABLE_CONFIG = "config";
// Old tables no longer exist.
private static final String TABLE_FAVORITES = "favorites";
@@ -414,9 +416,8 @@
case Settings.CALL_METHOD_PUT_CONFIG: {
String value = getSettingValue(args);
- String tag = getSettingTag(args);
final boolean makeDefault = getSettingMakeDefault(args);
- insertConfigSetting(name, value, tag, makeDefault, requestingUserId, false);
+ insertConfigSetting(name, value, null, makeDefault, requestingUserId, false);
break;
}
@@ -444,8 +445,8 @@
case Settings.CALL_METHOD_RESET_CONFIG: {
final int mode = getResetModeEnforcingPermission(args);
- String tag = getSettingTag(args);
- resetConfigSetting(requestingUserId, mode, tag);
+ String prefix = getSettingPrefix(args);
+ resetConfigSetting(requestingUserId, mode, prefix);
break;
}
@@ -463,15 +464,8 @@
break;
}
- case Settings.CALL_METHOD_DELETE_SYSTEM: {
- int rows = deleteSystemSetting(name, requestingUserId) ? 1 : 0;
- Bundle result = new Bundle();
- result.putInt(RESULT_ROWS_DELETED, rows);
- return result;
- }
-
- case Settings.CALL_METHOD_DELETE_SECURE: {
- int rows = deleteSecureSetting(name, requestingUserId, false) ? 1 : 0;
+ case Settings.CALL_METHOD_DELETE_CONFIG: {
+ int rows = deleteConfigSetting(name, requestingUserId, false) ? 1 : 0;
Bundle result = new Bundle();
result.putInt(RESULT_ROWS_DELETED, rows);
return result;
@@ -484,10 +478,32 @@
return result;
}
- case Settings.CALL_METHOD_LIST_SYSTEM: {
+ case Settings.CALL_METHOD_DELETE_SECURE: {
+ int rows = deleteSecureSetting(name, requestingUserId, false) ? 1 : 0;
+ Bundle result = new Bundle();
+ result.putInt(RESULT_ROWS_DELETED, rows);
+ return result;
+ }
+
+ case Settings.CALL_METHOD_DELETE_SYSTEM: {
+ int rows = deleteSystemSetting(name, requestingUserId) ? 1 : 0;
+ Bundle result = new Bundle();
+ result.putInt(RESULT_ROWS_DELETED, rows);
+ return result;
+ }
+
+ case Settings.CALL_METHOD_LIST_CONFIG: {
+ String prefix = getSettingPrefix(args);
+ Bundle result = new Bundle();
+ result.putSerializable(
+ Settings.NameValueTable.VALUE, (HashMap) getAllConfigFlags(prefix));
+ return result;
+ }
+
+ case Settings.CALL_METHOD_LIST_GLOBAL: {
Bundle result = new Bundle();
result.putStringArrayList(RESULT_SETTINGS_LIST,
- buildSettingsList(getAllSystemSettings(requestingUserId, null)));
+ buildSettingsList(getAllGlobalSettings(null)));
return result;
}
@@ -498,10 +514,10 @@
return result;
}
- case Settings.CALL_METHOD_LIST_GLOBAL: {
+ case Settings.CALL_METHOD_LIST_SYSTEM: {
Bundle result = new Bundle();
result.putStringArrayList(RESULT_SETTINGS_LIST,
- buildSettingsList(getAllGlobalSettings(null)));
+ buildSettingsList(getAllSystemSettings(requestingUserId, null)));
return result;
}
@@ -1061,36 +1077,47 @@
MUTATION_OPERATION_INSERT, forceNotify, 0);
}
- private void resetConfigSetting(int requestingUserId, int mode, String tag) {
+ private boolean deleteConfigSetting(String name, int requestingUserId, boolean forceNotify) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "deleteConfigSetting(" + name + ", " + requestingUserId
+ + ", " + forceNotify + ")");
+ }
+ return mutateConfigSetting(name, null, null, false, requestingUserId,
+ MUTATION_OPERATION_DELETE, forceNotify, 0);
+ }
+
+ private void resetConfigSetting(int requestingUserId, int mode, String prefix) {
if (DEBUG) {
Slog.v(LOG_TAG, "resetConfigSetting(" + requestingUserId + ", "
- + mode + ", " + tag + ")");
+ + mode + ", " + prefix + ")");
}
- mutateConfigSetting(null, null, tag, false, requestingUserId,
+ mutateConfigSetting(null, null, prefix, false, requestingUserId,
MUTATION_OPERATION_RESET, false, mode);
}
- private boolean mutateConfigSetting(String name, String value, String tag,
+ private boolean mutateConfigSetting(String name, String value, String prefix,
boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
int mode) {
// TODO(b/117663715): check the new permission when it's added.
// enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
- // Resolve the userId on whose behalf the call is made.
- final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
-
// Perform the mutation.
synchronized (mLock) {
switch (operation) {
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG,
- UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
+ UserHandle.USER_SYSTEM, name, value, null, makeDefault,
getCallingPackage(), forceNotify, null);
}
+ case MUTATION_OPERATION_DELETE: {
+ return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_CONFIG,
+ UserHandle.USER_SYSTEM, name, forceNotify, null);
+ }
+
case MUTATION_OPERATION_RESET: {
mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG,
- UserHandle.USER_SYSTEM, getCallingPackage(), mode, tag);
+ UserHandle.USER_SYSTEM, getCallingPackage(), mode, null, prefix);
} return true;
}
}
@@ -1098,6 +1125,34 @@
return false;
}
+ private Map<String, String> getAllConfigFlags(@Nullable String prefix) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
+ }
+
+ synchronized (mLock) {
+ // Get the settings.
+ SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
+ SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM);
+
+ List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_CONFIG,
+ UserHandle.USER_SYSTEM);
+
+ final int nameCount = names.size();
+ Map<String, String> flagsToValues = new HashMap<>(names.size());
+
+ for (int i = 0; i < nameCount; i++) {
+ String name = names.get(i);
+ Setting setting = settingsState.getSettingLocked(name);
+ if (prefix == null || setting.getName().startsWith(prefix)) {
+ flagsToValues.put(setting.getName(), setting.getValue());
+ }
+ }
+
+ return flagsToValues;
+ }
+ }
+
private Cursor getAllGlobalSettings(String[] projection) {
if (DEBUG) {
Slog.v(LOG_TAG, "getAllGlobalSettings()");
@@ -2085,6 +2140,13 @@
return (args != null) ? args.getString(Settings.CALL_METHOD_TAG_KEY) : null;
}
+ private static String getSettingPrefix(Bundle args) {
+ String prefix = (args != null) ? args.getString(Settings.CALL_METHOD_PREFIX_KEY) : null;
+ // Append '/' to ensure we only match properties with this exact prefix.
+ // i.e. "foo" should match "foo/property" but not "foobar/property"
+ return prefix != null ? prefix + "/" : null;
+ }
+
private static boolean getSettingMakeDefault(Bundle args) {
return (args != null) && args.getBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY);
}
@@ -2644,6 +2706,11 @@
public void resetSettingsLocked(int type, int userId, String packageName, int mode,
String tag) {
+ resetSettingsLocked(type, userId, packageName, mode, tag, null);
+ }
+
+ public void resetSettingsLocked(int type, int userId, String packageName, int mode,
+ String tag, @Nullable String prefix) {
final int key = makeKey(type, userId);
SettingsState settingsState = peekSettingsStateLocked(key);
if (settingsState == null) {
@@ -2656,7 +2723,8 @@
boolean someSettingChanged = false;
Setting setting = settingsState.getSettingLocked(name);
if (packageName.equals(setting.getPackageName())) {
- if (tag != null && !tag.equals(setting.getTag())) {
+ if ((tag != null && !tag.equals(setting.getTag()))
+ || (prefix != null && !setting.getName().startsWith(prefix))) {
continue;
}
if (settingsState.resetSettingLocked(name)) {
@@ -2676,6 +2744,9 @@
Setting setting = settingsState.getSettingLocked(name);
if (!SettingsState.isSystemPackage(getContext(),
setting.getPackageName())) {
+ if (prefix != null && !setting.getName().startsWith(prefix)) {
+ continue;
+ }
if (settingsState.resetSettingLocked(name)) {
someSettingChanged = true;
notifyForSettingsChange(key, name);
@@ -2693,6 +2764,9 @@
Setting setting = settingsState.getSettingLocked(name);
if (!SettingsState.isSystemPackage(getContext(),
setting.getPackageName())) {
+ if (prefix != null && !setting.getName().startsWith(prefix)) {
+ continue;
+ }
if (setting.isDefaultFromSystem()) {
if (settingsState.resetSettingLocked(name)) {
someSettingChanged = true;
@@ -2713,6 +2787,9 @@
for (String name : settingsState.getSettingNamesLocked()) {
Setting setting = settingsState.getSettingLocked(name);
boolean someSettingChanged = false;
+ if (prefix != null && !setting.getName().startsWith(prefix)) {
+ continue;
+ }
if (setting.isDefaultFromSystem()) {
if (settingsState.resetSettingLocked(name)) {
someSettingChanged = true;