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 if ret == 0: 202 return out 203 else: 204 raise AdbError( 205 cmd=args, stdout=out, stderr=err, ret_code=ret, serial=self.serial 206 ) 207 208 def _execute_and_process_stdout(self, args, shell, handler) -> bytes: 209 """Executes adb commands and processes the stdout with a handler. 210 211 Args: 212 args: string or list of strings, program arguments. 213 See subprocess.Popen() documentation. 214 shell: bool, True to run this command through the system shell, 215 False to invoke it directly. See subprocess.Popen() docs. 216 handler: func, a function to handle adb stdout line by line. 217 218 Returns: 219 The stderr of the adb command run if exit code is 0. 220 221 Raises: 222 AdbError: The adb command exit code is not 0. 223 """ 224 proc = subprocess.Popen( 225 args, 226 stdout=subprocess.PIPE, 227 stderr=subprocess.PIPE, 228 shell=shell, 229 bufsize=1, 230 ) 231 out = '[elided, processed via handler]' 232 try: 233 # Even if the process dies, stdout.readline still works 234 # and will continue until it runs out of stdout to process. 235 while True: 236 line = proc.stdout.readline() 237 if line: 238 handler(line) 239 else: 240 break 241 finally: 242 # Note, communicate will not contain any buffered output. 243 (unexpected_out, err) = proc.communicate() 244 if unexpected_out: 245 out = '[unexpected stdout] %s' % unexpected_out 246 for line in unexpected_out.splitlines(): 247 handler(line) 248 249 ret = proc.returncode 250 logging.debug( 251 'cmd: %s, stdout: %s, stderr: %s, ret: %s', 252 utils.cli_cmd_to_string(args), 253 out, 254 err, 255 ret, 256 ) 257 if ret == 0: 258 return err 259 else: 260 raise AdbError(cmd=args, stdout=out, stderr=err, ret_code=ret) 261 262 def _construct_adb_cmd(self, raw_name, args, shell): 263 """Constructs an adb command with arguments for a subprocess call. 264 265 Args: 266 raw_name: string, the raw unsanitized name of the adb command to 267 format. 268 args: string or list of strings, arguments to the adb command. 269 See subprocess.Proc() documentation. 270 shell: bool, True to run this command through the system shell, 271 False to invoke it directly. See subprocess.Proc() docs. 272 273 Returns: 274 The adb command in a format appropriate for subprocess. If shell is 275 True, then this is a string; otherwise, this is a list of 276 strings. 277 """ 278 args = args or '' 279 name = raw_name.replace('_', '-') 280 if shell: 281 args = utils.cli_cmd_to_string(args) 282 # Add quotes around "adb" in case the ADB path contains spaces. This 283 # is pretty common on Windows (e.g. Program Files). 284 if self.serial: 285 adb_cmd = '"%s" -s "%s" %s %s' % (ADB, self.serial, name, args) 286 else: 287 adb_cmd = '"%s" %s %s' % (ADB, name, args) 288 else: 289 adb_cmd = [ADB] 290 if self.serial: 291 adb_cmd.extend(['-s', self.serial]) 292 adb_cmd.append(name) 293 if args: 294 if isinstance(args, str): 295 adb_cmd.append(args) 296 else: 297 adb_cmd.extend(args) 298 return adb_cmd 299 300 def _exec_adb_cmd(self, name, args, shell, timeout, stderr) -> bytes: 301 adb_cmd = self._construct_adb_cmd(name, args, shell=shell) 302 out = self._exec_cmd(adb_cmd, shell=shell, timeout=timeout, stderr=stderr) 303 return out 304 305 def _execute_adb_and_process_stdout( 306 self, name, args, shell, handler 307 ) -> bytes: 308 adb_cmd = self._construct_adb_cmd(name, args, shell=shell) 309 err = self._execute_and_process_stdout( 310 adb_cmd, shell=shell, handler=handler 311 ) 312 return err 313 314 def _parse_getprop_output(self, output): 315 """Parses the raw output of `adb shell getprop` into a dictionary. 316 317 Args: 318 output: byte str, the raw output of the `adb shell getprop` call. 319 320 Returns: 321 dict, name-value pairs of the properties. 322 """ 323 output = output.decode('utf-8', errors='ignore').replace('\r\n', '\n') 324 results = {} 325 for line in output.split(']\n'): 326 if not line: 327 continue 328 try: 329 name, value = line.split(': ', 1) 330 except ValueError: 331 logging.debug('Failed to parse adb getprop line %s', line) 332 continue 333 name = name.strip()[1:-1] 334 # Remove any square bracket from either end of the value string. 335 if value and value[0] == '[': 336 value = value[1:] 337 results[name] = value 338 return results 339 340 @property 341 def current_user_id(self) -> int: 342 """The integer ID of the current Android user. 343 344 Some adb commands require specifying a user ID to work properly. Use 345 this to get the current user ID. 346 347 Note a "user" is not the same as an "account" in Android. See AOSP's 348 documentation for details. 349 https://source.android.com/devices/tech/admin/multi-user 350 """ 351 sdk_int = int(self.getprop('ro.build.version.sdk')) 352 if sdk_int >= 24: 353 return int(self.shell(['am', 'get-current-user'])) 354 if sdk_int >= 21: 355 user_info_str = self.shell(['dumpsys', 'user']).decode('utf-8') 356 return int(re.findall(r'\{(\d+):', user_info_str)[0]) 357 # Multi-user is not supported in SDK < 21, only user 0 exists. 358 return 0 359 360 def connect(self, address) -> bytes: 361 """Executes the `adb connect` command with proper status checking. 362 363 Args: 364 address: string, the address of the Android instance to connect to. 365 366 Returns: 367 The stdout content. 368 369 Raises: 370 AdbError: if the connection failed. 371 """ 372 stdout = self._exec_adb_cmd( 373 'connect', address, shell=False, timeout=None, stderr=None 374 ) 375 if PATTERN_ADB_CONNECT_SUCCESS.match(stdout.decode('utf-8')) is None: 376 raise AdbError( 377 cmd=f'connect {address}', stdout=stdout, stderr='', ret_code=0 378 ) 379 return stdout 380 381 def getprop(self, prop_name, timeout=DEFAULT_GETPROP_TIMEOUT_SEC): 382 """Get a property of the device. 383 384 This is a convenience wrapper for `adb shell getprop xxx`. 385 386 Args: 387 prop_name: A string that is the name of the property to get. 388 timeout: float, the number of seconds to wait before timing out. 389 If not specified, the DEFAULT_GETPROP_TIMEOUT_SEC is used. 390 391 Returns: 392 A string that is the value of the property, or None if the property 393 doesn't exist. 394 """ 395 return ( 396 self.shell(['getprop', prop_name], timeout=timeout) 397 .decode('utf-8') 398 .strip() 399 ) 400 401 def getprops(self, prop_names): 402 """Get multiple properties of the device. 403 404 This is a convenience wrapper for `adb shell getprop`. Use this to 405 reduce the number of adb calls when getting multiple properties. 406 407 Args: 408 prop_names: list of strings, the names of the properties to get. 409 410 Returns: 411 A dict containing name-value pairs of the properties requested, if 412 they exist. 413 """ 414 attempts = DEFAULT_GETPROPS_ATTEMPTS 415 results = {} 416 for attempt in range(attempts): 417 # The ADB getprop command can randomly return empty string, so try 418 # multiple times. This value should always be non-empty if the device 419 # in a working state. 420 raw_output = self.shell(['getprop'], timeout=DEFAULT_GETPROP_TIMEOUT_SEC) 421 properties = self._parse_getprop_output(raw_output) 422 if properties: 423 for name in prop_names: 424 if name in properties: 425 results[name] = properties[name] 426 break 427 # Don't call sleep on the last attempt. 428 if attempt < attempts - 1: 429 time.sleep(DEFAULT_GETPROPS_RETRY_SLEEP_SEC) 430 return results 431 432 def has_shell_command(self, command) -> bool: 433 """Checks to see if a given check command exists on the device. 434 435 Args: 436 command: A string that is the name of the command to check. 437 438 Returns: 439 A boolean that is True if the command exists and False otherwise. 440 """ 441 try: 442 output = self.shell(['command', '-v', command]).decode('utf-8').strip() 443 return command in output 444 except AdbError: 445 # If the command doesn't exist, then 'command -v' can return 446 # an exit code > 1. 447 return False 448 449 def forward(self, args=None, shell=False) -> bytes: 450 with ADB_PORT_LOCK: 451 return self._exec_adb_cmd( 452 'forward', args, shell, timeout=None, stderr=None 453 ) 454 455 def reverse(self, args=None, shell=False) -> bytes: 456 with ADB_PORT_LOCK: 457 return self._exec_adb_cmd( 458 'reverse', args, shell, timeout=None, stderr=None 459 ) 460 461 def instrument( 462 self, package, options=None, runner=None, handler=None 463 ) -> bytes: 464 """Runs an instrumentation command on the device. 465 466 This is a convenience wrapper to avoid parameter formatting. 467 468 Example: 469 470 .. code-block:: python 471 472 device.instrument( 473 'com.my.package.test', 474 options = { 475 'class': 'com.my.package.test.TestSuite', 476 }, 477 ) 478 479 Args: 480 package: string, the package of the instrumentation tests. 481 options: dict, the instrumentation options including the test 482 class. 483 runner: string, the test runner name, which defaults to 484 DEFAULT_INSTRUMENTATION_RUNNER. 485 handler: optional func, when specified the function is used to parse 486 the instrumentation stdout line by line as the output is 487 generated; otherwise, the stdout is simply returned once the 488 instrumentation is finished. 489 490 Returns: 491 The stdout of instrumentation command or the stderr if the handler 492 is set. 493 """ 494 if runner is None: 495 runner = DEFAULT_INSTRUMENTATION_RUNNER 496 if options is None: 497 options = {} 498 499 options_list = [] 500 for option_key, option_value in options.items(): 501 options_list.append('-e %s %s' % (option_key, option_value)) 502 options_string = ' '.join(options_list) 503 504 instrumentation_command = 'am instrument -r -w %s %s/%s' % ( 505 options_string, 506 package, 507 runner, 508 ) 509 logging.info( 510 'AndroidDevice|%s: Executing adb shell %s', 511 self.serial, 512 instrumentation_command, 513 ) 514 if handler is None: 515 return self._exec_adb_cmd( 516 'shell', 517 instrumentation_command, 518 shell=False, 519 timeout=None, 520 stderr=None, 521 ) 522 else: 523 return self._execute_adb_and_process_stdout( 524 'shell', instrumentation_command, shell=False, handler=handler 525 ) 526 527 def root(self) -> bytes: 528 """Enables ADB root mode on the device. 529 530 This method will retry to execute the command `adb root` when an 531 AdbError occurs, since sometimes the error `adb: unable to connect 532 for root: closed` is raised when executing `adb root` immediately after 533 the device is booted to OS. 534 535 Returns: 536 A string that is the stdout of root command. 537 538 Raises: 539 AdbError: If the command exit code is not 0. 540 """ 541 retry_interval = ADB_ROOT_RETRY_ATTEMPT_INTERVAL_SEC 542 for attempt in range(ADB_ROOT_RETRY_ATTEMPTS): 543 try: 544 return self._exec_adb_cmd( 545 'root', args=None, shell=False, timeout=None, stderr=None 546 ) 547 except AdbError as e: 548 if attempt + 1 < ADB_ROOT_RETRY_ATTEMPTS: 549 logging.debug( 550 'Retry the command "%s" since Error "%s" occurred.' 551 % ( 552 utils.cli_cmd_to_string(e.cmd), 553 e.stderr.decode('utf-8').strip(), 554 ) 555 ) 556 # Buffer between "adb root" commands. 557 time.sleep(retry_interval) 558 retry_interval *= 2 559 else: 560 raise e 561 562 def __getattr__(self, name): 563 def adb_call(args=None, shell=False, timeout=None, stderr=None) -> bytes: 564 """Wrapper for an ADB command. 565 566 Args: 567 args: string or list of strings, arguments to the adb command. 568 See subprocess.Proc() documentation. 569 shell: bool, True to run this command through the system shell, 570 False to invoke it directly. See subprocess.Proc() docs. 571 timeout: float, the number of seconds to wait before timing out. 572 If not specified, no timeout takes effect. 573 stderr: a Byte stream, like io.BytesIO, stderr of the command 574 will be written to this object if provided. 575 576 Returns: 577 The output of the adb command run if exit code is 0. 578 """ 579 return self._exec_adb_cmd( 580 name, args, shell=shell, timeout=timeout, stderr=stderr 581 ) 582 583 return adb_call 584