From 7d3033b11f6ddd75c9a6e49d69bc432053ce8cba Mon Sep 17 00:00:00 2001 From: Annie Meng Date: Thu, 8 Mar 2018 20:38:43 +0000 Subject: [PATCH] DO NOT MERGE Create a key value settings observer for backup parameters Extracts an abstract class to observe changes in backup parameter settings that are stored as a comma-separated key value list. This class is responsible for registering and unregistering a content observer on the setting and updating local references to the parameters. Refactor BackupManagerConstants and LocalTransportParameters to use this implementation. This will also be used for the new backup timeout setting. Bug: 74346317 Test: 1) m -j RunFrameworksServicesRoboTests ROBOTEST_FILTER=BackupManagerConstantsTest 2) gts-tradefed run commandAndExit gts-dev -m GtsBackupHostTestCases -t com.google.android.gts.backup.TransportFlagsHostSideTest Change-Id: Id4c50fbcf7479c925515887e3fa70e166dd9955c --- .../android/util/KeyValueSettingObserver.java | 97 ++++++++ .../backup/LocalTransportParameters.java | 44 +--- .../server/backup/BackupManagerConstants.java | 188 +++++++++------- .../server/backup/BackupManagerService.java | 4 + services/robotests/Android.mk | 3 +- .../backup/BackupManagerConstantsTest.java | 211 +++++++++++++++--- 6 files changed, 395 insertions(+), 152 deletions(-) create mode 100644 core/java/android/util/KeyValueSettingObserver.java 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); + } }