diff options
8 files changed, 425 insertions, 22 deletions
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index d7d1902b9b9f..25844053f14c 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -31,6 +31,7 @@ import android.content.Context; 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 @@ public final class DeviceConfig { } /** + * 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 c51c50685402..eac8cfa118ea 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -267,8 +267,40 @@ public final class Settings { /** @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_ALL_RETURN = "config_set_all_return"; + /** @hide */ - public static final String KEY_CONFIG_SET_RETURN = "config_set_return"; + 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 final class Settings { 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 @@ public final class Settings { /** @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 @@ public final class Settings { 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 @@ public final class Settings { 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 final class Settings { 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 @@ public final class Settings { * @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 @@ public final class Settings { * @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 @@ public final class Settings { 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 @@ public final class Settings { } /** + * 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 61f58b0d27ce..fd39cdee4c32 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -762,6 +762,62 @@ public class DeviceConfigTest { // } // } + @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 97e66c481dda..ee0b127c696b 100644 --- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java +++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java @@ -96,7 +96,8 @@ public class NameValueCacheTest { 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 b7560d207f0d..c9c3db8a2e27 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 @@ public class GlobalSettings { 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 8f7f1faa4d91..5220a04d73e6 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 @@ public class GlobalSettingsValidators { /* 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 df6ff73cc046..00fd19c2b984 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 @@ import java.util.Map; /** * 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 @@ public final class DeviceConfigService extends Binder { 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 @@ public final class DeviceConfigService extends Binder { final PrintWriter perr = getErrPrintWriter(); boolean isValid = false; + CommandVerb verb; if ("get".equalsIgnoreCase(cmd)) { verb = CommandVerb.GET; @@ -97,21 +101,33 @@ public final class DeviceConfigService extends Binder { } } 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 @@ public final class DeviceConfigService extends Binder { isValid = true; } } else { + // RESET 2nd arg (optional) namespace = arg; if (peekNextArg() == null) { isValid = true; @@ -136,7 +153,26 @@ public final class DeviceConfigService extends Binder { 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 @@ public final class DeviceConfigService extends Binder { } } } 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 @@ public final class DeviceConfigService extends Binder { } } } 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 @@ public final class DeviceConfigService extends Binder { 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 @@ public final class DeviceConfigService extends Binder { + "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 941f47f52551..13b3684647a2 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 @@ package com.android.providers.settings; 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.UserHandle; 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 @@ public class SettingsProvider extends ContentProvider { // 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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) { |