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 long BACKUP_SERVICE_INIT_TIMEOUT_SECS = TimeUnit.MINUTES.toSeconds(2); 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 /** 260 * Execute shell command and return output from this command. 261 */ executeShellCommandAndReturnOutput(String command)262 public String executeShellCommandAndReturnOutput(String command) throws IOException { 263 InputStream in = executeShellCommand(command); 264 BufferedReader br = new BufferedReader( 265 new InputStreamReader(in, StandardCharsets.UTF_8)); 266 String str; 267 StringBuilder out = new StringBuilder(); 268 while ((str = br.readLine()) != null) { 269 out.append(str).append("\n"); 270 } 271 return out.toString(); 272 } 273 274 // Copied over from BackupQuotaTest enableBackup(boolean enable)275 public boolean enableBackup(boolean enable) throws Exception { 276 boolean previouslyEnabled; 277 String output = getLineString(executeShellCommand("bmgr enabled")); 278 Matcher matcher = BACKUP_MANAGER_CURRENTLY_ENABLE_STATUS_PATTERN.matcher(output.trim()); 279 if (matcher.find()) { 280 previouslyEnabled = "enabled".equals(matcher.group(1)); 281 } else { 282 throw new RuntimeException("non-parsable output setting bmgr enabled: " + output); 283 } 284 285 executeShellCommand("bmgr enable " + enable); 286 return previouslyEnabled; 287 } 288 289 /** 290 * Execute shell command "bmgr --user <id> enable <enable> and return previous enabled state. 291 */ enableBackupForUser(boolean enable, int userId)292 public boolean enableBackupForUser(boolean enable, int userId) throws IOException { 293 boolean previouslyEnabled = isBackupEnabledForUser(userId); 294 executeShellCommand(String.format("bmgr --user %d enable %b", userId, enable)); 295 return previouslyEnabled; 296 } 297 298 /** Execute shell command "bmgr --user <id> activate <activate>." */ activateBackupForUser(boolean activate, int userId)299 public boolean activateBackupForUser(boolean activate, int userId) throws IOException { 300 boolean previouslyActivated = isBackupActivatedForUser(userId); 301 executeShellCommandSync(String.format("bmgr --user %d activate %b", userId, activate)); 302 return previouslyActivated; 303 } 304 305 /** 306 * Executes shell command "bmgr --user <id> activated" and returns if backup is activated for 307 * the user {@code userId}. 308 */ isBackupActivatedForUser(int userId)309 public boolean isBackupActivatedForUser(int userId) throws IOException { 310 return getShellCommandOutput(String.format("bmgr --user %d activated", userId)) 311 .contains("currently activated"); 312 } 313 getLineString(InputStream inputStream)314 private String getLineString(InputStream inputStream) throws IOException { 315 BufferedReader reader = 316 new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); 317 String str; 318 try { 319 str = reader.readLine(); 320 } finally { 321 StreamUtil.drainAndClose(reader); 322 } 323 return str; 324 } 325 waitForBackupInitialization()326 public void waitForBackupInitialization() throws IOException { 327 long tryUntilNanos = System.nanoTime() 328 + TimeUnit.SECONDS.toNanos(BACKUP_PROVISIONING_TIMEOUT_SECONDS); 329 while (System.nanoTime() < tryUntilNanos) { 330 String output = getLineString(executeShellCommand("dumpsys backup")); 331 if (output.matches(MATCH_LINE_BACKUP_MANAGER_IS_NOT_PENDING_INIT)) { 332 return; 333 } 334 try { 335 Thread.sleep(TimeUnit.SECONDS.toMillis(BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS)); 336 } catch (InterruptedException e) { 337 Thread.currentThread().interrupt(); 338 break; 339 } 340 } 341 throw new IOException("Timed out waiting for backup initialization"); 342 } 343 waitUntilBackupServiceIsRunning(int userId)344 public void waitUntilBackupServiceIsRunning(int userId) 345 throws IOException, InterruptedException { 346 waitUntilBackupServiceIsRunning(userId, BACKUP_SERVICE_INIT_TIMEOUT_SECS); 347 } 348 349 @VisibleForTesting waitUntilBackupServiceIsRunning(int userId, long timeout)350 void waitUntilBackupServiceIsRunning(int userId, long timeout) 351 throws IOException, InterruptedException { 352 CommonTestUtils.waitUntil( 353 "Backup Manager init timed out", 354 timeout, 355 () -> { 356 String output = getLineString(executeShellCommand("dumpsys backup users")); 357 return output.matches( 358 "Backup Manager is running for users:.* " + userId + "( .*)?"); 359 }); 360 } 361 362 /** 363 * Executes shell command "bmgr --user <id> list transports" and returns {@code true} if the 364 * user has the {@code transport} available. 365 */ userHasBackupTransport(String transport, int userId)366 public boolean userHasBackupTransport(String transport, int userId) throws IOException { 367 String output = 368 getLineString( 369 executeShellCommand( 370 String.format("bmgr --user %d list transports", userId))); 371 for (String t : output.split("\n")) { 372 // Parse out the '*' character used to denote the selected transport. 373 t = t.replace("*", "").trim(); 374 if (transport.equals(t)) { 375 return true; 376 } 377 } 378 return false; 379 } 380 381 /** 382 * Executes shell command "bmgr --user <id> transport <transport>" and returns the old 383 * transport. 384 */ setBackupTransportForUser(String transport, int userId)385 public String setBackupTransportForUser(String transport, int userId) throws IOException { 386 String output = 387 executeShellCommandAndReturnOutput( 388 String.format("bmgr --user %d transport %s", userId, transport)); 389 Pattern pattern = Pattern.compile("\\(formerly (.*)\\)$"); 390 Matcher matcher = pattern.matcher(output); 391 if (matcher.find()) { 392 return matcher.group(1); 393 } else { 394 throw new RuntimeException("Non-parsable output setting bmgr transport: " + output); 395 } 396 } 397 } 398 399