blob: 323c3001ea59b1a6a3f722a1fa14fe6cbcdbd4b1 [file] [log] [blame]
#
# Copyright (C) 2024 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.
#
import math
import os
import subprocess
import time
from validation_error import ValidationError
ADB_ROOT_TIMED_OUT_LIMIT_SECS = 5
ADB_BOOT_COMPLETED_TIMED_OUT_LIMIT_SECS = 30
POLLING_INTERVAL_SECS = 0.5
SIMPLEPERF_TRACE_FILE = "/data/misc/perfetto-traces/perf.data"
class AdbDevice:
"""
Class representing a device. APIs interact with the current device through
the adb bridge.
"""
def __init__(self, serial):
self.serial = serial
@staticmethod
def get_adb_devices():
"""
Returns a list of devices connected to the adb bridge.
The output of the command 'adb devices' is expected to be of the form:
List of devices attached
SOMEDEVICE1234 device
device2:5678 device
"""
command_output = subprocess.run(["adb", "devices"], capture_output=True)
output_lines = command_output.stdout.decode("utf-8").split("\n")
devices = []
for line in output_lines[:-2]:
if line[0] == "*" or line == "List of devices attached":
continue
words_in_line = line.split('\t')
if words_in_line[1] == "device":
devices.append(words_in_line[0])
return devices
def check_device_connection(self):
devices = self.get_adb_devices()
if len(devices) == 0:
return ValidationError("There are currently no devices connected.", None)
if self.serial is not None:
if self.serial not in devices:
return ValidationError(("Device with serial %s is not connected."
% self.serial), None)
elif "ANDROID_SERIAL" in os.environ:
if os.environ["ANDROID_SERIAL"] not in devices:
return ValidationError(("Device with serial %s is set as environment"
" variable, ANDROID_SERIAL, but is not"
" connected."
% os.environ["ANDROID_SERIAL"]), None)
self.serial = os.environ["ANDROID_SERIAL"]
elif len(devices) == 1:
self.serial = devices[0]
else:
return ValidationError(("There is more than one device currently"
" connected."),
("Run one of the following commands to choose one"
" of the connected devices:\n\t torq --serial %s"
% "\n\t torq --serial ".join(devices)))
return None
@staticmethod
def poll_is_task_completed(timed_out_limit, interval, check_is_completed):
start_time = time.time()
while True:
time.sleep(interval)
if check_is_completed():
return True
if time.time() - start_time > timed_out_limit:
return False
def root_device(self):
subprocess.run(["adb", "-s", self.serial, "root"])
if not self.poll_is_task_completed(ADB_ROOT_TIMED_OUT_LIMIT_SECS,
POLLING_INTERVAL_SECS,
lambda: self.serial in
self.get_adb_devices()):
raise Exception(("Device with serial %s took too long to reconnect after"
" being rooted." % self.serial))
def remove_file(self, file_path):
subprocess.run(["adb", "-s", self.serial, "shell", "rm", "-f", file_path])
def start_perfetto_trace(self, config):
return subprocess.Popen(("adb -s %s shell perfetto -c - --txt -o"
" /data/misc/perfetto-traces/"
"trace.perfetto-trace %s"
% (self.serial, config)), shell=True)
def start_simpleperf_trace(self, command):
events_param = "-e " + ",".join(command.simpleperf_event)
return subprocess.Popen(("adb -s %s shell simpleperf record -a -f 1000 "
"--post-unwind=yes -m 8192 -g --duration %d"
" %s -o %s"
% (self.serial,
int(math.ceil(command.dur_ms/1000)),
events_param, SIMPLEPERF_TRACE_FILE)),
shell=True)
def pull_file(self, file_path, host_file):
subprocess.run(["adb", "-s", self.serial, "pull", file_path, host_file])
def get_all_users(self):
command_output = subprocess.run(["adb", "-s", self.serial, "shell", "pm",
"list", "users"], capture_output=True)
output_lines = command_output.stdout.decode("utf-8").split("\n")[1:-1]
return [int((line.split("{", 1)[1]).split(":", 1)[0]) for line in
output_lines]
def user_exists(self, user):
users = self.get_all_users()
if user not in users:
return ValidationError(("User ID %s does not exist on device with serial"
" %s." % (user, self.serial)),
("Select from one of the following user IDs on"
" device with serial %s: %s"
% (self.serial, ", ".join(map(str, users)))))
return None
def get_current_user(self):
command_output = subprocess.run(["adb", "-s", self.serial, "shell", "am",
"get-current-user"], capture_output=True)
return int(command_output.stdout.decode("utf-8").split()[0])
def perform_user_switch(self, user):
subprocess.run(["adb", "-s", self.serial, "shell", "am", "switch-user",
str(user)])
def write_to_file(self, file_path, host_file_string):
subprocess.run(("adb -s %s shell 'cat > %s %s'"
% (self.serial, file_path, host_file_string)), shell=True)
def set_prop(self, prop, value):
subprocess.run(["adb", "-s", self.serial, "shell", "setprop", prop, value])
def reboot(self):
subprocess.run(["adb", "-s", self.serial, "reboot"])
if not self.poll_is_task_completed(ADB_ROOT_TIMED_OUT_LIMIT_SECS,
POLLING_INTERVAL_SECS,
lambda: self.serial not in
self.get_adb_devices()):
raise Exception(("Device with serial %s took too long to start"
" rebooting." % self.serial))
def wait_for_device(self):
subprocess.run(["adb", "-s", self.serial, "wait-for-device"])
def is_boot_completed(self):
command_output = subprocess.run(["adb", "-s", self.serial, "shell",
"getprop", "sys.boot_completed"],
capture_output=True)
return command_output.stdout.decode("utf-8").strip() == "1"
def wait_for_boot_to_complete(self):
if not self.poll_is_task_completed(ADB_BOOT_COMPLETED_TIMED_OUT_LIMIT_SECS,
POLLING_INTERVAL_SECS,
self.is_boot_completed):
raise Exception(("Device with serial %s took too long to finish"
" rebooting." % self.serial))
def get_packages(self):
return [package.removeprefix("package:") for package in subprocess.run(
["adb", "-s", self.serial, "shell", "pm", "list", "packages"],
capture_output=True).stdout.decode("utf-8").splitlines()]
def get_pid(self, package):
return subprocess.run("adb -s %s shell pidof %s" % (self.serial, package),
shell=True, capture_output=True
).stdout.decode("utf-8").split("\n")[0]
def is_package_running(self, package):
return self.get_pid(package) != ""
def start_package(self, package):
if subprocess.run(
["adb", "-s", self.serial, "shell", "am", "start", package],
capture_output=True).stderr.decode("utf-8").split("\n")[0] != "":
return ValidationError(("Cannot start package %s on device with"
" serial %s because %s is a service package,"
" which doesn't implement a MAIN activity."
% (package, self.serial, package)), None)
return None
def kill_pid(self, package):
pid = self.get_pid(package)
if pid != "":
subprocess.run(["adb", "-s", self.serial, "shell", "kill", "-9", pid])
def force_stop_package(self, package):
subprocess.run(["adb", "-s", self.serial, "shell", "am", "force-stop",
package])
def get_prop(self, prop):
return subprocess.run(
["adb", "-s", self.serial, "shell", "getprop", prop],
capture_output=True).stdout.decode("utf-8").split("\n")[0]
def get_android_sdk_version(self):
return int(self.get_prop("ro.build.version.sdk"))
def simpleperf_event_exists(self, simpleperf_events):
events_copy = simpleperf_events.copy()
grep_command = "grep"
for event in simpleperf_events:
grep_command += " -e " + event.lower()
output = subprocess.run(["adb", "-s", self.serial, "shell",
"simpleperf", "list", "|", grep_command],
capture_output=True)
if output is None or len(output.stdout) == 0:
raise Exception("Error while validating simpleperf events.")
lines = output.stdout.decode("utf-8").split("\n")
# Anything that does not start with two spaces is not a command.
# Any command with a space will have the command before the first space.
for line in lines:
if len(line) <= 3 or line[:2] != " " or line[2] == "#":
# Line doesn't contain a simpleperf event
continue
event = line[2:].split(" ")[0]
if event in events_copy:
events_copy.remove(event)
if len(events_copy) == 0:
# All of the events exist, exit early
break
if len(events_copy) > 0:
return ValidationError("The following simpleperf event(s) are invalid:"
" %s."
% events_copy,
"Run adb shell simpleperf list to"
" see valid simpleperf events.")
return None