CTS Test for quota exceeded callback in new Backup API.

Test creates large file using helper app, runs backup for it,
check that the callback was called.

Bug: 28061809
Change-Id: Ibfb95129df459ce124286a602ddb61fafa8cf2fc
diff --git a/OldCtsTestCaseList.mk b/OldCtsTestCaseList.mk
index 2e7de5e..577dcdf 100644
--- a/OldCtsTestCaseList.mk
+++ b/OldCtsTestCaseList.mk
@@ -86,6 +86,7 @@
     CtsAssistService \
     CtsAssistApp \
     CtsAtraceTestApp \
+    CtsBackupApp \
     CtsCertInstallerApp \
     CtsContactDirectoryProvider \
     CtsCustomizationApp \
@@ -154,6 +155,7 @@
     CtsAppTestCases \
     CtsAppWidgetTestCases \
     CtsAssistTestCases \
+    CtsBackupTestCases \
     CtsBluetoothTestCases \
     CtsBrowserTestCases \
     CtsCalendarcommon2TestCases \
diff --git a/tests/backup/Android.mk b/tests/backup/Android.mk
new file mode 100644
index 0000000..93e55aa
--- /dev/null
+++ b/tests/backup/Android.mk
@@ -0,0 +1,41 @@
+# Copyright (C) 2016 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 := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common voip-common org.apache.http.legacy
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner ctstestserver mockito-target
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsBackupTestCases
+
+LOCAL_CTS_MODULE_CONFIG := $(LOCAL_PATH)/Old$(CTS_MODULE_TEST_CONFIG)
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/backup/AndroidManifest.xml b/tests/backup/AndroidManifest.xml
new file mode 100644
index 0000000..333045a
--- /dev/null
+++ b/tests/backup/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 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.backup.cts">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.backup.cts"
+                     android:label="CTS tests of Android Backup/Restore">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/backup/AndroidTest.xml b/tests/backup/AndroidTest.xml
new file mode 100644
index 0000000..ce5d261
--- /dev/null
+++ b/tests/backup/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 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
+  -->
+<configuration description="Config for CTS Backup test cases">
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsBackupApp.apk" />
+        <option name="test-file-name" value="CtsBackupTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.backup.cts" />
+    </test>
+</configuration>
diff --git a/tests/backup/app/Android.mk b/tests/backup/app/Android.mk
new file mode 100644
index 0000000..cffb1d2
--- /dev/null
+++ b/tests/backup/app/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2016 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)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsBackupApp
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctsdeviceutil \
+    ctstestrunner
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/backup/app/AndroidManifest.xml b/tests/backup/app/AndroidManifest.xml
new file mode 100644
index 0000000..1507bc2
--- /dev/null
+++ b/tests/backup/app/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 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.backup.app" >
+
+    <application
+        android:allowBackup="true"
+        android:backupAgent="BackupCtsBackupAgent"
+        android:label="Android Backup CTS App"
+        android:fullBackupOnly="true">
+        <activity
+            android:name=".MainActivity"
+            android:label="Android Backup CTS App" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/backup/app/src/android/backup/app/BackupCtsBackupAgent.java b/tests/backup/app/src/android/backup/app/BackupCtsBackupAgent.java
new file mode 100644
index 0000000..f6fb8c1
--- /dev/null
+++ b/tests/backup/app/src/android/backup/app/BackupCtsBackupAgent.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 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.app;
+
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.FullBackupDataOutput;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+
+/*
+ * Backup agent for Backup CTS App.
+ *
+ * Logs callbacks into logcat.
+ */
+public class BackupCtsBackupAgent extends BackupAgent {
+
+    @Override
+    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+                         ParcelFileDescriptor newState) throws IOException {
+        Log.d(MainActivity.TAG, "Backup requested");
+    }
+
+    @Override
+    public void onRestore(BackupDataInput data, int appVersionCode,
+                          ParcelFileDescriptor newState) throws IOException {
+        Log.d(MainActivity.TAG, "Restore requested");
+    }
+
+    @Override
+    public void onFullBackup(FullBackupDataOutput data) throws IOException {
+        Log.d(MainActivity.TAG, "Full backup requested");
+        super.onFullBackup(data);
+    }
+
+    @Override
+    public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
+        Log.d(MainActivity.TAG, "Quota exceeded!");
+    }
+}
diff --git a/tests/backup/app/src/android/backup/app/MainActivity.java b/tests/backup/app/src/android/backup/app/MainActivity.java
new file mode 100644
index 0000000..93c6dd6
--- /dev/null
+++ b/tests/backup/app/src/android/backup/app/MainActivity.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 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.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * The activity could be invoked to create a test file.
+ *
+ * Here is an example of a call:
+ *
+ * am start -a android.intent.action.MAIN \
+ * -c android.intent.category.LAUNCHER \
+ * -n android.backup.app/android.backup.app.MainActivity \
+ * -e file_size 10000000
+ *
+ * "File created!" string is printed in logcat when file is created.
+ */
+public class MainActivity extends Activity {
+    public static final String TAG = "BackupCTSApp";
+
+    private static final String FILE_NAME = "file_name";
+    private static final String FILE_SIZE_EXTRA = "file_size";
+    private static final int DATA_CHUNK_SIZE = 1024 * 1024;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceActivity) {
+        super.onCreate(savedInstanceActivity);
+        if (getIntent().hasExtra(FILE_SIZE_EXTRA)) {
+            int fileSize = Integer.parseInt(getIntent().getStringExtra(FILE_SIZE_EXTRA));
+            Log.i(TAG, "Creating file of size: " + fileSize);
+            try {
+                createFile(fileSize);
+                Log.d(TAG, "File created!");
+            } catch (IOException e) {
+                Log.e(TAG, "IOException: " + e);
+            }
+        } else {
+            Log.d(TAG, "No file size was provided");
+        }
+    }
+
+    private void createFile(int size) throws IOException {
+        byte[] bytes = new byte[DATA_CHUNK_SIZE];
+        new Random().nextBytes(bytes);
+        File f = new File(getFilesDir().getAbsolutePath(), FILE_NAME);
+        f.getParentFile().mkdirs();
+        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(f))) {
+            while (size > 0) {
+                int bytesToWrite = Math.min(size, DATA_CHUNK_SIZE);
+                bos.write(bytes, 0, bytesToWrite);
+                size -= bytesToWrite;
+            }
+        }
+    }
+}
diff --git a/tests/backup/src/android/backup/cts/BackupQuotaTest.java b/tests/backup/src/android/backup/cts/BackupQuotaTest.java
new file mode 100644
index 0000000..6122ad0
--- /dev/null
+++ b/tests/backup/src/android/backup/cts/BackupQuotaTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2016 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;
+
+import android.app.Instrumentation;
+import android.os.ParcelFileDescriptor;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Verifies receiving quotaExceeded() callback on full backup.
+ *
+ * Uses test app that creates large file and receives the callback.
+ * {@link com.android.internal.backup.LocalTransport} is used, it has size quota 25MB.
+ */
+public class BackupQuotaTest extends InstrumentationTestCase {
+    private static final String APP_LOG_TAG = "BackupCTSApp";
+
+    private static final String LOCAL_TRANSPORT =
+            "android/com.android.internal.backup.LocalTransport";
+    private static final int LOCAL_TRANSPORT_EXCEEDING_FILE_SIZE = 30 * 1024 * 1024;
+    private static final String BACKUP_APP_NAME = "android.backup.app";
+
+    private static final int SMALL_LOGCAT_DELAY = 1000;
+
+    private boolean wasBackupEnabled;
+    private String oldTransport;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // Enable backup and select local backup transport
+        assertTrue("LocalTransport should be available.", hasBackupTransport(LOCAL_TRANSPORT));
+        wasBackupEnabled = enableBackup(true);
+        oldTransport = setBackupTransport(LOCAL_TRANSPORT);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Return old transport
+        setBackupTransport(oldTransport);
+        enableBackup(wasBackupEnabled);
+        super.tearDown();
+    }
+
+    public void testQuotaExceeded() throws Exception {
+        exec("logcat --clear");
+        exec("setprop log.tag." + APP_LOG_TAG +" VERBOSE");
+        // Launch test app and create file exceeding limit for local transport
+        exec("am start -W -a android.intent.action.MAIN " +
+                "-c android.intent.category.LAUNCHER " +
+                "-n " + BACKUP_APP_NAME + "/" + BACKUP_APP_NAME +".MainActivity " +
+                "-e file_size " + LOCAL_TRANSPORT_EXCEEDING_FILE_SIZE);
+        assertTrue("File was not created", waitForLogcat("File created!", 30));
+
+        // Request backup and wait for quota exceeded event in logcat
+        exec("bmgr backupnow " + BACKUP_APP_NAME);
+        assertTrue("Quota exceeded event is not received", waitForLogcat("Quota exceeded!", 10));
+    }
+
+    private boolean enableBackup(boolean enable) throws Exception {
+        boolean previouslyEnabled;
+        String output = exec("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);
+        }
+
+        exec("bmgr enable " + enable);
+        return previouslyEnabled;
+    }
+
+    private String setBackupTransport(String transport) throws Exception {
+        String output = exec("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);
+        }
+    }
+
+    private boolean hasBackupTransport(String transport) throws Exception {
+        String output = exec("bmgr list transports");
+        for (String t : output.split(" ")) {
+            if (transport.equals(t)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean waitForLogcat(String logcatString, int maxTimeoutInSeconds) throws Exception {
+        long timeout = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(maxTimeoutInSeconds);
+        while (timeout >= System.currentTimeMillis()) {
+            FileInputStream fis = executeStreamedShellCommand(getInstrumentation(),
+                    "logcat -v brief -d " + APP_LOG_TAG + ":* *:S");
+            BufferedReader log = new BufferedReader(new InputStreamReader(fis));
+            String line;
+            while ((line = log.readLine()) != null) {
+                if (line.contains(logcatString)) {
+                    return true;
+                }
+            }
+            closeQuietly(log);
+            // In case the key has not been found, wait for the log to update before
+            // performing the next search.
+            Thread.sleep(SMALL_LOGCAT_DELAY);
+        }
+        return false;
+    }
+
+    private String exec(String command) throws Exception {
+        BufferedReader br = null;
+        try (InputStream in = executeStreamedShellCommand(getInstrumentation(), command)) {
+            br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+            String str = null;
+            StringBuilder out = new StringBuilder();
+            while ((str = br.readLine()) != null) {
+                out.append(str);
+            }
+            return out.toString();
+        } finally {
+            if (br != null) {
+                closeQuietly(br);
+            }
+        }
+    }
+
+    private static FileInputStream executeStreamedShellCommand(Instrumentation instrumentation,
+                                                               String command) throws Exception {
+        final ParcelFileDescriptor pfd =
+                instrumentation.getUiAutomation().executeShellCommand(command);
+        return new FileInputStream(pfd.getFileDescriptor());
+    }
+
+    private static void closeQuietly(AutoCloseable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (RuntimeException rethrown) {
+                throw rethrown;
+            } catch (Exception ignored) {
+            }
+        }
+    }
+}