1# Lint as: python2, python3 2# Copyright 2015 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6from __future__ import absolute_import 7from __future__ import division 8from __future__ import print_function 9 10import collections 11import glob 12import logging 13import os 14import pipes 15import shutil 16import socket 17import sys 18import tempfile 19import time 20 21from autotest_lib.client.bin import test, utils 22from autotest_lib.client.common_lib import error 23from autotest_lib.client.common_lib.cros import chrome, arc_common 24from six.moves import range 25 26_ADB_KEYS_PATH = '/tmp/adb_keys' 27_ADB_VENDOR_KEYS = 'ADB_VENDOR_KEYS' 28_ANDROID_CONTAINER_PID_PATH = '/run/containers/android*/container.pid' 29_ANDROID_DATA_ROOT_PATH = '/opt/google/containers/android/rootfs/android-data' 30_ANDROID_CONTAINER_ROOT_PATH = '/opt/google/containers/android/rootfs' 31_SCREENSHOT_DIR_PATH = '/var/log/arc-screenshots' 32_SCREENSHOT_BASENAME = 'arc-screenshot' 33_MAX_SCREENSHOT_NUM = 10 34# This address should match the one present in 35# https://chromium.googlesource.com/chromiumos/overlays/chromiumos-overlay/+/main/chromeos-base/arc-sslh-init/files/sslh.conf 36_ADBD_ADDRESS = ('100.115.92.2', 5555) 37_ADBD_PID_PATH = '/run/arc/adbd.pid' 38_SDCARD_PID_PATH = '/run/arc/sdcard.pid' 39_ANDROID_ADB_KEYS_PATH = '/data/misc/adb/adb_keys' 40_PROCESS_CHECK_INTERVAL_SECONDS = 1 41_PROPERTY_CHECK_INTERVAL_SECONDS = 1 42_WAIT_FOR_ADB_READY = 60 43_WAIT_FOR_ANDROID_PROCESS_SECONDS = 60 44_PLAY_STORE_PKG = 'com.android.vending' 45_SETTINGS_PKG = 'com.android.settings' 46 47 48def setup_adb_host(): 49 """Setup ADB host keys. 50 51 This sets up the files and environment variables that wait_for_adb_ready() 52 needs""" 53 if _ADB_VENDOR_KEYS in os.environ: 54 return 55 if not os.path.exists(_ADB_KEYS_PATH): 56 os.mkdir(_ADB_KEYS_PATH) 57 # adb expects $HOME to be writable. 58 os.environ['HOME'] = _ADB_KEYS_PATH 59 60 # Generate and save keys for adb if needed 61 key_path = os.path.join(_ADB_KEYS_PATH, 'test_key') 62 if not os.path.exists(key_path): 63 utils.system('adb keygen ' + pipes.quote(key_path)) 64 os.environ[_ADB_VENDOR_KEYS] = key_path 65 66 67def restart_adbd(timeout): 68 """Restarts the adb daemon. 69 70 Follows the same logic as tast. 71 """ 72 logging.debug('restarting adbd') 73 config = 'adb' 74 _android_shell('setprop persist.sys.usb.config ' + config) 75 _android_shell('setprop sys.usb.config ' + config) 76 77 def property_check(): 78 return _android_shell('getprop sys.usb.state') == config 79 80 try: 81 utils.poll_for_condition( 82 condition=property_check, 83 desc='Wait for sys.usb.state', 84 timeout=timeout, 85 sleep_interval=_PROPERTY_CHECK_INTERVAL_SECONDS) 86 except utils.TimeoutError: 87 raise error.TestFail('Timed out waiting for sys.usb.state change') 88 89 _android_shell('setprop ctl.restart adbd') 90 91 92def restart_adb(): 93 """Restarts adb. 94 95 Follows the same logic as in tast, specifically avoiding kill-server 96 since it is unreliable (crbug.com/855325). 97 """ 98 logging.debug('killing and restarting adb server') 99 utils.system('killall --quiet --wait -KILL adb') 100 utils.system('adb start-server') 101 102 103def is_adb_connected(): 104 """Return true if adb is connected to the container.""" 105 output = utils.system_output('adb get-state', ignore_status=True) 106 logging.debug('adb get-state: %s', output) 107 return output.strip() == 'device' 108 109 110def _is_android_data_mounted(): 111 """Return true if Android's /data is mounted with partial boot enabled.""" 112 return _android_shell('getprop ro.data_mounted', ignore_status=True) == '1' 113 114 115def get_zygote_type(): 116 """Return zygote service type.""" 117 return _android_shell('getprop ro.zygote', ignore_status=True) 118 119 120def get_sdk_version(): 121 """Return the SDK level version for Android.""" 122 return _android_shell('getprop ro.build.version.sdk') 123 124 125def get_product(): 126 """Return the product string used for the Android build.""" 127 return _android_shell('getprop ro.build.product', ignore_status=True) 128 129 130def _is_tcp_port_reachable(address): 131 """Return whether a TCP port described by |address| is reachable.""" 132 try: 133 s = socket.create_connection(address) 134 s.close() 135 return True 136 except socket.error: 137 return False 138 139 140def _wait_for_data_mounted(timeout): 141 utils.poll_for_condition( 142 condition=_is_android_data_mounted, 143 desc='Wait for /data mounted', 144 timeout=timeout, 145 sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS) 146 147 148def wait_for_adb_ready(timeout=_WAIT_FOR_ADB_READY): 149 """Wait for the ADB client to connect to the ARC container. 150 151 @param timeout: Timeout in seconds. 152 """ 153 # Although adbd is started at login screen, we still need /data to be 154 # mounted to set up key-based authentication. /data should be mounted 155 # once the user has logged in. 156 157 initial_timeout = timeout 158 159 start_time = time.time() 160 _wait_for_data_mounted(timeout) 161 timeout -= (time.time() - start_time) 162 start_time = time.time() 163 arc_common.wait_for_android_boot(timeout) 164 timeout -= (time.time() - start_time) 165 166 setup_adb_host() 167 if is_adb_connected(): 168 return 169 170 # Push keys for adb. 171 pubkey_path = os.environ[_ADB_VENDOR_KEYS] + '.pub' 172 with open(pubkey_path, 'r') as f: 173 _write_android_file(_ANDROID_ADB_KEYS_PATH, f.read()) 174 _android_shell('chown shell ' + pipes.quote(_ANDROID_ADB_KEYS_PATH)) 175 _android_shell('restorecon ' + pipes.quote(_ANDROID_ADB_KEYS_PATH)) 176 177 attempt_count = 3 178 timeout = timeout / attempt_count 179 180 for i in range(attempt_count): 181 if _restart_adb_and_wait_for_ready(timeout): 182 return 183 raise error.TestFail( 184 'Failed to connect to adb in %d seconds.' % initial_timeout) 185 186 187def _restart_adb_and_wait_for_ready(timeout): 188 """Restart adb/adbd and wait adb connection is ready. 189 190 @param timeout: Timeout in seconds. 191 @return True in case adb connection was established or throw an error in 192 case persistent error occured. 193 """ 194 195 # Restart adbd and adb. 196 start_time = time.time() 197 restart_adbd(timeout) 198 timeout -= (time.time() - start_time) 199 start_time = time.time() 200 restart_adb() 201 timeout -= (time.time() - start_time) 202 203 try: 204 utils.poll_for_condition(condition=is_adb_connected, 205 timeout=timeout) 206 return True 207 except (utils.TimeoutError): 208 # The operation has failed, but let's try to clarify the failure to 209 # avoid shifting blame to adb. 210 211 # First, collect some information and log it. 212 arc_alive = is_android_container_alive() 213 arc_booted = _android_shell('getprop ro.arc.boot_completed', 214 ignore_status=True) 215 arc_system_events = _android_shell( 216 'logcat -d -b events *:S arc_system_event', ignore_status=True) 217 adbd_pid = _android_shell('pidof -s adbd', ignore_status=True) 218 adbd_port_reachable = _is_tcp_port_reachable(_ADBD_ADDRESS) 219 adb_state = utils.system_output('adb get-state', ignore_status=True) 220 logging.debug('ARC alive: %s', arc_alive) 221 logging.debug('ARC booted: %s', arc_booted) 222 logging.debug('ARC system events: %s', arc_system_events) 223 logging.debug('adbd process: %s', adbd_pid) 224 logging.debug('adbd port reachable: %s', adbd_port_reachable) 225 logging.debug('adb state: %s', adb_state) 226 227 # Now go through the usual suspects and raise nicer errors to make the 228 # actual failure clearer. 229 if not arc_alive: 230 raise error.TestFail('ARC is not alive.') 231 if arc_booted != '1': 232 raise error.TestFail('ARC did not finish booting.') 233 return False 234 235 236def grant_permissions(package, permissions): 237 """Grants permissions to a package. 238 239 @param package: Package name. 240 @param permissions: A list of permissions. 241 242 """ 243 for permission in permissions: 244 adb_shell('pm grant %s android.permission.%s' % ( 245 pipes.quote(package), pipes.quote(permission))) 246 247 248def adb_cmd(cmd, **kwargs): 249 """Executed cmd using adb. Must wait for adb ready. 250 251 @param cmd: Command to run. 252 """ 253 # TODO(b/79122489) - Assert if cmd == 'root' 254 wait_for_adb_ready() 255 return utils.system_output('adb %s' % cmd, **kwargs) 256 257 258def adb_shell(cmd, **kwargs): 259 """Executed shell command with adb. 260 261 @param cmd: Command to run. 262 """ 263 output = adb_cmd('shell %s' % pipes.quote(cmd), **kwargs) 264 # Some android commands include a trailing CRLF in their output. 265 if kwargs.pop('strip_trailing_whitespace', True): 266 output = output.rstrip() 267 return output 268 269 270def adb_install(apk, auto_grant_permissions=True, ignore_status=False): 271 """Install an apk into container. You must connect first. 272 273 @param apk: Package to install. 274 @param auto_grant_permissions: Set to false to not automatically grant all 275 permissions. Most tests should not care. 276 @param ignore_status: Set to true to allow the install command to fail, 277 for example if you are installing multiple architectures and only need 278 one to succeed. 279 """ 280 flags = '-g' if auto_grant_permissions else '' 281 return adb_cmd('install -r -t %s %s' % (flags, apk), 282 timeout=60*5, 283 ignore_status=ignore_status) 284 285 286def adb_uninstall(apk): 287 """Remove an apk from container. You must connect first. 288 289 @param apk: Package to uninstall. 290 """ 291 return adb_cmd('uninstall %s' % apk) 292 293 294def adb_reboot(): 295 """Reboots the container and block until container pid is gone. 296 297 You must connect first. 298 """ 299 old_pid = get_container_pid() 300 logging.info('Trying to reboot PID:%s', old_pid) 301 adb_cmd('reboot', ignore_status=True) 302 # Ensure that the old container is no longer booted 303 utils.poll_for_condition( 304 lambda: not utils.pid_is_alive(int(old_pid)), timeout=10) 305 306 307# This adb_root() function is deceiving in that it works just fine on debug 308# builds of ARC (user-debug, eng). However "adb root" does not work on user 309# builds as run by the autotest machines when testing prerelease images. In fact 310# it will silently fail. You will need to find another way to do do what you 311# need to do as root. 312# 313# TODO(b/79122489) - Remove this function. 314def adb_root(): 315 """Restart adbd with root permission.""" 316 317 adb_cmd('root') 318 319 320def get_container_root(): 321 """Returns path to Android container root directory.""" 322 return _ANDROID_CONTAINER_ROOT_PATH 323 324 325def get_container_pid_path(): 326 """Returns the container's PID file path. 327 328 Raises: 329 TestError if no PID file is found, or more than one files are found. 330 """ 331 # Find the PID file rather than the android-XXXXXX/ directory to ignore 332 # stale and empty android-XXXXXX/ directories when they exist. 333 arc_container_pid_files = glob.glob(_ANDROID_CONTAINER_PID_PATH) 334 335 if len(arc_container_pid_files) == 0: 336 raise error.TestError('Android container.pid not available') 337 338 if len(arc_container_pid_files) > 1: 339 raise error.TestError( 340 'Multiple Android container.pid files found: %r. ' 341 'Reboot your DUT to recover.' % (arc_container_pid_files)) 342 343 return arc_container_pid_files[0] 344 345 346def get_android_data_root(): 347 """Returns path to ChromeOS directory that bind-mounts Android's /data.""" 348 return _ANDROID_DATA_ROOT_PATH 349 350 351def get_container_pid(): 352 """Returns the PID of the container.""" 353 return utils.read_one_line(get_container_pid_path()) 354 355 356def get_adbd_pid(): 357 """Returns the PID of the adbd proxy container.""" 358 if not os.path.exists(_ADBD_PID_PATH): 359 # The adbd proxy does not run on all boards. 360 return None 361 return utils.read_one_line(_ADBD_PID_PATH) 362 363 364def is_android_process_running(process_name): 365 """Return whether Android has completed booting. 366 367 @param process_name: Process name. 368 """ 369 output = adb_shell('pgrep -c -f %s' % pipes.quote(process_name), 370 ignore_status=True) 371 return int(output) > 0 372 373 374def check_android_file_exists(filename): 375 """Checks whether the given file exists in the Android filesystem 376 377 @param filename: File to check. 378 """ 379 return adb_shell( 380 'test -e {} && echo FileExists'.format(pipes.quote(filename)), 381 ignore_status=True).find("FileExists") >= 0 382 383 384def read_android_file(filename): 385 """Reads a file in Android filesystem. 386 387 @param filename: File to read. 388 """ 389 with tempfile.NamedTemporaryFile() as tmpfile: 390 adb_cmd('pull %s %s' % (pipes.quote(filename), 391 pipes.quote(tmpfile.name))) 392 with open(tmpfile.name) as f: 393 return f.read() 394 395 return None 396 397 398def write_android_file(filename, data): 399 """Writes to a file in Android filesystem. 400 401 @param filename: File to write. 402 @param data: Data to write. 403 """ 404 with tempfile.NamedTemporaryFile() as tmpfile: 405 tmpfile.write(data) 406 tmpfile.flush() 407 408 adb_cmd('push %s %s' % (pipes.quote(tmpfile.name), 409 pipes.quote(filename))) 410 411 412def _write_android_file(filename, data): 413 """Writes to a file in Android filesystem. 414 415 This is an internal function used to bootstrap adb. 416 Tests should use write_android_file instead. 417 """ 418 android_cmd = 'cat > %s' % pipes.quote(filename) 419 cros_cmd = 'android-sh -c %s' % pipes.quote(android_cmd) 420 utils.run(cros_cmd, stdin=data) 421 422 423def get_android_file_stats(filename): 424 """Returns an object of file stats for an Android file. 425 426 The returned object supported limited attributes, but can be easily extended 427 if needed. Note that the value are all string. 428 429 This uses _android_shell to run as root, so that it can access to all files 430 inside the container. On non-debuggable build, adb shell is not rootable. 431 """ 432 mapping = { 433 '%a': 'mode', 434 '%g': 'gid', 435 '%h': 'nlink', 436 '%u': 'uid', 437 } 438 output = _android_shell( 439 'stat -c "%s" %s' % (' '.join(mapping.keys()), pipes.quote(filename)), 440 ignore_status=True) 441 stats = output.split(' ') 442 if len(stats) != len(mapping): 443 raise error.TestError('Unexpected output from stat: %s' % output) 444 _Stats = collections.namedtuple('_Stats', mapping.values()) 445 return _Stats(*stats) 446 447 448def remove_android_file(filename): 449 """Removes a file in Android filesystem. 450 451 @param filename: File to remove. 452 """ 453 adb_shell('rm -f %s' % pipes.quote(filename)) 454 455 456def wait_for_android_boot(timeout=None): 457 """Sleep until Android has completed booting or timeout occurs. 458 459 @param timeout: Timeout in seconds. 460 """ 461 arc_common.wait_for_android_boot(timeout) 462 463 464def wait_for_android_process(process_name, 465 timeout=_WAIT_FOR_ANDROID_PROCESS_SECONDS): 466 """Sleep until an Android process is running or timeout occurs. 467 468 @param process_name: Process name. 469 @param timeout: Timeout in seconds. 470 """ 471 condition = lambda: is_android_process_running(process_name) 472 utils.poll_for_condition(condition=condition, 473 desc='%s is running' % process_name, 474 timeout=timeout, 475 sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS) 476 477 478def _android_shell(cmd, **kwargs): 479 """Execute cmd instead the Android container. 480 481 This function is strictly for internal use only, as commands do not run in 482 a fully consistent Android environment. Prefer adb_shell instead. 483 """ 484 return utils.system_output('android-sh -c {}'.format(pipes.quote(cmd)), 485 **kwargs) 486 487 488def is_android_container_alive(): 489 """Check if android container is alive.""" 490 try: 491 container_pid = get_container_pid() 492 except Exception as e: 493 logging.error('is_android_container_alive failed: %r', e) 494 return False 495 return utils.pid_is_alive(int(container_pid)) 496 497 498def _is_in_installed_packages_list(package, option=None): 499 """Check if a package is in the list returned by pm list packages. 500 501 adb must be ready. 502 503 @param package: Package in request. 504 @param option: An option for the command adb shell pm list packages. 505 Valid values include '-s', '-3', '-d', and '-e'. 506 """ 507 command = 'pm list packages' 508 if option: 509 command += ' ' + option 510 packages = adb_shell(command).splitlines() 511 package_entry = 'package:' + package 512 ret = package_entry in packages 513 514 if not ret: 515 logging.info('Could not find "%s" in %s', 516 package_entry, str(packages)) 517 return ret 518 519 520def is_package_installed(package): 521 """Check if a package is installed. adb must be ready. 522 523 @param package: Package in request. 524 """ 525 return _is_in_installed_packages_list(package) 526 527 528def is_package_disabled(package): 529 """Check if an installed package is disabled. adb must be ready. 530 531 @param package: Package in request. 532 """ 533 return _is_in_installed_packages_list(package, '-d') 534 535 536def get_package_install_path(package): 537 """Returns the apk install location of the given package.""" 538 output = adb_shell('pm path {}'.format(pipes.quote(package))) 539 return output.split(':')[1] 540 541 542def _before_iteration_hook(obj): 543 """Executed by parent class before every iteration. 544 545 This function resets the run_once_finished flag before every iteration 546 so we can detect failure on every single iteration. 547 548 Args: 549 obj: the test itself 550 """ 551 obj.run_once_finished = False 552 553 554def _after_iteration_hook(obj): 555 """Executed by parent class after every iteration. 556 557 The parent class will handle exceptions and failures in the run and will 558 always call this hook afterwards. Take a screenshot if the run has not 559 been marked as finished (i.e. there was a failure/exception). 560 561 Args: 562 obj: the test itself 563 """ 564 if not obj.run_once_finished: 565 if is_adb_connected(): 566 logging.debug('Recent activities dump:\n%s', 567 adb_shell('dumpsys activity recents', 568 ignore_status=True)) 569 if not os.path.exists(_SCREENSHOT_DIR_PATH): 570 os.mkdir(_SCREENSHOT_DIR_PATH, 0o755) 571 obj.num_screenshots += 1 572 if obj.num_screenshots <= _MAX_SCREENSHOT_NUM: 573 logging.warning('Iteration %d failed, taking a screenshot.', 574 obj.iteration) 575 try: 576 utils.run('screenshot "{}/{}_iter{}.png"'.format( 577 _SCREENSHOT_DIR_PATH, _SCREENSHOT_BASENAME, obj.iteration)) 578 except Exception as e: 579 logging.warning('Unable to capture screenshot. %s', e) 580 else: 581 logging.warning('Too many failures, no screenshot taken') 582 583 584def send_keycode(keycode): 585 """Sends the given keycode to the container 586 587 @param keycode: keycode to send. 588 """ 589 adb_shell('input keyevent {}'.format(keycode)) 590 591 592def get_android_sdk_version(): 593 """Returns the Android SDK version. 594 595 This function can be called before Android container boots. 596 """ 597 with open('/etc/lsb-release') as f: 598 values = dict(line.split('=', 1) for line in f.read().splitlines()) 599 try: 600 return int(values['CHROMEOS_ARC_ANDROID_SDK_VERSION']) 601 except (KeyError, ValueError): 602 raise error.TestError('Could not determine Android SDK version') 603 604 605def set_device_mode(device_mode, use_fake_sensor_with_lifetime_secs=0): 606 """Sets the device in either Clamshell or Tablet mode. 607 608 "inject_powerd_input_event" might fail if the DUT does not support Tablet 609 mode, and it will raise an |error.CmdError| exception. To prevent that, use 610 the |use_fake_sensor_with_lifetime_secs| parameter. 611 612 @param device_mode: string with either 'clamshell' or 'tablet' 613 @param use_fake_sensor_with_lifetime_secs: if > 0, it will create the 614 input device with the given lifetime in seconds 615 @raise ValueError: if passed invalid parameters 616 @raise error.CmdError: if inject_powerd_input_event fails 617 """ 618 valid_value = ('tablet', 'clamshell') 619 if device_mode not in valid_value: 620 raise ValueError('Invalid device_mode parameter: %s' % device_mode) 621 622 value = 1 if device_mode == 'tablet' else 0 623 624 args = ['--code=tablet', '--value=%d' % value] 625 626 if use_fake_sensor_with_lifetime_secs > 0: 627 args.extend(['--create_dev', '--dev_lifetime=%d' % 628 use_fake_sensor_with_lifetime_secs]) 629 630 try: 631 utils.run('inject_powerd_input_event', args=args) 632 except error.CmdError as err: 633 # TODO: Fragile code ahead. Correct way to do it is to check 634 # if device is already in desired mode, and do nothing if so. 635 # ATM we don't have a way to check current device mode. 636 637 # Assuming that CmdError means that device does not support 638 # --code=tablet parameter, meaning that device only supports clamshell 639 # mode. 640 if device_mode == 'clamshell' and \ 641 use_fake_sensor_with_lifetime_secs == 0: 642 return 643 raise err 644 645 646def wait_for_userspace_ready(): 647 """Waits for userspace apps to be launchable. 648 649 Launches and then closes Android settings as a way to ensure all basic 650 services are ready. This goes a bit beyond waiting for boot-up to complete, 651 as being able to launch an activity requires more of the framework to have 652 started. The boot-complete signal happens fairly early, and the framework 653 system server is still starting services. By waiting for ActivityManager to 654 respond, we automatically wait on more services to be ready. 655 """ 656 output = adb_shell('am start -W -a android.settings.SETTINGS', 657 ignore_status=True) 658 if not output.endswith('Complete'): 659 logging.debug('Output was: %s', output) 660 raise error.TestError('Could not launch SETTINGS') 661 adb_shell('am force-stop com.android.settings', ignore_status=True) 662 663 664class ArcTest(test.test): 665 """ Base class of ARC Test. 666 667 This class could be used as super class of an ARC test for saving 668 redundant codes for container bringup, autotest-dep package(s) including 669 uiautomator setup if required, and apks install/remove during 670 arc_setup/arc_teardown, respectively. By default arc_setup() is called in 671 initialize() after Android have been brought up. It could also be 672 overridden to perform non-default tasks. For example, a simple 673 ArcHelloWorldTest can be just implemented with print 'HelloWorld' in its 674 run_once() and no other functions are required. We could expect 675 ArcHelloWorldTest would bring up browser and wait for container up, then 676 print 'Hello World', and shutdown browser after. As a precaution, if you 677 overwrite initialize(), arc_setup(), or cleanup() function(s) in ARC test, 678 remember to call the corresponding function(s) in this base class as well. 679 """ 680 version = 1 681 _PKG_UIAUTOMATOR = 'uiautomator' 682 _FULL_PKG_NAME_UIAUTOMATOR = 'com.github.uiautomator' 683 684 def __init__(self, *args, **kwargs): 685 """Initialize flag setting.""" 686 super(ArcTest, self).__init__(*args, **kwargs) 687 self.initialized = False 688 # Set the flag run_once_finished to detect if a test is executed 689 # successfully without any exception thrown. Otherwise, generate 690 # a screenshot in /var/log for debugging. 691 self.run_once_finished = False 692 self.logcat_proc = None 693 self.dep_package = None 694 self.apks = None 695 self.full_pkg_names = [] 696 self.uiautomator = False 697 self._should_reenable_play_store = False 698 self._chrome = None 699 if os.path.exists(_SCREENSHOT_DIR_PATH): 700 shutil.rmtree(_SCREENSHOT_DIR_PATH) 701 self.register_before_iteration_hook(_before_iteration_hook) 702 self.register_after_iteration_hook(_after_iteration_hook) 703 # Keep track of the number of debug screenshots taken and keep the 704 # total number valid to avoid issues. 705 self.num_screenshots = 0 706 707 def initialize(self, extension_path=None, username=None, password=None, 708 arc_mode=arc_common.ARC_MODE_ENABLED, **chrome_kargs): 709 """Log in to a test account.""" 710 extension_paths = [extension_path] if extension_path else [] 711 self._chrome = chrome.Chrome(extension_paths=extension_paths, 712 username=username, 713 password=password, 714 arc_mode=arc_mode, 715 **chrome_kargs) 716 if extension_path: 717 self._extension = self._chrome.get_extension(extension_path) 718 else: 719 self._extension = None 720 # With ARC enabled, Chrome will wait until container to boot up 721 # before returning here, see chrome.py. 722 self.initialized = True 723 try: 724 if is_android_container_alive(): 725 self.arc_setup() 726 else: 727 logging.error('Container is alive?') 728 except Exception as err: 729 raise error.TestFail(err) 730 731 def after_run_once(self): 732 """Executed after run_once() only if there were no errors. 733 734 This function marks the run as finished with a flag. If there was a 735 failure the flag won't be set and the failure can then be detected by 736 testing the run_once_finished flag. 737 """ 738 logging.info('After run_once') 739 self.run_once_finished = True 740 741 def cleanup(self): 742 """Log out of Chrome.""" 743 if not self.initialized: 744 logging.info('Skipping ARC cleanup: not initialized') 745 return 746 logging.info('Starting ARC cleanup') 747 try: 748 if is_android_container_alive(): 749 self.arc_teardown() 750 except Exception as err: 751 raise error.TestFail(err) 752 finally: 753 try: 754 if self.logcat_proc: 755 self.logcat_proc.close() 756 finally: 757 if self._chrome is not None: 758 self._chrome.close() 759 760 def _install_apks(self, dep_package, apks, full_pkg_names): 761 """"Install apks fetched from the specified package folder. 762 763 @param dep_package: A dependent package directory 764 @param apks: List of apk names to be installed 765 @param full_pkg_names: List of packages to be uninstalled at teardown 766 """ 767 apk_path = os.path.join(self.autodir, 'deps', dep_package) 768 if apks: 769 for apk in apks: 770 logging.info('Installing %s', apk) 771 out = adb_install('%s/%s' % (apk_path, apk), ignore_status=True) 772 logging.info('Install apk output: %s', str(out)) 773 # Verify if package(s) are installed correctly. We ignored 774 # individual install statuses above because some tests list apks for 775 # all arches and only need one installed. 776 if not full_pkg_names: 777 raise error.TestError('Package names of apks expected') 778 for pkg in full_pkg_names: 779 logging.info('Check if %s is installed', pkg) 780 if not is_package_installed(pkg): 781 raise error.TestError('Package %s not found' % pkg) 782 # Make sure full_pkg_names contains installed packages only 783 # so arc_teardown() knows what packages to uninstall. 784 self.full_pkg_names.append(pkg) 785 786 def _count_nested_array_level(self, array): 787 """Count the level of a nested array.""" 788 if isinstance(array, list): 789 return 1 + self._count_nested_array_level(array[0]) 790 return 0 791 792 def _fix_nested_array_level(self, var_name, expected_level, array): 793 """Enclose array one level deeper if needed.""" 794 level = self._count_nested_array_level(array) 795 if level == expected_level: 796 return array 797 if level == expected_level - 1: 798 return [array] 799 800 logging.error("Variable %s nested level is not fixable: " 801 "Expecting %d, seeing %d", 802 var_name, expected_level, level) 803 raise error.TestError('Format error with variable %s' % var_name) 804 805 def arc_setup(self, dep_packages=None, apks=None, full_pkg_names=None, 806 uiautomator=False, disable_play_store=False): 807 """ARC test setup: Setup dependencies and install apks. 808 809 This function disables package verification and enables non-market 810 APK installation. Then, it installs specified APK(s) and uiautomator 811 package and path if required in a test. 812 813 @param dep_packages: Array of package names of autotest_deps APK 814 packages. 815 @param apks: Array of APK name arrays to be installed in dep_package. 816 @param full_pkg_names: Array of full package name arrays to be removed 817 in teardown. 818 @param uiautomator: uiautomator python package is required or not. 819 @param disable_play_store: Set this to True if you want to prevent 820 GMS Core from updating. 821 """ 822 if not self.initialized: 823 logging.info('Skipping ARC setup: not initialized') 824 return 825 logging.info('Starting ARC setup') 826 827 # Sample parameters for multi-deps setup after fixup (if needed): 828 # dep_packages: ['Dep1-apk', 'Dep2-apk'] 829 # apks: [['com.dep1.arch1.apk', 'com.dep2.arch2.apk'], ['com.dep2.apk'] 830 # full_pkg_nmes: [['com.dep1.app'], ['com.dep2.app']] 831 # TODO(crbug/777787): once the parameters of all callers of arc_setup 832 # are refactored, we can delete the safety net here. 833 if dep_packages: 834 dep_packages = self._fix_nested_array_level( 835 'dep_packages', 1, dep_packages) 836 apks = self._fix_nested_array_level('apks', 2, apks) 837 full_pkg_names = self._fix_nested_array_level( 838 'full_pkg_names', 2, full_pkg_names) 839 if (len(dep_packages) != len(apks) or 840 len(apks) != len(full_pkg_names)): 841 logging.info('dep_packages length is %d', len(dep_packages)) 842 logging.info('apks length is %d', len(apks)) 843 logging.info('full_pkg_names length is %d', 844 len(full_pkg_names)) 845 raise error.TestFail( 846 'dep_packages/apks/full_pkg_names format error') 847 848 self.dep_packages = dep_packages 849 self.apks = apks 850 self.uiautomator = uiautomator or disable_play_store 851 # Setup dependent packages if required 852 packages = [] 853 if dep_packages: 854 packages = dep_packages[:] 855 if self.uiautomator: 856 packages.append(self._PKG_UIAUTOMATOR) 857 if packages: 858 logging.info('Setting up dependent package(s) %s', packages) 859 self.job.setup_dep(packages) 860 861 self.logcat_proc = arc_common.Logcat() 862 863 wait_for_adb_ready() 864 865 # Setting verifier_verify_adb_installs to zero suppresses a dialog box 866 # that can appear asking for the user to consent to the install. 867 adb_shell('settings put global verifier_verify_adb_installs 0') 868 869 # Install apks based on dep_packages/apks/full_pkg_names tuples 870 if dep_packages: 871 for i in range(len(dep_packages)): 872 self._install_apks(dep_packages[i], apks[i], full_pkg_names[i]) 873 874 if self.uiautomator: 875 path = os.path.join(self.autodir, 'deps', self._PKG_UIAUTOMATOR) 876 sys.path.append(path) 877 self._add_ui_object_not_found_handler() 878 if disable_play_store and not is_package_disabled(_PLAY_STORE_PKG): 879 self._disable_play_store() 880 if not is_package_disabled(_PLAY_STORE_PKG): 881 raise error.TestFail('Failed to disable Google Play Store.') 882 self._should_reenable_play_store = True 883 884 def arc_teardown(self): 885 """ARC test teardown. 886 887 This function removes all installed packages in arc_setup stage 888 first. Then, it restores package verification and disables non-market 889 APK installation. 890 891 """ 892 if self.full_pkg_names: 893 for pkg in self.full_pkg_names: 894 logging.info('Uninstalling %s', pkg) 895 if not is_package_installed(pkg): 896 raise error.TestError('Package %s was not installed' % pkg) 897 adb_uninstall(pkg) 898 if (self.uiautomator and 899 is_package_installed(self._FULL_PKG_NAME_UIAUTOMATOR)): 900 logging.info('Uninstalling %s', self._FULL_PKG_NAME_UIAUTOMATOR) 901 adb_uninstall(self._FULL_PKG_NAME_UIAUTOMATOR) 902 if self._should_reenable_play_store: 903 adb_shell('pm enable ' + _PLAY_STORE_PKG) 904 adb_shell('settings put secure install_non_market_apps 0') 905 adb_shell('settings put global package_verifier_enable 1') 906 adb_shell('settings put secure package_verifier_user_consent 0') 907 908 # Remove the adb keys without going through adb. This is because the 909 # 'rm' tool does not have permissions to remove the keys once they have 910 # been restorecon(8)ed. 911 utils.system_output('rm -f %s' % 912 pipes.quote(os.path.join( 913 get_android_data_root(), 914 os.path.relpath(_ANDROID_ADB_KEYS_PATH, '/')))) 915 utils.system_output('adb kill-server') 916 917 def _add_ui_object_not_found_handler(self): 918 """Logs the device dump upon uiautomator.UiObjectNotFoundException.""" 919 from uiautomator import device as d 920 d.handlers.on(lambda d: logging.debug('Device window dump:\n%s', 921 d.dump())) 922 923 def _disable_play_store(self): 924 """Disables the Google Play Store app.""" 925 if is_package_disabled(_PLAY_STORE_PKG): 926 return 927 adb_shell('am force-stop ' + _PLAY_STORE_PKG) 928 adb_shell('am start -a android.settings.APPLICATION_DETAILS_SETTINGS ' 929 '-d package:' + _PLAY_STORE_PKG) 930 931 # Note: the straightforward "pm disable <package>" command would be 932 # better, but that requires root permissions, which aren't available on 933 # a pre-release image being tested. The only other way is through the 934 # Settings UI, but which might change. 935 from uiautomator import device as d 936 d(textMatches='(?i)DISABLE', packageName=_SETTINGS_PKG).wait.exists() 937 d(textMatches='(?i)DISABLE', packageName=_SETTINGS_PKG).click.wait() 938 d(textMatches='(?i)DISABLE APP').click.wait() 939 ok_button = d(textMatches='(?i)OK') 940 if ok_button.exists: 941 ok_button.click.wait() 942 adb_shell('am force-stop ' + _SETTINGS_PKG) 943