Settings recovery support

This change allows the system to perform iterative reset
of changes to settings in order to recover from bad a
state such as a reboot loop.

To enable this we add the notion of a default value. The
default can be set by any package but if the package that
set it is a part of the system, i.e. trusted, then other
packages that are not a part of the system, i.e. untrusted,
cannot change the default. The settings setter APIs that
do not take a default effectively clear the default. Putting
a setting from a system component always makes it the
default and if the package in not trusted then value is
not made the default. The rationale is that the system is
tested and its values are safe but third-party components
are not trusted and their values are not safe.

The reset modes from the least intrusive are: untrusted
defaults - reset only settings set by untrusted components
to their defaults or clear them otherwise; untrusted clear
- clear settings set by untrusted components (or snap to
default if provided by the system); trusted defaults - reset
all settings to defaults set by the system or clear them
otherwise.

Also a package can reset to defaults changes it made to
the global and secure settings. It is also possible to
associate a setting with an optional token which can then
be used to reset settings set by this package and
associated with the token allowing parallel experiments
over disjoint settings subsets.

The default values are also useful for experiment (or
more precisely iterative tuning of devices' behavior in
production) as the stable configuration can be set to
the "graduated" safe defaults and set the values to the
experimental ones to measure impact.

Test: tests pass

Change-Id: I838955ea3bb28337f416ee244dff2fb1199b6943
This commit is contained in:
Svetoslav Ganov
2016-12-21 17:10:35 -08:00
parent 331b8156c7
commit e080da9ee0
14 changed files with 1573 additions and 259 deletions

View File

@@ -221,4 +221,9 @@ public abstract class PackageManagerInternal {
public abstract void requestEphemeralResolutionPhaseTwo(EphemeralResponse responseObj,
Intent origIntent, String resolvedType, Intent launchIntent, String callingPackage,
int userId);
/**
* @return The SetupWizard package name.
*/
public abstract String getSetupWizardPackageName();
}

View File

@@ -16,11 +16,17 @@
package android.provider;
import android.Manifest;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.app.Application;
@@ -66,6 +72,8 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ILockSettings;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
@@ -347,7 +355,6 @@ public final class Settings {
* Input: Nothing.
* <p>
* Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_WIFI_SETTINGS =
@@ -1211,8 +1218,6 @@ public final class Settings {
public static final String ACTION_HOME_SETTINGS
= "android.settings.HOME_SETTINGS";
/**
* Activity Action: Show Default apps settings.
* <p>
@@ -1387,6 +1392,21 @@ public final class Settings {
*/
public static final String CALL_METHOD_USER_KEY = "_user";
/**
* @hide - Boolean argument extra to the fast-path call()-based requests
*/
public static final String CALL_METHOD_MAKE_DEFAULT_KEY = "_make_default";
/**
* @hide - User handle argument extra to the fast-path call()-based requests
*/
public static final String CALL_METHOD_RESET_MODE_KEY = "_reset_mode";
/**
* @hide - String argument extra to the fast-path call()-based requests
*/
public static final String CALL_METHOD_TAG_KEY = "_tag";
/** @hide - Private call() method to write to 'system' table */
public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
@@ -1396,6 +1416,12 @@ 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 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 'secure' table */
public static final String CALL_METHOD_RESET_SECURE = "RESET_secure";
/**
* Activity Extra: Limit available options in launched activity based on the given authority.
* <p>
@@ -1471,6 +1497,55 @@ public final class Settings {
public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES =
"android.settings.extra.do_not_disturb_mode_minutes";
/**
* Reset mode: reset to defaults only settings changed by the
* calling package. If there is a default set the setting
* will be set to it, otherwise the setting will be deleted.
* This is the only type of reset available to non-system clients.
* @hide
*/
public static final int RESET_MODE_PACKAGE_DEFAULTS = 1;
/**
* Reset mode: reset all settings set by untrusted packages, which is
* packages that aren't a part of the system, to the current defaults.
* If there is a default set the setting will be set to it, otherwise
* the setting will be deleted. This mode is only available to the system.
* @hide
*/
public static final int RESET_MODE_UNTRUSTED_DEFAULTS = 2;
/**
* Reset mode: delete all settings set by untrusted packages, which is
* packages that aren't a part of the system. If a setting is set by an
* untrusted package it will be deleted if its default is not provided
* by the system, otherwise the setting will be set to its default.
* This mode is only available to the system.
* @hide
*/
public static final int RESET_MODE_UNTRUSTED_CHANGES = 3;
/**
* Reset mode: reset all settings to defaults specified by trusted
* packages, which is packages that are a part of the system, and
* delete all settings set by untrusted packages. If a setting has
* a default set by a system package it will be set to the default,
* otherwise the setting will be deleted. This mode is only available
* to the system.
* @hide
*/
public static final int RESET_MODE_TRUSTED_DEFAULTS = 4;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
RESET_MODE_PACKAGE_DEFAULTS,
RESET_MODE_UNTRUSTED_DEFAULTS,
RESET_MODE_UNTRUSTED_CHANGES,
RESET_MODE_TRUSTED_DEFAULTS
})
public @interface ResetMode{}
/**
* Activity Extra: Number of certificates
* <p>
@@ -1574,22 +1649,44 @@ public final class Settings {
}
}
private static final class ContentProviderHolder {
private final Object mLock = new Object();
@GuardedBy("mLock")
private final Uri mUri;
@GuardedBy("mLock")
private IContentProvider mContentProvider;
public ContentProviderHolder(Uri uri) {
mUri = uri;
}
public IContentProvider getProvider(ContentResolver contentResolver) {
synchronized (mLock) {
if (mContentProvider == null) {
mContentProvider = contentResolver
.acquireProvider(mUri.getAuthority());
}
return mContentProvider;
}
}
}
// Thread-safe.
private static class NameValueCache {
private static final boolean DEBUG = false;
private final Uri mUri;
private static final String[] SELECT_VALUE_PROJECTION = new String[] {
Settings.NameValueTable.VALUE
};
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<>();
// Initially null; set lazily and held forever. Synchronized on 'this'.
private IContentProvider mContentProvider = null;
private final Uri mUri;
private final ContentProviderHolder mProviderHolder;
// The method we'll call (or null, to not use) on the provider
// for the fast path of retrieving settings.
@@ -1599,30 +1696,27 @@ public final class Settings {
@GuardedBy("this")
private GenerationTracker mGenerationTracker;
public NameValueCache(Uri uri, String getCommand, String setCommand) {
public NameValueCache(Uri uri, String getCommand, String setCommand,
ContentProviderHolder providerHolder) {
mUri = uri;
mCallGetCommand = getCommand;
mCallSetCommand = setCommand;
}
private IContentProvider lazyGetProvider(ContentResolver cr) {
IContentProvider cp = null;
synchronized (NameValueCache.this) {
cp = mContentProvider;
if (cp == null) {
cp = mContentProvider = cr.acquireProvider(mUri.getAuthority());
}
}
return cp;
mProviderHolder = providerHolder;
}
public boolean putStringForUser(ContentResolver cr, String name, String value,
final int userHandle) {
String tag, boolean makeDefault, final int userHandle) {
try {
Bundle arg = new Bundle();
arg.putString(Settings.NameValueTable.VALUE, value);
arg.putInt(CALL_METHOD_USER_KEY, userHandle);
IContentProvider cp = lazyGetProvider(cr);
if (tag != null) {
arg.putString(CALL_METHOD_TAG_KEY, tag);
}
if (makeDefault) {
arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
IContentProvider cp = mProviderHolder.getProvider(cr);
cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
@@ -1653,7 +1747,7 @@ public final class Settings {
+ " by user " + UserHandle.myUserId() + " so skipping cache");
}
IContentProvider cp = lazyGetProvider(cr);
IContentProvider cp = mProviderHolder.getProvider(cr);
// Try the fast path first, not using query(). If this
// fails (alternate Settings provider that doesn't support
@@ -1802,10 +1896,14 @@ public final class Settings {
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/system");
private static final ContentProviderHolder sProviderHolder =
new ContentProviderHolder(CONTENT_URI);
private static final NameValueCache sNameValueCache = new NameValueCache(
CONTENT_URI,
CALL_METHOD_GET_SYSTEM,
CALL_METHOD_PUT_SYSTEM);
CALL_METHOD_PUT_SYSTEM,
sProviderHolder);
private static final HashSet<String> MOVED_TO_SECURE;
static {
@@ -1997,7 +2095,7 @@ public final class Settings {
+ " to android.provider.Settings.Global, value is unchanged.");
return false;
}
return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle);
}
/**
@@ -4153,11 +4251,15 @@ public final class Settings {
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/secure");
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_SECURE,
CALL_METHOD_PUT_SECURE);
CALL_METHOD_PUT_SECURE,
sProviderHolder);
private static ILockSettings sLockSettings = null;
@@ -4357,6 +4459,13 @@ public final class Settings {
/** @hide */
public static boolean putStringForUser(ContentResolver resolver, String name, String value,
int userHandle) {
return putStringForUser(resolver, name, value, null, false, userHandle);
}
/** @hide */
public static boolean putStringForUser(@NonNull ContentResolver resolver,
@NonNull String name, @Nullable String value, @Nullable String tag,
boolean makeDefault, @UserIdInt int userHandle) {
if (LOCATION_MODE.equals(name)) {
// Map LOCATION_MODE to underlying location provider storage API
return setLocationModeForUser(resolver, Integer.parseInt(value), userHandle);
@@ -4364,9 +4473,115 @@ public final class Settings {
if (MOVED_TO_GLOBAL.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ " to android.provider.Settings.Global");
return Global.putStringForUser(resolver, name, value, userHandle);
return Global.putStringForUser(resolver, name, value,
tag, makeDefault, userHandle);
}
return sNameValueCache.putStringForUser(resolver, name, value, tag,
makeDefault, userHandle);
}
/**
* 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)}. Anyone can override
* the current tag. Also if another package changes the setting
* then the tag will be set to the one specified in the set call
* which can be null. Also any of the settings setters that do not
* take a tag as an argument effectively clears the tag.
* </p><p>
* For example, if you set settings A and B with tags T1 and T2 and
* another app changes setting A (potentially to the same value), it
* can assign to it a tag T3 (note that now the package that changed
* the setting is not yours). Now if you reset your changes for T1 and
* T2 only setting B will be reset and A not (as it was changed by
* another package) but since A did not change you are in the desired
* initial state. Now if the other app changes the value of A (assuming
* you registered an observer in the beginning) you would detect that
* the setting was changed by another app and handle this appropriately
* (ignore, set back to some value, etc).
* </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 associate 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
*/
@SystemApi
@RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
public static boolean putString(@NonNull ContentResolver resolver,
@NonNull String name, @Nullable String value, @Nullable String tag,
boolean makeDefault) {
return putStringForUser(resolver, name, value, tag, makeDefault,
UserHandle.myUserId());
}
/**
* Reset the settings to their defaults. This would reset <strong>only</strong>
* settings set by the caller's package. Think of it of a way to undo your own
* changes to the global settings. 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
*/
@SystemApi
@RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
public static void resetToDefaults(@NonNull ContentResolver resolver,
@Nullable String tag) {
resetToDefaultsAsUser(resolver, tag, RESET_MODE_PACKAGE_DEFAULTS,
UserHandle.myUserId());
}
/**
*
* Reset the settings to their defaults for a given user with a specific mode. The
* optional tag argument is valid only for {@link #RESET_MODE_PACKAGE_DEFAULTS}
* allowing resetting the settings made by a package and associated with the tag.
*
* @param resolver Handle to the content resolver.
* @param tag Optional tag which should be associated with the settings to reset.
* @param mode The reset mode.
* @param userHandle The user for which to reset to defaults.
*
* @see #RESET_MODE_PACKAGE_DEFAULTS
* @see #RESET_MODE_UNTRUSTED_DEFAULTS
* @see #RESET_MODE_UNTRUSTED_CHANGES
* @see #RESET_MODE_TRUSTED_DEFAULTS
*
* @hide
*/
public static void resetToDefaultsAsUser(@NonNull ContentResolver resolver,
@Nullable String tag, @ResetMode int mode, @IntRange(from = 0) int userHandle) {
try {
Bundle arg = new Bundle();
arg.putInt(CALL_METHOD_USER_KEY, userHandle);
if (tag != null) {
arg.putString(CALL_METHOD_TAG_KEY, tag);
}
arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode);
IContentProvider cp = sProviderHolder.getProvider(resolver);
cp.call(resolver.getPackageName(), CALL_METHOD_RESET_SECURE, null, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e);
}
return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
}
/**
@@ -9133,11 +9348,15 @@ public final class Settings {
LOW_POWER_MODE_TRIGGER_LEVEL
};
private static final ContentProviderHolder sProviderHolder =
new ContentProviderHolder(CONTENT_URI);
// Populated lazily, guarded by class object:
private static NameValueCache sNameValueCache = new NameValueCache(
private static final NameValueCache sNameValueCache = new NameValueCache(
CONTENT_URI,
CALL_METHOD_GET_GLOBAL,
CALL_METHOD_PUT_GLOBAL);
CALL_METHOD_PUT_GLOBAL,
sProviderHolder);
// Certain settings have been moved from global to the per-user secure namespace
private static final HashSet<String> MOVED_TO_SECURE;
@@ -9181,12 +9400,122 @@ public final class Settings {
*/
public static boolean putString(ContentResolver resolver,
String name, String value) {
return putStringForUser(resolver, name, value, UserHandle.myUserId());
return putStringForUser(resolver, name, value, null, false, UserHandle.myUserId());
}
/**
* 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)}. Anyone can override
* the current tag. Also if another package changes the setting
* then the tag will be set to the one specified in the set call
* which can be null. Also any of the settings setters that do not
* take a tag as an argument effectively clears the tag.
* </p><p>
* For example, if you set settings A and B with tags T1 and T2 and
* another app changes setting A (potentially to the same value), it
* can assign to it a tag T3 (note that now the package that changed
* the setting is not yours). Now if you reset your changes for T1 and
* T2 only setting B will be reset and A not (as it was changed by
* another package) but since A did not change you are in the desired
* initial state. Now if the other app changes the value of A (assuming
* you registered an observer in the beginning) you would detect that
* the setting was changed by another app and handle this appropriately
* (ignore, set back to some value, etc).
* </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
*/
@SystemApi
@RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
public static boolean putString(@NonNull ContentResolver resolver,
@NonNull String name, @Nullable String value, @Nullable String tag,
boolean makeDefault) {
return putStringForUser(resolver, name, value, tag, makeDefault,
UserHandle.myUserId());
}
/**
* Reset the settings to their defaults. This would reset <strong>only</strong>
* settings set by the caller's package. Think of it of a way to undo your own
* changes to the secure settings. 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
*/
@SystemApi
@RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
public static void resetToDefaults(@NonNull ContentResolver resolver,
@Nullable String tag) {
resetToDefaultsAsUser(resolver, tag, RESET_MODE_PACKAGE_DEFAULTS,
UserHandle.myUserId());
}
/**
* Reset the settings to their defaults for a given user with a specific mode. The
* optional tag argument is valid only for {@link #RESET_MODE_PACKAGE_DEFAULTS}
* allowing resetting the settings made by a package and associated with the tag.
*
* @param resolver Handle to the content resolver.
* @param tag Optional tag which should be associated with the settings to reset.
* @param mode The reset mode.
* @param userHandle The user for which to reset to defaults.
*
* @see #RESET_MODE_PACKAGE_DEFAULTS
* @see #RESET_MODE_UNTRUSTED_DEFAULTS
* @see #RESET_MODE_UNTRUSTED_CHANGES
* @see #RESET_MODE_TRUSTED_DEFAULTS
*
* @hide
*/
public static void resetToDefaultsAsUser(@NonNull ContentResolver resolver,
@Nullable String tag, @ResetMode int mode, @IntRange(from = 0) int userHandle) {
try {
Bundle arg = new Bundle();
arg.putInt(CALL_METHOD_USER_KEY, userHandle);
if (tag != null) {
arg.putString(CALL_METHOD_TAG_KEY, tag);
}
arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode);
IContentProvider cp = sProviderHolder.getProvider(resolver);
cp.call(resolver.getPackageName(), CALL_METHOD_RESET_GLOBAL, null, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e);
}
}
/** @hide */
public static boolean putStringForUser(ContentResolver resolver,
String name, String value, int userHandle) {
return putStringForUser(resolver, name, value, null, false, userHandle);
}
/** @hide */
public static boolean putStringForUser(@NonNull ContentResolver resolver,
@NonNull String name, @Nullable String value, @Nullable String tag,
boolean makeDefault, @UserIdInt int userHandle) {
if (LOCAL_LOGV) {
Log.v(TAG, "Global.putString(name=" + name + ", value=" + value
+ " for " + userHandle);
@@ -9195,9 +9524,11 @@ public final class Settings {
if (MOVED_TO_SECURE.contains(name)) {
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, userHandle);
return Secure.putStringForUser(resolver, name, value, tag,
makeDefault, userHandle);
}
return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
return sNameValueCache.putStringForUser(resolver, name, value, tag,
makeDefault, userHandle);
}
/**
@@ -9418,7 +9749,6 @@ public final class Settings {
return putString(cr, name, Float.toString(value));
}
/**
* Subscription to be used for voice call on a multi sim device. The supported values
* are 0 = SUB1, 1 = SUB2 and etc.