From 484000fbf1cf1b12be5c564d2dd939d8e8bf1deb Mon Sep 17 00:00:00 2001 From: Ruslan Tkhakokhov Date: Thu, 9 Jan 2020 20:40:57 +0000 Subject: [PATCH 1/2] Add preserveValueInRestore flag to Setting object Bug: 144838882 Test: SettingsStateTest This is part of the feature to change settings backup&restore. If a setting has been modified, restore shouldn't override its value. To track such settings, add preserveValueInRestore flag and set it to true when a setting is modified by any package other than 'android' (which modifies settings during settings upgrade - situation we don't need to consider) Change-Id: Id6e8e93e9ec9af4c31c88e2aded9332e39fc87da --- .../providers/settings/SettingsState.java | 38 ++++++++++++++++--- .../providers/settings/SettingsStateTest.java | 6 +-- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 5b1b5305865eb..96db538e51e2e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -117,6 +117,8 @@ final class SettingsState { private static final String ATTR_NAMESPACE = "namespace"; private static final String ATTR_BANNED_HASH = "bannedHash"; + private static final String ATTR_PRESERVE_IN_RESTORE = "preserve_in_restore"; + /** * Non-binary value will be written in this attributes. */ @@ -797,7 +799,8 @@ final class SettingsState { writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(), setting.getValue(), setting.getDefaultValue(), setting.getPackageName(), - setting.getTag(), setting.isDefaultFromSystem()); + setting.getTag(), setting.isDefaultFromSystem(), + setting.isValuePreservedInRestore()); if (DEBUG_PERSISTENCE) { Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "=" @@ -886,7 +889,8 @@ final class SettingsState { static void writeSingleSetting(int version, XmlSerializer serializer, String id, String name, String value, String defaultValue, String packageName, - String tag, boolean defaultSysSet) throws IOException { + String tag, boolean defaultSysSet, boolean isValuePreservedInRestore) + throws IOException { if (id == null || isBinary(id) || name == null || isBinary(name) || packageName == null || isBinary(packageName)) { // This shouldn't happen. @@ -905,6 +909,9 @@ final class SettingsState { setValueAttribute(ATTR_TAG, ATTR_TAG_BASE64, version, serializer, tag); } + if (isValuePreservedInRestore) { + serializer.attribute(null, ATTR_PRESERVE_IN_RESTORE, Boolean.toString(true)); + } serializer.endTag(null, TAG_SETTING); } @@ -1041,6 +1048,10 @@ final class SettingsState { String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); String defaultValue = getValueAttribute(parser, ATTR_DEFAULT_VALUE, ATTR_DEFAULT_VALUE_BASE64); + String isPreservedInRestoreString = parser.getAttributeValue(null, + ATTR_PRESERVE_IN_RESTORE); + boolean isPreservedInRestore = isPreservedInRestoreString != null + && Boolean.parseBoolean(isPreservedInRestoreString); String tag = null; boolean fromSystem = false; if (defaultValue != null) { @@ -1049,7 +1060,7 @@ final class SettingsState { tag = getValueAttribute(parser, ATTR_TAG, ATTR_TAG_BASE64); } mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag, - fromSystem, id)); + fromSystem, id, isPreservedInRestore)); if (DEBUG_PERSISTENCE) { Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value); @@ -1133,6 +1144,8 @@ final class SettingsState { private String tag; // Whether the default is set by the system private boolean defaultFromSystem; + // Whether the value of this setting will be preserved when restore happens.. + private boolean isValuePreservedInRestore; public Setting(Setting other) { name = other.name; @@ -1152,15 +1165,23 @@ final class SettingsState { public Setting(String name, String value, String defaultValue, String packageName, String tag, boolean fromSystem, String id) { + this(name, value, defaultValue, packageName, tag, fromSystem, id, false); + } + + Setting(String name, String value, String defaultValue, + String packageName, String tag, boolean fromSystem, String id, + boolean isValuePreservedInRestore) { mNextId = Math.max(mNextId, Long.parseLong(id) + 1); if (NULL_VALUE.equals(value)) { value = null; } - init(name, value, tag, defaultValue, packageName, fromSystem, id); + init(name, value, tag, defaultValue, packageName, fromSystem, id, + isValuePreservedInRestore); } private void init(String name, String value, String tag, String defaultValue, - String packageName, boolean fromSystem, String id) { + String packageName, boolean fromSystem, String id, + boolean isValuePreservedInRestore) { this.name = name; this.value = value; this.tag = tag; @@ -1168,6 +1189,7 @@ final class SettingsState { this.packageName = packageName; this.id = id; this.defaultFromSystem = fromSystem; + this.isValuePreservedInRestore = isValuePreservedInRestore; } public String getName() { @@ -1198,6 +1220,10 @@ final class SettingsState { return defaultFromSystem; } + public boolean isValuePreservedInRestore() { + return isValuePreservedInRestore; + } + public String getId() { return id; } @@ -1263,7 +1289,7 @@ final class SettingsState { } init(name, value, tag, defaultValue, packageName, defaultFromSystem, - String.valueOf(mNextId++)); + String.valueOf(mNextId++), /* isValuePreservedInRestore */ true); return true; } diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 3f68554ffe879..918b2af2f148e 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -99,10 +99,10 @@ public class SettingsStateTest extends AndroidTestCase { checkWriteSingleSetting(serializer, CRAZY_STRING, null); SettingsState.writeSingleSetting( SettingsState.SETTINGS_VERSION_NEW_ENCODING, - serializer, null, "k", "v", null, "package", null, false); + serializer, null, "k", "v", null, "package", null, false, false); SettingsState.writeSingleSetting( SettingsState.SETTINGS_VERSION_NEW_ENCODING, - serializer, "1", "k", "v", null, null, null, false); + serializer, "1", "k", "v", null, null, null, false, false); } private void checkWriteSingleSetting(XmlSerializer serializer, String key, String value) @@ -115,7 +115,7 @@ public class SettingsStateTest extends AndroidTestCase { // Make sure the XML serializer won't crash. SettingsState.writeSingleSetting( SettingsState.SETTINGS_VERSION_NEW_ENCODING, - serializer, "1", key, value, null, "package", null, false); + serializer, "1", key, value, null, "package", null, false, false); } /** From 018f5534fa78ac1e297ae4b38c4e9a06e94c78ee Mon Sep 17 00:00:00 2001 From: Ruslan Tkhakokhov Date: Thu, 9 Jan 2020 21:16:10 +0000 Subject: [PATCH 2/2] Add Settings.(System/Secure).putStringOverrideableByRestore Bug: 144838882 Test: 1) SettingsStateTest 2) Manually change settings on the device; verify isModified flag is set; reboot; make sure changes are persisted. In ag/10056253 logic was introduced to make sure settings modifications are not overridden by restore. However, some callers still need to use the old behavior. Modification made through Settings.System.putStringOverrideableByRestore will be overridden by restore. Change-Id: I91084ef61711a77ff17b2a5ee1996fa15cb17e73 --- api/system-current.txt | 5 + core/java/android/provider/Settings.java | 113 ++++++-- core/res/AndroidManifest.xml | 7 + .../providers/settings/SettingsProvider.java | 241 +++++++++++------- .../providers/settings/SettingsState.java | 48 +++- .../providers/settings/SettingsStateTest.java | 65 +++++ 6 files changed, 358 insertions(+), 121 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index 588686a384893..75b1f921d3aeb 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -126,6 +126,7 @@ package android { field @Deprecated public static final String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING"; field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS"; field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE"; + field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE"; field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE"; field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE"; field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING"; @@ -7935,6 +7936,10 @@ package android.provider { field public static final int VOLUME_HUSH_VIBRATE = 1; // 0x1 } + public static final class Settings.System extends android.provider.Settings.NameValueTable { + method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean); + } + public static interface Telephony.CarrierColumns extends android.provider.BaseColumns { field @NonNull public static final android.net.Uri CONTENT_URI; field public static final String EXPIRATION_TIME = "expiration_time"; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 9ee4cc30949da..bc1eb987ba40e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -95,6 +95,7 @@ import java.util.Set; * The Settings provider contains global system-level device preferences. */ public final class Settings { + private static final boolean DEFAULT_OVERRIDEABLE_BY_RESTORE = false; // Intent actions for Settings @@ -2108,6 +2109,11 @@ public final class Settings { */ public static final String CALL_METHOD_FLAGS_KEY = "_flags"; + /** + * @hide - String argument extra to the fast-path call()-based requests + */ + public static final String CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY = "_overrideable_by_restore"; + /** @hide - Private call() method to write to 'system' table */ public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system"; @@ -2476,7 +2482,8 @@ public final class Settings { } public boolean putStringForUser(ContentResolver cr, String name, String value, - String tag, boolean makeDefault, final int userHandle) { + String tag, boolean makeDefault, final int userHandle, + boolean overrideableByRestore) { try { Bundle arg = new Bundle(); arg.putString(Settings.NameValueTable.VALUE, value); @@ -2487,6 +2494,9 @@ public final class Settings { if (makeDefault) { arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true); } + if (overrideableByRestore) { + arg.putBoolean(CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true); + } IContentProvider cp = mProviderHolder.getProvider(cr); cp.call(cr.getPackageName(), cr.getFeatureId(), mProviderHolder.mUri.getAuthority(), mCallSetCommand, name, arg); @@ -3037,10 +3047,36 @@ public final class Settings { return putStringForUser(resolver, name, value, resolver.getUserId()); } + /** + * Store a name/value pair into the database. Values written by this method will be + * overridden if a restore happens in the future. + * + * @param resolver to access the database with + * @param name to store + * @param value to associate with the name + * + * @return true if the value was set, false on database errors + * + * @hide + */ + @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) + @SystemApi + public static boolean putString(@NonNull ContentResolver resolver, + @NonNull String name, @Nullable String value, boolean overrideableByRestore) { + return putStringForUser(resolver, name, value, resolver.getUserId(), + overrideableByRestore); + } + /** @hide */ @UnsupportedAppUsage public static boolean putStringForUser(ContentResolver resolver, String name, String value, int userHandle) { + return putStringForUser(resolver, name, value, userHandle, + DEFAULT_OVERRIDEABLE_BY_RESTORE); + } + + private static boolean putStringForUser(ContentResolver resolver, String name, String value, + int userHandle, boolean overrideableByRestore) { if (MOVED_TO_SECURE.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Secure, value is unchanged."); @@ -3051,7 +3087,8 @@ public final class Settings { + " to android.provider.Settings.Global, value is unchanged."); return false; } - return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle); + return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle, + overrideableByRestore); } /** @@ -3375,7 +3412,7 @@ public final class Settings { // need to store the adjusted configuration as the initial settings. Settings.System.putStringForUser( cr, SYSTEM_LOCALES, outConfig.getLocales().toLanguageTags(), - userHandle); + userHandle, DEFAULT_OVERRIDEABLE_BY_RESTORE); } } } @@ -3408,7 +3445,8 @@ public final class Settings { int userHandle) { return Settings.System.putFloatForUser(cr, FONT_SCALE, config.fontScale, userHandle) && Settings.System.putStringForUser( - cr, SYSTEM_LOCALES, config.getLocales().toLanguageTags(), userHandle); + cr, SYSTEM_LOCALES, config.getLocales().toLanguageTags(), userHandle, + DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** @hide */ @@ -5210,6 +5248,24 @@ public final class Settings { return sNameValueCache.getStringForUser(resolver, name, userHandle); } + /** + * Store a name/value pair into the database. Values written by this method will be + * overridden if a restore happens in the future. + * + * @param resolver to access the database with + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + * + * @hide + */ + @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) + public static boolean putString(ContentResolver resolver, String name, + String value, boolean overrideableByRestore) { + return putStringForUser(resolver, name, value, /* tag */ null, /* makeDefault */ false, + resolver.getUserId(), overrideableByRestore); + } + /** * Store a name/value pair into the database. * @param resolver to access the database with @@ -5225,22 +5281,23 @@ public final class Settings { @UnsupportedAppUsage public static boolean putStringForUser(ContentResolver resolver, String name, String value, int userHandle) { - return putStringForUser(resolver, name, value, null, false, userHandle); + return putStringForUser(resolver, name, value, null, false, userHandle, + DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** @hide */ @UnsupportedAppUsage public static boolean putStringForUser(@NonNull ContentResolver resolver, @NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault, @UserIdInt int userHandle) { + boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore) { if (MOVED_TO_GLOBAL.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure" + " to android.provider.Settings.Global"); return Global.putStringForUser(resolver, name, value, - tag, makeDefault, userHandle); + tag, makeDefault, userHandle, DEFAULT_OVERRIDEABLE_BY_RESTORE); } return sNameValueCache.putStringForUser(resolver, name, value, tag, - makeDefault, userHandle); + makeDefault, userHandle, overrideableByRestore); } /** @@ -5289,7 +5346,7 @@ public final class Settings { @NonNull String name, @Nullable String value, @Nullable String tag, boolean makeDefault) { return putStringForUser(resolver, name, value, tag, makeDefault, - resolver.getUserId()); + resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** @@ -12972,7 +13029,29 @@ public final class Settings { */ public static boolean putString(ContentResolver resolver, String name, String value) { - return putStringForUser(resolver, name, value, null, false, resolver.getUserId()); + return putStringForUser(resolver, name, value, null, false, resolver.getUserId(), + DEFAULT_OVERRIDEABLE_BY_RESTORE); + } + + /** + * Store a name/value pair into the database. + * + * @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. + * @param overrideableByRestore whether restore can override this value + * @return true if the value was set, false on database errors + * + * @hide + */ + @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) + public static boolean putString(@NonNull ContentResolver resolver, + @NonNull String name, @Nullable String value, @Nullable String tag, + boolean makeDefault, boolean overrideableByRestore) { + return putStringForUser(resolver, name, value, tag, makeDefault, + resolver.getUserId(), overrideableByRestore); } /** @@ -13021,7 +13100,7 @@ public final class Settings { @NonNull String name, @Nullable String value, @Nullable String tag, boolean makeDefault) { return putStringForUser(resolver, name, value, tag, makeDefault, - resolver.getUserId()); + resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** @@ -13083,13 +13162,14 @@ public final class Settings { @UnsupportedAppUsage public static boolean putStringForUser(ContentResolver resolver, String name, String value, int userHandle) { - return putStringForUser(resolver, name, value, null, false, userHandle); + return putStringForUser(resolver, name, value, null, false, userHandle, + DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** @hide */ public static boolean putStringForUser(@NonNull ContentResolver resolver, @NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault, @UserIdInt int userHandle) { + boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore) { if (LOCAL_LOGV) { Log.v(TAG, "Global.putString(name=" + name + ", value=" + value + " for " + userHandle); @@ -13099,10 +13179,10 @@ public final class Settings { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global" + " to android.provider.Settings.Secure, value is unchanged."); return Secure.putStringForUser(resolver, name, value, tag, - makeDefault, userHandle); + makeDefault, userHandle, overrideableByRestore); } return sNameValueCache.putStringForUser(resolver, name, value, tag, - makeDefault, userHandle); + makeDefault, userHandle, overrideableByRestore); } /** @@ -13969,7 +14049,8 @@ public final class Settings { static boolean putString(@NonNull ContentResolver resolver, @NonNull String namespace, @NonNull String name, @Nullable String value, boolean makeDefault) { return sNameValueCache.putStringForUser(resolver, createCompositeName(namespace, name), - value, null, makeDefault, resolver.getUserId()); + value, null, makeDefault, resolver.getUserId(), + DEFAULT_OVERRIDEABLE_BY_RESTORE); } /** diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a808ed8fcc811..f4ee50eeabe3d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3967,6 +3967,13 @@ + + +