1# Copyright (C) 2016 The Android Open Source Project 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 15'''Module that contains the class UtilAndroid, providing utility method to 16interface with Android ADB.''' 17 18from __future__ import absolute_import 19 20import logging 21import re 22import subprocess 23import time 24import collections 25import multiprocessing 26try: 27 # Python 3 28 import queue 29except ImportError: 30 import Queue as queue 31 32from .exception import TestSuiteException 33from . import util_log 34 35 36class UtilAndroid(object): 37 '''Provides some utility methods that interface with Android using adb.''' 38 # pylint: disable=too-many-public-methods 39 40 def __init__(self, adb_path, lldb_server_path_device, device): 41 # The path to the adb binary on the local machine 42 self._path_adb = adb_path 43 # The path to the lldb server binary on the device 44 self._path_lldbserver = lldb_server_path_device 45 self._log = util_log.get_logger() 46 self.device = device 47 self._prop_stacks = collections.defaultdict(list) 48 return 49 50 @staticmethod 51 def _validate_string(string): 52 '''Check that a string is valid and not empty. 53 54 Args: 55 string: The string to be checked. 56 ''' 57 assert isinstance(string, str) 58 assert len(string) > 0 59 60 def adb(self, args, async=False, device=True, timeout=None): 61 '''Run an adb command (async optional). 62 63 Args: 64 args: The command (including arguments) to run in adb. 65 async: Boolean to specify whether adb should run the command 66 asynchronously. 67 device: boolean to specify whether the serial id of the android 68 device should be inserted in the adb command. 69 timeout: it specifies the number of seconds to wait for 70 a synchronous invocation before aborting. If unspecified or 71 None it waits indefinitely for the command to complete. 72 73 Raises: 74 ValueError: it can be caused by any of the following situations: 75 - when both the combination async=True and timeout are 76 given. 77 - when a timeout <= 0 is specified. 78 79 Returns: 80 If adb was synchronously run and the command completed by the 81 specified timeout, a string which is the output (standard out and 82 error) from adb. Otherwise it returns None. 83 ''' 84 85 # Form the command 86 if device: 87 cmd = '{0} -s {1} {2}'.format(self._path_adb, self.device, args) 88 else: 89 cmd = '{0} {1}'.format(self._path_adb, args) 90 91 self._log.debug('Execute ADB: %s', cmd) 92 93 if timeout is None: 94 # local invocation 95 return_code, output = UtilAndroid._execute_command_local(cmd, async) 96 97 else: 98 # remote invocation 99 if async: 100 raise ValueError('Invalid combination: asynchronous invocation ' 101 'with timeout specified') 102 103 return_code, output = UtilAndroid._execute_command_remote(cmd, 104 timeout) 105 106 if return_code is None: 107 self._log.warn('[ADB] The command timed out: %s', cmd) 108 109 # log the output message 110 if output is not None: 111 self._adb_log_output(cmd, output, return_code) 112 113 return output 114 115 def adb_retry(self, args, max_num_attempts, timeout): 116 '''Attempt to execute the given adb command a certain number of times. 117 118 The function executes the given command through adb, waiting for its 119 completion up to 'timeout' seconds. If the command completes then it 120 returns its output. Otherwise it aborts the execution of the adb 121 command and re-issues it anew with the same parameters. In case of 122 timeout this process is repeated up to 'max_num_attempts'. 123 124 The purpose of this function is to handle the cases when, for some 125 reason, a command sent to 'adb' freezes, blocking the whole test suite 126 indefinitely. 127 128 Args: 129 args: The command (including arguments) to run in adb. 130 max_num_attempts: the max number of attempts to repeat the command 131 in case of timeout. 132 timeout: it specifies the number of seconds to wait for the adb 133 command to complete. 134 135 Raises: 136 ValueError: when the parameter timeout is invalid (None or <= 0). 137 138 Returns: 139 If adb was synchronously run and the command completes by the 140 specified timeout, a string which is the output (standard out and 141 error) from adb. Otherwise it returns None. 142 ''' 143 if timeout is None or timeout <= 0: 144 raise ValueError('Invalid value for timeout') 145 146 output = None 147 148 for attempt in range(max_num_attempts): 149 self._log.debug('[ADB] Attempt #%d: %s', attempt + 1, args) 150 output = self.adb(args, False, True, timeout) 151 if output: 152 break 153 154 return output 155 156 def _adb_log_output(self, cmd, output, return_code): 157 '''Save in the log the command & output from `adb`. 158 159 Internal function, helper to record in the log the issued adb command 160 together with its output and return code. 161 162 Params: 163 cmd: string, the command issued to `adb`. 164 output: string, the output retrieved from `adb`. 165 return_code: int, the return code from `adb`. 166 ''' 167 168 message = output.strip() 169 170 # if return_code != 0, we wish to also record the command executed 171 # (which occurs if and only if we are in verbose mode) 172 is_warning = return_code != 0 173 threshold = self._log.getEffectiveLevel() 174 if is_warning and threshold > logging.DEBUG: 175 self._log.warn("[ADB] Command executed: {0}".format(cmd)) 176 177 level = logging.WARNING if is_warning else logging.DEBUG 178 if message: 179 # if message is composed by multiple lines, then print it after 180 # the log preamble 181 if re.search('\n', message): 182 message = '\n' + message 183 else: 184 message = '<empty>' 185 186 self._log.log(level, 'RC: {0}, Output: {1}'.format(return_code, 187 message)) 188 189 def check_adb_alive(self): 190 '''Ping the device and raise an exception in case of timeout. 191 192 It sends a ping message through 'adb shell'. The emulator/device should 193 echo the same message back by one minute. If it does not, it raises 194 a TestSuiteException. 195 196 Purpose of this method is to check whether 'adb' became frozen or 197 stuck. 198 199 Raises: 200 TestSuiteException: in case the device/emulator does not reply by 201 one minute or the `ping' message is not echoed 202 back. 203 ''' 204 token = 'PING' 205 log = util_log.get_logger() 206 cmd = "echo {0}".format(token) 207 208 tries = 10 209 try_number = tries 210 while try_number > 0: 211 log.debug('Sending a ping through "adb shell" (try #%s)...', 212 try_number) 213 output = self.shell(cmd, False, 60) 214 215 if output is None: 216 raise TestSuiteException( 217 'Timeout when pinging the device/emulator through ' 218 '"adb shell". Is "adb" stuck or dead?') 219 elif token not in output: 220 log.debug('Ping failed. Cannot match the token "%s" in "adb ' 221 'shell %s"', token, cmd) 222 else: 223 log.debug('Pong message received') 224 return 225 226 try_number -= 1 227 time.sleep(5) 228 229 raise TestSuiteException('Cannot ping the device/emulator through ' 230 '"adb shell". Tried %s times. Is "adb" stuck ' 231 'or dead?' % tries) 232 233 def shell(self, cmd, async=False, timeout=None): 234 '''Run a command via the adb shell. 235 236 Args: 237 cmd: The command (including arguments) to run in the adb shell. 238 async: Boolean to specify whether adb should run the command 239 asynchronously. 240 timeout: it specifies the number of seconds to wait for 241 a synchronous invocation before aborting. If unspecified or 242 None it waits indefinitely for the command to complete 243 244 Returns: 245 If adb was synchronously run, a string which is the output (standard 246 out and error) from adb. Otherwise None. 247 ''' 248 return self.adb('shell "{0}"'.format(cmd), async, True, timeout) 249 250 def find_app_pid(self, process_name): 251 '''Find the process ID of a process with a given name. 252 253 If more than one instance of the process is running return the first pid 254 it finds. 255 256 Args: 257 process_name: A string representing the name of the package or 258 binary for which the id should be found. I.e. the 259 string or part of the string that shows up in the "ps" 260 command. 261 262 Returns: 263 An integer representing the id of the process, or None if it was not 264 found. 265 ''' 266 self._validate_string(process_name) 267 268 pid_output = self.shell('pidof ' + process_name) 269 pid_output = re.sub(r'\*.+\*', '', pid_output) 270 pids = pid_output.split() 271 272 if len(pids) < 1: 273 self._log.warn('Unable to find pid of: {0}'.format(process_name)) 274 return None 275 276 if len(pids) > 1: 277 self._log.warn('Found multiple instances of {0} running: {1}' 278 .format(process_name, pids)) 279 280 try: 281 pid = int(pids[0]) 282 self._log.info('App pid found: {0}'.format(pids[0])) 283 return pid 284 except ValueError: 285 return None 286 287 def adb_root(self): 288 '''Set adb to be in root mode.''' 289 self.adb('root') 290 291 def _adb_remount(self): 292 '''Remount the filesystem of the device.''' 293 self.adb('remount') 294 295 def validate_adb(self): 296 '''Validate adb that it can be run. 297 298 Raises: 299 TestSuiteException: Unable to validate that adb exists and runs 300 successfully. 301 ''' 302 out = self.adb('version', False, False) 303 if out and 'Android' in out and 'version' in out: 304 self._log.info('adb found: {0}'.format(out)) 305 return None 306 raise TestSuiteException('unable to validate adb') 307 308 def is_booted(self): 309 ''' Check if the device/emulator has finished booting. 310 311 Returns: True if the property sys.boot_completed is true, False 312 otherwise. 313 ''' 314 return self._get_prop('sys.boot_completed').strip() == '1' 315 316 def validate_device(self, check_boot=True, device_substring=''): 317 '''Validate that there is at least one device. 318 319 Args: 320 check_boot: Boolean to specify whether to check whether the device 321 has finished booting as well as being present. 322 device_substring: String that needs to be part of the name of the 323 device. 324 325 Raises: 326 TestSuiteException: There was a failure to run adb to list the 327 devices or there is no device connected or 328 multiple devices connected without the user 329 having specified the device to use. 330 ''' 331 332 out = self.adb('devices', False, False) 333 if not 'List of devices attached' in out: 334 raise TestSuiteException('Unable to list devices') 335 336 lines = out.split('\n') 337 found_device = False # True if the specified device is found 338 devices = [] 339 340 for line in lines[1:]: 341 if '\tdevice' in line and device_substring in line: 342 device = line.split()[0] 343 devices.append(device) 344 if self.device: 345 if self.device == device: 346 found_device = True 347 348 if len(devices) == 0: 349 raise TestSuiteException('adb is unable to find a connected ' 350 'device/emulator to test.') 351 352 if not self.device: 353 if len(devices) == 1: 354 self.device = devices[0] 355 else: 356 raise TestSuiteException('Multiple devices connected,' 357 'specify -d device id.') 358 else: 359 if not found_device: 360 raise TestSuiteException('Couldn\'t find the device {0} that ' 361 'was specified, please check -d ' 362 'argument'.format(self.device)) 363 364 if check_boot and not self.is_booted(): 365 raise TestSuiteException( 366 'The device {0} has not yet finished booting.' 367 .format(self.device)) 368 369 def device_with_substring_exists(self, device_substring): 370 '''Check whether a device exists whose name contains a given string. 371 372 Args: 373 device_substring: String that is part of the name of the device to 374 look for. 375 376 Raises: 377 TestSuiteException: There was a failure to run adb to list the 378 devices. 379 ''' 380 out = self.adb('devices', False, False) 381 if not 'List of devices attached' in out: 382 raise TestSuiteException('Unable to list devices') 383 384 lines = out.split('\n') 385 386 for line in lines[1:]: 387 if '\tdevice' in line: 388 device = line.split()[0] 389 if device.find(device_substring) != -1: 390 return True 391 392 return False 393 394 def get_device_id(self): 395 '''Return ID of the device that will be used for running the tests on. 396 397 Returns: 398 String representing device ID. 399 ''' 400 return self.device 401 402 def _kill_pid(self, pid): 403 '''Kill a process identified by its pid by issuing a "kill" command. 404 405 Args: 406 pid: The integer that is the process id of the process to be killed. 407 ''' 408 self.shell('kill -9 ' + str(pid)) 409 410 def stop_app(self, package_name): 411 '''Terminate an app by calling am force-stop. 412 413 Args: 414 package_name: The string representing the name of the package of the 415 app that is to be stopped. 416 ''' 417 self._validate_string(package_name) 418 self.shell('am force-stop ' + package_name) 419 420 def kill_process(self, name): 421 '''Kill a process identified by its name (package name in case of apk). 422 423 Issues the "kill" command. 424 425 Args: 426 name: The string representing the name of the binary of the process 427 that is to be killed. 428 429 Returns: 430 True if the kill command was executed, False if it could not be 431 found. 432 ''' 433 pid = self.find_app_pid(name) 434 if pid: 435 self._kill_pid(pid) 436 return True 437 return False 438 439 def kill_all_processes(self, name): 440 '''Repeatedly try to call "kill" on a process to ensure it is gone. 441 442 If the process is still there after 5 attempts reboot the device. 443 444 Args: 445 name: The string representing the name of the binary of the process 446 that is to be killed. 447 448 Raises: 449 TestSuiteException: If the process could not be killed after 5 450 attempts and the device then failed to boot 451 after rebooting. 452 ''' 453 454 # try 5 times to kill this process 455 for _ in range(1, 5): 456 if not self.kill_process(name): 457 return 458 # stalled process must reboot 459 self._reboot_device() 460 461 def kill_servers(self): 462 '''Kill all gdbserver and lldb-server instances. 463 464 Raises: 465 TestSuiteException: If gdbserver or lldb-server could not be killed 466 after 5 attempts and the device then failed to 467 boot after rebooting. 468 ''' 469 self.kill_all_processes('gdbserver') 470 self.kill_all_processes('lldb-server') 471 472 def launch_elf(self, binary_name): 473 '''Launch a binary (compiled with the NDK). 474 475 Args: 476 binary_name: The string representing the name of the binary that is 477 to be launched. 478 479 Returns: 480 Boolean, failure if the app is not installed, success otherwise. 481 ''' 482 # Ensure the apk is actually installed. 483 output = self.shell('ls /data/ | grep ' + binary_name) 484 if binary_name not in output: 485 return False 486 487 stdout = self.shell('exec /data/' + binary_name, True) 488 self._log.info(str(stdout)) 489 490 return True 491 492 def wait_for_device(self): 493 '''Ask ADB to wait for a device to become ready.''' 494 self.adb('wait-for-device') 495 496 def _reboot_device(self): 497 '''Reboot the remote device. 498 499 Raises: 500 TestSuiteException: If the device failed to boot after rebooting. 501 ''' 502 self.adb('reboot') 503 self.wait_for_device() 504 # Allow 20 mins boot time to give emulators such as MIPS enough time 505 sleeping_countdown = 60*20 506 while not self.is_booted(): 507 time.sleep(1) 508 sleeping_countdown -= 1 509 if sleeping_countdown == 0: 510 raise TestSuiteException('Failed to reboot. Terminating.') 511 512 self.adb_root() 513 self.wait_for_device() 514 self._adb_remount() 515 self.wait_for_device() 516 517 def launch_app(self, name, activity): 518 '''Launch a Renderscript application. 519 520 Args: 521 name: The string representing the name of the app that is to be 522 launched. 523 activity: The string representing the activity of the app that is to 524 be started. 525 526 Returns: 527 Boolean, failure if the apk is not installed, success otherwise. 528 ''' 529 assert name and activity 530 531 # Ensure the apk is actually installed. 532 output = self.shell('pm list packages ' + name) 533 if not output: 534 return False 535 536 cmd = 'am start -S -W {0}/{0}.{1}'.format(name, activity) 537 stdout = self.shell(cmd) 538 539 self._log.info(str(stdout)) 540 541 return True 542 543 def launch_lldb_platform(self, port): 544 '''Launch lldb server and attach to target app. 545 546 Args: 547 port: The integer that is the port on which lldb should listen. 548 ''' 549 cmd = "export LLDB_DEBUGSERVER_PATH='{0}';{0} p --listen *:{1}"\ 550 .format(self._path_lldbserver, port) 551 self.shell(cmd, True) 552 time.sleep(5) 553 554 def forward_port(self, local, remote): 555 '''Use adb to forward a device port onto the local machine. 556 557 Args: 558 local: The integer that is the local port to forward. 559 remote: The integer that is the remote port to which to forward. 560 ''' 561 cmd = 'forward tcp:%s tcp:%s' % (str(local), str(remote)) 562 self.adb(cmd) 563 564 def remove_port_forwarding(self): 565 '''Remove all of the forward socket connections open in adb. 566 567 Avoids a windows adb error where we can't bind to a listener 568 because too many files are open. 569 ''' 570 self.adb('forward --remove-all') 571 572 def _get_prop(self, name): 573 '''Get the value of an Android system property. 574 575 Args: 576 name: Name of the property of interest [string]. 577 578 Returns: 579 Current value of the property [string]. 580 ''' 581 return self.shell('getprop %s' % str(name)) 582 583 def _set_prop(self, name, value): 584 '''Set the value of an Android system property. 585 586 Args: 587 name: Name of the property of interest [string]. 588 value: Desired new value for the property [string or integer]. 589 ''' 590 self.shell("setprop %s '%s'" % (str(name), str(value))) 591 592 def push_prop(self, name, new_value): 593 '''Save the value of an Android system property and set a new value. 594 595 Saves the old value onto a stack so it can be restored later. 596 597 Args: 598 name: Name of the property of interest [string]. 599 new_value: Desired new value for the property [string or integer]. 600 ''' 601 old_value = self._get_prop(name) 602 self._set_prop(name, new_value) 603 self._prop_stacks[name].append(old_value.strip()) 604 605 def pop_prop(self, name): 606 '''Restore the value of an Android system property previously set by 607 push_prop. 608 609 Args: 610 name: Name of the property of interest [string]. 611 612 Returns: 613 Current value of the property [string]. 614 ''' 615 old_value = self._prop_stacks[name].pop() 616 self._set_prop(name, old_value) 617 618 def reset_all_props(self): 619 '''Restore all the android properties to the state before the first push 620 621 This is equivalent to popping each property the number of times it has 622 been pushed. 623 ''' 624 for name in self._prop_stacks: 625 if self._prop_stacks[name] != []: 626 self._set_prop(name, self._prop_stacks[name][0]) 627 self._prop_stacks[name] = [] 628 629 def make_device_writeable(self): 630 ''' Ensure the device is full writable, in particular the system folder. 631 632 This disables verity and remounts. 633 ''' 634 output = self.adb('disable-verity') 635 636 # if the remote is an emulator do not even try to reboot 637 # otherwise check whether a reboot is advised 638 if (self._get_prop('ro.kernel.qemu') != '1' and output and 639 'Now reboot your device for settings to take effect' in output): 640 self._reboot_device() 641 642 self._adb_remount() 643 self.wait_for_device() 644 self.adb_root() 645 self.wait_for_device() 646 647 @staticmethod 648 def _execute_command_local(command, async=False): 649 '''Execute the given shell command in the same process. 650 651 Args: 652 command: String, the command to execute 653 async: Boolean to specify whether adb should run the command 654 asynchronously. 655 656 Returns: 657 if async == False, it returns a tuple with the return code and 658 the output from the executed command. Otherwise the tuple 659 (None, None). 660 ''' 661 proc = subprocess.Popen(command, 662 stdout=subprocess.PIPE, 663 stderr=subprocess.STDOUT, 664 shell=True) 665 if async: 666 return None, None 667 668 # read the whole output from the command 669 with proc.stdout as file_proc: 670 output = ''.join(line for line in file_proc) 671 672 # release the process state 673 proc.terminate() 674 return_code = proc.wait() 675 676 return return_code, output 677 678 @staticmethod 679 def _execute_command_remote(command, timeout): 680 '''Execute the given shell command remotely, in a separate process. 681 682 It spawns an ad hoc process to execute the given command. It waits up 683 to timeout for the command to complete, otherwise it aborts the 684 execution and returns None. 685 686 Args: 687 command: String, the command to execute. 688 timeout: the number of seconds to wait for the command to complete. 689 690 Returns: 691 a pair with the return code and the output from the command, if it 692 completed by the specified 'timeout' seconds. Otherwise the tuple 693 (None, None). 694 ''' 695 696 channel = multiprocessing.Queue() 697 proc = multiprocessing.Process( 698 target=_handle_remote_request, 699 name="Executor of `{0}'".format(command), 700 args=(command, channel) 701 ) 702 703 # execute the command 704 proc.start() 705 return_code = None 706 output = None 707 708 # wait for the result 709 try: 710 return_code, output = channel.get(True, timeout) 711 except queue.Empty: 712 # timeout hit, the remote process has not fulfilled our request by 713 # the given time. We are going to return <None, None>, nothing to 714 # do here as it already holds return_code = output = None. 715 pass 716 717 # terminate the helper process 718 proc.terminate() 719 720 return return_code, output 721 722 723def _handle_remote_request(command, channel): 724 '''Entry point for the remote process. 725 726 It executes the given command and reports the result into the channel. 727 This function is supposed to be only called by 728 UtilAndroid._execute_command_remote to handle the inter-process 729 communication. 730 731 Args: 732 command: the command to execute. 733 channel: the channel to communicate with the caller process. 734 ''' 735 channel.put(UtilAndroid._execute_command_local(command)) 736 737