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);
+ }
+}