diff --git a/core/java/android/util/KeyValueSettingObserver.java b/core/java/android/util/KeyValueSettingObserver.java new file mode 100644 index 0000000000000..9fca8b2cfab2b --- /dev/null +++ b/core/java/android/util/KeyValueSettingObserver.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; + +/** + * Abstract class for observing changes to a specified setting stored as a comma-separated key value + * list of parameters. Registers and unregisters a {@link ContentObserver} and handles updates when + * the setting changes. + * + *
Subclasses should pass in the relevant setting's {@link Uri} in the constructor and implement + * {@link #update(KeyValueListParser)} to receive updates when the value changes. + * Calls to {@link #update(KeyValueListParser)} only trigger after calling {@link + * #start()}. + * + *
To get the most up-to-date parameter values, first call {@link #start()} before accessing the + * values to start observing changes, and then call {@link #stop()} once finished. + * + * @hide + */ +public abstract class KeyValueSettingObserver { + private static final String TAG = "KeyValueSettingObserver"; + + private final KeyValueListParser mParser = new KeyValueListParser(','); + + private final ContentObserver mObserver; + private final ContentResolver mResolver; + private final Uri mSettingUri; + + public KeyValueSettingObserver(Handler handler, ContentResolver resolver, + Uri uri) { + mObserver = new SettingObserver(handler); + mResolver = resolver; + mSettingUri = uri; + } + + /** Starts observing changes for the setting. Pair with {@link #stop()}. */ + public void start() { + mResolver.registerContentObserver(mSettingUri, false, mObserver); + setParserValue(); + update(mParser); + } + + /** Stops observing changes for the setting. */ + public void stop() { + mResolver.unregisterContentObserver(mObserver); + } + + /** + * Returns the {@link String} representation of the setting. Subclasses should implement this + * for their setting. + */ + public abstract String getSettingValue(ContentResolver resolver); + + /** Updates the parser with the current setting value. */ + private void setParserValue() { + String setting = getSettingValue(mResolver); + try { + mParser.setString(setting); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Malformed setting: " + setting); + } + } + + /** Subclasses should implement this to update references to their parameters. */ + public abstract void update(KeyValueListParser parser); + + private class SettingObserver extends ContentObserver { + private SettingObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + setParserValue(); + update(mParser); + } + } +} diff --git a/core/java/com/android/internal/backup/LocalTransportParameters.java b/core/java/com/android/internal/backup/LocalTransportParameters.java index 390fae96f8102..154e79d4f7efd 100644 --- a/core/java/com/android/internal/backup/LocalTransportParameters.java +++ b/core/java/com/android/internal/backup/LocalTransportParameters.java @@ -16,62 +16,32 @@ package com.android.internal.backup; +import android.util.KeyValueSettingObserver; import android.content.ContentResolver; -import android.database.ContentObserver; import android.os.Handler; import android.provider.Settings; import android.util.KeyValueListParser; -import android.util.Slog; -class LocalTransportParameters { +class LocalTransportParameters extends KeyValueSettingObserver { private static final String TAG = "LocalTransportParams"; private static final String SETTING = Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS; private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag"; - private final KeyValueListParser mParser = new KeyValueListParser(','); - private final ContentObserver mObserver; - private final ContentResolver mResolver; private boolean mFakeEncryptionFlag; LocalTransportParameters(Handler handler, ContentResolver resolver) { - mObserver = new Observer(handler); - mResolver = resolver; - } - - /** Observes for changes in the setting. This method MUST be paired with {@link #stop()}. */ - void start() { - mResolver.registerContentObserver(Settings.Secure.getUriFor(SETTING), false, mObserver); - update(); - } - - /** Stop observing for changes in the setting. */ - void stop() { - mResolver.unregisterContentObserver(mObserver); + super(handler, resolver, Settings.Secure.getUriFor(SETTING)); } boolean isFakeEncryptionFlag() { return mFakeEncryptionFlag; } - private void update() { - String parameters = ""; - try { - parameters = Settings.Secure.getString(mResolver, SETTING); - } catch (IllegalArgumentException e) { - Slog.e(TAG, "Malformed " + SETTING + " setting: " + e.getMessage()); - } - mParser.setString(parameters); - mFakeEncryptionFlag = mParser.getBoolean(KEY_FAKE_ENCRYPTION_FLAG, false); + public String getSettingValue(ContentResolver resolver) { + return Settings.Secure.getString(resolver, SETTING); } - private class Observer extends ContentObserver { - private Observer(Handler handler) { - super(handler); - } - - @Override - public void onChange(boolean selfChange) { - update(); - } + public void update(KeyValueListParser parser) { + mFakeEncryptionFlag = parser.getBoolean(KEY_FAKE_ENCRYPTION_FLAG, false); } } diff --git a/services/backup/java/com/android/server/backup/BackupManagerConstants.java b/services/backup/java/com/android/server/backup/BackupManagerConstants.java index 99160c24d3b49..dd6e6ab2ece19 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerConstants.java +++ b/services/backup/java/com/android/server/backup/BackupManagerConstants.java @@ -18,53 +18,76 @@ package com.android.server.backup; import android.app.AlarmManager; import android.content.ContentResolver; -import android.database.ContentObserver; -import android.net.Uri; import android.os.Handler; import android.provider.Settings; import android.text.TextUtils; import android.util.KeyValueListParser; +import android.util.KeyValueSettingObserver; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; /** * Class to access backup manager constants. * - * The backup manager constants are encoded as a key value list separated by commas - * and stored as a Settings.Secure. + *
The backup manager constants are encoded as a key value list separated by commas and stored as + * a Settings.Secure. */ -class BackupManagerConstants extends ContentObserver { +class BackupManagerConstants extends KeyValueSettingObserver { private static final String TAG = "BackupManagerConstants"; + private static final String SETTING = Settings.Secure.BACKUP_MANAGER_CONSTANTS; // Key names stored in the secure settings value. - private static final String KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS = + @VisibleForTesting + public static final String KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS = "key_value_backup_interval_milliseconds"; - private static final String KEY_VALUE_BACKUP_FUZZ_MILLISECONDS = + + @VisibleForTesting + public static final String KEY_VALUE_BACKUP_FUZZ_MILLISECONDS = "key_value_backup_fuzz_milliseconds"; - private static final String KEY_VALUE_BACKUP_REQUIRE_CHARGING = + + @VisibleForTesting + public static final String KEY_VALUE_BACKUP_REQUIRE_CHARGING = "key_value_backup_require_charging"; - private static final String KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE = + + @VisibleForTesting + public static final String KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE = "key_value_backup_required_network_type"; - private static final String FULL_BACKUP_INTERVAL_MILLISECONDS = + + @VisibleForTesting + public static final String FULL_BACKUP_INTERVAL_MILLISECONDS = "full_backup_interval_milliseconds"; - private static final String FULL_BACKUP_REQUIRE_CHARGING = - "full_backup_require_charging"; - private static final String FULL_BACKUP_REQUIRED_NETWORK_TYPE = + + @VisibleForTesting + public static final String FULL_BACKUP_REQUIRE_CHARGING = "full_backup_require_charging"; + + @VisibleForTesting + public static final String FULL_BACKUP_REQUIRED_NETWORK_TYPE = "full_backup_required_network_type"; - private static final String BACKUP_FINISHED_NOTIFICATION_RECEIVERS = + + @VisibleForTesting + public static final String BACKUP_FINISHED_NOTIFICATION_RECEIVERS = "backup_finished_notification_receivers"; // Hard coded default values. - private static final long DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS = + @VisibleForTesting + public static final long DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS = 4 * AlarmManager.INTERVAL_HOUR; - private static final long DEFAULT_KEY_VALUE_BACKUP_FUZZ_MILLISECONDS = - 10 * 60 * 1000; - private static final boolean DEFAULT_KEY_VALUE_BACKUP_REQUIRE_CHARGING = true; - private static final int DEFAULT_KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE = 1; - private static final long DEFAULT_FULL_BACKUP_INTERVAL_MILLISECONDS = + + @VisibleForTesting + public static final long DEFAULT_KEY_VALUE_BACKUP_FUZZ_MILLISECONDS = 10 * 60 * 1000; + + @VisibleForTesting public static final boolean DEFAULT_KEY_VALUE_BACKUP_REQUIRE_CHARGING = true; + @VisibleForTesting public static final int DEFAULT_KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE = 1; + + @VisibleForTesting + public static final long DEFAULT_FULL_BACKUP_INTERVAL_MILLISECONDS = 24 * AlarmManager.INTERVAL_HOUR; - private static final boolean DEFAULT_FULL_BACKUP_REQUIRE_CHARGING = true; - private static final int DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE = 2; - private static final String DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS = ""; + + @VisibleForTesting public static final boolean DEFAULT_FULL_BACKUP_REQUIRE_CHARGING = true; + @VisibleForTesting public static final int DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE = 2; + + @VisibleForTesting + public static final String DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS = ""; // Backup manager constants. private long mKeyValueBackupIntervalMilliseconds; @@ -76,49 +99,46 @@ class BackupManagerConstants extends ContentObserver { private int mFullBackupRequiredNetworkType; private String[] mBackupFinishedNotificationReceivers; - private ContentResolver mResolver; - private final KeyValueListParser mParser = new KeyValueListParser(','); - public BackupManagerConstants(Handler handler, ContentResolver resolver) { - super(handler); - mResolver = resolver; - updateSettings(); - mResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.BACKUP_MANAGER_CONSTANTS), false, this); + super(handler, resolver, Settings.Secure.getUriFor(SETTING)); } - @Override - public void onChange(boolean selfChange, Uri uri) { - updateSettings(); + public String getSettingValue(ContentResolver resolver) { + return Settings.Secure.getString(resolver, SETTING); } - private synchronized void updateSettings() { - try { - mParser.setString(Settings.Secure.getString(mResolver, - Settings.Secure.BACKUP_MANAGER_CONSTANTS)); - } catch (IllegalArgumentException e) { - // Failed to parse the settings string. Use defaults. - Slog.e(TAG, "Bad backup manager constants: " + e.getMessage()); - } - - mKeyValueBackupIntervalMilliseconds = mParser.getLong( - KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS, - DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS); - mKeyValueBackupFuzzMilliseconds = mParser.getLong(KEY_VALUE_BACKUP_FUZZ_MILLISECONDS, - DEFAULT_KEY_VALUE_BACKUP_FUZZ_MILLISECONDS); - mKeyValueBackupRequireCharging = mParser.getBoolean(KEY_VALUE_BACKUP_REQUIRE_CHARGING, - DEFAULT_KEY_VALUE_BACKUP_REQUIRE_CHARGING); - mKeyValueBackupRequiredNetworkType = mParser.getInt(KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE, - DEFAULT_KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE); - mFullBackupIntervalMilliseconds = mParser.getLong(FULL_BACKUP_INTERVAL_MILLISECONDS, - DEFAULT_FULL_BACKUP_INTERVAL_MILLISECONDS); - mFullBackupRequireCharging = mParser.getBoolean(FULL_BACKUP_REQUIRE_CHARGING, - DEFAULT_FULL_BACKUP_REQUIRE_CHARGING); - mFullBackupRequiredNetworkType = mParser.getInt(FULL_BACKUP_REQUIRED_NETWORK_TYPE, - DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE); - String backupFinishedNotificationReceivers = mParser.getString( - BACKUP_FINISHED_NOTIFICATION_RECEIVERS, - DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS); + public synchronized void update(KeyValueListParser parser) { + mKeyValueBackupIntervalMilliseconds = + parser.getLong( + KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS, + DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS); + mKeyValueBackupFuzzMilliseconds = + parser.getLong( + KEY_VALUE_BACKUP_FUZZ_MILLISECONDS, + DEFAULT_KEY_VALUE_BACKUP_FUZZ_MILLISECONDS); + mKeyValueBackupRequireCharging = + parser.getBoolean( + KEY_VALUE_BACKUP_REQUIRE_CHARGING, + DEFAULT_KEY_VALUE_BACKUP_REQUIRE_CHARGING); + mKeyValueBackupRequiredNetworkType = + parser.getInt( + KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE, + DEFAULT_KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE); + mFullBackupIntervalMilliseconds = + parser.getLong( + FULL_BACKUP_INTERVAL_MILLISECONDS, + DEFAULT_FULL_BACKUP_INTERVAL_MILLISECONDS); + mFullBackupRequireCharging = + parser.getBoolean( + FULL_BACKUP_REQUIRE_CHARGING, DEFAULT_FULL_BACKUP_REQUIRE_CHARGING); + mFullBackupRequiredNetworkType = + parser.getInt( + FULL_BACKUP_REQUIRED_NETWORK_TYPE, + DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE); + String backupFinishedNotificationReceivers = + parser.getString( + BACKUP_FINISHED_NOTIFICATION_RECEIVERS, + DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS); if (backupFinishedNotificationReceivers.isEmpty()) { mBackupFinishedNotificationReceivers = new String[] {}; } else { @@ -132,40 +152,50 @@ class BackupManagerConstants extends ContentObserver { // a reference of this object. public synchronized long getKeyValueBackupIntervalMilliseconds() { if (BackupManagerService.DEBUG_SCHEDULING) { - Slog.v(TAG, "getKeyValueBackupIntervalMilliseconds(...) returns " - + mKeyValueBackupIntervalMilliseconds); + Slog.v( + TAG, + "getKeyValueBackupIntervalMilliseconds(...) returns " + + mKeyValueBackupIntervalMilliseconds); } return mKeyValueBackupIntervalMilliseconds; } public synchronized long getKeyValueBackupFuzzMilliseconds() { if (BackupManagerService.DEBUG_SCHEDULING) { - Slog.v(TAG, "getKeyValueBackupFuzzMilliseconds(...) returns " - + mKeyValueBackupFuzzMilliseconds); + Slog.v( + TAG, + "getKeyValueBackupFuzzMilliseconds(...) returns " + + mKeyValueBackupFuzzMilliseconds); } return mKeyValueBackupFuzzMilliseconds; } public synchronized boolean getKeyValueBackupRequireCharging() { if (BackupManagerService.DEBUG_SCHEDULING) { - Slog.v(TAG, "getKeyValueBackupRequireCharging(...) returns " - + mKeyValueBackupRequireCharging); + Slog.v( + TAG, + "getKeyValueBackupRequireCharging(...) returns " + + mKeyValueBackupRequireCharging); } return mKeyValueBackupRequireCharging; } public synchronized int getKeyValueBackupRequiredNetworkType() { if (BackupManagerService.DEBUG_SCHEDULING) { - Slog.v(TAG, "getKeyValueBackupRequiredNetworkType(...) returns " - + mKeyValueBackupRequiredNetworkType); + Slog.v( + TAG, + "getKeyValueBackupRequiredNetworkType(...) returns " + + mKeyValueBackupRequiredNetworkType); } return mKeyValueBackupRequiredNetworkType; } public synchronized long getFullBackupIntervalMilliseconds() { if (BackupManagerService.DEBUG_SCHEDULING) { - Slog.v(TAG, "getFullBackupIntervalMilliseconds(...) returns " - + mFullBackupIntervalMilliseconds); + Slog.v( + TAG, + "getFullBackupIntervalMilliseconds(...) returns " + + mFullBackupIntervalMilliseconds); } return mFullBackupIntervalMilliseconds; } @@ -179,19 +209,21 @@ class BackupManagerConstants extends ContentObserver { public synchronized int getFullBackupRequiredNetworkType() { if (BackupManagerService.DEBUG_SCHEDULING) { - Slog.v(TAG, "getFullBackupRequiredNetworkType(...) returns " - + mFullBackupRequiredNetworkType); + Slog.v( + TAG, + "getFullBackupRequiredNetworkType(...) returns " + + mFullBackupRequiredNetworkType); } return mFullBackupRequiredNetworkType; } - /** - * Returns an array of package names that should be notified whenever a backup finishes. - */ + /** Returns an array of package names that should be notified whenever a backup finishes. */ public synchronized String[] getBackupFinishedNotificationReceivers() { if (BackupManagerService.DEBUG_SCHEDULING) { - Slog.v(TAG, "getBackupFinishedNotificationReceivers(...) returns " - + TextUtils.join(", ", mBackupFinishedNotificationReceivers)); + Slog.v( + TAG, + "getBackupFinishedNotificationReceivers(...) returns " + + TextUtils.join(", ", mBackupFinishedNotificationReceivers)); } return mBackupFinishedNotificationReceivers; } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 646909177e72c..b6e2dca3db5cc 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -851,6 +851,10 @@ public class BackupManagerService implements BackupManagerServiceInterface { mJournal = null; // will be created on first use mConstants = new BackupManagerConstants(mBackupHandler, mContext.getContentResolver()); + // We are observing changes to the constants throughout the lifecycle of BMS. This is + // because we reference the constants in multiple areas of BMS, which otherwise would + // require frequent starting and stopping. + mConstants.start(); // Set up the various sorts of package tracking we do mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule"); diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk index aed57e3b6dccd..3d7fdbdd74360 100644 --- a/services/robotests/Android.mk +++ b/services/robotests/Android.mk @@ -62,7 +62,8 @@ LOCAL_SRC_FILES := \ $(call all-java-files-under, ../../core/java/android/app/backup) \ $(call all-Iaidl-files-under, ../../core/java/android/app/backup) \ ../../core/java/android/content/pm/PackageInfo.java \ - ../../core/java/android/app/IBackupAgent.aidl + ../../core/java/android/app/IBackupAgent.aidl \ + ../../core/java/android/util/KeyValueSettingObserver.java LOCAL_AIDL_INCLUDES := \ $(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \ diff --git a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java index 0752537abfccf..2a32c2eef6ca7 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java @@ -18,79 +18,218 @@ package com.android.server.backup; import static com.google.common.truth.Truth.assertThat; -import android.app.AlarmManager; +import android.content.ContentResolver; import android.content.Context; import android.os.Handler; import android.platform.test.annotations.Presubmit; import android.provider.Settings; - +import android.util.KeyValueSettingObserver; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; import com.android.server.testing.SystemLoaderPackages; - +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @RunWith(FrameworkRobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 26) @SystemLoaderPackages({"com.android.server.backup"}) +@SystemLoaderClasses({KeyValueSettingObserver.class}) @Presubmit public class BackupManagerConstantsTest { private static final String PACKAGE_NAME = "some.package.name"; private static final String ANOTHER_PACKAGE_NAME = "another.package.name"; + private ContentResolver mContentResolver; + private BackupManagerConstants mConstants; + @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + public void setUp() { + final Context context = RuntimeEnvironment.application.getApplicationContext(); + + mContentResolver = context.getContentResolver(); + mConstants = new BackupManagerConstants(new Handler(), mContentResolver); + mConstants.start(); + } + + @After + public void tearDown() { + mConstants.stop(); } @Test - public void testDefaultValues() throws Exception { - final Context context = RuntimeEnvironment.application.getApplicationContext(); - final Handler handler = new Handler(); + public void testGetConstants_afterConstructorWithStart_returnsDefaultValues() { + long keyValueBackupIntervalMilliseconds = + mConstants.getKeyValueBackupIntervalMilliseconds(); + long keyValueBackupFuzzMilliseconds = mConstants.getKeyValueBackupFuzzMilliseconds(); + boolean keyValueBackupRequireCharging = mConstants.getKeyValueBackupRequireCharging(); + int keyValueBackupRequiredNetworkType = mConstants.getKeyValueBackupRequiredNetworkType(); + long fullBackupIntervalMilliseconds = mConstants.getFullBackupIntervalMilliseconds(); + boolean fullBackupRequireCharging = mConstants.getFullBackupRequireCharging(); + int fullBackupRequiredNetworkType = mConstants.getFullBackupRequiredNetworkType(); + String[] backupFinishedNotificationReceivers = + mConstants.getBackupFinishedNotificationReceivers(); - Settings.Secure.putString( - context.getContentResolver(), Settings.Secure.BACKUP_MANAGER_CONSTANTS, null); + assertThat(keyValueBackupIntervalMilliseconds) + .isEqualTo(BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS); + assertThat(keyValueBackupFuzzMilliseconds) + .isEqualTo(BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_FUZZ_MILLISECONDS); + assertThat(keyValueBackupRequireCharging) + .isEqualTo(BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_REQUIRE_CHARGING); + assertThat(keyValueBackupRequiredNetworkType) + .isEqualTo(BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE); + assertThat(fullBackupIntervalMilliseconds) + .isEqualTo(BackupManagerConstants.DEFAULT_FULL_BACKUP_INTERVAL_MILLISECONDS); + assertThat(fullBackupRequireCharging) + .isEqualTo(BackupManagerConstants.DEFAULT_FULL_BACKUP_REQUIRE_CHARGING); + assertThat(fullBackupRequiredNetworkType) + .isEqualTo(BackupManagerConstants.DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE); + assertThat(backupFinishedNotificationReceivers).isEqualTo(new String[0]); + } - final BackupManagerConstants constants = - new BackupManagerConstants(handler, context.getContentResolver()); + /** + * Tests that if there is a setting change when we are not currently observing the setting, that + * once we start observing again, we receive the most up-to-date value. + */ + @Test + public void testGetConstant_withSettingChangeBeforeStart_updatesValues() { + mConstants.stop(); + long testInterval = + BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS * 2; + final String setting = + BackupManagerConstants.KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS + "=" + testInterval; + putStringAndNotify(setting); - assertThat(constants.getKeyValueBackupIntervalMilliseconds()) - .isEqualTo(4 * AlarmManager.INTERVAL_HOUR); - assertThat(constants.getKeyValueBackupFuzzMilliseconds()).isEqualTo(10 * 60 * 1000); - assertThat(constants.getKeyValueBackupRequireCharging()).isEqualTo(true); - assertThat(constants.getKeyValueBackupRequiredNetworkType()).isEqualTo(1); + mConstants.start(); - assertThat(constants.getFullBackupIntervalMilliseconds()) - .isEqualTo(24 * AlarmManager.INTERVAL_HOUR); - assertThat(constants.getFullBackupRequireCharging()).isEqualTo(true); - assertThat(constants.getFullBackupRequiredNetworkType()).isEqualTo(2); - assertThat(constants.getBackupFinishedNotificationReceivers()).isEqualTo(new String[0]); + long keyValueBackupIntervalMilliseconds = + mConstants.getKeyValueBackupIntervalMilliseconds(); + assertThat(keyValueBackupIntervalMilliseconds).isEqualTo(testInterval); } @Test - public void testParseNotificationReceivers() throws Exception { - final Context context = RuntimeEnvironment.application.getApplicationContext(); - final Handler handler = new Handler(); + public void testGetConstants_whenSettingIsNull_returnsDefaultValues() { + putStringAndNotify(null); - final String recieversSetting = - "backup_finished_notification_receivers=" + long keyValueBackupIntervalMilliseconds = + mConstants.getKeyValueBackupIntervalMilliseconds(); + long keyValueBackupFuzzMilliseconds = mConstants.getKeyValueBackupFuzzMilliseconds(); + boolean keyValueBackupRequireCharging = mConstants.getKeyValueBackupRequireCharging(); + int keyValueBackupRequiredNetworkType = mConstants.getKeyValueBackupRequiredNetworkType(); + long fullBackupIntervalMilliseconds = mConstants.getFullBackupIntervalMilliseconds(); + boolean fullBackupRequireCharging = mConstants.getFullBackupRequireCharging(); + int fullBackupRequiredNetworkType = mConstants.getFullBackupRequiredNetworkType(); + String[] backupFinishedNotificationReceivers = + mConstants.getBackupFinishedNotificationReceivers(); + + assertThat(keyValueBackupIntervalMilliseconds) + .isEqualTo(BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS); + assertThat(keyValueBackupFuzzMilliseconds) + .isEqualTo(BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_FUZZ_MILLISECONDS); + assertThat(keyValueBackupRequireCharging) + .isEqualTo(BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_REQUIRE_CHARGING); + assertThat(keyValueBackupRequiredNetworkType) + .isEqualTo(BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE); + assertThat(fullBackupIntervalMilliseconds) + .isEqualTo(BackupManagerConstants.DEFAULT_FULL_BACKUP_INTERVAL_MILLISECONDS); + assertThat(fullBackupRequireCharging) + .isEqualTo(BackupManagerConstants.DEFAULT_FULL_BACKUP_REQUIRE_CHARGING); + assertThat(fullBackupRequiredNetworkType) + .isEqualTo(BackupManagerConstants.DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE); + assertThat(backupFinishedNotificationReceivers).isEqualTo(new String[0]); + } + + /** + * Test passing in a malformed setting string. The setting expects + * "key1=value,key2=value,key3=value..." but we pass in "key1=,value" + */ + @Test + public void testGetConstant_whenSettingIsMalformed_doesNotUpdateParamsOrThrow() { + long testFuzz = BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_FUZZ_MILLISECONDS * 2; + final String invalidSettingFormat = + BackupManagerConstants.KEY_VALUE_BACKUP_FUZZ_MILLISECONDS + "=," + testFuzz; + putStringAndNotify(invalidSettingFormat); + + long keyValueBackupFuzzMilliseconds = mConstants.getKeyValueBackupFuzzMilliseconds(); + + assertThat(keyValueBackupFuzzMilliseconds) + .isEqualTo(BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_FUZZ_MILLISECONDS); + } + + /** + * Test passing in an invalid value type. {@link + * BackupManagerConstants#KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE} expects an integer, but we + * pass in a boolean. + */ + @Test + public void testGetConstant_whenSettingHasInvalidType_doesNotUpdateParamsOrThrow() { + boolean testValue = true; + final String invalidSettingType = + BackupManagerConstants.KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE + "=" + testValue; + putStringAndNotify(invalidSettingType); + + int keyValueBackupRequiredNetworkType = mConstants.getKeyValueBackupRequiredNetworkType(); + + assertThat(keyValueBackupRequiredNetworkType) + .isEqualTo(BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE); + } + + @Test + public void testGetConstants_afterSettingChange_updatesValues() { + long testKVInterval = + BackupManagerConstants.DEFAULT_KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS * 2; + long testFullInterval = + BackupManagerConstants.DEFAULT_FULL_BACKUP_INTERVAL_MILLISECONDS * 2; + final String intervalSetting = + BackupManagerConstants.KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS + + "=" + + testKVInterval + + "," + + BackupManagerConstants.FULL_BACKUP_INTERVAL_MILLISECONDS + + "=" + + testFullInterval; + putStringAndNotify(intervalSetting); + + long keyValueBackupIntervalMilliseconds = + mConstants.getKeyValueBackupIntervalMilliseconds(); + long fullBackupIntervalMilliseconds = mConstants.getFullBackupIntervalMilliseconds(); + + assertThat(keyValueBackupIntervalMilliseconds).isEqualTo(testKVInterval); + assertThat(fullBackupIntervalMilliseconds).isEqualTo(testFullInterval); + } + + @Test + public void testBackupNotificationReceivers_afterSetting_updatesAndParsesCorrectly() { + final String receiversSetting = + BackupManagerConstants.BACKUP_FINISHED_NOTIFICATION_RECEIVERS + + "=" + PACKAGE_NAME + ':' + ANOTHER_PACKAGE_NAME; - Settings.Secure.putString( - context.getContentResolver(), - Settings.Secure.BACKUP_MANAGER_CONSTANTS, - recieversSetting); + putStringAndNotify(receiversSetting); - final BackupManagerConstants constants = - new BackupManagerConstants(handler, context.getContentResolver()); - - assertThat(constants.getBackupFinishedNotificationReceivers()) + String[] backupFinishedNotificationReceivers = + mConstants.getBackupFinishedNotificationReceivers(); + assertThat(backupFinishedNotificationReceivers) .isEqualTo(new String[] {PACKAGE_NAME, ANOTHER_PACKAGE_NAME}); } + + /** + * Robolectric does not notify observers of changes to settings so we have to trigger it here. + * Currently, the mock of {@link Settings.Secure#putString(ContentResolver, String, String)} + * only stores the value. TODO: Implement properly in ShadowSettings. + */ + private void putStringAndNotify(String value) { + Settings.Secure.putString( + mContentResolver, Settings.Secure.BACKUP_MANAGER_CONSTANTS, value); + + // We pass null as the observer since notifyChange iterates over all available observers and + // we don't have access to the local observer. + mContentResolver.notifyChange( + Settings.Secure.getUriFor(Settings.Secure.BACKUP_MANAGER_CONSTANTS), + /*observer*/ null); + } }