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