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