xref: /aosp_15_r20/system/extras/torq/device.py (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
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