Add CTS test for notification of successful backups

The backup manager can be configured to notify one or more apps whenever
a backup succeeds. This CL adds a CTS test for that functionality, for
both full backups and key/value backups

Bug: 63885845
Test: cts-tradefed run cts-dev --module CtsBackupHostTestCase
Change-Id: Iae175392c445d2dc386b87731e2b67f8274cbb15
diff --git a/hostsidetests/backup/SuccessNotificationApp/Android.mk b/hostsidetests/backup/SuccessNotificationApp/Android.mk
new file mode 100644
index 0000000..501cf14
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/Android.mk
@@ -0,0 +1,39 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsBackupSuccessNotificationApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml b/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml
new file mode 100644
index 0000000..b7f8c2c
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.backup.successnotificationapp">
+
+    <application>
+        <receiver android:name=".SuccessNotificationReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.BACKUP_FINISHED" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.successnotificationapp" />
+</manifest>
diff --git a/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationReceiver.java b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationReceiver.java
new file mode 100644
index 0000000..2122151
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationReceiver.java
@@ -0,0 +1,39 @@
+/*
+ * 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.cts.backup.successnotificationapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class SuccessNotificationReceiver extends BroadcastReceiver {
+    private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
+    private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (!BACKUP_FINISHED_ACTION.equals(intent.getAction())) {
+            return;
+        }
+        final String packageName = intent.getStringExtra(BACKUP_FINISHED_PACKAGE_EXTRA);
+        if (packageName == null || packageName.isEmpty()) {
+            return;
+        }
+        context.getSharedPreferences(SuccessNotificationTest.PREFS_FILE, Context.MODE_PRIVATE)
+                .edit().putBoolean(packageName, true).commit();
+    }
+}
diff --git a/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java
new file mode 100644
index 0000000..3fb8ef2
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.cts.backup.successnotificationapp;
+
+import static android.support.test.InstrumentationRegistry.getTargetContext;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SuccessNotificationTest {
+    protected static final String PREFS_FILE = "android.cts.backup.successnotificationapp.PREFS";
+    private static final String KEY_VALUE_RESTORE_APP_PACKAGE =
+            "android.cts.backup.keyvaluerestoreapp";
+    private static final String FULLBACKUPONLY_APP_PACKAGE = "android.cts.backup.fullbackuponlyapp";
+
+    @Test
+    public void clearBackupSuccessNotificationsReceived() {
+        getTargetContext().getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE).edit().clear()
+                .commit();
+    }
+
+    @Test
+    public void verifyBackupSuccessNotificationReceivedForKeyValueApp() {
+        assertTrue(getTargetContext().getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
+                .getBoolean(KEY_VALUE_RESTORE_APP_PACKAGE, false));
+    }
+
+    @Test
+    public void verifyBackupSuccessNotificationReceivedForFullBackupApp() {
+        assertTrue(getTargetContext().getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
+                .getBoolean(FULLBACKUPONLY_APP_PACKAGE, false));
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java
new file mode 100644
index 0000000..0e2abb8
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java
@@ -0,0 +1,233 @@
+/*
+ * 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.cts.backup;
+
+import static org.junit.Assert.assertNull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for checking that an observer app is notified by a broadcast Intent whenever a backup
+ * succeeds.
+ *
+ * NB: The tests use "bmgr backupnow" for backup, which works on N+ devices.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SuccessNotificationHostSideTest extends BaseBackupHostSideTest {
+
+    /** The name of the package that a key/value backup will be run for */
+    private static final String KEY_VALUE_BACKUP_APP_PACKAGE =
+            "android.cts.backup.keyvaluerestoreapp";
+
+    /** The name of the package that a full backup will be run for */
+    private static final String FULL_BACKUP_APP_PACKAGE = "android.cts.backup.fullbackuponlyapp";
+
+    /** The name of the package that observes backup results. */
+    private static final String SUCCESS_NOTIFICATION_APP_PACKAGE =
+            "android.cts.backup.successnotificationapp";
+
+    /** The name of the device side test class in the APK that a key/value backup will be run for */
+    private static final String KEY_VALUE_BACKUP_DEVICE_TEST_NAME =
+            KEY_VALUE_BACKUP_APP_PACKAGE + ".KeyValueBackupRestoreTest";
+
+    /** The name of the device side test class in the APK that a full backup will be run for */
+    private static final String FULL_BACKUP_DEVICE_TEST_CLASS_NAME =
+            FULL_BACKUP_APP_PACKAGE + ".FullBackupOnlyTest";
+
+    /** The name of the device side test class in the APK that observes backup results */
+    private static final String SUCCESS_NOTIFICATION_DEVICE_TEST_NAME =
+            SUCCESS_NOTIFICATION_APP_PACKAGE + ".SuccessNotificationTest";
+
+    /** The name of the APK that a key/value backup will be run for */
+    private static final String KEY_VALUE_BACKUP_APP_APK = "CtsKeyValueBackupRestoreApp.apk";
+
+    /** The name of the APK that a full backup will be run for */
+    private static final String FULL_BACKUP_APP_APK = "FullBackupOnlyFalseWithAgentApp.apk";
+
+    /** The name of the APK that observes backup results */
+    private static final String SUCCESS_NOTIFICATION_APP_APK =
+            "CtsBackupSuccessNotificationApp.apk";
+
+    /** Secure setting that holds the backup manager configuration as key-value pairs */
+    private static final String BACKUP_MANAGER_CONSTANTS_PREF = "backup_manager_constants";
+
+    /** Key for specifying the apps that the backup manager should notify of successful backups */
+    private static final String BACKUP_FINISHED_NOTIFICATION_RECEIVERS =
+            "backup_finished_notification_receivers";
+
+    /** The original backup manager configuration */
+    private String mOriginalBackupManagerConstants = null;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        if (!mIsBackupSupported) {
+            CLog.i("android.software.backup feature is not supported on this device");
+            return;
+        }
+
+        installPackage(KEY_VALUE_BACKUP_APP_APK);
+        installPackage(FULL_BACKUP_APP_APK);
+
+        installPackage(SUCCESS_NOTIFICATION_APP_APK);
+        checkDeviceTest("clearBackupSuccessNotificationsReceived");
+        addBackupFinishedNotificationReceiver();
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        if (!mIsBackupSupported) {
+            return;
+        }
+
+        restoreBackupFinishedNotificationReceivers();
+        assertNull(uninstallPackage(SUCCESS_NOTIFICATION_APP_PACKAGE));
+
+        // Clear backup data and uninstall the package (in that order!)
+        clearBackupDataInLocalTransport(KEY_VALUE_BACKUP_APP_PACKAGE);
+        assertNull(uninstallPackage(KEY_VALUE_BACKUP_APP_PACKAGE));
+
+        clearBackupDataInLocalTransport(FULL_BACKUP_APP_PACKAGE);
+        assertNull(uninstallPackage(FULL_BACKUP_APP_PACKAGE));
+    }
+
+    /**
+     * Test that the observer app is notified when a key/value backup succeeds.
+     *
+     * Test logic:
+     *   1. Change a test app's data, trigger a key/value backup and wait for it to complete.
+     *   2. Verify that the observer app was informed about the backup.
+     */
+    @Test
+    public void testSuccessNotificationForKeyValueBackup() throws Exception {
+        if (!mIsBackupSupported) {
+            return;
+        }
+
+        checkDeviceTest(KEY_VALUE_BACKUP_APP_PACKAGE, KEY_VALUE_BACKUP_DEVICE_TEST_NAME,
+                "saveSharedPreferencesAndNotifyBackupManager");
+        backupNowAndAssertSuccess(KEY_VALUE_BACKUP_APP_PACKAGE);
+
+        checkDeviceTest("verifyBackupSuccessNotificationReceivedForKeyValueApp");
+    }
+
+    /**
+     * Test that the observer app is notified when a full backup succeeds.
+     *
+     * Test logic:
+     *   1. Change a test app's data, trigger a full backup and wait for it to complete.
+     *   2. Verify that the observer app was informed about the backup.
+     */
+    @Test
+    public void testSuccessNotificationForFullBackup() throws Exception {
+        if (!mIsBackupSupported) {
+            return;
+        }
+
+        checkDeviceTest(FULL_BACKUP_APP_PACKAGE, FULL_BACKUP_DEVICE_TEST_CLASS_NAME, "createFiles");
+        backupNowAndAssertSuccess(FULL_BACKUP_APP_PACKAGE);
+
+        checkDeviceTest("verifyBackupSuccessNotificationReceivedForFullBackupApp");
+    }
+
+    /**
+     * Instructs the backup manager to notify the observer app whenever a backup succeeds. The old
+     * backup manager configuration is stored in a member variable and can be restored by calling
+     * {@link restoreBackupFinishedNotificationReceivers}.
+     */
+    private void addBackupFinishedNotificationReceiver()
+            throws DeviceNotAvailableException {
+        mOriginalBackupManagerConstants = getDevice().executeShellCommand(String.format(
+                "settings get secure %s", BACKUP_MANAGER_CONSTANTS_PREF));
+        String backupManagerConstants = null;
+
+        if (mOriginalBackupManagerConstants == null ||
+                mOriginalBackupManagerConstants.trim().isEmpty()) {
+            backupManagerConstants =
+                    BACKUP_FINISHED_NOTIFICATION_RECEIVERS + "=" + SUCCESS_NOTIFICATION_APP_PACKAGE;
+        } else {
+            final List<String> keyValuePairs =
+                    new ArrayList<>(Arrays.asList(mOriginalBackupManagerConstants.split(",")));
+            boolean present = false;
+            for (int i = 0; i < keyValuePairs.size(); ++i) {
+                final String keyValuePair = keyValuePairs.get(i);
+                final String[] fields = keyValuePair.split("=");
+                final String key = fields[0].trim();
+                if (BACKUP_FINISHED_NOTIFICATION_RECEIVERS.equals(key)) {
+                    if (fields.length == 1 || fields[1].trim().isEmpty()) {
+                        keyValuePairs.set(i, key + "=" + SUCCESS_NOTIFICATION_APP_PACKAGE);
+                    } else {
+                        final String[] values = fields[1].split(":");
+                        for (int j = 0; j < values.length; ++j) {
+                            if (SUCCESS_NOTIFICATION_APP_PACKAGE.equals(values[j].trim())) {
+                                present = true;
+                                break;
+                            }
+                        }
+                        if (!present) {
+                            keyValuePairs.set(i,
+                                    keyValuePair + ":" + SUCCESS_NOTIFICATION_APP_PACKAGE);
+                        }
+                    }
+                    present = true;
+                    break;
+                }
+            }
+            if (!present) {
+                keyValuePairs.add(BACKUP_FINISHED_NOTIFICATION_RECEIVERS + "=" +
+                        SUCCESS_NOTIFICATION_APP_PACKAGE);
+            }
+            backupManagerConstants = String.join(",", keyValuePairs);
+        }
+        setBackupManagerConstants(backupManagerConstants);
+    }
+
+    /**
+     * Restores the backup manager configuration stored by a previous call to
+     * {@link addBackupFinishedNotificationReceiver}.
+     */
+    private void restoreBackupFinishedNotificationReceivers() throws DeviceNotAvailableException {
+        setBackupManagerConstants(mOriginalBackupManagerConstants);
+    }
+
+    private void setBackupManagerConstants(String backupManagerConstants)
+            throws DeviceNotAvailableException {
+        getDevice().executeShellCommand(String.format("settings put secure %s %s",
+                BACKUP_MANAGER_CONSTANTS_PREF, backupManagerConstants));
+    }
+
+    private void checkDeviceTest(String methodName) throws DeviceNotAvailableException {
+        checkDeviceTest(SUCCESS_NOTIFICATION_APP_PACKAGE, SUCCESS_NOTIFICATION_DEVICE_TEST_NAME,
+                methodName);
+    }
+}