Verify SharedPreference correctness across data restore
Following a restore we now instruct any live SharedPreferences
instances to reload from disk. This CTS test verifies that a
live instance has its content view updated from its (now stale)
in-memory cache state once a restore has taken place.
This test fails without the corresponding frameworks/base CL,
and passes with it.
Bug 12061817
Test: cts-tradefed run cts -m CtsBackupHostTestCases
Change-Id: Ie6e46e49220eb79128ab55a4c82b5c6ef3e1de04
diff --git a/hostsidetests/backup/app/AndroidManifest.xml b/hostsidetests/backup/app/AndroidManifest.xml
index 0d3aee8..8730385 100644
--- a/hostsidetests/backup/app/AndroidManifest.xml
+++ b/hostsidetests/backup/app/AndroidManifest.xml
@@ -19,7 +19,9 @@
package="android.backup.cts.backuprestoreapp">
<application
- android:backupAgent="CtsBackupRestoreBackupAgent">
+ android:backupAgent="CtsBackupRestoreBackupAgent"
+ android:killAfterRestore="false" >
+
<uses-library android:name="android.test.runner" />
<activity
@@ -31,6 +33,15 @@
</intent-filter>
</activity>
+ <activity android:name=".SharedPrefsRestoreTestActivity"
+ android:launchMode="singleInstance">
+ <intent-filter>
+ <action android:name="android.backup.cts.backuprestore.INIT" />
+ <action android:name="android.backup.cts.backuprestore.UPDATE" />
+ <action android:name="android.backup.cts.backuprestore.TEST" />
+ </intent-filter>
+ </activity>
+
</application>
<instrumentation
diff --git a/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/Constants.java b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/Constants.java
new file mode 100644
index 0000000..08f5854
--- /dev/null
+++ b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/Constants.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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.backup.cts.backuprestoreapp;
+
+final class Constants {
+ // Names of raw data files used by the tests
+ static final String TEST_FILE_1 = "test-file-1";
+ static final String TEST_FILE_2 = "test-file-2";
+
+ static final String TEST_PREFS_1 = "test-prefs-1";
+ static final String INT_PREF = "int-pref";
+ static final String BOOL_PREF = "bool-pref";
+
+ static final String TEST_PREFS_2 = "test-prefs-2";
+ static final String FLOAT_PREF = "float-pref";
+ static final String LONG_PREF = "long-pref";
+ static final String STRING_PREF = "string-pref";
+
+ static final int DEFAULT_INT_VALUE = 0;
+ static final boolean DEFAULT_BOOL_VALUE = false;
+
+ static final float DEFAULT_FLOAT_VALUE = 0.0f;
+ static final long DEFAULT_LONG_VALUE = 0L;
+ static final String DEFAULT_STRING_VALUE = null;
+
+ // Shared prefs test activity actions
+ static final String INIT_ACTION = "android.backup.cts.backuprestore.INIT";
+ static final String UPDATE_ACTION = "android.backup.cts.backuprestore.UPDATE";
+ static final String TEST_ACTION = "android.backup.cts.backuprestore.TEST";
+}
diff --git a/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/CtsBackupRestoreBackupAgent.java b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/CtsBackupRestoreBackupAgent.java
index e692fdd..ebf9c24 100644
--- a/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/CtsBackupRestoreBackupAgent.java
+++ b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/CtsBackupRestoreBackupAgent.java
@@ -19,6 +19,7 @@
import android.app.backup.BackupAgentHelper;
public class CtsBackupRestoreBackupAgent extends BackupAgentHelper {
+
private static final String KEY_BACKUP_TEST_PREFS_PREFIX = "backup-test-prefs";
private static final String KEY_BACKUP_TEST_FILES_PREFIX = "backup-test-files";
diff --git a/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/KeyValueBackupRandomDataActivity.java b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/KeyValueBackupRandomDataActivity.java
index 92db3f3..9a874db 100644
--- a/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/KeyValueBackupRandomDataActivity.java
+++ b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/KeyValueBackupRandomDataActivity.java
@@ -32,6 +32,8 @@
import java.util.Random;
import java.util.Scanner;
+import static android.backup.cts.backuprestoreapp.Constants.*;
+
/**
* Test activity that reads/writes to shared preferences and files.
*
@@ -49,25 +51,7 @@
* Migrated from BackupTestActivity in former BackupTest CTS Verfifier test.
*/
public class KeyValueBackupRandomDataActivity extends Activity {
- private static final String TAG = KeyValueBackupRandomDataActivity.class.getSimpleName();
-
- private static final String TEST_PREFS_1 = "test-prefs-1";
- private static final String INT_PREF = "int-pref";
- private static final String BOOL_PREF = "bool-pref";
-
- private static final String TEST_PREFS_2 = "test-prefs-2";
- private static final String FLOAT_PREF = "float-pref";
- private static final String LONG_PREF = "long-pref";
- private static final String STRING_PREF = "string-pref";
-
- private static final String TEST_FILE_1 = "test-file-1";
- private static final String TEST_FILE_2 = "test-file-2";
-
- private static final int DEFAULT_INT_VALUE = 0;
- private static final boolean DEFAULT_BOOL_VALUE = false;
- private static final float DEFAULT_FLOAT_VALUE = 0.0f;
- private static final long DEFAULT_LONG_VALUE = 0L;
- private static final String DEFAULT_STRING_VALUE = null;
+ static final String TAG = KeyValueBackupRandomDataActivity.class.getSimpleName();
private static final String VALUES_LOADED_MESSAGE = "ValuesLoaded";
private static final String EMPTY_STRING_LOG = "empty";
@@ -76,7 +60,7 @@
private boolean mValuesWereGenerated;
@Override
- public void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new LoadBackupItemsTask().execute();
diff --git a/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/SharedPrefsRestoreTestActivity.java b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/SharedPrefsRestoreTestActivity.java
new file mode 100644
index 0000000..ed06f33
--- /dev/null
+++ b/hostsidetests/backup/app/src/android/backup/cts/backuprestoreapp/SharedPrefsRestoreTestActivity.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 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.backup.cts.backuprestoreapp;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+
+import static android.backup.cts.backuprestoreapp.Constants.*;
+
+/**
+ * Test activity that verifies SharedPreference restore behavior.
+ *
+ * Test logic:
+ * 1. This activity is launched; it creates a new SharedPreferences instance and writes
+ * a known value to the INT_PREF element's via that instance. The instance is
+ * kept live.
+ * 2. The app is backed up, storing this known value in the backup dataset.
+ * 3. Next, the activity is instructed to write a different value to the INT_PREF
+ * shared preferences element. At this point, the app's current on-disk state
+ * and the live shared preferences instance are in agreement, holding a value
+ * different from that in the backup.
+ * 4. The runner triggers a restore for this app. This will rewrite the shared prefs
+ * file itself with the backed-up content (i.e. different from what was just
+ * committed from this activity).
+ * 5. Finally, the runner instructs the activity to compare the value of its existing
+ * shared prefs instance's INT_PREF element with what was previously written.
+ * The test passes if these differ, i.e. if the live shared prefs instance picked
+ * up the newly-restored data.
+ */
+public class SharedPrefsRestoreTestActivity extends Activity {
+ static final String TAG = "SharedPrefsTest";
+
+ SharedPreferences mPrefs;
+ int mLastValue;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mPrefs = getSharedPreferences(TEST_PREFS_1, MODE_PRIVATE);
+ mLastValue = 0;
+
+ processLaunchCommand(getIntent());
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ processLaunchCommand(intent);
+ }
+
+ private void processLaunchCommand(Intent intent) {
+ final String action = intent.getAction();
+ Log.i(TAG, "processLaunchCommand: " + action);
+ if (INIT_ACTION.equals(action)) {
+ // We'll issue a backup after setting this value in shared prefs
+ setPrefValue(55);
+ } else if (UPDATE_ACTION.equals(action)) {
+ // We'll issue a *restore* after setting this value, which will send a broadcast
+ // to our receiver to read from the live instance and ensure that the value is
+ // different from what was just written.
+ setPrefValue(999);
+ } else if (TEST_ACTION.equals(action)) {
+ final int currentValue = mPrefs.getInt(INT_PREF, mLastValue);
+ Log.i(TAG, "Shared prefs changed: " + (currentValue != mLastValue));
+ }
+ }
+
+ // Write a known value prior to backup
+ private void setPrefValue(int value) {
+ mLastValue = value;
+ mPrefs.edit().putInt(INT_PREF, value).commit();
+ }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java
index 7c2f25f..b816f02 100644
--- a/hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java
@@ -50,16 +50,27 @@
private static final String PACKAGE_UNDER_TEST = "android.backup.cts.backuprestoreapp";
/** The class name of the main activity in the APK */
- private static final String CLASS_UNDER_TEST = "KeyValueBackupRandomDataActivity";
+ private static final String RANDOM_DATA_ACTIVITY = "KeyValueBackupRandomDataActivity";
- /** The command to launch the main activity */
- private static final String START_ACTIVITY_UNDER_TEST_COMMAND = String.format(
+ /** Class name of the shared preferences test activity */
+ private static final String SHARED_PREFS_ACTIVITY = "SharedPrefsRestoreTestActivity";
+
+ /** The command to launch the random-data backup test activity */
+ private static final String CMD_START_RANDOM_DATA_ACTIVITY = String.format(
"am start -W -a android.intent.action.MAIN -n %s/%s.%s", PACKAGE_UNDER_TEST,
PACKAGE_UNDER_TEST,
- CLASS_UNDER_TEST);
+ RANDOM_DATA_ACTIVITY);
+
+ /** Shell commands to launch the shared prefs restore test activity */
+ private static final String CMD_START_SHARED_PREFS_ACTIVITY = String.format(
+ "am start -W -n %s/%s.%s", PACKAGE_UNDER_TEST, PACKAGE_UNDER_TEST,
+ SHARED_PREFS_ACTIVITY);
+
+ /** Shared prefs restore test logging tag */
+ private static final String SHARED_PREFS_TAG = "SharedPrefsTest";
/** The command to clear the user data of the package */
- private static final String CLEAR_DATA_IN_PACKAGE_UNDER_TEST_COMMAND = String.format(
+ private static final String CMD_CLEAR_DATA_IN_PACKAGE = String.format(
"pm clear %s", PACKAGE_UNDER_TEST);
/**
@@ -98,6 +109,13 @@
private static final String DEFAULT_STRING_STRING = "null";
private static final String DEFAULT_FILE_STRING = "empty";
+ /*
+ * Shared prefs test activity actions
+ */
+ static final String INIT_ACTION = "android.backup.cts.backuprestore.INIT";
+ static final String UPDATE_ACTION = "android.backup.cts.backuprestore.UPDATE";
+ static final String TEST_ACTION = "android.backup.cts.backuprestore.TEST";
+
private boolean mIsBackupSupported;
private boolean mWasBackupEnabled;
private String mOldTransport;
@@ -111,11 +129,11 @@
@Test
public void testKeyValueBackupAndRestore() throws Exception {
// Clear app data if any
- mDevice.executeShellCommand(CLEAR_DATA_IN_PACKAGE_UNDER_TEST_COMMAND);
+ mDevice.executeShellCommand(CMD_CLEAR_DATA_IN_PACKAGE);
// Clear logcat
mDevice.executeAdbCommand("logcat", "-c");
// Start the main activity of the app
- mDevice.executeShellCommand(START_ACTIVITY_UNDER_TEST_COMMAND);
+ mDevice.executeShellCommand(CMD_START_RANDOM_DATA_ACTIVITY);
// The app will generate some random values onCreate. Save them to mSavedValues
saveDataValuesReportedByApp();
@@ -136,7 +154,7 @@
mDevice.executeAdbCommand("logcat", "-c");
// Start the reinstalled app
- mDevice.executeShellCommand(START_ACTIVITY_UNDER_TEST_COMMAND);
+ mDevice.executeShellCommand(CMD_START_RANDOM_DATA_ACTIVITY);
// If the app data was restored successfully, the app should not generate new values and
// the values reported by the app should match values saved in mSavedValues
@@ -206,13 +224,13 @@
// repeatedly until we read VALUES_LOADED_MESSAGE, which is the last message the app logs.
search:
while (timeout >= System.currentTimeMillis()) {
- String logs = getLogcatForClass(CLASS_UNDER_TEST);
+ String logs = getLogcatForClass(RANDOM_DATA_ACTIVITY);
Scanner in = new Scanner(logs);
while (in.hasNextLine()) {
String line = in.nextLine();
// Filter by TAG.
- if (line.startsWith("I/" + CLASS_UNDER_TEST)) {
+ if (line.startsWith("I/" + RANDOM_DATA_ACTIVITY)) {
// Get rid of the TAG.
String message = line.split(":", 2)[1].trim();
@@ -239,6 +257,77 @@
return result;
}
+ @Test
+ public void testSharedPreferencesRestore() throws Exception {
+ // Clear app data if any
+ mDevice.executeShellCommand(CMD_CLEAR_DATA_IN_PACKAGE);
+ // Clear logcat
+ mDevice.executeAdbCommand("logcat", "-c");
+
+ // Start the main test activity and generate some data in shared prefs.
+ mDevice.executeShellCommand(
+ CMD_START_SHARED_PREFS_ACTIVITY + " -a " + INIT_ACTION);
+ waitForLogcat(SHARED_PREFS_TAG, "processLaunchCommand: " + INIT_ACTION);
+
+ // Back up that shared prefs state
+ backupNow(PACKAGE_UNDER_TEST);
+
+ // Update the shared-prefs contents via the activity, post-backup
+ mDevice.executeAdbCommand("logcat", "-c");
+ mDevice.executeShellCommand(
+ CMD_START_SHARED_PREFS_ACTIVITY + " -a " + UPDATE_ACTION);
+ waitForLogcat(SHARED_PREFS_TAG, "processLaunchCommand: " + UPDATE_ACTION);
+
+ // Issue a restore operation for the package, which will rewrite shared prefs
+ // out from under the activity's live SharedPreferences instance
+ restore(PACKAGE_UNDER_TEST);
+
+ // Tell the activity to report its shared prefs state, and evaluate.
+ mDevice.executeAdbCommand("logcat", "-c");
+ mDevice.executeShellCommand(
+ CMD_START_SHARED_PREFS_ACTIVITY + " -a " + TEST_ACTION);
+ final String result = waitForLogcat(SHARED_PREFS_TAG, "Shared prefs changed:");
+ assertTrue("Shared prefs instance not reinitialized from disk", result.contains("true"));
+ }
+
+ /**
+ * Watch logcat until we see a string from Log.i() under the given tag that contains
+ * the stated string, and return the line.
+ */
+ private String waitForLogcat(String tag, String contents)
+ throws InterruptedException, DeviceNotAvailableException {
+ final long timeout = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30);
+
+ // Read the shared prefs restore result from the activity
+ while (timeout >= System.currentTimeMillis()) {
+ String logs = getLogcatForClass(tag);
+
+ Scanner in = new Scanner(logs);
+ try {
+ while (in.hasNextLine()) {
+ String line = in.nextLine();
+ // Filter by TAG.
+ if (line.startsWith("I/" + tag)) {
+ // Get rid of the TAG.
+ line = line.split(":", 2)[1].trim();
+ if (line.contains(contents)) {
+ return line;
+ }
+ }
+ }
+ } finally {
+ in.close();
+ }
+
+ // In case the key has not been found, wait for the log to update before
+ // performing the next search.
+ Thread.sleep(SMALL_LOGCAT_DELAY_MS);
+ }
+ assertTrue("Timeout while waiting for logged string: I/" + tag + " : " + contents,
+ timeout > System.currentTimeMillis());
+ return null;
+ }
+
/**
* Returns the logcat string with the tag {@param className} and clears everything else.
*/