blob: 4bf6abc048c64056b82d72ab155dec8798ffbc61 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.build.gradle.internal.testing
import com.android.build.gradle.internal.SdkComponentsBuildService
import com.android.utils.GrabProcessOutput
import com.android.utils.ILogger
import java.io.File
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import java.util.concurrent.TimeoutException
private const val ADB_TIMEOUT_SEC = 60L
/**
* Helper class for interacting with adb for use with managed virtual devices.
*/
class AdbHelper(
private val versionedSdkLoader: Provider<SdkComponentsBuildService.VersionedSdkLoader>,
private val processFactory: (List<String>) -> ProcessBuilder = { ProcessBuilder(it) }
) {
val adbExecutable: File by lazy {
versionedSdkLoader.get().adbExecutableProvider.get().asFile
}
/**
* Checks whether the emulator with the given serial has successfully booted.
*
* Checks the boot_completed system property and the bootcomplete device property to check
* whether the given device has successfully booted. If the device does not exist or is not
* connected to adb, this method returns false.
*
* @param emulatorSerial the serial identifier for the emulator to be checked.
* @param logger logs the method by which the boot was successfully verified.
*/
fun isBootCompleted(emulatorSerial: String, logger: ILogger): Boolean {
val bootCompleted = AtomicBoolean(false)
getDeviceProperty("sys.boot_completed", emulatorSerial) {
if (it.toIntOrNull() == 1) {
logger.info("sys.boot_completed=1")
bootCompleted.set(true)
}
}
if (bootCompleted.get()) {
return true
}
getDeviceProperty("dev.bootcomplete", emulatorSerial) {
if (it.toIntOrNull() == 1) {
logger.info("dev.bootcomplete=1")
bootCompleted.set(true)
}
}
return bootCompleted.get()
}
/**
* Checks whether the package manager has started for the given device.
*/
fun isPackageManagerStarted (emulatorSerial: String): Boolean {
val result = AtomicBoolean(false)
runAdbShell(emulatorSerial, listOf("/system/bin/pm", "path", "android")) {
if (it.contains("package:")) {
result.set(true)
}
}
return result.get()
}
/**
* Finds the emulator serial with the given idValue.
*
* This is done in a two step process:
*
* 1. Get all serials of the devices attached to adb.
* 2. Query each serial for the id to check against the requested idValue.
*
* At least one serial is expected to have the given [idValue] associated with it. An exception
* is thrown if the serial cannot be found.
*
* @param idValue the id value for the emulator to look for. This is the value passed in with
* the "-id" flag to the emulator command, or the name of the avd launched if no value is
* passed in.
* @return The serial of the emulator that can be used for subsequent adb commands. See
* [isBootCompleted], [killDevice].
*/
fun findDeviceSerialWithId(idValue: String): String {
val serials = allSerials()
for (serial in serials) {
if (getIdForSerial(serial) == idValue) {
return serial
}
}
error("Failed to find serial for device id: $idValue")
}
/**
* Returns the list of all emulator serials whose id value starts with [idPrefix]
*
* This is done in a two step process:
*
* 1. Get all serials of the devices attached to adb.
* 2. Query each serial for the id to check if it starts with [idPrefix]
*
* @param idPrefix the prefix to check each emulator id against.
* @return a list of all serials that match the [idPrefix], an empty list if none are found.
*/
fun findAllDeviceSerialsWithIdPrefix(idPrefix: String): List<String> =
allSerials().filter { serial ->
val id = getIdForSerial(serial)
id != null && id.startsWith(idPrefix)
}
/**
* Closes the given device via adb using the "emu kill" command.
*/
fun killDevice(serial: String) {
val killProcess = processFactory(
listOf(
adbExecutable.absolutePath,
"-s",
serial,
"emu",
"kill"
)
).start()
killProcess.waitFor()
}
private fun allSerials(): List<String> {
val serials = mutableListOf<String>()
val listDevicesProcess = processFactory(
listOf(
adbExecutable.absolutePath,
"devices"
)
).start()
try {
runWithTimeout(ADB_TIMEOUT_SEC) {
GrabProcessOutput.grabProcessOutput(
listDevicesProcess,
GrabProcessOutput.Wait.WAIT_FOR_READERS,
object : GrabProcessOutput.IProcessOutput {
override fun out(line: String?) {
line ?: return
val trimmed = line.trim()
val values = trimmed.split("\\s+".toRegex())
// Looking for "<serial> device"
if (values.size == 2 && values[1] == "device") {
serials.add(values[0])
}
}
override fun err(line: String?) {}
}
)
listDevicesProcess.waitFor()
}
} catch (e: TimeoutException) {
listDevicesProcess.destroy()
listDevicesProcess.waitFor()
error("Adb device retrieval timed out. Failed to destroy emulator properly")
}
return serials
}
private fun getIdForSerial(serial: String): String? {
var id: String? = null
val idDetectionProcess = processFactory(
listOf(
adbExecutable.absolutePath,
"-s",
serial,
"emu",
"avd",
"id"
)
).start()
try {
runWithTimeout(ADB_TIMEOUT_SEC) {
GrabProcessOutput.grabProcessOutput(
idDetectionProcess,
GrabProcessOutput.Wait.WAIT_FOR_READERS,
object : GrabProcessOutput.IProcessOutput {
override fun out(line: String?) {
line ?: return
val trimmed = line.trim()
if (trimmed.isNotEmpty() && trimmed != "OK") {
id = trimmed
}
}
override fun err(line: String?) {}
}
)
idDetectionProcess.waitFor()
}
} catch (e: TimeoutException) {
// If we fail to retrieve the id, simply return null. This should not be an error.
idDetectionProcess.destroy()
idDetectionProcess.waitFor()
}
return id
}
private fun getDeviceProperty(
propertyName: String,
emulatorSerial: String,
stdoutTextProcessor: (String)->Unit) {
runAdbShell(
emulatorSerial,
listOf("getprop", propertyName),
stdoutTextProcessor
)
}
private fun runAdbShell(
emulatorSerial: String,
shellCommandArgs: List<String>,
stdoutTextProcessor: (String)->Unit) {
val getPropProcess = processFactory(
listOf(
adbExecutable.absolutePath,
"-s",
emulatorSerial,
"shell",
) + shellCommandArgs
).start()
GrabProcessOutput.grabProcessOutput(
getPropProcess,
GrabProcessOutput.Wait.WAIT_FOR_READERS,
object : GrabProcessOutput.IProcessOutput {
override fun out(line: String?) {
line ?: return
stdoutTextProcessor(line.trim())
}
override fun err(line: String?) {}
}
)
}
private fun <T> runWithTimeout(timeoutSeconds: Long, function: () -> T): T {
val executor = Executors.newSingleThreadExecutor()
val future = executor.submit(function)
try {
return future.get(timeoutSeconds, TimeUnit.SECONDS)
} catch (e: TimeoutException) {
future.cancel(true)
throw e
} finally {
executor.shutdown()
}
}
}