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