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 package android.perftests.utils;
17 
18 import android.app.UiAutomation;
19 import android.os.ParcelFileDescriptor;
20 import android.text.TextUtils;
21 import android.util.AndroidRuntimeException;
22 
23 import androidx.annotation.NonNull;
24 import androidx.test.InstrumentationRegistry;
25 
26 import java.io.FileInputStream;
27 import java.util.concurrent.CountDownLatch;
28 import java.util.concurrent.TimeUnit;
29 import java.util.concurrent.TimeoutException;
30 import java.util.concurrent.atomic.AtomicReference;
31 
32 /**
33  * Provides Shell-based utilities such as running a command.
34  */
35 public final class ShellHelper {
36 
37     /**
38      * Runs a Shell command with a timeout, returning a trimmed response.
39      */
40     @NonNull
runShellCommandWithTimeout(@onNull String command, long timeoutInSecond)41     public static String runShellCommandWithTimeout(@NonNull String command, long timeoutInSecond)
42             throws TimeoutException {
43         AtomicReference<Exception> exception = new AtomicReference<>(null);
44         AtomicReference<String> result = new AtomicReference<>(null);
45 
46         CountDownLatch latch = new CountDownLatch(1);
47 
48         new Thread(() -> {
49             try {
50                 result.set(runShellCommandRaw(command));
51             } catch (Exception e) {
52                 exception.set(e);
53             } finally {
54                 latch.countDown();
55             }
56         }).start();
57 
58         try {
59             if (!latch.await(timeoutInSecond, TimeUnit.SECONDS)) {
60                 throw new TimeoutException("Command: '" + command + "' could not run in "
61                         + timeoutInSecond + " seconds");
62             }
63         } catch (InterruptedException e) {
64             Thread.currentThread().interrupt();
65         }
66 
67         if (exception.get() != null) {
68             throw new AndroidRuntimeException(exception.get());
69         }
70 
71         return result.get();
72     }
73 
74     /**
75      * Runs a Shell command, returning a trimmed response.
76      */
77     @NonNull
runShellCommand(@onNull String template, Object...args)78     public static String runShellCommand(@NonNull String template, Object...args) {
79         String command = String.format(template, args);
80         return runShellCommandRaw(command);
81     }
82 
83     /**
84      * Runs a Shell command, returning a trimmed response.
85      */
86     @NonNull
runShellCommandRaw(@onNull String command)87     public static String runShellCommandRaw(@NonNull String command) {
88         UiAutomation automan = InstrumentationRegistry.getInstrumentation()
89                 .getUiAutomation();
90         ParcelFileDescriptor pfd = automan.executeShellCommand(command);
91         byte[] buf = new byte[512];
92         int bytesRead;
93         try(FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
94             StringBuilder stdout = new StringBuilder();
95             while ((bytesRead = fis.read(buf)) != -1) {
96                 stdout.append(new String(buf, 0, bytesRead));
97             }
98             String result = stdout.toString();
99             return TextUtils.isEmpty(result) ? "" : result.trim();
100         } catch (Exception e) {
101             throw new AndroidRuntimeException("Command '" + command + "' failed: ", e);
102         } finally {
103             // Must disconnect UI automation after every call, otherwise its accessibility service
104             // skews the performance tests.
105             automan.destroy();
106         }
107     }
108 
ShellHelper()109     private ShellHelper() {
110         throw new UnsupportedOperationException("contain static methods only");
111     }
112 }
113