1*288bf522SAndroid Build Coastguard Worker# 2*288bf522SAndroid Build Coastguard Worker# Copyright (C) 2024 The Android Open Source Project 3*288bf522SAndroid Build Coastguard Worker# 4*288bf522SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*288bf522SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*288bf522SAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*288bf522SAndroid Build Coastguard Worker# 8*288bf522SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*288bf522SAndroid Build Coastguard Worker# 10*288bf522SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*288bf522SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*288bf522SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*288bf522SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*288bf522SAndroid Build Coastguard Worker# limitations under the License. 15*288bf522SAndroid Build Coastguard Worker# 16*288bf522SAndroid Build Coastguard Worker 17*288bf522SAndroid Build Coastguard Workerimport math 18*288bf522SAndroid Build Coastguard Workerimport os 19*288bf522SAndroid Build Coastguard Workerimport subprocess 20*288bf522SAndroid Build Coastguard Workerimport time 21*288bf522SAndroid Build Coastguard Worker 22*288bf522SAndroid Build Coastguard Workerfrom validation_error import ValidationError 23*288bf522SAndroid Build Coastguard Worker 24*288bf522SAndroid Build Coastguard WorkerADB_ROOT_TIMED_OUT_LIMIT_SECS = 5 25*288bf522SAndroid Build Coastguard WorkerADB_BOOT_COMPLETED_TIMED_OUT_LIMIT_SECS = 30 26*288bf522SAndroid Build Coastguard WorkerPOLLING_INTERVAL_SECS = 0.5 27*288bf522SAndroid Build Coastguard WorkerSIMPLEPERF_TRACE_FILE = "/data/misc/perfetto-traces/perf.data" 28*288bf522SAndroid Build Coastguard Worker 29*288bf522SAndroid Build Coastguard Workerclass AdbDevice: 30*288bf522SAndroid Build Coastguard Worker """ 31*288bf522SAndroid Build Coastguard Worker Class representing a device. APIs interact with the current device through 32*288bf522SAndroid Build Coastguard Worker the adb bridge. 33*288bf522SAndroid Build Coastguard Worker """ 34*288bf522SAndroid Build Coastguard Worker def __init__(self, serial): 35*288bf522SAndroid Build Coastguard Worker self.serial = serial 36*288bf522SAndroid Build Coastguard Worker 37*288bf522SAndroid Build Coastguard Worker @staticmethod 38*288bf522SAndroid Build Coastguard Worker def get_adb_devices(): 39*288bf522SAndroid Build Coastguard Worker """ 40*288bf522SAndroid Build Coastguard Worker Returns a list of devices connected to the adb bridge. 41*288bf522SAndroid Build Coastguard Worker The output of the command 'adb devices' is expected to be of the form: 42*288bf522SAndroid Build Coastguard Worker List of devices attached 43*288bf522SAndroid Build Coastguard Worker SOMEDEVICE1234 device 44*288bf522SAndroid Build Coastguard Worker device2:5678 device 45*288bf522SAndroid Build Coastguard Worker """ 46*288bf522SAndroid Build Coastguard Worker command_output = subprocess.run(["adb", "devices"], capture_output=True) 47*288bf522SAndroid Build Coastguard Worker output_lines = command_output.stdout.decode("utf-8").split("\n") 48*288bf522SAndroid Build Coastguard Worker devices = [] 49*288bf522SAndroid Build Coastguard Worker for line in output_lines[:-2]: 50*288bf522SAndroid Build Coastguard Worker if line[0] == "*" or line == "List of devices attached": 51*288bf522SAndroid Build Coastguard Worker continue 52*288bf522SAndroid Build Coastguard Worker words_in_line = line.split('\t') 53*288bf522SAndroid Build Coastguard Worker if words_in_line[1] == "device": 54*288bf522SAndroid Build Coastguard Worker devices.append(words_in_line[0]) 55*288bf522SAndroid Build Coastguard Worker return devices 56*288bf522SAndroid Build Coastguard Worker 57*288bf522SAndroid Build Coastguard Worker def check_device_connection(self): 58*288bf522SAndroid Build Coastguard Worker devices = self.get_adb_devices() 59*288bf522SAndroid Build Coastguard Worker if len(devices) == 0: 60*288bf522SAndroid Build Coastguard Worker return ValidationError("There are currently no devices connected.", None) 61*288bf522SAndroid Build Coastguard Worker if self.serial is not None: 62*288bf522SAndroid Build Coastguard Worker if self.serial not in devices: 63*288bf522SAndroid Build Coastguard Worker return ValidationError(("Device with serial %s is not connected." 64*288bf522SAndroid Build Coastguard Worker % self.serial), None) 65*288bf522SAndroid Build Coastguard Worker elif "ANDROID_SERIAL" in os.environ: 66*288bf522SAndroid Build Coastguard Worker if os.environ["ANDROID_SERIAL"] not in devices: 67*288bf522SAndroid Build Coastguard Worker return ValidationError(("Device with serial %s is set as environment" 68*288bf522SAndroid Build Coastguard Worker " variable, ANDROID_SERIAL, but is not" 69*288bf522SAndroid Build Coastguard Worker " connected." 70*288bf522SAndroid Build Coastguard Worker % os.environ["ANDROID_SERIAL"]), None) 71*288bf522SAndroid Build Coastguard Worker self.serial = os.environ["ANDROID_SERIAL"] 72*288bf522SAndroid Build Coastguard Worker elif len(devices) == 1: 73*288bf522SAndroid Build Coastguard Worker self.serial = devices[0] 74*288bf522SAndroid Build Coastguard Worker else: 75*288bf522SAndroid Build Coastguard Worker return ValidationError(("There is more than one device currently" 76*288bf522SAndroid Build Coastguard Worker " connected."), 77*288bf522SAndroid Build Coastguard Worker ("Run one of the following commands to choose one" 78*288bf522SAndroid Build Coastguard Worker " of the connected devices:\n\t torq --serial %s" 79*288bf522SAndroid Build Coastguard Worker % "\n\t torq --serial ".join(devices))) 80*288bf522SAndroid Build Coastguard Worker return None 81*288bf522SAndroid Build Coastguard Worker 82*288bf522SAndroid Build Coastguard Worker @staticmethod 83*288bf522SAndroid Build Coastguard Worker def poll_is_task_completed(timed_out_limit, interval, check_is_completed): 84*288bf522SAndroid Build Coastguard Worker start_time = time.time() 85*288bf522SAndroid Build Coastguard Worker while True: 86*288bf522SAndroid Build Coastguard Worker time.sleep(interval) 87*288bf522SAndroid Build Coastguard Worker if check_is_completed(): 88*288bf522SAndroid Build Coastguard Worker return True 89*288bf522SAndroid Build Coastguard Worker if time.time() - start_time > timed_out_limit: 90*288bf522SAndroid Build Coastguard Worker return False 91*288bf522SAndroid Build Coastguard Worker 92*288bf522SAndroid Build Coastguard Worker def root_device(self): 93*288bf522SAndroid Build Coastguard Worker subprocess.run(["adb", "-s", self.serial, "root"]) 94*288bf522SAndroid Build Coastguard Worker if not self.poll_is_task_completed(ADB_ROOT_TIMED_OUT_LIMIT_SECS, 95*288bf522SAndroid Build Coastguard Worker POLLING_INTERVAL_SECS, 96*288bf522SAndroid Build Coastguard Worker lambda: self.serial in 97*288bf522SAndroid Build Coastguard Worker self.get_adb_devices()): 98*288bf522SAndroid Build Coastguard Worker raise Exception(("Device with serial %s took too long to reconnect after" 99*288bf522SAndroid Build Coastguard Worker " being rooted." % self.serial)) 100*288bf522SAndroid Build Coastguard Worker 101*288bf522SAndroid Build Coastguard Worker def remove_file(self, file_path): 102*288bf522SAndroid Build Coastguard Worker subprocess.run(["adb", "-s", self.serial, "shell", "rm", "-f", file_path]) 103*288bf522SAndroid Build Coastguard Worker 104*288bf522SAndroid Build Coastguard Worker def start_perfetto_trace(self, config): 105*288bf522SAndroid Build Coastguard Worker return subprocess.Popen(("adb -s %s shell perfetto -c - --txt -o" 106*288bf522SAndroid Build Coastguard Worker " /data/misc/perfetto-traces/" 107*288bf522SAndroid Build Coastguard Worker "trace.perfetto-trace %s" 108*288bf522SAndroid Build Coastguard Worker % (self.serial, config)), shell=True) 109*288bf522SAndroid Build Coastguard Worker 110*288bf522SAndroid Build Coastguard Worker def start_simpleperf_trace(self, command): 111*288bf522SAndroid Build Coastguard Worker events_param = "-e " + ",".join(command.simpleperf_event) 112*288bf522SAndroid Build Coastguard Worker return subprocess.Popen(("adb -s %s shell simpleperf record -a -f 1000 " 113*288bf522SAndroid Build Coastguard Worker "--post-unwind=yes -m 8192 -g --duration %d" 114*288bf522SAndroid Build Coastguard Worker " %s -o %s" 115*288bf522SAndroid Build Coastguard Worker % (self.serial, 116*288bf522SAndroid Build Coastguard Worker int(math.ceil(command.dur_ms/1000)), 117*288bf522SAndroid Build Coastguard Worker events_param, SIMPLEPERF_TRACE_FILE)), 118*288bf522SAndroid Build Coastguard Worker shell=True) 119*288bf522SAndroid Build Coastguard Worker 120*288bf522SAndroid Build Coastguard Worker def pull_file(self, file_path, host_file): 121*288bf522SAndroid Build Coastguard Worker subprocess.run(["adb", "-s", self.serial, "pull", file_path, host_file]) 122*288bf522SAndroid Build Coastguard Worker 123*288bf522SAndroid Build Coastguard Worker def get_all_users(self): 124*288bf522SAndroid Build Coastguard Worker command_output = subprocess.run(["adb", "-s", self.serial, "shell", "pm", 125*288bf522SAndroid Build Coastguard Worker "list", "users"], capture_output=True) 126*288bf522SAndroid Build Coastguard Worker output_lines = command_output.stdout.decode("utf-8").split("\n")[1:-1] 127*288bf522SAndroid Build Coastguard Worker return [int((line.split("{", 1)[1]).split(":", 1)[0]) for line in 128*288bf522SAndroid Build Coastguard Worker output_lines] 129*288bf522SAndroid Build Coastguard Worker 130*288bf522SAndroid Build Coastguard Worker def user_exists(self, user): 131*288bf522SAndroid Build Coastguard Worker users = self.get_all_users() 132*288bf522SAndroid Build Coastguard Worker if user not in users: 133*288bf522SAndroid Build Coastguard Worker return ValidationError(("User ID %s does not exist on device with serial" 134*288bf522SAndroid Build Coastguard Worker " %s." % (user, self.serial)), 135*288bf522SAndroid Build Coastguard Worker ("Select from one of the following user IDs on" 136*288bf522SAndroid Build Coastguard Worker " device with serial %s: %s" 137*288bf522SAndroid Build Coastguard Worker % (self.serial, ", ".join(map(str, users))))) 138*288bf522SAndroid Build Coastguard Worker return None 139*288bf522SAndroid Build Coastguard Worker 140*288bf522SAndroid Build Coastguard Worker def get_current_user(self): 141*288bf522SAndroid Build Coastguard Worker command_output = subprocess.run(["adb", "-s", self.serial, "shell", "am", 142*288bf522SAndroid Build Coastguard Worker "get-current-user"], capture_output=True) 143*288bf522SAndroid Build Coastguard Worker return int(command_output.stdout.decode("utf-8").split()[0]) 144*288bf522SAndroid Build Coastguard Worker 145*288bf522SAndroid Build Coastguard Worker def perform_user_switch(self, user): 146*288bf522SAndroid Build Coastguard Worker subprocess.run(["adb", "-s", self.serial, "shell", "am", "switch-user", 147*288bf522SAndroid Build Coastguard Worker str(user)]) 148*288bf522SAndroid Build Coastguard Worker 149*288bf522SAndroid Build Coastguard Worker def write_to_file(self, file_path, host_file_string): 150*288bf522SAndroid Build Coastguard Worker subprocess.run(("adb -s %s shell 'cat > %s %s'" 151*288bf522SAndroid Build Coastguard Worker % (self.serial, file_path, host_file_string)), shell=True) 152*288bf522SAndroid Build Coastguard Worker 153*288bf522SAndroid Build Coastguard Worker def set_prop(self, prop, value): 154*288bf522SAndroid Build Coastguard Worker subprocess.run(["adb", "-s", self.serial, "shell", "setprop", prop, value]) 155*288bf522SAndroid Build Coastguard Worker 156*288bf522SAndroid Build Coastguard Worker def reboot(self): 157*288bf522SAndroid Build Coastguard Worker subprocess.run(["adb", "-s", self.serial, "reboot"]) 158*288bf522SAndroid Build Coastguard Worker if not self.poll_is_task_completed(ADB_ROOT_TIMED_OUT_LIMIT_SECS, 159*288bf522SAndroid Build Coastguard Worker POLLING_INTERVAL_SECS, 160*288bf522SAndroid Build Coastguard Worker lambda: self.serial not in 161*288bf522SAndroid Build Coastguard Worker self.get_adb_devices()): 162*288bf522SAndroid Build Coastguard Worker raise Exception(("Device with serial %s took too long to start" 163*288bf522SAndroid Build Coastguard Worker " rebooting." % self.serial)) 164*288bf522SAndroid Build Coastguard Worker 165*288bf522SAndroid Build Coastguard Worker def wait_for_device(self): 166*288bf522SAndroid Build Coastguard Worker subprocess.run(["adb", "-s", self.serial, "wait-for-device"]) 167*288bf522SAndroid Build Coastguard Worker 168*288bf522SAndroid Build Coastguard Worker def is_boot_completed(self): 169*288bf522SAndroid Build Coastguard Worker command_output = subprocess.run(["adb", "-s", self.serial, "shell", 170*288bf522SAndroid Build Coastguard Worker "getprop", "sys.boot_completed"], 171*288bf522SAndroid Build Coastguard Worker capture_output=True) 172*288bf522SAndroid Build Coastguard Worker return command_output.stdout.decode("utf-8").strip() == "1" 173*288bf522SAndroid Build Coastguard Worker 174*288bf522SAndroid Build Coastguard Worker def wait_for_boot_to_complete(self): 175*288bf522SAndroid Build Coastguard Worker if not self.poll_is_task_completed(ADB_BOOT_COMPLETED_TIMED_OUT_LIMIT_SECS, 176*288bf522SAndroid Build Coastguard Worker POLLING_INTERVAL_SECS, 177*288bf522SAndroid Build Coastguard Worker self.is_boot_completed): 178*288bf522SAndroid Build Coastguard Worker raise Exception(("Device with serial %s took too long to finish" 179*288bf522SAndroid Build Coastguard Worker " rebooting." % self.serial)) 180*288bf522SAndroid Build Coastguard Worker 181*288bf522SAndroid Build Coastguard Worker def get_packages(self): 182*288bf522SAndroid Build Coastguard Worker return [package.removeprefix("package:") for package in subprocess.run( 183*288bf522SAndroid Build Coastguard Worker ["adb", "-s", self.serial, "shell", "pm", "list", "packages"], 184*288bf522SAndroid Build Coastguard Worker capture_output=True).stdout.decode("utf-8").splitlines()] 185*288bf522SAndroid Build Coastguard Worker 186*288bf522SAndroid Build Coastguard Worker def get_pid(self, package): 187*288bf522SAndroid Build Coastguard Worker return subprocess.run("adb -s %s shell pidof %s" % (self.serial, package), 188*288bf522SAndroid Build Coastguard Worker shell=True, capture_output=True 189*288bf522SAndroid Build Coastguard Worker ).stdout.decode("utf-8").split("\n")[0] 190*288bf522SAndroid Build Coastguard Worker 191*288bf522SAndroid Build Coastguard Worker def is_package_running(self, package): 192*288bf522SAndroid Build Coastguard Worker return self.get_pid(package) != "" 193*288bf522SAndroid Build Coastguard Worker 194*288bf522SAndroid Build Coastguard Worker def start_package(self, package): 195*288bf522SAndroid Build Coastguard Worker if subprocess.run( 196*288bf522SAndroid Build Coastguard Worker ["adb", "-s", self.serial, "shell", "am", "start", package], 197*288bf522SAndroid Build Coastguard Worker capture_output=True).stderr.decode("utf-8").split("\n")[0] != "": 198*288bf522SAndroid Build Coastguard Worker return ValidationError(("Cannot start package %s on device with" 199*288bf522SAndroid Build Coastguard Worker " serial %s because %s is a service package," 200*288bf522SAndroid Build Coastguard Worker " which doesn't implement a MAIN activity." 201*288bf522SAndroid Build Coastguard Worker % (package, self.serial, package)), None) 202*288bf522SAndroid Build Coastguard Worker return None 203*288bf522SAndroid Build Coastguard Worker 204*288bf522SAndroid Build Coastguard Worker def kill_pid(self, package): 205*288bf522SAndroid Build Coastguard Worker pid = self.get_pid(package) 206*288bf522SAndroid Build Coastguard Worker if pid != "": 207*288bf522SAndroid Build Coastguard Worker subprocess.run(["adb", "-s", self.serial, "shell", "kill", "-9", pid]) 208*288bf522SAndroid Build Coastguard Worker 209*288bf522SAndroid Build Coastguard Worker def force_stop_package(self, package): 210*288bf522SAndroid Build Coastguard Worker subprocess.run(["adb", "-s", self.serial, "shell", "am", "force-stop", 211*288bf522SAndroid Build Coastguard Worker package]) 212*288bf522SAndroid Build Coastguard Worker 213*288bf522SAndroid Build Coastguard Worker def get_prop(self, prop): 214*288bf522SAndroid Build Coastguard Worker return subprocess.run( 215*288bf522SAndroid Build Coastguard Worker ["adb", "-s", self.serial, "shell", "getprop", prop], 216*288bf522SAndroid Build Coastguard Worker capture_output=True).stdout.decode("utf-8").split("\n")[0] 217*288bf522SAndroid Build Coastguard Worker 218*288bf522SAndroid Build Coastguard Worker def get_android_sdk_version(self): 219*288bf522SAndroid Build Coastguard Worker return int(self.get_prop("ro.build.version.sdk")) 220*288bf522SAndroid Build Coastguard Worker 221*288bf522SAndroid Build Coastguard Worker def simpleperf_event_exists(self, simpleperf_events): 222*288bf522SAndroid Build Coastguard Worker events_copy = simpleperf_events.copy() 223*288bf522SAndroid Build Coastguard Worker grep_command = "grep" 224*288bf522SAndroid Build Coastguard Worker for event in simpleperf_events: 225*288bf522SAndroid Build Coastguard Worker grep_command += " -e " + event.lower() 226*288bf522SAndroid Build Coastguard Worker 227*288bf522SAndroid Build Coastguard Worker output = subprocess.run(["adb", "-s", self.serial, "shell", 228*288bf522SAndroid Build Coastguard Worker "simpleperf", "list", "|", grep_command], 229*288bf522SAndroid Build Coastguard Worker capture_output=True) 230*288bf522SAndroid Build Coastguard Worker 231*288bf522SAndroid Build Coastguard Worker if output is None or len(output.stdout) == 0: 232*288bf522SAndroid Build Coastguard Worker raise Exception("Error while validating simpleperf events.") 233*288bf522SAndroid Build Coastguard Worker lines = output.stdout.decode("utf-8").split("\n") 234*288bf522SAndroid Build Coastguard Worker 235*288bf522SAndroid Build Coastguard Worker # Anything that does not start with two spaces is not a command. 236*288bf522SAndroid Build Coastguard Worker # Any command with a space will have the command before the first space. 237*288bf522SAndroid Build Coastguard Worker for line in lines: 238*288bf522SAndroid Build Coastguard Worker if len(line) <= 3 or line[:2] != " " or line[2] == "#": 239*288bf522SAndroid Build Coastguard Worker # Line doesn't contain a simpleperf event 240*288bf522SAndroid Build Coastguard Worker continue 241*288bf522SAndroid Build Coastguard Worker event = line[2:].split(" ")[0] 242*288bf522SAndroid Build Coastguard Worker if event in events_copy: 243*288bf522SAndroid Build Coastguard Worker events_copy.remove(event) 244*288bf522SAndroid Build Coastguard Worker if len(events_copy) == 0: 245*288bf522SAndroid Build Coastguard Worker # All of the events exist, exit early 246*288bf522SAndroid Build Coastguard Worker break 247*288bf522SAndroid Build Coastguard Worker 248*288bf522SAndroid Build Coastguard Worker if len(events_copy) > 0: 249*288bf522SAndroid Build Coastguard Worker return ValidationError("The following simpleperf event(s) are invalid:" 250*288bf522SAndroid Build Coastguard Worker " %s." 251*288bf522SAndroid Build Coastguard Worker % events_copy, 252*288bf522SAndroid Build Coastguard Worker "Run adb shell simpleperf list to" 253*288bf522SAndroid Build Coastguard Worker " see valid simpleperf events.") 254*288bf522SAndroid Build Coastguard Worker return None 255