• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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