1# Copyright 2016 Google Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import logging 16import re 17import subprocess 18import threading 19import time 20 21from mobly import utils 22 23# Command to use for running ADB commands. 24ADB = 'adb' 25 26# adb gets confused if we try to manage bound ports in parallel, so anything to 27# do with port forwarding must happen under this lock. 28ADB_PORT_LOCK = threading.Lock() 29 30# Number of attempts to execute "adb root", and seconds for interval time of 31# this commands. 32ADB_ROOT_RETRY_ATTEMPTS = 3 33ADB_ROOT_RETRY_ATTEMPT_INTERVAL_SEC = 10 34 35# Qualified class name of the default instrumentation test runner. 36DEFAULT_INSTRUMENTATION_RUNNER = 'com.android.common.support.test.runner.AndroidJUnitRunner' 37 38# `adb shell getprop` can take surprisingly long, when the device is a 39# networked virtual device. 40DEFAULT_GETPROP_TIMEOUT_SEC = 10 41DEFAULT_GETPROPS_ATTEMPTS = 3 42DEFAULT_GETPROPS_RETRY_SLEEP_SEC = 1 43 44# The regex pattern indicating the `adb connect` command did not fail. 45PATTERN_ADB_CONNECT_SUCCESS = re.compile( 46 r'^connected to .*|^already connected to .*') 47 48 49class Error(Exception): 50 """Base error type for adb proxy module.""" 51 52 53class AdbError(Error): 54 """Raised when an adb command encounters an error. 55 56 Attributes: 57 cmd: list of strings, the adb command executed. 58 stdout: byte string, the raw stdout of the command. 59 stderr: byte string, the raw stderr of the command. 60 ret_code: int, the return code of the command. 61 serial: string, the serial of the device the command is executed on. 62 This is an empty string if the adb command is not specific to a 63 device. 64 """ 65 66 def __init__(self, cmd, stdout, stderr, ret_code, serial=''): 67 super().__init__() 68 self.cmd = cmd 69 self.stdout = stdout 70 self.stderr = stderr 71 self.ret_code = ret_code 72 self.serial = serial 73 74 def __str__(self): 75 return ('Error executing adb cmd "%s". ret: %d, stdout: %s, stderr: %s') % ( 76 utils.cli_cmd_to_string( 77 self.cmd), self.ret_code, self.stdout, self.stderr) 78 79 80class AdbTimeoutError(Error): 81 """Raised when an command did not complete within expected time. 82 83 Attributes: 84 cmd: list of strings, the adb command that timed out 85 timeout: float, the number of seconds passed before timing out. 86 serial: string, the serial of the device the command is executed on. 87 This is an empty string if the adb command is not specific to a 88 device. 89 """ 90 91 def __init__(self, cmd, timeout, serial=''): 92 super().__init__() 93 self.cmd = cmd 94 self.timeout = timeout 95 self.serial = serial 96 97 def __str__(self): 98 return 'Timed out executing command "%s" after %ss.' % ( 99 utils.cli_cmd_to_string(self.cmd), self.timeout) 100 101 102def is_adb_available(): 103 """Checks if adb is available as a command line tool. 104 105 Returns: 106 True if adb binary is available in console, False otherwise. 107 """ 108 ret, out, err = utils.run_command('which adb', shell=True) 109 clean_out = out.decode('utf-8').strip() 110 if clean_out: 111 return True 112 return False 113 114 115def list_occupied_adb_ports(): 116 """Lists all the host ports occupied by adb forward. 117 118 This is useful because adb will silently override the binding if an attempt 119 to bind to a port already used by adb was made, instead of throwing binding 120 error. So one should always check what ports adb is using before trying to 121 bind to a port with adb. 122 123 Returns: 124 A list of integers representing occupied host ports. 125 """ 126 out = AdbProxy().forward('--list') 127 clean_lines = str(out, 'utf-8').strip().split('\n') 128 used_ports = [] 129 for line in clean_lines: 130 tokens = line.split(' tcp:') 131 if len(tokens) != 3: 132 continue 133 used_ports.append(int(tokens[1])) 134 return used_ports 135 136 137class AdbProxy: 138 """Proxy class for ADB. 139 140 For syntactic reasons, the '-' in adb commands need to be replaced with 141 '_'. Can directly execute adb commands on an object: 142 >> adb = AdbProxy(<serial>) 143 >> adb.start_server() 144 >> adb.devices() # will return the console output of "adb devices". 145 146 By default, command args are expected to be an iterable which is passed 147 directly to subprocess.Popen(): 148 >> adb.shell(['echo', 'a', 'b']) 149 150 This way of launching commands is recommended by the subprocess 151 documentation to avoid shell injection vulnerabilities and avoid having to 152 deal with multiple layers of shell quoting and different shell environments 153 between different OSes. 154 155 If you really want to run the command through the system shell, this is 156 possible by supplying shell=True, but try to avoid this if possible: 157 >> adb.shell('cat /foo > /tmp/file', shell=True) 158 """ 159 160 def __init__(self, serial=''): 161 self.serial = serial 162 163 def _exec_cmd(self, args, shell, timeout, stderr) -> bytes: 164 """Executes adb commands. 165 166 Args: 167 args: string or list of strings, program arguments. 168 See subprocess.Popen() documentation. 169 shell: bool, True to run this command through the system shell, 170 False to invoke it directly. See subprocess.Popen() docs. 171 timeout: float, the number of seconds to wait before timing out. 172 If not specified, no timeout takes effect. 173 stderr: a Byte stream, like io.BytesIO, stderr of the command will 174 be written to this object if provided. 175 176 Returns: 177 The output of the adb command run if exit code is 0. 178 179 Raises: 180 ValueError: timeout value is invalid. 181 AdbError: The adb command exit code is not 0. 182 AdbTimeoutError: The adb command timed out. 183 """ 184 if timeout and timeout <= 0: 185 raise ValueError('Timeout is not a positive value: %s' % timeout) 186 try: 187 (ret, out, err) = utils.run_command(args, shell=shell, timeout=timeout) 188 except subprocess.TimeoutExpired: 189 raise AdbTimeoutError(cmd=args, timeout=timeout, serial=self.serial) 190 191 if stderr: 192 stderr.write(err) 193 logging.debug('cmd: %s, stdout: %s, stderr: %s, ret: %s', 194 utils.cli_cmd_to_string(args), out, err, ret) 195 if ret == 0: 196 return out 197 else: 198 raise AdbError(cmd=args, 199 stdout=out, 200 stderr=err, 201 ret_code=ret, 202 serial=self.serial) 203 204 def _execute_and_process_stdout(self, args, shell, handler) -> bytes: 205 """Executes adb commands and processes the stdout with a handler. 206 207 Args: 208 args: string or list of strings, program arguments. 209 See subprocess.Popen() documentation. 210 shell: bool, True to run this command through the system shell, 211 False to invoke it directly. See subprocess.Popen() docs. 212 handler: func, a function to handle adb stdout line by line. 213 214 Returns: 215 The stderr of the adb command run if exit code is 0. 216 217 Raises: 218 AdbError: The adb command exit code is not 0. 219 """ 220 proc = subprocess.Popen(args, 221 stdout=subprocess.PIPE, 222 stderr=subprocess.PIPE, 223 shell=shell, 224 bufsize=1) 225 out = '[elided, processed via handler]' 226 try: 227 # Even if the process dies, stdout.readline still works 228 # and will continue until it runs out of stdout to process. 229 while True: 230 line = proc.stdout.readline() 231 if line: 232 handler(line) 233 else: 234 break 235 finally: 236 # Note, communicate will not contain any buffered output. 237 (unexpected_out, err) = proc.communicate() 238 if unexpected_out: 239 out = '[unexpected stdout] %s' % unexpected_out 240 for line in unexpected_out.splitlines(): 241 handler(line) 242 243 ret = proc.returncode 244 logging.debug('cmd: %s, stdout: %s, stderr: %s, ret: %s', 245 utils.cli_cmd_to_string(args), out, err, ret) 246 if ret == 0: 247 return err 248 else: 249 raise AdbError(cmd=args, stdout=out, stderr=err, ret_code=ret) 250 251 def _construct_adb_cmd(self, raw_name, args, shell): 252 """Constructs an adb command with arguments for a subprocess call. 253 254 Args: 255 raw_name: string, the raw unsanitized name of the adb command to 256 format. 257 args: string or list of strings, arguments to the adb command. 258 See subprocess.Proc() documentation. 259 shell: bool, True to run this command through the system shell, 260 False to invoke it directly. See subprocess.Proc() docs. 261 262 Returns: 263 The adb command in a format appropriate for subprocess. If shell is 264 True, then this is a string; otherwise, this is a list of 265 strings. 266 """ 267 args = args or '' 268 name = raw_name.replace('_', '-') 269 if shell: 270 args = utils.cli_cmd_to_string(args) 271 # Add quotes around "adb" in case the ADB path contains spaces. This 272 # is pretty common on Windows (e.g. Program Files). 273 if self.serial: 274 adb_cmd = '"%s" -s "%s" %s %s' % (ADB, self.serial, name, args) 275 else: 276 adb_cmd = '"%s" %s %s' % (ADB, name, args) 277 else: 278 adb_cmd = [ADB] 279 if self.serial: 280 adb_cmd.extend(['-s', self.serial]) 281 adb_cmd.append(name) 282 if args: 283 if isinstance(args, str): 284 adb_cmd.append(args) 285 else: 286 adb_cmd.extend(args) 287 return adb_cmd 288 289 def _exec_adb_cmd(self, name, args, shell, timeout, stderr) -> bytes: 290 adb_cmd = self._construct_adb_cmd(name, args, shell=shell) 291 out = self._exec_cmd(adb_cmd, shell=shell, timeout=timeout, stderr=stderr) 292 return out 293 294 def _execute_adb_and_process_stdout(self, name, args, shell, 295 handler) -> bytes: 296 adb_cmd = self._construct_adb_cmd(name, args, shell=shell) 297 err = self._execute_and_process_stdout(adb_cmd, 298 shell=shell, 299 handler=handler) 300 return err 301 302 def _parse_getprop_output(self, output): 303 """Parses the raw output of `adb shell getprop` into a dictionary. 304 305 Args: 306 output: byte str, the raw output of the `adb shell getprop` call. 307 308 Returns: 309 dict, name-value pairs of the properties. 310 """ 311 output = output.decode('utf-8', errors='ignore').replace('\r\n', '\n') 312 results = {} 313 for line in output.split(']\n'): 314 if not line: 315 continue 316 try: 317 name, value = line.split(': ', 1) 318 except ValueError: 319 logging.debug('Failed to parse adb getprop line %s', line) 320 continue 321 name = name.strip()[1:-1] 322 # Remove any square bracket from either end of the value string. 323 if value and value[0] == '[': 324 value = value[1:] 325 results[name] = value 326 return results 327 328 @property 329 def current_user_id(self) -> int: 330 """The integer ID of the current Android user. 331 332 Some adb commands require specifying a user ID to work properly. Use 333 this to get the current user ID. 334 335 Note a "user" is not the same as an "account" in Android. See AOSP's 336 documentation for details. 337 https://source.android.com/devices/tech/admin/multi-user 338 """ 339 sdk_int = int(self.getprop('ro.build.version.sdk')) 340 if sdk_int >= 24: 341 return int(self.shell(['am', 'get-current-user'])) 342 if sdk_int >= 21: 343 user_info_str = self.shell(['dumpsys', 'user']).decode('utf-8') 344 return int(re.findall(r'\{(\d+):', user_info_str)[0]) 345 # Multi-user is not supported in SDK < 21, only user 0 exists. 346 return 0 347 348 def connect(self, address) -> bytes: 349 """Executes the `adb connect` command with proper status checking. 350 351 Args: 352 address: string, the address of the Android instance to connect to. 353 354 Returns: 355 The stdout content. 356 357 Raises: 358 AdbError: if the connection failed. 359 """ 360 stdout = self._exec_adb_cmd('connect', 361 address, 362 shell=False, 363 timeout=None, 364 stderr=None) 365 if PATTERN_ADB_CONNECT_SUCCESS.match(stdout.decode('utf-8')) is None: 366 raise AdbError(cmd=f'connect {address}', 367 stdout=stdout, 368 stderr='', 369 ret_code=0) 370 return stdout 371 372 def getprop(self, prop_name, timeout=DEFAULT_GETPROP_TIMEOUT_SEC): 373 """Get a property of the device. 374 375 This is a convenience wrapper for `adb shell getprop xxx`. 376 377 Args: 378 prop_name: A string that is the name of the property to get. 379 timeout: float, the number of seconds to wait before timing out. 380 If not specified, the DEFAULT_GETPROP_TIMEOUT_SEC is used. 381 382 Returns: 383 A string that is the value of the property, or None if the property 384 doesn't exist. 385 """ 386 return self.shell(['getprop', prop_name], 387 timeout=timeout).decode('utf-8').strip() 388 389 def getprops(self, prop_names): 390 """Get multiple properties of the device. 391 392 This is a convenience wrapper for `adb shell getprop`. Use this to 393 reduce the number of adb calls when getting multiple properties. 394 395 Args: 396 prop_names: list of strings, the names of the properties to get. 397 398 Returns: 399 A dict containing name-value pairs of the properties requested, if 400 they exist. 401 """ 402 attempts = DEFAULT_GETPROPS_ATTEMPTS 403 results = {} 404 for attempt in range(attempts): 405 # The ADB getprop command can randomly return empty string, so try 406 # multiple times. This value should always be non-empty if the device 407 # in a working state. 408 raw_output = self.shell(['getprop'], timeout=DEFAULT_GETPROP_TIMEOUT_SEC) 409 properties = self._parse_getprop_output(raw_output) 410 if properties: 411 for name in prop_names: 412 if name in properties: 413 results[name] = properties[name] 414 break 415 # Don't call sleep on the last attempt. 416 if attempt < attempts - 1: 417 time.sleep(DEFAULT_GETPROPS_RETRY_SLEEP_SEC) 418 return results 419 420 def has_shell_command(self, command) -> bool: 421 """Checks to see if a given check command exists on the device. 422 423 Args: 424 command: A string that is the name of the command to check. 425 426 Returns: 427 A boolean that is True if the command exists and False otherwise. 428 """ 429 try: 430 output = self.shell(['command', '-v', command]).decode('utf-8').strip() 431 return command in output 432 except AdbError: 433 # If the command doesn't exist, then 'command -v' can return 434 # an exit code > 1. 435 return False 436 437 def forward(self, args=None, shell=False) -> bytes: 438 with ADB_PORT_LOCK: 439 return self._exec_adb_cmd('forward', 440 args, 441 shell, 442 timeout=None, 443 stderr=None) 444 445 def instrument(self, 446 package, 447 options=None, 448 runner=None, 449 handler=None) -> bytes: 450 """Runs an instrumentation command on the device. 451 452 This is a convenience wrapper to avoid parameter formatting. 453 454 Example: 455 456 .. code-block:: python 457 458 device.instrument( 459 'com.my.package.test', 460 options = { 461 'class': 'com.my.package.test.TestSuite', 462 }, 463 ) 464 465 Args: 466 package: string, the package of the instrumentation tests. 467 options: dict, the instrumentation options including the test 468 class. 469 runner: string, the test runner name, which defaults to 470 DEFAULT_INSTRUMENTATION_RUNNER. 471 handler: optional func, when specified the function is used to parse 472 the instrumentation stdout line by line as the output is 473 generated; otherwise, the stdout is simply returned once the 474 instrumentation is finished. 475 476 Returns: 477 The stdout of instrumentation command or the stderr if the handler 478 is set. 479 """ 480 if runner is None: 481 runner = DEFAULT_INSTRUMENTATION_RUNNER 482 if options is None: 483 options = {} 484 485 options_list = [] 486 for option_key, option_value in options.items(): 487 options_list.append('-e %s %s' % (option_key, option_value)) 488 options_string = ' '.join(options_list) 489 490 instrumentation_command = 'am instrument -r -w %s %s/%s' % (options_string, 491 package, runner) 492 logging.info('AndroidDevice|%s: Executing adb shell %s', self.serial, 493 instrumentation_command) 494 if handler is None: 495 return self._exec_adb_cmd('shell', 496 instrumentation_command, 497 shell=False, 498 timeout=None, 499 stderr=None) 500 else: 501 return self._execute_adb_and_process_stdout('shell', 502 instrumentation_command, 503 shell=False, 504 handler=handler) 505 506 def root(self) -> bytes: 507 """Enables ADB root mode on the device. 508 509 This method will retry to execute the command `adb root` when an 510 AdbError occurs, since sometimes the error `adb: unable to connect 511 for root: closed` is raised when executing `adb root` immediately after 512 the device is booted to OS. 513 514 Returns: 515 A string that is the stdout of root command. 516 517 Raises: 518 AdbError: If the command exit code is not 0. 519 """ 520 retry_interval = ADB_ROOT_RETRY_ATTEMPT_INTERVAL_SEC 521 for attempt in range(ADB_ROOT_RETRY_ATTEMPTS): 522 try: 523 return self._exec_adb_cmd('root', 524 args=None, 525 shell=False, 526 timeout=None, 527 stderr=None) 528 except AdbError as e: 529 if attempt + 1 < ADB_ROOT_RETRY_ATTEMPTS: 530 logging.debug('Retry the command "%s" since Error "%s" occurred.' % 531 (utils.cli_cmd_to_string( 532 e.cmd), e.stderr.decode('utf-8').strip())) 533 # Buffer between "adb root" commands. 534 time.sleep(retry_interval) 535 retry_interval *= 2 536 else: 537 raise e 538 539 def __getattr__(self, name): 540 541 def adb_call(args=None, shell=False, timeout=None, stderr=None) -> bytes: 542 """Wrapper for an ADB command. 543 544 Args: 545 args: string or list of strings, arguments to the adb command. 546 See subprocess.Proc() documentation. 547 shell: bool, True to run this command through the system shell, 548 False to invoke it directly. See subprocess.Proc() docs. 549 timeout: float, the number of seconds to wait before timing out. 550 If not specified, no timeout takes effect. 551 stderr: a Byte stream, like io.BytesIO, stderr of the command 552 will be written to this object if provided. 553 554 Returns: 555 The output of the adb command run if exit code is 0. 556 """ 557 return self._exec_adb_cmd(name, 558 args, 559 shell=shell, 560 timeout=timeout, 561 stderr=stderr) 562 563 return adb_call 564