From 1b31a331b0accfb545b9e747d48028fa8d9b7f34 Mon Sep 17 00:00:00 2001 From: Matt Pape Date: Wed, 17 Oct 2018 09:58:28 -0700 Subject: [PATCH] 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 --- core/java/android/provider/Settings.java | 117 +++++++++++++++++ .../settings/SettingsProtoDumpUtil.java | 7 + .../providers/settings/SettingsProvider.java | 124 +++++++++++++++++- .../providers/settings/SettingsState.java | 6 +- 4 files changed, 250 insertions(+), 4 deletions(-) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index aa178fb9ff36a..15fe19da3d4d8 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1602,6 +1602,11 @@ public final class Settings { */ 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 @@ -1661,9 +1666,15 @@ public final class Settings { /** @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"; @@ -13365,6 +13376,112 @@ public final class Settings { } + /** + * 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. + *

+ * 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. + *

+ * 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 + * not be set as the default. + *

+ * + * @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 only 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 diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index bd21b83531369..cff88f0c7c492 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 @@ class SettingsProtoDumpUtil { 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 63978ba60171f..256ebbb6e2a95 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 class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { private static final Set OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS = new ArraySet<>(); private static final Set 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 class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { @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 @@ public class SettingsProvider extends ContentProvider { }); } + 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { // 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 @@ public class SettingsProvider extends ContentProvider { } } + 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 @@ public class SettingsProvider extends ContentProvider { } 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 @@ public class SettingsProvider extends ContentProvider { } 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 @@ public class SettingsProvider extends ContentProvider { 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 389d627a9bd6d..ae2ca3f87ae7f 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.SystemClock; 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.nio.charset.StandardCharsets; 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 @@ final class SettingsState { 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 @@ final class SettingsState { public static String settingTypeToString(int type) { switch (type) { + case SETTINGS_TYPE_CONFIG: { + return "SETTINGS_CONFIG"; + } case SETTINGS_TYPE_GLOBAL: { return "SETTINGS_GLOBAL"; }