1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.compatibility.common.util; 18 19 import static org.junit.Assert.assertTrue; 20 import static org.junit.Assert.fail; 21 22 import com.google.common.annotations.VisibleForTesting; 23 24 import java.io.BufferedReader; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.io.InputStreamReader; 28 import java.nio.charset.StandardCharsets; 29 import java.util.Scanner; 30 import java.util.concurrent.TimeUnit; 31 import java.util.regex.Matcher; 32 import java.util.regex.Pattern; 33 34 /** 35 * Utility class for backup and restore. 36 */ 37 public abstract class BackupUtils { 38 private static final String LOCAL_TRANSPORT_NAME = 39 "com.android.localtransport/.LocalTransport"; 40 private static final String LOCAL_TRANSPORT_NAME_PRE_Q = 41 "android/com.android.internal.backup.LocalTransport"; 42 private static final String LOCAL_TRANSPORT_PACKAGE = "com.android.localtransport"; 43 public static final String LOCAL_TRANSPORT_TOKEN = "1"; 44 45 private static final int BACKUP_PROVISIONING_TIMEOUT_SECONDS = 30; 46 private static final int BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS = 1; 47 private static final int BACKUP_SERVICE_INIT_TIMEOUT_SECS = 30; 48 49 private static final Pattern BACKUP_MANAGER_CURRENTLY_ENABLE_STATUS_PATTERN = 50 Pattern.compile("^Backup Manager currently (enabled|disabled)$"); 51 private static final String MATCH_LINE_BACKUP_MANAGER_IS_NOT_PENDING_INIT = 52 "(?s)" + "^Backup Manager is .* not pending init.*"; // DOTALL 53 54 private static final String BACKUP_DUMPSYS_CURRENT_TOKEN_FIELD = "Current:"; 55 56 /** 57 * Kicks off adb shell {@param command} and return an {@link InputStream} with the command 58 * output stream. 59 */ executeShellCommand(String command)60 protected abstract InputStream executeShellCommand(String command) throws IOException; 61 executeShellCommandSync(String command)62 public void executeShellCommandSync(String command) throws IOException { 63 StreamUtil.drainAndClose(new InputStreamReader(executeShellCommand(command))); 64 } 65 getShellCommandOutput(String command)66 public String getShellCommandOutput(String command) throws IOException { 67 return StreamUtil.readInputStream(executeShellCommand(command)); 68 } 69 70 /** Executes shell command "bmgr backupnow <package>" and assert success. */ backupNowAndAssertSuccess(String packageName)71 public void backupNowAndAssertSuccess(String packageName) throws IOException { 72 assertBackupIsSuccessful(packageName, backupNow(packageName)); 73 } 74 75 /** Executes "bmgr --user <id> backupnow <package>" and assert success. */ backupNowAndAssertSuccessForUser(String packageName, int userId)76 public void backupNowAndAssertSuccessForUser(String packageName, int userId) 77 throws IOException { 78 assertBackupIsSuccessful(packageName, backupNowForUser(packageName, userId)); 79 } 80 backupNowAndAssertBackupNotAllowed(String packageName)81 public void backupNowAndAssertBackupNotAllowed(String packageName) throws IOException { 82 assertBackupNotAllowed(packageName, getBackupNowOutput(packageName)); 83 } 84 85 /** Executes shell command "bmgr backupnow <package>" and waits for completion. */ backupNowSync(String packageName)86 public void backupNowSync(String packageName) throws IOException { 87 StreamUtil.drainAndClose(new InputStreamReader(backupNow(packageName))); 88 } 89 getBackupNowOutput(String packageName)90 public String getBackupNowOutput(String packageName) throws IOException { 91 return StreamUtil.readInputStream(backupNow(packageName)); 92 } 93 94 /** Executes shell command "bmgr restore <token> <package>" and assert success. */ restoreAndAssertSuccess(String token, String packageName)95 public void restoreAndAssertSuccess(String token, String packageName) throws IOException { 96 assertRestoreIsSuccessful(restore(token, packageName)); 97 } 98 99 /** Executes shell command "bmgr --user <id> restore <token> <package>" and assert success. */ restoreAndAssertSuccessForUser(String token, String packageName, int userId)100 public void restoreAndAssertSuccessForUser(String token, String packageName, int userId) 101 throws IOException { 102 assertRestoreIsSuccessful(restoreForUser(token, packageName, userId)); 103 } 104 restoreSync(String token, String packageName)105 public void restoreSync(String token, String packageName) throws IOException { 106 StreamUtil.drainAndClose(new InputStreamReader(restore(token, packageName))); 107 } 108 getRestoreOutput(String token, String packageName)109 public String getRestoreOutput(String token, String packageName) throws IOException { 110 return StreamUtil.readInputStream(restore(token, packageName)); 111 } 112 isLocalTransportSelected()113 public boolean isLocalTransportSelected() throws IOException { 114 return getShellCommandOutput("bmgr list transports") 115 .contains("* " + getLocalTransportName()); 116 } 117 118 /** 119 * Executes shell command "bmgr --user <id> list transports" to check the currently selected 120 * transport and returns {@code true} if the local transport is the selected one. 121 */ isLocalTransportSelectedForUser(int userId)122 public boolean isLocalTransportSelectedForUser(int userId) throws IOException { 123 return getShellCommandOutput(String.format("bmgr --user %d list transports", userId)) 124 .contains("* " + getLocalTransportName()); 125 } 126 isBackupEnabled()127 public boolean isBackupEnabled() throws IOException { 128 return getShellCommandOutput("bmgr enabled").contains("currently enabled"); 129 } 130 131 /** 132 * Executes shell command "bmgr --user <id> enabled" and returns if backup is enabled for the 133 * user {@code userId}. 134 */ isBackupEnabledForUser(int userId)135 public boolean isBackupEnabledForUser(int userId) throws IOException { 136 return getShellCommandOutput(String.format("bmgr --user %d enabled", userId)) 137 .contains("currently enabled"); 138 } 139 wakeAndUnlockDevice()140 public void wakeAndUnlockDevice() throws IOException { 141 executeShellCommandSync("input keyevent KEYCODE_WAKEUP"); 142 executeShellCommandSync("wm dismiss-keyguard"); 143 } 144 145 /** 146 * Returns {@link #LOCAL_TRANSPORT_NAME} if it's available on the device, or 147 * {@link #LOCAL_TRANSPORT_NAME_PRE_Q} otherwise. 148 */ getLocalTransportName()149 public String getLocalTransportName() throws IOException { 150 return getShellCommandOutput("pm list packages").contains(LOCAL_TRANSPORT_PACKAGE) 151 ? LOCAL_TRANSPORT_NAME : LOCAL_TRANSPORT_NAME_PRE_Q; 152 } 153 154 /** Executes "bmgr backupnow <package>" and returns an {@link InputStream} for its output. */ backupNow(String packageName)155 private InputStream backupNow(String packageName) throws IOException { 156 return executeShellCommand("bmgr backupnow " + packageName); 157 } 158 159 /** 160 * Executes "bmgr --user <id> backupnow <package>" and returns an {@link InputStream} for its 161 * output. 162 */ backupNowForUser(String packageName, int userId)163 private InputStream backupNowForUser(String packageName, int userId) throws IOException { 164 return executeShellCommand( 165 String.format("bmgr --user %d backupnow %s", userId, packageName)); 166 } 167 168 /** 169 * Parses the output of "bmgr backupnow" command and checks that {@code packageName} wasn't 170 * allowed to backup. 171 * 172 * Expected format: "Package <packageName> with result: Backup is not allowed" 173 * 174 * TODO: Read input stream instead of string. 175 */ assertBackupNotAllowed(String packageName, String backupNowOutput)176 private void assertBackupNotAllowed(String packageName, String backupNowOutput) { 177 Scanner in = new Scanner(backupNowOutput); 178 boolean found = false; 179 while (in.hasNextLine()) { 180 String line = in.nextLine(); 181 182 if (line.contains(packageName)) { 183 String result = line.split(":")[1].trim(); 184 if ("Backup is not allowed".equals(result)) { 185 found = true; 186 } 187 } 188 } 189 in.close(); 190 assertTrue("Didn't find \'Backup not allowed\' in the output", found); 191 } 192 193 /** 194 * Parses the output of "bmgr backupnow" command checking that the package {@code packageName} 195 * was backed up successfully. Closes the input stream. 196 * 197 * Expected format: "Package <package> with result: Success" 198 */ assertBackupIsSuccessful(String packageName, InputStream backupNowOutput)199 private void assertBackupIsSuccessful(String packageName, InputStream backupNowOutput) 200 throws IOException { 201 BufferedReader reader = 202 new BufferedReader(new InputStreamReader(backupNowOutput, StandardCharsets.UTF_8)); 203 try { 204 String line; 205 while ((line = reader.readLine()) != null) { 206 if (line.contains(packageName)) { 207 String result = line.split(":")[1].trim().toLowerCase(); 208 if ("success".equals(result)) { 209 return; 210 } 211 } 212 } 213 fail("Couldn't find package in output or backup wasn't successful"); 214 } finally { 215 StreamUtil.drainAndClose(reader); 216 } 217 } 218 219 /** 220 * Executes "bmgr restore <token> <packageName>" and returns an {@link InputStream} for its 221 * output. 222 */ restore(String token, String packageName)223 private InputStream restore(String token, String packageName) throws IOException { 224 return executeShellCommand(String.format("bmgr restore %s %s", token, packageName)); 225 } 226 227 /** 228 * Executes "bmgr --user <id> restore <token> <packageName>" and returns an {@link InputStream} 229 * for its output. 230 */ restoreForUser(String token, String packageName, int userId)231 private InputStream restoreForUser(String token, String packageName, int userId) 232 throws IOException { 233 return executeShellCommand( 234 String.format("bmgr --user %d restore %s %s", userId, token, packageName)); 235 } 236 237 /** 238 * Parses the output of "bmgr restore" command and checks that the package under test 239 * was restored successfully. Closes the input stream. 240 * 241 * Expected format: "restoreFinished: 0" 242 */ assertRestoreIsSuccessful(InputStream restoreOutput)243 private void assertRestoreIsSuccessful(InputStream restoreOutput) throws IOException { 244 BufferedReader reader = 245 new BufferedReader(new InputStreamReader(restoreOutput, StandardCharsets.UTF_8)); 246 try { 247 String line; 248 while ((line = reader.readLine()) != null) { 249 if (line.contains("restoreFinished: 0")) { 250 return; 251 } 252 } 253 fail("Restore not successful"); 254 } finally { 255 StreamUtil.drainAndClose(reader); 256 } 257 } 258 259 /** Executes "dumpsys backup" and returns an {@link InputStream} for its output. */ dumpsysBackup()260 private InputStream dumpsysBackup() throws IOException { 261 return executeShellCommand("dumpsys backup"); 262 } 263 264 /** 265 * Parses the output of "dumpsys backup" command to get token. Closes the input stream finally. 266 * 267 * Expected format: "Current: token" 268 */ getCurrentTokenOrFail(InputStream dumpsysOutput)269 private String getCurrentTokenOrFail(InputStream dumpsysOutput) throws IOException { 270 BufferedReader reader = 271 new BufferedReader(new InputStreamReader(dumpsysOutput, StandardCharsets.UTF_8)); 272 try { 273 String line; 274 while ((line = reader.readLine()) != null) { 275 if (line.contains(BACKUP_DUMPSYS_CURRENT_TOKEN_FIELD)) { 276 return line.split(BACKUP_DUMPSYS_CURRENT_TOKEN_FIELD)[1].trim(); 277 } 278 } 279 throw new AssertionError("Couldn't find token in output"); 280 } finally { 281 StreamUtil.drainAndClose(reader); 282 } 283 } 284 285 /** 286 * Execute shell command and return output from this command. 287 */ executeShellCommandAndReturnOutput(String command)288 public String executeShellCommandAndReturnOutput(String command) throws IOException { 289 InputStream in = executeShellCommand(command); 290 BufferedReader br = new BufferedReader( 291 new InputStreamReader(in, StandardCharsets.UTF_8)); 292 String str; 293 StringBuilder out = new StringBuilder(); 294 while ((str = br.readLine()) != null) { 295 out.append(str).append("\n"); 296 } 297 return out.toString(); 298 } 299 300 // Copied over from BackupQuotaTest enableBackup(boolean enable)301 public boolean enableBackup(boolean enable) throws Exception { 302 boolean previouslyEnabled; 303 String output = getLineString(executeShellCommand("bmgr enabled")); 304 Matcher matcher = BACKUP_MANAGER_CURRENTLY_ENABLE_STATUS_PATTERN.matcher(output.trim()); 305 if (matcher.find()) { 306 previouslyEnabled = "enabled".equals(matcher.group(1)); 307 } else { 308 throw new RuntimeException("non-parsable output setting bmgr enabled: " + output); 309 } 310 311 executeShellCommand("bmgr enable " + enable); 312 return previouslyEnabled; 313 } 314 315 /** 316 * Execute shell command "bmgr --user <id> enable <enable> and return previous enabled state. 317 */ enableBackupForUser(boolean enable, int userId)318 public boolean enableBackupForUser(boolean enable, int userId) throws IOException { 319 boolean previouslyEnabled = isBackupEnabledForUser(userId); 320 executeShellCommand(String.format("bmgr --user %d enable %b", userId, enable)); 321 return previouslyEnabled; 322 } 323 324 /** Execute shell command "bmgr --user <id> activate <activate>." */ activateBackupForUser(boolean activate, int userId)325 public void activateBackupForUser(boolean activate, int userId) throws IOException { 326 executeShellCommandSync(String.format("bmgr --user %d activate %b", userId, activate)); 327 } 328 329 /** 330 * Executes shell command "bmgr --user <id> activated" and returns if backup is activated for 331 * the user {@code userId}. 332 */ isBackupActivatedForUser(int userId)333 public boolean isBackupActivatedForUser(int userId) throws IOException { 334 return getShellCommandOutput(String.format("bmgr --user %d activated", userId)) 335 .contains("currently activated"); 336 } 337 getLineString(InputStream inputStream)338 private String getLineString(InputStream inputStream) throws IOException { 339 BufferedReader reader = 340 new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); 341 String str; 342 try { 343 str = reader.readLine(); 344 } finally { 345 StreamUtil.drainAndClose(reader); 346 } 347 return str; 348 } 349 waitForBackupInitialization()350 public void waitForBackupInitialization() throws IOException { 351 long tryUntilNanos = System.nanoTime() 352 + TimeUnit.SECONDS.toNanos(BACKUP_PROVISIONING_TIMEOUT_SECONDS); 353 while (System.nanoTime() < tryUntilNanos) { 354 String output = getLineString(executeShellCommand("dumpsys backup")); 355 if (output.matches(MATCH_LINE_BACKUP_MANAGER_IS_NOT_PENDING_INIT)) { 356 return; 357 } 358 try { 359 Thread.sleep(TimeUnit.SECONDS.toMillis(BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS)); 360 } catch (InterruptedException e) { 361 Thread.currentThread().interrupt(); 362 break; 363 } 364 } 365 throw new IOException("Timed out waiting for backup initialization"); 366 } 367 waitUntilBackupServiceIsRunning(int userId)368 public void waitUntilBackupServiceIsRunning(int userId) 369 throws IOException, InterruptedException { 370 waitUntilBackupServiceIsRunning(userId, BACKUP_SERVICE_INIT_TIMEOUT_SECS); 371 } 372 373 @VisibleForTesting waitUntilBackupServiceIsRunning(int userId, int timeout)374 void waitUntilBackupServiceIsRunning(int userId, int timeout) 375 throws IOException, InterruptedException { 376 CommonTestUtils.waitUntil( 377 "Backup Manager init timed out", 378 timeout, 379 () -> { 380 String output = getLineString(executeShellCommand("dumpsys backup users")); 381 return output.matches( 382 "Backup Manager is running for users:.* " + userId + "( .*)?"); 383 }); 384 } 385 386 /** 387 * Executes shell command "bmgr --user <id> list transports" and returns {@code true} if the 388 * user has the {@code transport} available. 389 */ userHasBackupTransport(String transport, int userId)390 public boolean userHasBackupTransport(String transport, int userId) throws IOException { 391 String output = 392 getLineString( 393 executeShellCommand( 394 String.format("bmgr --user %d list transports", userId))); 395 for (String t : output.split("\n")) { 396 // Parse out the '*' character used to denote the selected transport. 397 t = t.replace("*", "").trim(); 398 if (transport.equals(t)) { 399 return true; 400 } 401 } 402 return false; 403 } 404 405 /** 406 * Executes shell command "bmgr --user <id> transport <transport>" and returns the old 407 * transport. 408 */ setBackupTransportForUser(String transport, int userId)409 public String setBackupTransportForUser(String transport, int userId) throws IOException { 410 String output = 411 executeShellCommandAndReturnOutput( 412 String.format("bmgr --user %d transport %s", userId, transport)); 413 Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$"); 414 Matcher matcher = pattern.matcher(output); 415 if (matcher.find()) { 416 return matcher.group(1); 417 } else { 418 throw new RuntimeException("Non-parsable output setting bmgr transport: " + output); 419 } 420 } 421 } 422 423