1 /* 2 * Copyright 2019 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 androidx.benchmark.gradle 18 19 import java.util.concurrent.TimeUnit 20 import org.gradle.api.GradleException 21 import org.gradle.api.logging.LogLevel 22 import org.gradle.api.logging.Logger 23 24 /** 25 * Helper class wrapping the adb cli tool. 26 * 27 * Provides an interface to execute adb commands in a way that automatically handles directing 28 * stdout and stderr to gradle output. Typical usage of this class is as input into gradle tasks or 29 * plugins that need to interact with adb. 30 */ 31 class Adb { 32 data class ProcessResult(val exitValue: Int, val stdout: String, val stderr: String) 33 34 private val adbPath: String 35 private val logger: Logger 36 37 constructor(adbPath: String, logger: Logger) { 38 this.adbPath = adbPath 39 this.logger = logger 40 } 41 42 /** 43 * Check if adb shell runs as root by default. 44 * 45 * The most common case for this is when adbd is running as root. 46 * 47 * @return `true` if adb shell runs as root by default, `false` otherwise. 48 */ isAdbdRootnull49 fun isAdbdRoot(): Boolean { 50 val defaultUser = execSync("shell id").stdout 51 return defaultUser.contains("uid=0(root)") 52 } 53 54 /** Check if the `su` binary is installed. */ isSuInstallednull55 fun isSuInstalled(): Boolean { 56 // Not all devices / methods of rooting support su -c, but sh -c is usually supported. 57 // Although the root group is su's default, using syntax different from "su gid cmd", can 58 // cause the adb shell command to hang on some devices. 59 return execSync("shell su 0 sh -c exit", shouldThrow = false).exitValue == 0 60 } 61 execSyncnull62 fun execSync( 63 adbCmd: String, 64 deviceId: String? = null, 65 shouldThrow: Boolean = true, 66 silent: Boolean = false 67 ): ProcessResult { 68 val subCmd = adbCmd.trim().split(Regex("\\s+")).toTypedArray() 69 val adbArgs = if (!deviceId.isNullOrEmpty()) arrayOf("-s", deviceId) else emptyArray() 70 val cmd = arrayOf(adbPath, *adbArgs, *subCmd) 71 72 if (!silent) { 73 logger.log(LogLevel.INFO, cmd.joinToString(" ")) 74 } 75 val process = Runtime.getRuntime().exec(cmd) 76 77 if (!process.waitFor(5, TimeUnit.SECONDS)) { 78 throw GradleException("Timeout waiting for ${cmd.joinToString(" ")}") 79 } 80 81 val stdout = process.inputStream.bufferedReader().use { it.readText() } 82 val stderr = process.errorStream.bufferedReader().use { it.readText() } 83 84 if (!stdout.isBlank() && !silent) { 85 logger.log(LogLevel.QUIET, stdout.trim()) 86 } 87 88 if (!stderr.isBlank() && shouldThrow && !silent) { 89 logger.log(LogLevel.ERROR, stderr.trim()) 90 } 91 92 if (shouldThrow && process.exitValue() != 0) { 93 throw GradleException(stderr) 94 } 95 96 return ProcessResult(process.exitValue(), stdout, stderr) 97 } 98 } 99