Add device_config cmds for disabling syncs
This adds a "sync disabled" mode which disables bulk updates to
device_config (AKA server flags). This is intended for use during
automated and manual tests that use device_config settings to set the
device into specific states for tests. Without this, devices can sync at
an arbitrary point during a test which can undo device_config changes
the tests have made and cause them to fail / flake.
This mechanism is independent of the mechanism used to sync, thereby
making it suitable for use in CTS or other AOSP tests, i.e. to disable
sync regardless of whether GMS core or an alternative is handling the
sync.
Test: atest core/tests/coretests/src/android/provider/DeviceConfigTest.java
Bug: 185786624
Change-Id: Icd0ce798642eb136dc8b9b1a58a4ecbc6212fdba
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index d7d1902..2584405 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -31,6 +31,7 @@
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
+import android.provider.Settings.Config.SyncDisabledMode;
import android.provider.Settings.ResetMode;
import android.util.ArrayMap;
import android.util.Log;
@@ -832,6 +833,37 @@
}
/**
+ * Disables or re-enables bulk modifications ({@link #setProperties(Properties)}) to device
+ * config values. This is intended for use during tests to prevent a sync operation clearing
+ * config values, which could influence the outcome of the tests, i.e. by changing behavior.
+ *
+ * @param syncDisabledMode the mode to use, see {@link Settings.Config#SYNC_DISABLED_MODE_NONE},
+ * {@link Settings.Config#SYNC_DISABLED_MODE_PERSISTENT} and {@link
+ * Settings.Config#SYNC_DISABLED_MODE_UNTIL_REBOOT}
+ *
+ * @see #isSyncDisabled()
+ * @hide
+ */
+ @RequiresPermission(WRITE_DEVICE_CONFIG)
+ public static void setSyncDisabled(@SyncDisabledMode int syncDisabledMode) {
+ ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+ Settings.Config.setSyncDisabled(contentResolver, syncDisabledMode);
+ }
+
+ /**
+ * Returns the current state of sync disabling, {@code true} when disabled, {@code false}
+ * otherwise.
+ *
+ * @see #setSyncDisabled(int)
+ * @hide
+ */
+ @RequiresPermission(WRITE_DEVICE_CONFIG)
+ public static boolean isSyncDisabled() {
+ ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+ return Settings.Config.isSyncDisabled(contentResolver);
+ }
+
+ /**
* Add a listener for property changes.
* <p>
* This listener will be called whenever properties in the specified namespace change. Callbacks
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c51c506..eac8cfa 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -267,8 +267,40 @@
/** @hide */
public static final String EXTRA_NETWORK_TEMPLATE = "network_template";
+ /**
+ * The return values for {@link Settings.Config#set}
+ * @hide
+ */
+ @IntDef(prefix = "SET_ALL_RESULT_",
+ value = { SET_ALL_RESULT_FAILURE, SET_ALL_RESULT_SUCCESS, SET_ALL_RESULT_DISABLED })
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface SetAllResult {}
+
+ /**
+ * A return value for {@link #KEY_CONFIG_SET_ALL_RETURN}, indicates failure.
+ * @hide
+ */
+ public static final int SET_ALL_RESULT_FAILURE = 0;
+
+ /**
+ * A return value for {@link #KEY_CONFIG_SET_ALL_RETURN}, indicates success.
+ * @hide
+ */
+ public static final int SET_ALL_RESULT_SUCCESS = 1;
+
+ /**
+ * A return value for {@link #KEY_CONFIG_SET_ALL_RETURN}, indicates a set all is disabled.
+ * @hide
+ */
+ public static final int SET_ALL_RESULT_DISABLED = 2;
+
/** @hide */
- public static final String KEY_CONFIG_SET_RETURN = "config_set_return";
+ public static final String KEY_CONFIG_SET_ALL_RETURN = "config_set_all_return";
+
+ /** @hide */
+ public static final String KEY_CONFIG_IS_SYNC_DISABLED_RETURN =
+ "config_is_sync_disabled_return";
/**
* An int extra specifying a subscription ID.
@@ -2324,6 +2356,11 @@
public static final String CALL_METHOD_PREFIX_KEY = "_prefix";
/**
+ * @hide - String argument extra to the fast-path call()-based requests
+ */
+ public static final String CALL_METHOD_SYNC_DISABLED_MODE_KEY = "_disabled_mode";
+
+ /**
* @hide - RemoteCallback monitor callback argument extra to the fast-path call()-based requests
*/
public static final String CALL_METHOD_MONITOR_CALLBACK_KEY = "_monitor_callback_key";
@@ -2386,6 +2423,15 @@
/** @hide - Private call() method to reset to defaults the 'configuration' table */
public static final String CALL_METHOD_LIST_CONFIG = "LIST_config";
+ /** @hide - Private call() method to disable / re-enable syncs to the 'configuration' table */
+ public static final String CALL_METHOD_SET_SYNC_DISABLED_CONFIG = "SET_SYNC_DISABLED_config";
+
+ /**
+ * @hide - Private call() method to return whether syncs are disabled for the 'configuration'
+ * table
+ */
+ public static final String CALL_METHOD_IS_SYNC_DISABLED_CONFIG = "IS_SYNC_DISABLED_config";
+
/** @hide - Private call() method to register monitor callback for 'configuration' table */
public static final String CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG =
"REGISTER_MONITOR_CALLBACK_config";
@@ -2762,11 +2808,11 @@
return true;
}
- public boolean setStringsForPrefix(ContentResolver cr, String prefix,
+ public @SetAllResult int setStringsForPrefix(ContentResolver cr, String prefix,
HashMap<String, String> keyValues) {
if (mCallSetAllCommand == null) {
// This NameValueCache does not support atomically setting multiple flags
- return false;
+ return SET_ALL_RESULT_FAILURE;
}
try {
Bundle args = new Bundle();
@@ -2776,10 +2822,10 @@
Bundle bundle = cp.call(cr.getAttributionSource(),
mProviderHolder.mUri.getAuthority(),
mCallSetAllCommand, null, args);
- return bundle.getBoolean(KEY_CONFIG_SET_RETURN);
+ return bundle.getInt(KEY_CONFIG_SET_ALL_RETURN);
} catch (RemoteException e) {
// Not supported by the remote side
- return false;
+ return SET_ALL_RESULT_FAILURE;
}
}
@@ -14187,6 +14233,15 @@
public static final String ARE_USER_DISABLED_HDR_FORMATS_ALLOWED =
"are_user_disabled_hdr_formats_allowed";
+ /**
+ * Whether or not syncs (bulk set operations) for {@link DeviceConfig} are disabled
+ * currently. The value is boolean (1 or 0). The value '1' means that {@link
+ * DeviceConfig#setProperties(DeviceConfig.Properties)} will return {@code false}.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_SYNC_DISABLED = "device_config_sync_disabled";
+
/** @hide */ public static String zenModeToString(int mode) {
if (mode == ZEN_MODE_IMPORTANT_INTERRUPTIONS) return "ZEN_MODE_IMPORTANT_INTERRUPTIONS";
if (mode == ZEN_MODE_ALARMS) return "ZEN_MODE_ALARMS";
@@ -16021,6 +16076,39 @@
* @hide
*/
public static final class Config extends NameValueTable {
+
+ /**
+ * The modes that can be used when disabling syncs to the 'config' settings.
+ * @hide
+ */
+ @IntDef(prefix = "DISABLE_SYNC_MODE_",
+ value = { SYNC_DISABLED_MODE_NONE, SYNC_DISABLED_MODE_PERSISTENT,
+ SYNC_DISABLED_MODE_UNTIL_REBOOT })
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface SyncDisabledMode {}
+
+ /**
+ * Sync is not not disabled.
+ *
+ * @hide
+ */
+ public static final int SYNC_DISABLED_MODE_NONE = 0;
+
+ /**
+ * Disabling of Config bulk update / syncing is persistent, i.e. it survives a device
+ * reboot.
+ * @hide
+ */
+ public static final int SYNC_DISABLED_MODE_PERSISTENT = 1;
+
+ /**
+ * Disabling of Config bulk update / syncing is not persistent, i.e. it will not survive a
+ * device reboot.
+ * @hide
+ */
+ public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT = 2;
+
private static final ContentProviderHolder sProviderHolder =
new ContentProviderHolder(DeviceConfig.CONTENT_URI);
@@ -16113,7 +16201,7 @@
* @param resolver to access the database with.
* @param namespace to which the names should be set.
* @param keyValues map of key names (without the prefix) to values.
- * @return
+ * @return true if the name/value pairs were set, false if setting was blocked
*
* @hide
*/
@@ -16126,12 +16214,15 @@
compositeKeyValueMap.put(
createCompositeName(namespace, entry.getKey()), entry.getValue());
}
- // If can't set given configuration that means it's bad
- if (!sNameValueCache.setStringsForPrefix(resolver, createPrefix(namespace),
- compositeKeyValueMap)) {
- throw new DeviceConfig.BadConfigException();
+ int result = sNameValueCache.setStringsForPrefix(
+ resolver, createPrefix(namespace), compositeKeyValueMap);
+ if (result == SET_ALL_RESULT_SUCCESS) {
+ return true;
+ } else if (result == SET_ALL_RESULT_DISABLED) {
+ return false;
}
- return true;
+ // If can't set given configuration that means it's bad
+ throw new DeviceConfig.BadConfigException();
}
/**
@@ -16167,6 +16258,50 @@
}
/**
+ * Bridge method between {@link DeviceConfig#setSyncDisabled(int)} and the
+ * {@link com.android.providers.settings.SettingsProvider} implementation.
+ *
+ * @hide
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
+ static void setSyncDisabled(
+ @NonNull ContentResolver resolver, @SyncDisabledMode int disableSyncMode) {
+ try {
+ Bundle args = new Bundle();
+ args.putInt(CALL_METHOD_SYNC_DISABLED_MODE_KEY, disableSyncMode);
+ IContentProvider cp = sProviderHolder.getProvider(resolver);
+ cp.call(resolver.getAttributionSource(),
+ sProviderHolder.mUri.getAuthority(), CALL_METHOD_SET_SYNC_DISABLED_CONFIG,
+ null, args);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't set sync disabled " + DeviceConfig.CONTENT_URI, e);
+ }
+ }
+
+ /**
+ * Bridge method between {@link DeviceConfig#isSyncDisabled()} and the
+ * {@link com.android.providers.settings.SettingsProvider} implementation.
+ *
+ * @hide
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
+ static boolean isSyncDisabled(@NonNull ContentResolver resolver) {
+ try {
+ Bundle args = Bundle.EMPTY;
+ IContentProvider cp = sProviderHolder.getProvider(resolver);
+ Bundle bundle = cp.call(resolver.getAttributionSource(),
+ sProviderHolder.mUri.getAuthority(), CALL_METHOD_IS_SYNC_DISABLED_CONFIG,
+ null, args);
+ return bundle.getBoolean(KEY_CONFIG_IS_SYNC_DISABLED_RETURN);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't query sync disabled " + DeviceConfig.CONTENT_URI, e);
+ }
+ return false;
+ }
+
+ /**
* Register callback for monitoring Config table.
*
* @param resolver Handle to the content resolver.
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 61f58b0..fd39cde 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -762,6 +762,62 @@
// }
// }
+ @Test
+ public void syncDisabling() throws Exception {
+ Properties properties1 = new Properties.Builder(NAMESPACE)
+ .setString(KEY, VALUE)
+ .build();
+ Properties properties2 = new Properties.Builder(NAMESPACE)
+ .setString(KEY, VALUE2)
+ .build();
+
+ try {
+ // Ensure the device starts in a known state.
+ DeviceConfig.setSyncDisabled(Settings.Config.SYNC_DISABLED_MODE_NONE);
+
+ // Assert starting state.
+ assertThat(DeviceConfig.isSyncDisabled()).isFalse();
+ assertThat(DeviceConfig.setProperties(properties1)).isTrue();
+ assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
+ .isEqualTo(VALUE);
+
+ // Test disabled (persistent). Persistence is not actually tested, that would require
+ // a host test.
+ DeviceConfig.setSyncDisabled(Settings.Config.SYNC_DISABLED_MODE_PERSISTENT);
+ assertThat(DeviceConfig.isSyncDisabled()).isTrue();
+ assertThat(DeviceConfig.setProperties(properties2)).isFalse();
+ assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
+ .isEqualTo(VALUE);
+
+ // Return to not disabled.
+ DeviceConfig.setSyncDisabled(Settings.Config.SYNC_DISABLED_MODE_NONE);
+ assertThat(DeviceConfig.isSyncDisabled()).isFalse();
+ assertThat(DeviceConfig.setProperties(properties2)).isTrue();
+ assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
+ .isEqualTo(VALUE2);
+
+ // Test disabled (persistent). Absence of persistence is not actually tested, that would
+ // require a host test.
+ DeviceConfig.setSyncDisabled(Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT);
+ assertThat(DeviceConfig.isSyncDisabled()).isTrue();
+ assertThat(DeviceConfig.setProperties(properties1)).isFalse();
+ assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
+ .isEqualTo(VALUE2);
+
+ // Return to not disabled.
+ DeviceConfig.setSyncDisabled(Settings.Config.SYNC_DISABLED_MODE_NONE);
+ assertThat(DeviceConfig.isSyncDisabled()).isFalse();
+ assertThat(DeviceConfig.setProperties(properties1)).isTrue();
+ assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
+ .isEqualTo(VALUE);
+ } finally {
+ // Try to return to the default sync disabled state in case of failure.
+ DeviceConfig.setSyncDisabled(Settings.Config.SYNC_DISABLED_MODE_NONE);
+
+ // NAMESPACE will be cleared by cleanUp()
+ }
+ }
+
private static boolean deleteViaContentProvider(String namespace, String key) {
ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
String compositeName = namespace + "/" + key;
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index 97e66c4..ee0b127 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -96,7 +96,8 @@
mCacheGenerationStore.set(0, ++mCurrentGeneration);
Bundle result = new Bundle();
- result.putBoolean(Settings.KEY_CONFIG_SET_RETURN, true);
+ result.putInt(Settings.KEY_CONFIG_SET_ALL_RETURN,
+ Settings.SET_ALL_RESULT_SUCCESS);
return result;
});
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index b7560d2..c9c3db8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -73,6 +73,7 @@
Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER,
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
Settings.Global.USER_DISABLED_HDR_FORMATS,
- Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED
+ Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
+ Settings.Global.DEVICE_CONFIG_SYNC_DISABLED,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 8f7f1fa..5220a04 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -139,6 +139,7 @@
/* first= */Global.ONE_HANDED_KEYGUARD_SIDE_LEFT,
/* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT));
VALIDATORS.put(Global.DISABLE_WINDOW_BLURS, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.DEVICE_CONFIG_SYNC_DISABLED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index df6ff73..00fd19c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -16,7 +16,11 @@
package com.android.providers.settings;
-import android.annotation.SystemApi;
+import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE;
+import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
+import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
+
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.AttributionSource;
import android.content.IContentProvider;
@@ -41,10 +45,7 @@
/**
* Receives shell commands from the command line related to device config flags, and dispatches them
* to the SettingsProvider.
- *
- * @hide
*/
-@SystemApi
public final class DeviceConfigService extends Binder {
final SettingsProvider mProvider;
@@ -62,18 +63,20 @@
final SettingsProvider mProvider;
enum CommandVerb {
- UNSPECIFIED,
GET,
PUT,
DELETE,
LIST,
RESET,
+ SET_SYNC_DISABLED_FOR_TESTS,
+ IS_SYNC_DISABLED_FOR_TESTS,
}
MyShellCommand(SettingsProvider provider) {
mProvider = provider;
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
@Override
public int onCommand(String cmd) {
if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
@@ -83,6 +86,7 @@
final PrintWriter perr = getErrPrintWriter();
boolean isValid = false;
+
CommandVerb verb;
if ("get".equalsIgnoreCase(cmd)) {
verb = CommandVerb.GET;
@@ -97,21 +101,33 @@
}
} else if ("reset".equalsIgnoreCase(cmd)) {
verb = CommandVerb.RESET;
+ } else if ("set_sync_disabled_for_tests".equalsIgnoreCase(cmd)) {
+ verb = CommandVerb.SET_SYNC_DISABLED_FOR_TESTS;
+ } else if ("is_sync_disabled_for_tests".equalsIgnoreCase(cmd)) {
+ verb = CommandVerb.IS_SYNC_DISABLED_FOR_TESTS;
+ if (peekNextArg() != null) {
+ perr.println("Bad arguments");
+ return -1;
+ }
+ isValid = true;
} else {
// invalid
perr.println("Invalid command: " + cmd);
return -1;
}
+ // Parse args for those commands that have them.
+ int disableSyncMode = -1;
int resetMode = -1;
boolean makeDefault = false;
String namespace = null;
String key = null;
String value = null;
- String arg = null;
+ String arg;
while ((arg = getNextArg()) != null) {
if (verb == CommandVerb.RESET) {
if (resetMode == -1) {
+ // RESET 1st arg (required)
if ("untrusted_defaults".equalsIgnoreCase(arg)) {
resetMode = Settings.RESET_MODE_UNTRUSTED_DEFAULTS;
} else if ("untrusted_clear".equalsIgnoreCase(arg)) {
@@ -127,6 +143,7 @@
isValid = true;
}
} else {
+ // RESET 2nd arg (optional)
namespace = arg;
if (peekNextArg() == null) {
isValid = true;
@@ -136,7 +153,26 @@
return -1;
}
}
+ } else if (verb == CommandVerb.SET_SYNC_DISABLED_FOR_TESTS) {
+ if (disableSyncMode == -1) {
+ // DISABLE_SYNC_FOR_TESTS 1st arg (required)
+ if ("none".equalsIgnoreCase(arg)) {
+ disableSyncMode = SYNC_DISABLED_MODE_NONE;
+ } else if ("persistent".equalsIgnoreCase(arg)) {
+ disableSyncMode = SYNC_DISABLED_MODE_PERSISTENT;
+ } else if ("until_reboot".equalsIgnoreCase(arg)) {
+ disableSyncMode = SYNC_DISABLED_MODE_UNTIL_REBOOT;
+ } else {
+ // invalid
+ perr.println("Invalid sync disabled mode: " + arg);
+ return -1;
+ }
+ if (peekNextArg() == null) {
+ isValid = true;
+ }
+ }
} else if (namespace == null) {
+ // GET, PUT, DELETE, LIST 1st arg
namespace = arg;
if (verb == CommandVerb.LIST) {
if (peekNextArg() == null) {
@@ -148,8 +184,10 @@
}
}
} else if (key == null) {
+ // GET, PUT, DELETE 2nd arg
key = arg;
if ((verb == CommandVerb.GET || verb == CommandVerb.DELETE)) {
+ // GET, DELETE only have 2 args
if (peekNextArg() == null) {
isValid = true;
} else {
@@ -159,11 +197,13 @@
}
}
} else if (value == null) {
+ // PUT 3rd arg (required)
value = arg;
if (verb == CommandVerb.PUT && peekNextArg() == null) {
isValid = true;
}
} else if ("default".equalsIgnoreCase(arg)) {
+ // PUT 4th arg (optional)
makeDefault = true;
if (verb == CommandVerb.PUT && peekNextArg() == null) {
isValid = true;
@@ -211,6 +251,12 @@
case RESET:
DeviceConfig.resetToDefaults(resetMode, namespace);
break;
+ case SET_SYNC_DISABLED_FOR_TESTS:
+ DeviceConfig.setSyncDisabled(disableSyncMode);
+ break;
+ case IS_SYNC_DISABLED_FOR_TESTS:
+ pout.println(DeviceConfig.isSyncDisabled());
+ break;
default:
perr.println("Unspecified command");
return -1;
@@ -241,6 +287,16 @@
+ "trusted_defaults}");
pw.println(" NAMESPACE limits which flags are reset if provided, otherwise all "
+ "flags are reset");
+ pw.println(" set_sync_disabled_for_tests SYNC_DISABLED_MODE");
+ pw.println(" Modifies bulk property setting behavior for tests. When in one of the"
+ + " disabled modes this ensures that config isn't overwritten.");
+ pw.println(" SYNC_DISABLED_MODE is one of:");
+ pw.println(" none: Sync is not disabled. A reboot may be required to restart"
+ + " syncing.");
+ pw.println(" persistent: Sync is disabled, this state will survive a reboot.");
+ pw.println(" until_reboot: Sync is disabled until the next reboot.");
+ pw.println(" is_sync_disabled_for_tests");
+ pw.println(" Prints 'true' if sync is disabled, 'false' otherwise.");
}
private boolean delete(IContentProvider provider, String namespace, String key) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 941f47f..13b3684 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -19,6 +19,12 @@
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
+import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE;
+import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
+import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
+import static android.provider.Settings.SET_ALL_RESULT_DISABLED;
+import static android.provider.Settings.SET_ALL_RESULT_FAILURE;
+import static android.provider.Settings.SET_ALL_RESULT_SUCCESS;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
@@ -80,8 +86,10 @@
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.provider.Settings.Config.SyncDisabledMode;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
+import android.provider.Settings.SetAllResult;
import android.provider.settings.validators.SystemSettingsValidators;
import android.provider.settings.validators.Validator;
import android.text.TextUtils;
@@ -335,6 +343,9 @@
// We have to call in the package manager with no lock held,
private volatile IPackageManager mPackageManager;
+ @GuardedBy("mLock")
+ private boolean mSyncConfigDisabledUntilReboot;
+
public static int makeKey(int type, int userId) {
return SettingsState.makeKey(type, userId);
}
@@ -440,11 +451,24 @@
String prefix = getSettingPrefix(args);
Map<String, String> flags = getSettingFlags(args);
Bundle result = new Bundle();
- result.putBoolean(Settings.KEY_CONFIG_SET_RETURN,
+ result.putInt(Settings.KEY_CONFIG_SET_ALL_RETURN,
setAllConfigSettings(prefix, flags));
return result;
}
+ case Settings.CALL_METHOD_SET_SYNC_DISABLED_CONFIG: {
+ final int mode = getSyncDisabledMode(args);
+ setSyncDisabledConfig(mode);
+ break;
+ }
+
+ case Settings.CALL_METHOD_IS_SYNC_DISABLED_CONFIG: {
+ Bundle result = new Bundle();
+ result.putBoolean(Settings.KEY_CONFIG_IS_SYNC_DISABLED_RETURN,
+ isSyncDisabledConfig());
+ return result;
+ }
+
case Settings.CALL_METHOD_RESET_CONFIG: {
final int mode = getResetModeEnforcingPermission(args);
String prefix = getSettingPrefix(args);
@@ -1099,7 +1123,8 @@
MUTATION_OPERATION_INSERT, 0);
}
- private boolean setAllConfigSettings(String prefix, Map<String, String> keyValues) {
+
+ private @SetAllResult int setAllConfigSettings(String prefix, Map<String, String> keyValues) {
if (DEBUG) {
Slog.v(LOG_TAG, "setAllConfigSettings for prefix: " + prefix);
}
@@ -1107,9 +1132,95 @@
enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG);
synchronized (mLock) {
+ if (isSyncDisabledConfigLocked()) {
+ return SET_ALL_RESULT_DISABLED;
+ }
final int key = makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM);
- return mSettingsRegistry.setConfigSettingsLocked(key, prefix, keyValues,
+ boolean success = mSettingsRegistry.setConfigSettingsLocked(key, prefix, keyValues,
resolveCallingPackage());
+ return success ? SET_ALL_RESULT_SUCCESS : SET_ALL_RESULT_FAILURE;
+ }
+ }
+
+ private void setSyncDisabledConfig(@SyncDisabledMode int syncDisabledMode) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "setSyncDisabledConfig(" + syncDisabledMode + ")");
+ }
+
+ enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG);
+
+ synchronized (mLock) {
+ setSyncDisabledConfigLocked(syncDisabledMode);
+ }
+ }
+
+ private boolean isSyncDisabledConfig() {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "isSyncDisabledConfig");
+ }
+
+ enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG);
+
+ synchronized (mLock) {
+ return isSyncDisabledConfigLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void setSyncDisabledConfigLocked(@SyncDisabledMode int syncDisabledMode) {
+ boolean persistentValue;
+ boolean inMemoryValue;
+ if (syncDisabledMode == SYNC_DISABLED_MODE_NONE) {
+ persistentValue = false;
+ inMemoryValue = false;
+ } else if (syncDisabledMode == SYNC_DISABLED_MODE_PERSISTENT) {
+ persistentValue = true;
+ inMemoryValue = false;
+ } else if (syncDisabledMode == SYNC_DISABLED_MODE_UNTIL_REBOOT) {
+ persistentValue = false;
+ inMemoryValue = true;
+ } else {
+ throw new IllegalArgumentException(Integer.toString(syncDisabledMode));
+ }
+
+ mSyncConfigDisabledUntilReboot = inMemoryValue;
+
+ CallingIdentity callingIdentity = clearCallingIdentity();
+ try {
+ String globalSettingValue = persistentValue ? "1" : "0";
+ mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
+ UserHandle.USER_SYSTEM, Settings.Global.DEVICE_CONFIG_SYNC_DISABLED,
+ globalSettingValue, /*tag=*/null, /*makeDefault=*/false,
+ SettingsState.SYSTEM_PACKAGE_NAME, /*forceNotify=*/false,
+ /*criticalSettings=*/null, Settings.DEFAULT_OVERRIDEABLE_BY_RESTORE);
+ } finally {
+ restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isSyncDisabledConfigLocked() {
+ // Check the values used for both SYNC_DISABLED_MODE_PERSISTENT and
+ // SYNC_DISABLED_MODE_UNTIL_REBOOT.
+
+ // The SYNC_DISABLED_MODE_UNTIL_REBOOT value is cheap to check first.
+ if (mSyncConfigDisabledUntilReboot) {
+ return true;
+ }
+
+ // Now check the global setting used to implement SYNC_DISABLED_MODE_PERSISTENT.
+ CallingIdentity callingIdentity = clearCallingIdentity();
+ try {
+ Setting settingLocked = mSettingsRegistry.getSettingLocked(
+ SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM,
+ Global.DEVICE_CONFIG_SYNC_DISABLED);
+ if (settingLocked == null) {
+ return false;
+ }
+ String settingValue = settingLocked.getValue();
+ return settingValue != null && !"0".equals(settingValue);
+ } finally {
+ restoreCallingIdentity(callingIdentity);
}
}
@@ -2202,6 +2313,16 @@
return (args != null) && args.getBoolean(Settings.CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY);
}
+ private static int getSyncDisabledMode(Bundle args) {
+ final int mode = (args != null)
+ ? args.getInt(Settings.CALL_METHOD_SYNC_DISABLED_MODE_KEY) : -1;
+ if (mode == SYNC_DISABLED_MODE_NONE || mode == SYNC_DISABLED_MODE_UNTIL_REBOOT
+ || mode == SYNC_DISABLED_MODE_PERSISTENT) {
+ return mode;
+ }
+ throw new IllegalArgumentException("Invalid sync disabled mode: " + mode);
+ }
+
private static int getResetModeEnforcingPermission(Bundle args) {
final int mode = (args != null) ? args.getInt(Settings.CALL_METHOD_RESET_MODE_KEY) : 0;
switch (mode) {