Merge "DO NOT MERGE ANYWHERE Add test for files in no_backup folder." into nougat-mr1-cts-dev
diff --git a/hostsidetests/backup/AndroidTest.xml b/hostsidetests/backup/AndroidTest.xml
index 0a39104..2ea4663 100644
--- a/hostsidetests/backup/AndroidTest.xml
+++ b/hostsidetests/backup/AndroidTest.xml
@@ -17,6 +17,7 @@
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsBackupRestoreDeviceApp.apk" />
+        <option name="test-file-name" value="CtsFullbackupApp.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsBackupHostTestCases.jar" />
diff --git a/hostsidetests/backup/fullbackupapp/Android.mk b/hostsidetests/backup/fullbackupapp/Android.mk
new file mode 100644
index 0000000..46af984
--- /dev/null
+++ b/hostsidetests/backup/fullbackupapp/Android.mk
@@ -0,0 +1,39 @@
+# 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.
+
+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 := CtsFullbackupApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/backup/fullbackupapp/AndroidManifest.xml b/hostsidetests/backup/fullbackupapp/AndroidManifest.xml
new file mode 100644
index 0000000..58f9306
--- /dev/null
+++ b/hostsidetests/backup/fullbackupapp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.backup.fullbackupapp">
+
+    <application android:label="FullbackupApp" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.fullbackupapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java b/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java
new file mode 100644
index 0000000..7c3a6f6
--- /dev/null
+++ b/hostsidetests/backup/fullbackupapp/src/android/cts/backup/fullbackupapp/FullbackupTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.cts.backup.fullbackupapp;
+
+import static android.support.test.InstrumentationRegistry.getTargetContext;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * Device side routines to be invoked by the host side NoBackupFolderHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class FullbackupTest {
+    public static final String TAG = "FullbackupCTSApp";
+    private static final int FILE_SIZE_BYTES = 1024 * 1024;
+
+    private Context mContext;
+
+    private File mNoBackupFile;
+    private File mNoBackupFile2;
+    private File mDoBackupFile;
+    private File mDoBackupFile2;
+
+    @Before
+    public void setUp() {
+        mContext = getTargetContext();
+        setupFiles();
+    }
+
+    private void setupFiles() {
+        // Files in the 'no_backup' directory. We expect these to not be backed up.
+        File noBackupDir = mContext.getNoBackupFilesDir();
+        File folderInNoBackup = new File(noBackupDir, "folder_not_to_backup");
+
+        mNoBackupFile = new File(noBackupDir, "file_not_backed_up");
+        mNoBackupFile2 = new File(folderInNoBackup, "file_not_backed_up2");
+
+        // Files in the normal files directory. These should be backed up and restored.
+        File filesDir = mContext.getFilesDir();
+        File normalFolder = new File(filesDir, "normal_folder");
+
+        mDoBackupFile = new File(filesDir, "file_to_backup");
+        mDoBackupFile2 = new File(normalFolder, "file_to_backup2");
+    }
+
+    @Test
+    public void createFiles() throws Exception {
+        // Make sure the data does not exist from before
+        deleteAllFiles();
+        checkNoFilesExist();
+
+        // Create test data
+        generateFiles();
+        checkAllFilesExist();
+
+        Log.d(TAG, "Test files created:");
+        Log.d(TAG, mNoBackupFile.getAbsolutePath());
+        Log.d(TAG, mNoBackupFile2.getAbsolutePath());
+        Log.d(TAG, mDoBackupFile.getAbsolutePath());
+        Log.d(TAG, mDoBackupFile2.getAbsolutePath());
+    }
+
+    @Test
+    public void deleteFilesAfterBackup() throws Exception {
+        // Make sure the test data exists first
+        checkAllFilesExist();
+
+        // Delete test data
+        deleteAllFiles();
+
+        // No there should be no files left
+        checkNoFilesExist();
+    }
+
+    @Test
+    public void checkRestoredFiles() throws Exception {
+        // After a restore, only files outside the 'no_backup' folder should exist
+        checkNoBackupFilesDoNotExist();
+        checkDoBackupFilesDoExist();
+    }
+
+    private void generateFiles() {
+        try {
+            // Add data to all the files we created
+            addData(mNoBackupFile);
+            addData(mNoBackupFile2);
+            addData(mDoBackupFile);
+            addData(mDoBackupFile2);
+            Log.d(TAG, "Files generated!");
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to generate files", e);
+        }
+    }
+
+    private void deleteAllFiles() {
+        mNoBackupFile.delete();
+        mNoBackupFile2.delete();
+        mDoBackupFile.delete();
+        mDoBackupFile2.delete();
+        Log.d(TAG, "Files deleted!");
+    }
+
+    private void addData(File file) throws IOException {
+        file.getParentFile().mkdirs();
+        byte[] bytes = new byte[FILE_SIZE_BYTES];
+        new Random().nextBytes(bytes);
+
+        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
+            bos.write(bytes, 0, bytes.length);
+        }
+    }
+
+    private void checkAllFilesExist() {
+        assertTrue("File in 'no_backup' did not exist!", mNoBackupFile.exists());
+        assertTrue("File in folder inside 'no_backup' did not exist!", mNoBackupFile2.exists());
+        assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
+        assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
+    }
+
+    private void checkNoFilesExist() {
+        assertFalse("File in 'no_backup' did exist!", mNoBackupFile.exists());
+        assertFalse("File in folder inside 'no_backup' did exist!", mNoBackupFile2.exists());
+        assertFalse("File in 'files' did exist!", mDoBackupFile.exists());
+        assertFalse("File in folder inside 'files' did exist!", mDoBackupFile2.exists());
+    }
+
+    private void checkNoBackupFilesDoNotExist() {
+        assertFalse("File in 'no_backup' did exist!", mNoBackupFile.exists());
+        assertFalse("File in folder inside 'no_backup' did exist!", mNoBackupFile2.exists());
+    }
+
+    private void checkDoBackupFilesDoExist() {
+        assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
+        assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
+    }
+}
diff --git a/hostsidetests/backup/src/android/backup/cts/BackupRestoreHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java
similarity index 64%
rename from hostsidetests/backup/src/android/backup/cts/BackupRestoreHostSideTest.java
rename to hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java
index 2c239cd..f156e68 100644
--- a/hostsidetests/backup/src/android/backup/cts/BackupRestoreHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/BackupRestoreHostSideTest.java
@@ -14,37 +14,20 @@
  * limitations under the License
  */
 
-package android.backup.cts.backup;
+package android.cts.backup;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
-import static org.junit.Assume.assumeTrue;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.cts.migration.MigrationHelper;
-import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.runner.RunWith;
-import org.junit.Test;
 
 import java.io.FileNotFoundException;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.Scanner;
 import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Test for checking that key/value backup and restore works correctly.
@@ -54,14 +37,7 @@
  *
  * NB: The tests uses "bmgr backupnow" for backup, which works on N+ devices.
  */
-public class BackupRestoreHostSideTest extends DeviceTestCase implements IBuildReceiver {
-
-    /** Value of PackageManager.FEATURE_BACKUP */
-    private static final String FEATURE_BACKUP = "android.software.backup";
-
-    private static final String LOCAL_TRANSPORT =
-            "android/com.android.internal.backup.LocalTransport";
-
+public class BackupRestoreHostSideTest extends BaseBackupHostSideTest {
     /** The name of the APK of the app under test */
     private static final String TEST_APP_APK = "CtsBackupRestoreDeviceApp.apk";
 
@@ -120,9 +96,6 @@
     private boolean mIsBackupSupported;
     private boolean mWasBackupEnabled;
     private String mOldTransport;
-    private ITestDevice mDevice;
-    private HashSet<String> mAvailableFeatures;
-    private IBuildInfo mCtsBuildInfo;
 
     /**
      * Map of the shared preferences/files values reported by the app.
@@ -130,31 +103,6 @@
      */
     private Map<String, String> mSavedValues;
 
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuildInfo = buildInfo;
-    }
-
-    @Override
-    public void setUp() throws DeviceNotAvailableException, Exception {
-        mDevice = getDevice();
-        mIsBackupSupported = hasDeviceFeature(FEATURE_BACKUP);
-        assumeTrue(mIsBackupSupported);
-        // Enable backup and select local backup transport
-        assertTrue("LocalTransport should be available.", hasBackupTransport(LOCAL_TRANSPORT));
-        mWasBackupEnabled = enableBackup(true);
-        mOldTransport = setBackupTransport(LOCAL_TRANSPORT);
-        assertNotNull(mCtsBuildInfo);
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        if (mIsBackupSupported) {
-            setBackupTransport(mOldTransport);
-            enableBackup(mWasBackupEnabled);
-        }
-    }
-
     public void testKeyValueBackupAndRestore() throws Exception {
         // Clear app data if any
         mDevice.executeShellCommand(CLEAR_DATA_IN_PACKAGE_UNDER_TEST_COMMAND);
@@ -171,15 +119,13 @@
 
         // Run backup
         // TODO: make this compatible with N-, potentially by replacing 'backupnow' with 'run'.
-        String backupnowOutput = mDevice.executeShellCommand(
-                "bmgr backupnow " + PACKAGE_UNDER_TEST);
+        String backupnowOutput = backupNow(PACKAGE_UNDER_TEST);
 
-        assertBackupIsSuccessful(backupnowOutput);
+        assertBackupIsSuccessful(PACKAGE_UNDER_TEST, backupnowOutput);
 
         mDevice.uninstallPackage(PACKAGE_UNDER_TEST);
 
-        assertNull(mDevice.installPackage(MigrationHelper.getTestFile(mCtsBuildInfo, TEST_APP_APK),
-                true));
+        assertNull(super.installPackage(TEST_APP_APK));
 
         mDevice.executeAdbCommand("logcat", "-c");
 
@@ -217,27 +163,6 @@
     }
 
     /**
-     * Parsing the output of "bmgr backupnow" command and checking that the package under test
-     * was backed up successfully.
-     *
-     * Expected format: "Package android.backup.cts.backuprestoreapp with result: Success"
-     */
-    private void assertBackupIsSuccessful(String backupnowOutput) {
-        // Assert backup was successful.
-        Scanner in = new Scanner(backupnowOutput);
-        while (in.hasNextLine()) {
-            String line = in.nextLine();
-
-            if (line.contains(PACKAGE_UNDER_TEST)) {
-                String result = line.split(":")[1].trim();
-
-                assertEquals(result, "Success");
-            }
-        }
-        in.close();
-    }
-
-    /**
      * Reads the values logged by the app and verifies that they are the same as the ones we saved
      * in {@code mSavedValues}.
      */
@@ -312,71 +237,6 @@
      * Returns the logcat string with the tag {@param className} and clears everything else.
      */
     private String getLogcatForClass(String className) throws DeviceNotAvailableException {
-        return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d",
-                className + ":I", "*:S");
-    }
-
-    // Copied over from BackupQuotaTest
-    private boolean enableBackup(boolean enable) throws Exception {
-        boolean previouslyEnabled;
-        String output = mDevice.executeShellCommand("bmgr enabled");
-        Pattern pattern = Pattern.compile("^Backup Manager currently (enabled|disabled)$");
-        Matcher matcher = pattern.matcher(output.trim());
-        if (matcher.find()) {
-            previouslyEnabled = "enabled".equals(matcher.group(1));
-        } else {
-            throw new RuntimeException("non-parsable output setting bmgr enabled: " + output);
-        }
-
-        mDevice.executeShellCommand("bmgr enable " + enable);
-        return previouslyEnabled;
-    }
-
-    // Copied over from BackupQuotaTest
-    private String setBackupTransport(String transport) throws Exception {
-        String output = mDevice.executeShellCommand("bmgr transport " + transport);
-        Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$");
-        Matcher matcher = pattern.matcher(output);
-        if (matcher.find()) {
-            return matcher.group(1);
-        } else {
-            throw new RuntimeException("non-parsable output setting bmgr transport: " + output);
-        }
-    }
-
-    // Copied over from BackupQuotaTest
-    private boolean hasBackupTransport(String transport) throws Exception {
-        String output = mDevice.executeShellCommand("bmgr list transports");
-        for (String t : output.split(" ")) {
-            if (transport.equals(t.trim())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
-        if (mAvailableFeatures == null) {
-            String command = "pm list features";
-            String commandOutput = getDevice().executeShellCommand(command);
-            CLog.i("Output for command " + command + ": " + commandOutput);
-
-            // Extract the id of the new user.
-            mAvailableFeatures = new HashSet<>();
-            for (String feature: commandOutput.split("\\s+")) {
-                // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
-                String[] tokens = feature.split(":");
-                assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
-                        tokens.length > 1);
-                assertEquals(feature, "feature", tokens[0]);
-                mAvailableFeatures.add(tokens[1]);
-            }
-        }
-        boolean result = mAvailableFeatures.contains(requiredFeature);
-        if (!result) {
-            CLog.d("Device doesn't have required feature "
-            + requiredFeature + ". Test won't run.");
-        }
-        return result;
+        return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", className + ":I", "*:S");
     }
 }
diff --git a/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
new file mode 100644
index 0000000..a3c2467
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.cts.backup;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.cts.migration.MigrationHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.ddmlib.testrunner.TestRunResult;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.FileNotFoundException;
+import java.util.Map;
+import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Scanner;
+
+/**
+ * Base class for CTS backup/restore hostside tests
+ */
+public abstract class BaseBackupHostSideTest extends DeviceTestCase implements IBuildReceiver {
+
+    /** Value of PackageManager.FEATURE_BACKUP */
+    private static final String FEATURE_BACKUP = "android.software.backup";
+
+    private static final String LOCAL_TRANSPORT =
+            "android/com.android.internal.backup.LocalTransport";
+
+    protected ITestDevice mDevice;
+
+    private boolean mIsBackupSupported;
+    private boolean mWasBackupEnabled;
+    private String mOldTransport;
+    private HashSet<String> mAvailableFeatures;
+    private IBuildInfo mCtsBuildInfo;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuildInfo = buildInfo;
+    }
+
+    @Override
+    public void setUp() throws DeviceNotAvailableException, Exception {
+        mDevice = getDevice();
+        mIsBackupSupported = hasDeviceFeature(FEATURE_BACKUP);
+        assumeTrue(mIsBackupSupported);
+        // Enable backup and select local backup transport
+        assertTrue("LocalTransport should be available.", hasBackupTransport(LOCAL_TRANSPORT));
+        mWasBackupEnabled = enableBackup(true);
+        mOldTransport = setBackupTransport(LOCAL_TRANSPORT);
+        assertNotNull(mCtsBuildInfo);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (mIsBackupSupported) {
+            setBackupTransport(mOldTransport);
+            enableBackup(mWasBackupEnabled);
+        }
+    }
+
+    /**
+     * Execute shell command "bmgr backupnow <packageName>" and return output from this command.
+     */
+    protected String backupNow(String packageName) throws DeviceNotAvailableException {
+        return mDevice.executeShellCommand("bmgr backupnow " + packageName);
+    }
+
+    /**
+     * Execute shell command "bmgr restore <packageName>" and return output from this command.
+     */
+    protected String restore(String packageName) throws DeviceNotAvailableException {
+        return mDevice.executeShellCommand("bmgr restore " + packageName);
+    }
+
+    /**
+     * Copied from com.android.cts.net.HostsideNetworkTestCase.
+     */
+    protected void runDeviceTest(String packageName, String className, String testName)
+            throws DeviceNotAvailableException {
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(packageName,
+                "android.support.test.runner.AndroidJUnitRunner", mDevice.getIDevice());
+
+        if (className != null) {
+            if (testName != null) {
+                testRunner.setMethodName(className, testName);
+            } else {
+                testRunner.setClassName(className);
+            }
+        }
+
+        final CollectingTestListener listener = new CollectingTestListener();
+        mDevice.runInstrumentationTests(testRunner, listener);
+
+        final TestRunResult result = listener.getCurrentRunResults();
+        if (result.isRunFailure()) {
+            throw new AssertionError("Failed to successfully run device tests for "
+                    + result.getName() + ": " + result.getRunFailureMessage());
+        }
+        if (result.getNumTests() == 0) {
+            throw new AssertionError("No tests were run on the device");
+        }
+        if (result.hasFailedTests()) {
+            // build a meaningful error message
+            StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
+            for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+                result.getTestResults().entrySet()) {
+                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+                    errorBuilder.append(resultEntry.getKey().toString());
+                    errorBuilder.append(":\n");
+                    errorBuilder.append(resultEntry.getValue().getStackTrace());
+                }
+            }
+            throw new AssertionError(errorBuilder.toString());
+        }
+    }
+
+    /**
+     * Parsing the output of "bmgr backupnow" command and checking that the package under test
+     * was backed up successfully.
+     *
+     * Expected format: "Package <packageName> with result: Success"
+     */
+    protected void assertBackupIsSuccessful(String packageName, String backupnowOutput) {
+        // Assert backup was successful.
+        Scanner in = new Scanner(backupnowOutput);
+        boolean success = false;
+        while (in.hasNextLine()) {
+            String line = in.nextLine();
+
+            if (line.contains(packageName)) {
+                String result = line.split(":")[1].trim();
+                if ("Success".equals(result)) {
+                    success = true;
+                }
+            }
+        }
+        in.close();
+        assertTrue(success);
+    }
+
+    protected String installPackage(String apkName)
+            throws DeviceNotAvailableException, FileNotFoundException {
+        return mDevice.installPackage(MigrationHelper.getTestFile(mCtsBuildInfo, apkName), true);
+    }
+
+    /**
+     * Parsing the output of "bmgr restore" command and checking that the package under test
+     * was restored successfully.
+     *
+     * Expected format: "restoreFinished: 0"
+     */
+    protected void assertRestoreIsSuccessful(String restoreOutput) {
+        assertTrue("Restore not successful", restoreOutput.contains("restoreFinished: 0"));
+    }
+
+    private boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
+        if (mAvailableFeatures == null) {
+            String command = "pm list features";
+            String commandOutput = getDevice().executeShellCommand(command);
+            CLog.i("Output for command " + command + ": " + commandOutput);
+
+            // Extract the id of the new user.
+            mAvailableFeatures = new HashSet<>();
+            for (String feature: commandOutput.split("\\s+")) {
+                // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
+                String[] tokens = feature.split(":");
+                assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
+                        tokens.length > 1);
+                assertEquals(feature, "feature", tokens[0]);
+                mAvailableFeatures.add(tokens[1]);
+            }
+        }
+        boolean result = mAvailableFeatures.contains(requiredFeature);
+        if (!result) {
+            CLog.d("Device doesn't have required feature "
+            + requiredFeature + ". Test won't run.");
+        }
+        return result;
+    }
+
+    // Copied over from BackupQuotaTest
+    private boolean enableBackup(boolean enable) throws Exception {
+        boolean previouslyEnabled;
+        String output = mDevice.executeShellCommand("bmgr enabled");
+        Pattern pattern = Pattern.compile("^Backup Manager currently (enabled|disabled)$");
+        Matcher matcher = pattern.matcher(output.trim());
+        if (matcher.find()) {
+            previouslyEnabled = "enabled".equals(matcher.group(1));
+        } else {
+            throw new RuntimeException("non-parsable output setting bmgr enabled: " + output);
+        }
+
+        mDevice.executeShellCommand("bmgr enable " + enable);
+        return previouslyEnabled;
+    }
+
+    // Copied over from BackupQuotaTest
+    private String setBackupTransport(String transport) throws Exception {
+        String output = mDevice.executeShellCommand("bmgr transport " + transport);
+        Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$");
+        Matcher matcher = pattern.matcher(output);
+        if (matcher.find()) {
+            return matcher.group(1);
+        } else {
+            throw new RuntimeException("non-parsable output setting bmgr transport: " + output);
+        }
+    }
+
+    // Copied over from BackupQuotaTest
+    private boolean hasBackupTransport(String transport) throws Exception {
+        String output = mDevice.executeShellCommand("bmgr list transports");
+        for (String t : output.split(" ")) {
+            if (transport.equals(t.trim())) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/NoBackupFolderHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/NoBackupFolderHostSideTest.java
new file mode 100644
index 0000000..d51b46d
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/NoBackupFolderHostSideTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.cts.backup;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test checking that files created by an app are restored successfully after a backup, but that
+ * files put in the folder provided by getNoBackupFilesDir() [files/no_backup] are NOT backed up.
+ *
+ * Invokes device side tests provided by android.cts.backup.fullbackupapp.FullbackupTest.
+ */
+public class NoBackupFolderHostSideTest extends BaseBackupHostSideTest {
+
+    private static final String TESTS_APP_NAME = "android.cts.backup.fullbackupapp";
+    private static final String DEVICE_TEST_CLASS_NAME = TESTS_APP_NAME + ".FullbackupTest";
+
+    public void testNoBackupFolder() throws Exception {
+        // Generate the files that are going to be backed up.
+        runDeviceTest(TESTS_APP_NAME, DEVICE_TEST_CLASS_NAME, "createFiles");
+
+        // Do a backup
+        String backupnowOutput = backupNow(TESTS_APP_NAME);
+
+        assertBackupIsSuccessful(TESTS_APP_NAME, backupnowOutput);
+
+        // Delete the files
+        runDeviceTest(TESTS_APP_NAME, DEVICE_TEST_CLASS_NAME, "deleteFilesAfterBackup");
+
+        // Do a restore
+        String restoreOutput = restore(TESTS_APP_NAME);
+
+        assertRestoreIsSuccessful(restoreOutput);
+
+        // Check that the right files were restored
+        runDeviceTest(TESTS_APP_NAME, DEVICE_TEST_CLASS_NAME, "checkRestoredFiles");
+    }
+}