Add a new config table to the settings provider for remotely configured parameters. This includes the minimum number of changes necessary to make the table work, but no API surface yet.
Test: local tests
bug: 113100523
Change-Id: I47f89f5e6657a2a347e62cb40924bba4547f7dd9
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index aa178fb..15fe19d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1603,6 +1603,11 @@
public static final String CALL_METHOD_GET_GLOBAL = "GET_global";
/**
+ * @hide - Private call() method on SettingsProvider to read from 'config' table.
+ */
+ public static final String CALL_METHOD_GET_CONFIG = "GET_config";
+
+ /**
* @hide - Specifies that the caller of the fast-path call()-based flow tracks
* the settings generation in order to cache values locally. If this key is
* mapped to a <code>null</code> string extra in the request bundle, the response
@@ -1661,9 +1666,15 @@
/** @hide - Private call() method to write to 'global' table */
public static final String CALL_METHOD_PUT_GLOBAL= "PUT_global";
+ /** @hide - Private call() method to write to 'configuration' table */
+ public static final String CALL_METHOD_PUT_CONFIG = "PUT_config";
+
/** @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";
@@ -13366,6 +13377,112 @@
}
/**
+ * Configuration system settings, containing settings which are applied identically for all
+ * defined users. Only Android can read these and only a specific configuration service can
+ * write these.
+ *
+ * @hide
+ */
+ public static final class Config extends NameValueTable {
+ /**
+ * The content:// style URL for the config table.
+ *
+ * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a
+ * System API.
+ */
+ private static final Uri CONTENT_URI =
+ Uri.parse("content://" + AUTHORITY + "/config");
+
+ private static final ContentProviderHolder sProviderHolder =
+ new ContentProviderHolder(CONTENT_URI);
+
+ // Populated lazily, guarded by class object:
+ private static final NameValueCache sNameValueCache = new NameValueCache(
+ CONTENT_URI,
+ CALL_METHOD_GET_CONFIG,
+ CALL_METHOD_PUT_CONFIG,
+ sProviderHolder);
+
+ /**
+ * Look up a name in the database.
+ * @param resolver to access the database with
+ * @param name to look up in the table
+ * @return the corresponding value, or null if not present
+ *
+ * @hide
+ */
+ // TODO(b/117663715): require a new read permission
+ static String getString(ContentResolver resolver, String name) {
+ return sNameValueCache.getStringForUser(resolver, name, resolver.getUserId());
+ }
+
+ /**
+ * Store a name/value pair into the database.
+ * <p>
+ * The method takes an optional tag to associate with the setting which can be used to clear
+ * only settings made by your package and associated with this tag by passing the tag to
+ * {@link #resetToDefaults(ContentResolver, String)}. The value of this setting can be
+ * overridden by future calls to this or other put methods, and the tag provided in those
+ * calls, which may be null, will override the tag provided in this call. Any call to a put
+ * method which does not accept a tag will effectively set the tag to null.
+ * </p><p>
+ * Also the method takes an argument whether to make the value the default for this setting.
+ * If the system already specified a default value, then the one passed in here will
+ * <strong>not</strong> be set as the default.
+ * </p>
+ *
+ * @param resolver to access the database with.
+ * @param name to store.
+ * @param value to associate with the name.
+ * @param tag to associated with the setting.
+ * @param makeDefault whether to make the value the default one.
+ * @return true if the value was set, false on database errors.
+ *
+ * @see #resetToDefaults(ContentResolver, String)
+ *
+ * @hide
+ */
+ // TODO(b/117663715): require a new write permission restricted to a single source
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ static boolean putString(@NonNull ContentResolver resolver,
+ @NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault) {
+ return sNameValueCache.putStringForUser(resolver, name, value, tag, makeDefault,
+ resolver.getUserId());
+ }
+
+ /**
+ * Reset the settings to their defaults. This would reset <strong>only</strong> settings set
+ * by the caller's package. Passing in the optional tag will reset only settings changed by
+ * your package and associated with this tag.
+ *
+ * @param resolver Handle to the content resolver.
+ * @param tag Optional tag which should be associated with the settings to reset.
+ *
+ * @see #putString(ContentResolver, String, String, String, boolean)
+ *
+ * @hide
+ */
+ // TODO(b/117663715): require a new write permission restricted to a single source
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ static void resetToDefaults(@NonNull ContentResolver resolver,
+ @Nullable String tag) {
+ try {
+ Bundle arg = new Bundle();
+ arg.putInt(CALL_METHOD_USER_KEY, resolver.getUserId());
+ if (tag != null) {
+ arg.putString(CALL_METHOD_TAG_KEY, tag);
+ }
+ arg.putInt(CALL_METHOD_RESET_MODE_KEY, RESET_MODE_PACKAGE_DEFAULTS);
+ IContentProvider cp = sProviderHolder.getProvider(resolver);
+ cp.call(resolver.getPackageName(), CALL_METHOD_RESET_CONFIG, null, arg);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't reset to defaults for " + CONTENT_URI, e);
+ }
+ }
+ }
+
+ /**
* User-defined bookmarks and shortcuts. The target of each bookmark is an
* Intent URL, allowing it to be either a web page or a particular
* application activity.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index bd21b83..cff88f0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -35,6 +35,13 @@
static void dumpProtoLocked(SettingsProvider.SettingsRegistry settingsRegistry,
ProtoOutputStream proto) {
+ // Config settings
+ SettingsState configSettings = settingsRegistry.getSettingsLocked(
+ SettingsProvider.SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM);
+ if (configSettings != null) {
+ // TODO(b/113100523): dump configuration settings after they are added
+ }
+
// Global settings
SettingsState globalSettings = settingsRegistry.getSettingsLocked(
SettingsProvider.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 63978ba..256ebbb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -180,6 +180,7 @@
public static final int SETTINGS_TYPE_SYSTEM = SettingsState.SETTINGS_TYPE_SYSTEM;
public static final int SETTINGS_TYPE_SECURE = SettingsState.SETTINGS_TYPE_SECURE;
public static final int SETTINGS_TYPE_SSAID = SettingsState.SETTINGS_TYPE_SSAID;
+ public static final int SETTINGS_TYPE_CONFIG = SettingsState.SETTINGS_TYPE_CONFIG;
private static final Bundle NULL_SETTING_BUNDLE = Bundle.forPair(
Settings.NameValueTable.VALUE, null);
@@ -189,6 +190,13 @@
private static final Set<String> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS = new ArraySet<>();
private static final Set<String> OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS = new ArraySet<>();
+ /**
+ * 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");
+
static {
for (String name : Resources.getSystem().getStringArray(
com.android.internal.R.array.config_allowedGlobalInstantAppSettings)) {
@@ -380,6 +388,11 @@
public Bundle call(String method, String name, Bundle args) {
final int requestingUserId = getRequestingUserId(args);
switch (method) {
+ case Settings.CALL_METHOD_GET_CONFIG: {
+ Setting setting = getConfigSetting(name);
+ return packageValueForCallResult(setting, isTrackingGeneration(args));
+ }
+
case Settings.CALL_METHOD_GET_GLOBAL: {
Setting setting = getGlobalSetting(name);
return packageValueForCallResult(setting, isTrackingGeneration(args));
@@ -396,6 +409,14 @@
return packageValueForCallResult(setting, isTrackingGeneration(args));
}
+ 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);
+ break;
+ }
+
case Settings.CALL_METHOD_PUT_GLOBAL: {
String value = getSettingValue(args);
String tag = getSettingTag(args);
@@ -418,6 +439,13 @@
break;
}
+ case Settings.CALL_METHOD_RESET_CONFIG: {
+ final int mode = getResetModeEnforcingPermission(args);
+ String tag = getSettingTag(args);
+ resetConfigSetting(requestingUserId, mode, tag);
+ break;
+ }
+
case Settings.CALL_METHOD_RESET_GLOBAL: {
final int mode = getResetModeEnforcingPermission(args);
String tag = getSettingTag(args);
@@ -725,6 +753,15 @@
@GuardedBy("mLock")
private void dumpForUserLocked(int userId, PrintWriter pw) {
if (userId == UserHandle.USER_SYSTEM) {
+ pw.println("CONFIG SETTINGS (user " + userId + ")");
+ SettingsState configSettings = mSettingsRegistry.getSettingsLocked(
+ SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM);
+ if (configSettings != null) {
+ dumpSettingsLocked(configSettings, pw);
+ pw.println();
+ configSettings.dumpHistoricalOperations(pw);
+ }
+
pw.println("GLOBAL SETTINGS (user " + userId + ")");
SettingsState globalSettings = mSettingsRegistry.getSettingsLocked(
SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
@@ -935,6 +972,69 @@
});
}
+ private Setting getConfigSetting(String name) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "getConfigSetting(" + name + ")");
+ }
+
+ // TODO(b/117663715): Ensure the caller can access the setting.
+ // enforceSettingReadable(name, SETTINGS_TYPE_CONFIG, UserHandle.getCallingUserId());
+
+ // Get the value.
+ synchronized (mLock) {
+ return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_CONFIG,
+ UserHandle.USER_SYSTEM, name);
+ }
+ }
+
+ private boolean insertConfigSetting(String name, String value, String tag,
+ boolean makeDefault, int requestingUserId, boolean forceNotify) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "insertConfigSetting(" + name + ", " + value + ", "
+ + ", " + tag + ", " + makeDefault + ", " + requestingUserId
+ + ", " + forceNotify + ")");
+ }
+ return mutateConfigSetting(name, value, tag, makeDefault, requestingUserId,
+ MUTATION_OPERATION_INSERT, forceNotify, 0);
+ }
+
+ private void resetConfigSetting(int requestingUserId, int mode, String tag) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "resetConfigSetting(" + requestingUserId + ", "
+ + mode + ", " + tag + ")");
+ }
+ mutateConfigSetting(null, null, tag, false, requestingUserId,
+ MUTATION_OPERATION_RESET, false, mode);
+ }
+
+ private boolean mutateConfigSetting(String name, String value, String tag,
+ 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,
+ getCallingPackage(), forceNotify, null);
+ }
+
+ case MUTATION_OPERATION_RESET: {
+ mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG,
+ UserHandle.USER_SYSTEM, getCallingPackage(), mode, tag);
+ } return true;
+ }
+ }
+
+ return false;
+ }
+
private Cursor getAllGlobalSettings(String[] projection) {
if (DEBUG) {
Slog.v(LOG_TAG, "getAllGlobalSettings()");
@@ -2128,6 +2228,7 @@
private static final String SETTINGS_FILE_SYSTEM = "settings_system.xml";
private static final String SETTINGS_FILE_SECURE = "settings_secure.xml";
private static final String SETTINGS_FILE_SSAID = "settings_ssaid.xml";
+ private static final String SETTINGS_FILE_CONFIG = "settings_config.xml";
private static final String SSAID_USER_KEY = "userkey";
@@ -2299,6 +2400,13 @@
// Migrate the setting for this user if needed.
migrateLegacySettingsForUserIfNeededLocked(userId);
+ // Ensure config settings loaded if owner.
+ if (userId == UserHandle.USER_SYSTEM) {
+ final int configKey
+ = makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM);
+ ensureSettingsStateLocked(configKey);
+ }
+
// Ensure global settings loaded if owner.
if (userId == UserHandle.USER_SYSTEM) {
final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
@@ -2849,6 +2957,10 @@
}
}
+ private boolean isConfigSettingsKey(int key) {
+ return getTypeFromKey(key) == SETTINGS_TYPE_CONFIG;
+ }
+
private boolean isGlobalSettingsKey(int key) {
return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
}
@@ -2866,7 +2978,11 @@
}
private File getSettingsFile(int key) {
- if (isGlobalSettingsKey(key)) {
+ if (isConfigSettingsKey(key)) {
+ final int userId = getUserIdFromKey(key);
+ return new File(Environment.getUserSystemDirectory(userId),
+ SETTINGS_FILE_CONFIG);
+ } else if (isGlobalSettingsKey(key)) {
final int userId = getUserIdFromKey(key);
return new File(Environment.getUserSystemDirectory(userId),
SETTINGS_FILE_GLOBAL);
@@ -2888,7 +3004,10 @@
}
private Uri getNotificationUriFor(int key, String name) {
- if (isGlobalSettingsKey(key)) {
+ if (isConfigSettingsKey(key)) {
+ return (name != null) ? Uri.withAppendedPath(CONFIG_CONTENT_URI, name)
+ : CONFIG_CONTENT_URI;
+ } else if (isGlobalSettingsKey(key)) {
return (name != null) ? Uri.withAppendedPath(Settings.Global.CONTENT_URI, name)
: Settings.Global.CONTENT_URI;
} else if (isSecureSettingsKey(key)) {
@@ -2904,6 +3023,7 @@
private int getMaxBytesPerPackageForType(int type) {
switch (type) {
+ case SETTINGS_TYPE_CONFIG:
case SETTINGS_TYPE_GLOBAL:
case SETTINGS_TYPE_SECURE:
case SETTINGS_TYPE_SSAID: {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 389d627..ae2ca3f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -34,7 +34,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
-import android.providers.settings.GlobalSettingsProto;
import android.providers.settings.SettingsOperationProto;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -67,7 +66,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
/**
* This class contains the state for one type of settings. It is responsible
@@ -205,6 +203,7 @@
public static final int SETTINGS_TYPE_SYSTEM = 1;
public static final int SETTINGS_TYPE_SECURE = 2;
public static final int SETTINGS_TYPE_SSAID = 3;
+ public static final int SETTINGS_TYPE_CONFIG = 4;
public static final int SETTINGS_TYPE_MASK = 0xF0000000;
public static final int SETTINGS_TYPE_SHIFT = 28;
@@ -223,6 +222,9 @@
public static String settingTypeToString(int type) {
switch (type) {
+ case SETTINGS_TYPE_CONFIG: {
+ return "SETTINGS_CONFIG";
+ }
case SETTINGS_TYPE_GLOBAL: {
return "SETTINGS_GLOBAL";
}