Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ad3d45a6 authored by Neil Fuller's avatar Neil Fuller
Browse files

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
parent 0541a042
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
@@ -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;
@@ -831,6 +832,37 @@ public final class DeviceConfig {
        Settings.Config.resetToDefaults(contentResolver, resetMode, namespace);
    }

    /**
     * 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>
+146 −11
Original line number Diff line number Diff line
@@ -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.
@@ -2323,6 +2355,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
     */
@@ -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,13 +16214,16 @@ public final class Settings {
                compositeKeyValueMap.put(
                        createCompositeName(namespace, entry.getKey()), entry.getValue());
            }
            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;
            }
            // If can't set given configuration that means it's bad
            if (!sNameValueCache.setStringsForPrefix(resolver, createPrefix(namespace),
                    compositeKeyValueMap)) {
            throw new DeviceConfig.BadConfigException();
        }
            return true;
        }
        /**
         * Reset the values to their defaults.
@@ -16166,6 +16257,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.
         *
+56 −0
Original line number Diff line number Diff line
@@ -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;
+2 −1
Original line number Diff line number Diff line
@@ -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;
                });

+2 −1
Original line number Diff line number Diff line
@@ -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,
    };
}
Loading