blob: 67acd7065cbc292410a639de9f21f68d53b0aec3 [file] [log] [blame]
/*
* 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 com.android.compatibility.common.util;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility class for backup and restore.
*/
public abstract class BackupUtils {
private static final String LOCAL_TRANSPORT_NAME = "com.android.localtransport/.LocalTransport";
private static final String LOCAL_TRANSPORT_NAME_PRE_Q =
"android/com.android.internal.backup.LocalTransport";
private static final String LOCAL_TRANSPORT_PACKAGE = "com.android.localtransport";
public static final String LOCAL_TRANSPORT_TOKEN = "1";
private static final int BACKUP_PROVISIONING_TIMEOUT_SECONDS = 30;
private static final int BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS = 1;
private static final Pattern BACKUP_MANAGER_CURRENTLY_ENABLE_STATUS_PATTERN =
Pattern.compile("^Backup Manager currently (enabled|disabled)$");
private static final String MATCH_LINE_BACKUP_MANAGER_IS_NOT_PENDING_INIT =
"(?s)" + "^Backup Manager is .* not pending init.*"; // DOTALL
private static final String BACKUP_DUMPSYS_CURRENT_TOKEN_FIELD = "Current:";
/**
* Kicks off adb shell {@param command} and return an {@link InputStream} with the command
* output stream.
*/
protected abstract InputStream executeShellCommand(String command) throws IOException;
public void executeShellCommandSync(String command) throws IOException {
StreamUtil.drainAndClose(new InputStreamReader(executeShellCommand(command)));
}
public String getShellCommandOutput(String command) throws IOException {
return StreamUtil.readInputStream(executeShellCommand(command));
}
/** Executes shell command "bmgr backupnow <package>" and assert success. */
public void backupNowAndAssertSuccess(String packageName) throws IOException {
assertBackupIsSuccessful(packageName, backupNow(packageName));
}
/** Executes "bmgr --user <id> backupnow <package>" and assert success. */
public void backupNowAndAssertSuccessForUser(String packageName, int userId)
throws IOException {
assertBackupIsSuccessful(packageName, backupNowForUser(packageName, userId));
}
public void backupNowAndAssertBackupNotAllowed(String packageName) throws IOException {
assertBackupNotAllowed(packageName, getBackupNowOutput(packageName));
}
/** Executes shell command "bmgr backupnow <package>" and waits for completion. */
public void backupNowSync(String packageName) throws IOException {
StreamUtil.drainAndClose(new InputStreamReader(backupNow(packageName)));
}
public String getBackupNowOutput(String packageName) throws IOException {
return StreamUtil.readInputStream(backupNow(packageName));
}
/** Executes shell command "bmgr restore <token> <package>" and assert success. */
public void restoreAndAssertSuccess(String token, String packageName) throws IOException {
assertRestoreIsSuccessful(restore(token, packageName));
}
/** Executes shell command "bmgr --user <id> restore <token> <package>" and assert success. */
public void restoreAndAssertSuccessForUser(String token, String packageName, int userId)
throws IOException {
assertRestoreIsSuccessful(restoreForUser(token, packageName, userId));
}
public void restoreSync(String token, String packageName) throws IOException {
StreamUtil.drainAndClose(new InputStreamReader(restore(token, packageName)));
}
public String getRestoreOutput(String token, String packageName) throws IOException {
return StreamUtil.readInputStream(restore(token, packageName));
}
public boolean isLocalTransportSelected() throws IOException {
return getShellCommandOutput("bmgr list transports")
.contains("* " + getLocalTransportName());
}
/**
* Executes shell command "bmgr --user <id> list transports" to check the currently selected
* transport and returns {@code true} if the local transport is the selected one.
*/
public boolean isLocalTransportSelectedForUser(int userId) throws IOException {
return getShellCommandOutput(String.format("bmgr --user %d list transports", userId))
.contains("* " + getLocalTransportName());
}
public boolean isBackupEnabled() throws IOException {
return getShellCommandOutput("bmgr enabled").contains("currently enabled");
}
/**
* Executes shell command "bmgr --user <id> enabled" and returns if backup is enabled for the
* user {@code userId}.
*/
public boolean isBackupEnabledForUser(int userId) throws IOException {
return getShellCommandOutput(String.format("bmgr --user %d enabled", userId))
.contains("currently enabled");
}
public void wakeAndUnlockDevice() throws IOException {
executeShellCommandSync("input keyevent KEYCODE_WAKEUP");
executeShellCommandSync("wm dismiss-keyguard");
}
/**
* Returns {@link #LOCAL_TRANSPORT_NAME} if it's available on the device, or {@link
* #LOCAL_TRANSPORT_NAME_PRE_Q} otherwise.
*/
public String getLocalTransportName() throws IOException {
return getShellCommandOutput("pm list packages").contains(LOCAL_TRANSPORT_PACKAGE)
? LOCAL_TRANSPORT_NAME
: LOCAL_TRANSPORT_NAME_PRE_Q;
}
/** Executes "bmgr backupnow <package>" and returns an {@link InputStream} for its output. */
private InputStream backupNow(String packageName) throws IOException {
return executeShellCommand("bmgr backupnow " + packageName);
}
/**
* Executes "bmgr --user <id> backupnow <package>" and returns an {@link InputStream} for its
* output.
*/
private InputStream backupNowForUser(String packageName, int userId) throws IOException {
return executeShellCommand(
String.format("bmgr --user %d backupnow %s", userId, packageName));
}
/**
* Parses the output of "bmgr backupnow" command and checks that {@code packageName} wasn't
* allowed to backup.
*
* Expected format: "Package <packageName> with result: Backup is not allowed"
*
* TODO: Read input stream instead of string.
*/
private void assertBackupNotAllowed(String packageName, String backupNowOutput) {
Scanner in = new Scanner(backupNowOutput);
boolean found = false;
while (in.hasNextLine()) {
String line = in.nextLine();
if (line.contains(packageName)) {
String result = line.split(":")[1].trim();
if ("Backup is not allowed".equals(result)) {
found = true;
}
}
}
in.close();
assertTrue("Didn't find \'Backup not allowed\' in the output", found);
}
/**
* Parses the output of "bmgr backupnow" command checking that the package {@code packageName}
* was backed up successfully. Closes the input stream.
*
* Expected format: "Package <package> with result: Success"
*/
private void assertBackupIsSuccessful(String packageName, InputStream backupNowOutput)
throws IOException {
BufferedReader reader =
new BufferedReader(new InputStreamReader(backupNowOutput, StandardCharsets.UTF_8));
try {
String line;
while ((line = reader.readLine()) != null) {
if (line.contains(packageName)) {
String result = line.split(":")[1].trim().toLowerCase();
if ("success".equals(result)) {
return;
}
}
}
fail("Couldn't find package in output or backup wasn't successful");
} finally {
StreamUtil.drainAndClose(reader);
}
}
/**
* Executes "bmgr restore <token> <packageName>" and returns an {@link InputStream} for its
* output.
*/
private InputStream restore(String token, String packageName) throws IOException {
return executeShellCommand(String.format("bmgr restore %s %s", token, packageName));
}
/**
* Executes "bmgr --user <id> restore <token> <packageName>" and returns an {@link InputStream}
* for its output.
*/
private InputStream restoreForUser(String token, String packageName, int userId)
throws IOException {
return executeShellCommand(
String.format("bmgr --user %d restore %s %s", userId, token, packageName));
}
/**
* Parses the output of "bmgr restore" command and checks that the package under test
* was restored successfully. Closes the input stream.
*
* Expected format: "restoreFinished: 0"
*/
private void assertRestoreIsSuccessful(InputStream restoreOutput) throws IOException {
BufferedReader reader =
new BufferedReader(new InputStreamReader(restoreOutput, StandardCharsets.UTF_8));
try {
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("restoreFinished: 0")) {
return;
}
}
fail("Restore not successful");
} finally {
StreamUtil.drainAndClose(reader);
}
}
/** Executes "dumpsys backup" and returns an {@link InputStream} for its output. */
private InputStream dumpsysBackup() throws IOException {
return executeShellCommand("dumpsys backup");
}
/**
* Parses the output of "dumpsys backup" command to get token. Closes the input stream finally.
*
* Expected format: "Current: token"
*/
private String getCurrentTokenOrFail(InputStream dumpsysOutput) throws IOException {
BufferedReader reader =
new BufferedReader(new InputStreamReader(dumpsysOutput, StandardCharsets.UTF_8));
try {
String line;
while ((line = reader.readLine()) != null) {
if (line.contains(BACKUP_DUMPSYS_CURRENT_TOKEN_FIELD)) {
return line.split(BACKUP_DUMPSYS_CURRENT_TOKEN_FIELD)[1].trim();
}
}
throw new AssertionError("Couldn't find token in output");
} finally {
StreamUtil.drainAndClose(reader);
}
}
/**
* Execute shell command and return output from this command.
*/
public String executeShellCommandAndReturnOutput(String command) throws IOException {
InputStream in = executeShellCommand(command);
BufferedReader br = new BufferedReader(
new InputStreamReader(in, StandardCharsets.UTF_8));
String str;
StringBuilder out = new StringBuilder();
while ((str = br.readLine()) != null) {
out.append(str).append("\n");
}
return out.toString();
}
// Copied over from BackupQuotaTest
public boolean enableBackup(boolean enable) throws Exception {
boolean previouslyEnabled;
String output = getLineString(executeShellCommand("bmgr enabled"));
Matcher matcher = BACKUP_MANAGER_CURRENTLY_ENABLE_STATUS_PATTERN.matcher(output.trim());
if (matcher.find()) {
previouslyEnabled = "enabled".equals(matcher.group(1));
} else {
throw new RuntimeException("non-parsable output setting bmgr enabled: " + output);
}
executeShellCommand("bmgr enable " + enable);
return previouslyEnabled;
}
/**
* Execute shell command "bmgr --user <id> enable <enable> and return previous enabled state.
*/
public boolean enableBackupForUser(boolean enable, int userId) throws IOException {
boolean previouslyEnabled = isBackupEnabledForUser(userId);
executeShellCommand(String.format("bmgr --user %d enable %b", userId, enable));
return previouslyEnabled;
}
private String getLineString(InputStream inputStream) throws IOException {
BufferedReader reader =
new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String str;
try {
str = reader.readLine();
} finally {
StreamUtil.drainAndClose(reader);
}
return str;
}
public void waitForBackupInitialization() throws IOException {
long tryUntilNanos = System.nanoTime()
+ TimeUnit.SECONDS.toNanos(BACKUP_PROVISIONING_TIMEOUT_SECONDS);
while (System.nanoTime() < tryUntilNanos) {
String output = getLineString(executeShellCommand("dumpsys backup"));
if (output.matches(MATCH_LINE_BACKUP_MANAGER_IS_NOT_PENDING_INIT)) {
return;
}
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
throw new IOException("Timed out waiting for backup initialization");
}
/**
* Executes shell command "bmgr --user <id> list transports" and returns {@code true} if the
* user has the {@code transport} available.
*/
public boolean userHasBackupTransport(String transport, int userId) throws IOException {
String output =
getLineString(
executeShellCommand(
String.format("bmgr --user %d list transports", userId)));
for (String t : output.split("\n")) {
// Parse out the '*' character used to denote the selected transport.
t = t.replace("*", "").trim();
if (transport.equals(t)) {
return true;
}
}
return false;
}
/**
* Executes shell command "bmgr --user <id> transport <transport>" and returns the old
* transport.
*/
public String setBackupTransportForUser(String transport, int userId) throws IOException {
String output =
executeShellCommandAndReturnOutput(
String.format("bmgr --user %d transport %s", userId, 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);
}
}
}