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.
      */