• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# Copyright (C) 2024 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
17import math
18import os
19import subprocess
20import sys
21import time
22from .handle_input import HandleInput
23from .validation_error import ValidationError
24
25ADB_ROOT_TIMED_OUT_LIMIT_SECS = 5
26ADB_BOOT_COMPLETED_TIMED_OUT_LIMIT_SECS = 30
27POLLING_INTERVAL_SECS = 0.5
28SIMPLEPERF_TRACE_FILE = "/data/misc/perfetto-traces/perf.data"
29
30class AdbDevice:
31  """
32  Class representing a device. APIs interact with the current device through
33  the adb bridge.
34  """
35  def __init__(self, serial):
36    self.serial = serial
37
38  @staticmethod
39  def get_adb_devices():
40    """
41    Returns a list of devices connected to the adb bridge.
42    The output of the command 'adb devices' is expected to be of the form:
43    List of devices attached
44    SOMEDEVICE1234    device
45    device2:5678    device
46    """
47    command_output = subprocess.run(["adb", "devices"], capture_output=True)
48    output_lines = command_output.stdout.decode("utf-8").split("\n")
49    devices = []
50    for line in output_lines[:-2]:
51      if line[0] == "*" or line == "List of devices attached":
52        continue
53      words_in_line = line.split('\t')
54      if words_in_line[1] == "device":
55        devices.append(words_in_line[0])
56    return devices
57
58  def check_device_connection(self):
59    devices = self.get_adb_devices()
60    if len(devices) == 0:
61      return ValidationError("There are currently no devices connected.", None)
62    if self.serial is not None:
63      if self.serial not in devices:
64        return ValidationError(("Device with serial %s is not connected."
65                                % self.serial), None)
66    elif "ANDROID_SERIAL" in os.environ:
67      if os.environ["ANDROID_SERIAL"] not in devices:
68        return ValidationError(("Device with serial %s is set as environment"
69                                " variable, ANDROID_SERIAL, but is not"
70                                " connected."
71                                % os.environ["ANDROID_SERIAL"]), None)
72      self.serial = os.environ["ANDROID_SERIAL"]
73    elif len(devices) == 1:
74      self.serial = devices[0]
75    else:
76      options = ""
77      choices = {}
78      for i, device in enumerate(devices):
79        options += ("%d: torq --serial %s %s\n\t"
80                    % (i, device, " ".join(sys.argv[1:])))
81        # Lambdas are bound to local scope, so assign var d to prevent
82        # future values of device from overriding the current value we want
83        choices[str(i)] = lambda d=device: d
84      # Remove last \t
85      options = options[:-1]
86      chosen_serial = (HandleInput("There is more than one device currently "
87                                  "connected. Press the corresponding number "
88                                  "for the following options to choose the "
89                                  "device you want to use.\n\t%sSelect "
90                                  "device[0-%d]: "
91                                  % (options, len(devices) - 1),
92                                  "Please select a valid option.",
93                                  choices)
94                       .handle_input())
95      if isinstance(chosen_serial, ValidationError):
96        return chosen_serial
97      print("Using device with serial %s" % chosen_serial)
98      self.serial = chosen_serial
99    return None
100
101  @staticmethod
102  def poll_is_task_completed(timed_out_limit, interval, check_is_completed):
103    start_time = time.time()
104    while True:
105      time.sleep(interval)
106      if check_is_completed():
107        return True
108      if time.time() - start_time > timed_out_limit:
109        return False
110
111  def root_device(self):
112    subprocess.run(["adb", "-s", self.serial, "root"])
113    if not self.poll_is_task_completed(ADB_ROOT_TIMED_OUT_LIMIT_SECS,
114                                       POLLING_INTERVAL_SECS,
115                                       lambda: self.serial in
116                                               self.get_adb_devices()):
117      raise Exception(("Device with serial %s took too long to reconnect after"
118                       " being rooted." % self.serial))
119
120  def remove_file(self, file_path):
121    subprocess.run(["adb", "-s", self.serial, "shell", "rm", "-f", file_path])
122
123  def start_perfetto_trace(self, config):
124    return subprocess.Popen(("adb -s %s shell perfetto -c - --txt -o"
125                             " /data/misc/perfetto-traces/"
126                             "trace.perfetto-trace %s"
127                             % (self.serial, config)), shell=True)
128
129  def start_simpleperf_trace(self, command):
130    events_param = "-e " + ",".join(command.simpleperf_event)
131    return subprocess.Popen(("adb -s %s shell simpleperf record -a -f 1000 "
132                             "--exclude-perf --post-unwind=yes -m 8192 -g "
133                             "--duration %d %s -o %s"
134                             % (self.serial,
135                                int(math.ceil(command.dur_ms/1000)),
136                                events_param, SIMPLEPERF_TRACE_FILE)),
137                            shell=True)
138
139  def pull_file(self, file_path, host_file):
140    subprocess.run(["adb", "-s", self.serial, "pull", file_path, host_file])
141
142  def get_all_users(self):
143    command_output = subprocess.run(["adb", "-s", self.serial, "shell", "pm",
144                                     "list", "users"], capture_output=True)
145    output_lines = command_output.stdout.decode("utf-8").split("\n")[1:-1]
146    return [int((line.split("{", 1)[1]).split(":", 1)[0]) for line in
147            output_lines]
148
149  def user_exists(self, user):
150    users = self.get_all_users()
151    if user not in users:
152      return ValidationError(("User ID %s does not exist on device with serial"
153                              " %s." % (user, self.serial)),
154                             ("Select from one of the following user IDs on"
155                              " device with serial %s: %s"
156                              % (self.serial, ", ".join(map(str, users)))))
157    return None
158
159  def get_current_user(self):
160    command_output = subprocess.run(["adb", "-s", self.serial, "shell", "am",
161                                     "get-current-user"], capture_output=True)
162    return int(command_output.stdout.decode("utf-8").split()[0])
163
164  def perform_user_switch(self, user):
165    subprocess.run(["adb", "-s", self.serial, "shell", "am", "switch-user",
166                    str(user)])
167
168  def write_to_file(self, file_path, host_file_string):
169    subprocess.run(("adb -s %s shell 'cat > %s %s'"
170                    % (self.serial, file_path, host_file_string)), shell=True)
171
172  def set_prop(self, prop, value):
173    subprocess.run(["adb", "-s", self.serial, "shell", "setprop", prop, value])
174
175  def clear_prop(self, prop):
176    subprocess.run(["adb", "-s", self.serial, "shell", "setprop", prop, "\"\""])
177
178  def reboot(self):
179    subprocess.run(["adb", "-s", self.serial, "reboot"])
180    if not self.poll_is_task_completed(ADB_ROOT_TIMED_OUT_LIMIT_SECS,
181                                       POLLING_INTERVAL_SECS,
182                                       lambda: self.serial not in
183                                               self.get_adb_devices()):
184      raise Exception(("Device with serial %s took too long to start"
185                       " rebooting." % self.serial))
186
187  def wait_for_device(self):
188    subprocess.run(["adb", "-s", self.serial, "wait-for-device"])
189
190  def is_boot_completed(self):
191    command_output = subprocess.run(["adb", "-s", self.serial, "shell",
192                                     "getprop", "sys.boot_completed"],
193                                    capture_output=True)
194    return command_output.stdout.decode("utf-8").strip() == "1"
195
196  def wait_for_boot_to_complete(self):
197    if not self.poll_is_task_completed(ADB_BOOT_COMPLETED_TIMED_OUT_LIMIT_SECS,
198                                       POLLING_INTERVAL_SECS,
199                                       self.is_boot_completed):
200      raise Exception(("Device with serial %s took too long to finish"
201                       " rebooting." % self.serial))
202
203  def get_packages(self):
204    return [package.removeprefix("package:") for package in subprocess.run(
205        ["adb", "-s", self.serial, "shell", "pm", "list", "packages"],
206        capture_output=True).stdout.decode("utf-8").splitlines()]
207
208  def get_pid(self, package):
209    return subprocess.run("adb -s %s shell pidof %s" % (self.serial, package),
210                          shell=True, capture_output=True
211                          ).stdout.decode("utf-8").split("\n")[0]
212
213  def is_package_running(self, package):
214    return self.get_pid(package) != ""
215
216  def start_package(self, package):
217    if subprocess.run(
218        ["adb", "-s", self.serial, "shell", "am", "start", package],
219        capture_output=True).stderr.decode("utf-8").split("\n")[0] != "":
220      return ValidationError(("Cannot start package %s on device with"
221                              " serial %s because %s is a service package,"
222                              " which doesn't implement a MAIN activity."
223                              % (package, self.serial, package)), None)
224    return None
225
226  def kill_pid(self, package):
227    pid = self.get_pid(package)
228    if pid != "":
229      subprocess.run(["adb", "-s", self.serial, "shell", "kill", "-9", pid])
230
231  def force_stop_package(self, package):
232    subprocess.run(["adb", "-s", self.serial, "shell", "am", "force-stop",
233                    package])
234
235  def get_prop(self, prop):
236    return subprocess.run(
237        ["adb", "-s", self.serial, "shell", "getprop", prop],
238        capture_output=True).stdout.decode("utf-8").split("\n")[0]
239
240  def get_android_sdk_version(self):
241    return int(self.get_prop("ro.build.version.sdk"))
242
243  def simpleperf_event_exists(self, simpleperf_events):
244    events_copy = simpleperf_events.copy()
245    grep_command = "grep"
246    for event in simpleperf_events:
247      grep_command += " -e " + event.lower()
248
249    output = subprocess.run(["adb", "-s", self.serial, "shell",
250                             "simpleperf", "list", "|", grep_command],
251                            capture_output=True)
252
253    if output is None or len(output.stdout) == 0:
254      if "not found" in output.stderr.decode("utf-8"):
255        return ValidationError("Simpleperf was not found in the device",
256                               "Push the simpleperf binary to the device")
257      raise Exception("Error while validating simpleperf events.")
258    lines = output.stdout.decode("utf-8").split("\n")
259
260    # Anything that does not start with two spaces is not a command.
261    # Any command with a space will have the command before the first space.
262    for line in lines:
263      if len(line) <= 3 or line[:2] != "  " or line[2] == "#":
264        # Line doesn't contain a simpleperf event
265        continue
266      event = line[2:].split(" ")[0]
267      if event in events_copy:
268        events_copy.remove(event)
269        if len(events_copy) == 0:
270          # All of the events exist, exit early
271          break
272
273    if len(events_copy) > 0:
274      return ValidationError("The following simpleperf event(s) are invalid:"
275                             " %s."
276                             % events_copy,
277                             "Run adb shell simpleperf list to"
278                             " see valid simpleperf events.")
279    return None
280