1 /* 2 * Copyright (C) 2017 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 android.text.TextUtils; 20 import android.util.AndroidRuntimeException; 21 import android.util.Log; 22 import android.view.View; 23 24 import androidx.annotation.NonNull; 25 import androidx.test.InstrumentationRegistry; 26 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 ShellUtils { 36 37 private static final String TAG = "ShellHelper"; 38 39 private static final UserHelper sUserHelper = new UserHelper(); 40 41 /** 42 * Runs a Shell command with a timeout, returning a trimmed response. 43 */ 44 @NonNull runShellCommandWithTimeout(@onNull String command, long timeoutInSecond)45 public static String runShellCommandWithTimeout(@NonNull String command, long timeoutInSecond) 46 throws TimeoutException { 47 AtomicReference<Exception> exception = new AtomicReference<>(null); 48 AtomicReference<String> result = new AtomicReference<>(null); 49 50 CountDownLatch latch = new CountDownLatch(1); 51 52 new Thread(() -> { 53 try { 54 result.set(runShellCommand(command)); 55 } catch (Exception e) { 56 exception.set(e); 57 } finally { 58 latch.countDown(); 59 } 60 }).start(); 61 62 try { 63 if (!latch.await(timeoutInSecond, TimeUnit.SECONDS)) { 64 throw new TimeoutException("Command: '" + command + "' could not run in " 65 + timeoutInSecond + " seconds"); 66 } 67 } catch (InterruptedException e) { 68 Thread.currentThread().interrupt(); 69 } 70 71 if (exception.get() != null) { 72 throw new AndroidRuntimeException(exception.get()); 73 } 74 75 return result.get(); 76 } 77 78 /** 79 * Runs a Shell command, returning a trimmed response. 80 */ 81 @NonNull runShellCommand(@onNull String template, Object...args)82 public static String runShellCommand(@NonNull String template, Object...args) { 83 final String command = String.format(template, args); 84 Log.d(TAG, "runShellCommand(): " + command); 85 try { 86 final String result = SystemUtil 87 .runShellCommand(InstrumentationRegistry.getInstrumentation(), command); 88 return TextUtils.isEmpty(result) ? "" : result.trim(); 89 } catch (Exception e) { 90 throw new RuntimeException("Command '" + command + "' failed: ", e); 91 } 92 } 93 94 /** 95 * Tap on the view center, it may change window focus. 96 */ tap(View view)97 public static void tap(View view) { 98 final int[] xy = new int[2]; 99 view.getLocationOnScreen(xy); 100 final int viewWidth = view.getWidth(); 101 final int viewHeight = view.getHeight(); 102 final int x = (int) (xy[0] + (viewWidth / 2.0f)); 103 final int y = (int) (xy[1] + (viewHeight / 2.0f)); 104 105 runShellCommand("%s tap %d %d", sUserHelper.getInputCmd("touchscreen"), x, y); 106 } 107 ShellUtils()108 private ShellUtils() { 109 throw new UnsupportedOperationException("contain static methods only"); 110 } 111 112 /** 113 * Simulates input of key event. 114 * 115 * @param keyCode key event to fire. 116 */ sendKeyEvent(String keyCode)117 public static void sendKeyEvent(String keyCode) { 118 runShellCommand("%s %s", sUserHelper.getInputCmd("keyevent"), keyCode); 119 } 120 121 /** 122 * Allows an app to draw overlaid windows. 123 */ setOverlayPermissions(@onNull String packageName, boolean allowed)124 public static void setOverlayPermissions(@NonNull String packageName, boolean allowed) { 125 final String action = allowed ? "allow" : "ignore"; 126 runShellCommand("%s %s SYSTEM_ALERT_WINDOW %s", 127 sUserHelper.getAppopsCmd("set"), packageName, action); 128 } 129 } 130