Home | History | Annotate | Download | only in cros
      1 # Copyright 2015 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import collections
      6 import glob
      7 import logging
      8 import os
      9 import pipes
     10 import re
     11 import shutil
     12 import socket
     13 import sys
     14 import tempfile
     15 import time
     16 
     17 from autotest_lib.client.bin import test, utils
     18 from autotest_lib.client.common_lib import error
     19 from autotest_lib.client.common_lib.cros import chrome, arc_common
     20 
     21 _ADB_KEYS_PATH = '/tmp/adb_keys'
     22 _ADB_VENDOR_KEYS = 'ADB_VENDOR_KEYS'
     23 _ANDROID_CONTAINER_PID_PATH = '/run/containers/android*/container.pid'
     24 _ANDROID_DATA_ROOT_PATH = '/opt/google/containers/android/rootfs/android-data'
     25 _ANDROID_CONTAINER_ROOT_PATH = '/opt/google/containers/android/rootfs'
     26 _SCREENSHOT_DIR_PATH = '/var/log/arc-screenshots'
     27 _SCREENSHOT_BASENAME = 'arc-screenshot'
     28 _MAX_SCREENSHOT_NUM = 10
     29 # This address should match the one present in
     30 # https://chromium.googlesource.com/chromiumos/overlays/chromiumos-overlay/+/master/chromeos-base/arc-sslh-init/files/sslh.conf
     31 _ADBD_ADDRESS = ('100.115.92.2', 5555)
     32 _ADBD_PID_PATH = '/run/arc/adbd.pid'
     33 _SDCARD_PID_PATH = '/run/arc/sdcard.pid'
     34 _ANDROID_ADB_KEYS_PATH = '/data/misc/adb/adb_keys'
     35 _PROCESS_CHECK_INTERVAL_SECONDS = 1
     36 _WAIT_FOR_ADB_READY = 60
     37 _WAIT_FOR_ANDROID_PROCESS_SECONDS = 60
     38 _PLAY_STORE_PKG = 'com.android.vending'
     39 _SETTINGS_PKG = 'com.android.settings'
     40 
     41 
     42 def setup_adb_host():
     43     """Setup ADB host keys.
     44 
     45     This sets up the files and environment variables that wait_for_adb_ready()
     46     needs"""
     47     if _ADB_VENDOR_KEYS in os.environ:
     48         return
     49     if not os.path.exists(_ADB_KEYS_PATH):
     50         os.mkdir(_ADB_KEYS_PATH)
     51     # adb expects $HOME to be writable.
     52     os.environ['HOME'] = _ADB_KEYS_PATH
     53 
     54     # Generate and save keys for adb if needed
     55     key_path = os.path.join(_ADB_KEYS_PATH, 'test_key')
     56     if not os.path.exists(key_path):
     57         utils.system('adb keygen ' + pipes.quote(key_path))
     58     os.environ[_ADB_VENDOR_KEYS] = key_path
     59 
     60 
     61 def adb_connect(attempts=1):
     62     """Attempt to connect ADB to the Android container.
     63 
     64     Returns true if successful. Do not call this function directly. Call
     65     wait_for_adb_ready() instead.
     66     """
     67     # Kill existing adb server every other invocation to ensure that a full
     68     # reconnect is performed.
     69     if attempts % 2 == 1:
     70         utils.system('adb kill-server', ignore_status=True)
     71 
     72     if utils.system('adb connect localhost:22', ignore_status=True) != 0:
     73         return False
     74     return is_adb_connected()
     75 
     76 
     77 def is_adb_connected():
     78     """Return true if adb is connected to the container."""
     79     output = utils.system_output('adb get-state', ignore_status=True)
     80     logging.debug('adb get-state: %s', output)
     81     return output.strip() == 'device'
     82 
     83 
     84 def _is_android_data_mounted():
     85     """Return true if Android's /data is mounted with partial boot enabled."""
     86     return _android_shell('getprop ro.data_mounted') == '1'
     87 
     88 
     89 def get_zygote_type():
     90     """Return zygote service type."""
     91     return _android_shell('getprop ro.zygote')
     92 
     93 
     94 def get_sdk_version():
     95     """Return the SDK level version for Android."""
     96     return _android_shell('getprop ro.build.version.sdk')
     97 
     98 
     99 def get_product():
    100     """Return the product string used for the Android build."""
    101     return _android_shell('getprop ro.build.product')
    102 
    103 
    104 def _is_tcp_port_reachable(address):
    105     """Return whether a TCP port described by |address| is reachable."""
    106     try:
    107         s = socket.create_connection(address)
    108         s.close()
    109         return True
    110     except socket.error:
    111         return False
    112 
    113 
    114 def _wait_for_data_mounted(timeout):
    115     utils.poll_for_condition(
    116             condition=_is_android_data_mounted,
    117             desc='Wait for /data mounted',
    118             timeout=timeout,
    119             sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS)
    120 
    121 
    122 def wait_for_adb_ready(timeout=_WAIT_FOR_ADB_READY):
    123     """Wait for the ADB client to connect to the ARC container.
    124 
    125     @param timeout: Timeout in seconds.
    126     """
    127     # Although adbd is started at login screen, we still need /data to be
    128     # mounted to set up key-based authentication. /data should be mounted
    129     # once the user has logged in.
    130     start_time = time.time()
    131     _wait_for_data_mounted(timeout)
    132     timeout -= (time.time() - start_time)
    133     start_time = time.time()
    134     arc_common.wait_for_android_boot(timeout)
    135     timeout -= (time.time() - start_time)
    136 
    137     setup_adb_host()
    138     if is_adb_connected():
    139         return
    140 
    141     # Push keys for adb.
    142     pubkey_path = os.environ[_ADB_VENDOR_KEYS] + '.pub'
    143     with open(pubkey_path, 'r') as f:
    144         _write_android_file(_ANDROID_ADB_KEYS_PATH, f.read())
    145     _android_shell('chown shell ' + pipes.quote(_ANDROID_ADB_KEYS_PATH))
    146     _android_shell('restorecon ' + pipes.quote(_ANDROID_ADB_KEYS_PATH))
    147 
    148     # This starts adbd, restarting it if needed so it can read the updated key.
    149     _android_shell('setprop sys.usb.config mtp')
    150     _android_shell('setprop sys.usb.config mtp,adb')
    151 
    152     exception = error.TestFail('Failed to connect to adb in %d seconds.' % timeout)
    153 
    154     # Keeps track of how many times adb has attempted to establish a
    155     # connection.
    156     def _adb_connect_wrapper():
    157         _adb_connect_wrapper.attempts += 1
    158         return adb_connect(_adb_connect_wrapper.attempts)
    159     _adb_connect_wrapper.attempts = 0
    160     try:
    161         utils.poll_for_condition(_adb_connect_wrapper,
    162                                  exception,
    163                                  timeout)
    164     except (utils.TimeoutError, error.TestFail):
    165         # The operation has failed, but let's try to clarify the failure to
    166         # avoid shifting blame to adb.
    167 
    168         # First, collect some information and log it.
    169         arc_alive = is_android_container_alive()
    170         arc_booted = _android_shell('getprop sys.boot_completed',
    171                                     ignore_status=True)
    172         arc_system_events = _android_shell(
    173             'logcat -d -b events *:S arc_system_event', ignore_status=True)
    174         adbd_pid = _android_shell('pidof -s adbd', ignore_status=True)
    175         adbd_port_reachable = _is_tcp_port_reachable(_ADBD_ADDRESS)
    176         adb_state = utils.system_output('adb get-state', ignore_status=True)
    177         logging.debug('ARC alive: %s', arc_alive)
    178         logging.debug('ARC booted: %s', arc_booted)
    179         logging.debug('ARC system events: %s', arc_system_events)
    180         logging.debug('adbd process: %s', adbd_pid)
    181         logging.debug('adbd port reachable: %s', adbd_port_reachable)
    182         logging.debug('adb state: %s', adb_state)
    183 
    184         # Now go through the usual suspects and raise nicer errors to make the
    185         # actual failure clearer.
    186         if not arc_alive:
    187             raise error.TestFail('ARC is not alive.')
    188         if not adbd_pid:
    189             raise error.TestFail('adbd is not running.')
    190         if arc_booted != '1':
    191             raise error.TestFail('ARC did not finish booting.')
    192         if not adbd_port_reachable:
    193             raise error.TestFail('adbd TCP port is not reachable.')
    194 
    195         # We exhausted all possibilities. Fall back to printing the generic
    196         # error.
    197         raise
    198 
    199 
    200 def grant_permissions(package, permissions):
    201     """Grants permissions to a package.
    202 
    203     @param package: Package name.
    204     @param permissions: A list of permissions.
    205 
    206     """
    207     for permission in permissions:
    208         adb_shell('pm grant %s android.permission.%s' % (
    209                   pipes.quote(package), pipes.quote(permission)))
    210 
    211 
    212 def adb_cmd(cmd, **kwargs):
    213     """Executed cmd using adb. Must wait for adb ready.
    214 
    215     @param cmd: Command to run.
    216     """
    217     # TODO(b/79122489) - Assert if cmd == 'root'
    218     wait_for_adb_ready()
    219     return utils.system_output('adb %s' % cmd, **kwargs)
    220 
    221 
    222 def adb_shell(cmd, **kwargs):
    223     """Executed shell command with adb.
    224 
    225     @param cmd: Command to run.
    226     """
    227     output = adb_cmd('shell %s' % pipes.quote(cmd), **kwargs)
    228     # Some android commands include a trailing CRLF in their output.
    229     if kwargs.pop('strip_trailing_whitespace', True):
    230         output = output.rstrip()
    231     return output
    232 
    233 
    234 def adb_install(apk, auto_grant_permissions=True):
    235     """Install an apk into container. You must connect first.
    236 
    237     @param apk: Package to install.
    238     @param auto_grant_permissions: Set to false to not automatically grant all
    239     permissions. Most tests should not care.
    240     """
    241     flags = '-g' if auto_grant_permissions else ''
    242     return adb_cmd('install -r -t %s %s' % (flags, apk), timeout=60*5)
    243 
    244 
    245 def adb_uninstall(apk):
    246     """Remove an apk from container. You must connect first.
    247 
    248     @param apk: Package to uninstall.
    249     """
    250     return adb_cmd('uninstall %s' % apk)
    251 
    252 
    253 def adb_reboot():
    254     """Reboots the container and block until container pid is gone.
    255 
    256     You must connect first.
    257     """
    258     old_pid = get_container_pid()
    259     logging.info('Trying to reboot PID:%s', old_pid)
    260     adb_cmd('reboot', ignore_status=True)
    261     # Ensure that the old container is no longer booted
    262     utils.poll_for_condition(
    263         lambda: not utils.pid_is_alive(int(old_pid)), timeout=10)
    264 
    265 
    266 # This adb_root() function is deceiving in that it works just fine on debug
    267 # builds of ARC (user-debug, eng). However "adb root" does not work on user
    268 # builds as run by the autotest machines when testing prerelease images. In fact
    269 # it will silently fail. You will need to find another way to do do what you
    270 # need to do as root.
    271 #
    272 # TODO(b/79122489) - Remove this function.
    273 def adb_root():
    274     """Restart adbd with root permission."""
    275 
    276     adb_cmd('root')
    277 
    278 
    279 def get_container_root():
    280     """Returns path to Android container root directory."""
    281     return _ANDROID_CONTAINER_ROOT_PATH
    282 
    283 
    284 def get_container_pid_path():
    285     """Returns the container's PID file path.
    286 
    287     Raises:
    288       TestError if no PID file is found, or more than one files are found.
    289     """
    290     # Find the PID file rather than the android-XXXXXX/ directory to ignore
    291     # stale and empty android-XXXXXX/ directories when they exist.
    292     arc_container_pid_files = glob.glob(_ANDROID_CONTAINER_PID_PATH)
    293 
    294     if len(arc_container_pid_files) == 0:
    295         raise error.TestError('Android container.pid not available')
    296 
    297     if len(arc_container_pid_files) > 1:
    298         raise error.TestError(
    299                 'Multiple Android container.pid files found: %r. '
    300                 'Reboot your DUT to recover.' % (arc_container_pid_files))
    301 
    302     return arc_container_pid_files[0]
    303 
    304 
    305 def get_android_data_root():
    306     """Returns path to Chrome OS directory that bind-mounts Android's /data."""
    307     return _ANDROID_DATA_ROOT_PATH
    308 
    309 
    310 def get_job_pid(job_name):
    311     """Returns the PID of an upstart job."""
    312     status = utils.system_output('status %s' % job_name)
    313     match = re.match(r'^%s start/running, process (\d+)$' % job_name,
    314                      status)
    315     if not match:
    316         raise error.TestError('Unexpected status: "%s"' % status)
    317     return match.group(1)
    318 
    319 
    320 def get_container_pid():
    321     """Returns the PID of the container."""
    322     return utils.read_one_line(get_container_pid_path())
    323 
    324 
    325 def get_adbd_pid():
    326     """Returns the PID of the adbd proxy container."""
    327     if not os.path.exists(_ADBD_PID_PATH):
    328         # The adbd proxy does not run on all boards.
    329         return None
    330     return utils.read_one_line(_ADBD_PID_PATH)
    331 
    332 
    333 def get_sdcard_pid():
    334     """Returns the PID of the sdcard container."""
    335     return utils.read_one_line(_SDCARD_PID_PATH)
    336 
    337 
    338 def _get_mount_passthrough_pid_internal(job_name):
    339     """Returns the PID of the mount-passthrough daemon job."""
    340     job_pid = get_job_pid(job_name)
    341     # |job_pid| is the minijail process, the fuse process should be
    342     # the only direct child of the minijail process
    343     return utils.system_output('pgrep -P %s' % job_pid)
    344 
    345 
    346 def get_mount_passthrough_pid_list():
    347     """Returns PIDs of ARC mount-passthrough daemon jobs."""
    348     JOB_NAMES = [ 'arc-myfiles', 'arc-myfiles-default',
    349                   'arc-myfiles-read', 'arc-myfiles-write',
    350                   'arc-removable-media', 'arc-removable-media-default',
    351                   'arc-removable-media-read', 'arc-removable-media-write' ]
    352     pid_list = []
    353     for job_name in JOB_NAMES:
    354         try:
    355             pid = _get_mount_passthrough_pid_internal(job_name)
    356             pid_list.append(pid)
    357         except Exception, e:
    358             logging.warning('Failed to find PID for %s : %s', job_name, e)
    359             continue
    360 
    361     return pid_list
    362 
    363 
    364 def get_obb_mounter_pid():
    365     """Returns the PID of the OBB mounter."""
    366     return utils.system_output('pgrep -f -u root ^/usr/bin/arc-obb-mounter')
    367 
    368 
    369 def is_android_process_running(process_name):
    370     """Return whether Android has completed booting.
    371 
    372     @param process_name: Process name.
    373     """
    374     output = adb_shell('pgrep -c -f %s' % pipes.quote(process_name))
    375     return int(output) > 0
    376 
    377 
    378 def check_android_file_exists(filename):
    379     """Checks whether the given file exists in the Android filesystem
    380 
    381     @param filename: File to check.
    382     """
    383     return adb_shell('test -e {} && echo FileExists'.format(
    384             pipes.quote(filename))).find("FileExists") >= 0
    385 
    386 
    387 def read_android_file(filename):
    388     """Reads a file in Android filesystem.
    389 
    390     @param filename: File to read.
    391     """
    392     with tempfile.NamedTemporaryFile() as tmpfile:
    393         adb_cmd('pull %s %s' % (pipes.quote(filename),
    394                                 pipes.quote(tmpfile.name)))
    395         with open(tmpfile.name) as f:
    396             return f.read()
    397 
    398     return None
    399 
    400 
    401 def write_android_file(filename, data):
    402     """Writes to a file in Android filesystem.
    403 
    404     @param filename: File to write.
    405     @param data: Data to write.
    406     """
    407     with tempfile.NamedTemporaryFile() as tmpfile:
    408         tmpfile.write(data)
    409         tmpfile.flush()
    410 
    411         adb_cmd('push %s %s' % (pipes.quote(tmpfile.name),
    412                                 pipes.quote(filename)))
    413 
    414 
    415 def _write_android_file(filename, data):
    416     """Writes to a file in Android filesystem.
    417 
    418     This is an internal function used to bootstrap adb.
    419     Tests should use write_android_file instead.
    420     """
    421     android_cmd = 'cat > %s' % pipes.quote(filename)
    422     cros_cmd = 'android-sh -c %s' % pipes.quote(android_cmd)
    423     utils.run(cros_cmd, stdin=data)
    424 
    425 
    426 def get_android_file_stats(filename):
    427     """Returns an object of file stats for an Android file.
    428 
    429     The returned object supported limited attributes, but can be easily extended
    430     if needed. Note that the value are all string.
    431 
    432     This uses _android_shell to run as root, so that it can access to all files
    433     inside the container. On non-debuggable build, adb shell is not rootable.
    434     """
    435     mapping = {
    436         '%a': 'mode',
    437         '%g': 'gid',
    438         '%h': 'nlink',
    439         '%u': 'uid',
    440     }
    441     output = _android_shell(
    442         'stat -c "%s" %s' % (' '.join(mapping.keys()), pipes.quote(filename)))
    443     stats = output.split(' ')
    444     if len(stats) != len(mapping):
    445       raise error.TestError('Unexpected output from stat: %s' % output)
    446     _Stats = collections.namedtuple('_Stats', mapping.values())
    447     return _Stats(*stats)
    448 
    449 
    450 def remove_android_file(filename):
    451     """Removes a file in Android filesystem.
    452 
    453     @param filename: File to remove.
    454     """
    455     adb_shell('rm -f %s' % pipes.quote(filename))
    456 
    457 
    458 def wait_for_android_boot(timeout=None):
    459     """Sleep until Android has completed booting or timeout occurs.
    460 
    461     @param timeout: Timeout in seconds.
    462     """
    463     arc_common.wait_for_android_boot(timeout)
    464 
    465 
    466 def wait_for_android_process(process_name,
    467                              timeout=_WAIT_FOR_ANDROID_PROCESS_SECONDS):
    468     """Sleep until an Android process is running or timeout occurs.
    469 
    470     @param process_name: Process name.
    471     @param timeout: Timeout in seconds.
    472     """
    473     condition = lambda: is_android_process_running(process_name)
    474     utils.poll_for_condition(condition=condition,
    475                              desc='%s is running' % process_name,
    476                              timeout=timeout,
    477                              sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS)
    478 
    479 
    480 def _android_shell(cmd, **kwargs):
    481     """Execute cmd instead the Android container.
    482 
    483     This function is strictly for internal use only, as commands do not run in
    484     a fully consistent Android environment. Prefer adb_shell instead.
    485     """
    486     return utils.system_output('android-sh -c {}'.format(pipes.quote(cmd)),
    487                                **kwargs)
    488 
    489 
    490 def is_android_container_alive():
    491     """Check if android container is alive."""
    492     try:
    493         container_pid = get_container_pid()
    494     except Exception, e:
    495         logging.error('is_android_container_alive failed: %r', e)
    496         return False
    497     return utils.pid_is_alive(int(container_pid))
    498 
    499 
    500 def _is_in_installed_packages_list(package, option=None):
    501     """Check if a package is in the list returned by pm list packages.
    502 
    503     adb must be ready.
    504 
    505     @param package: Package in request.
    506     @param option: An option for the command adb shell pm list packages.
    507                    Valid values include '-s', '-3', '-d', and '-e'.
    508     """
    509     command = 'pm list packages'
    510     if option:
    511         command += ' ' + option
    512     packages = adb_shell(command).splitlines()
    513     package_entry = 'package:' + package
    514     ret = package_entry in packages
    515 
    516     if not ret:
    517         logging.info('Could not find "%s" in %s',
    518                      package_entry, str(packages))
    519     return ret
    520 
    521 
    522 def is_package_installed(package):
    523     """Check if a package is installed. adb must be ready.
    524 
    525     @param package: Package in request.
    526     """
    527     return _is_in_installed_packages_list(package)
    528 
    529 
    530 def is_package_disabled(package):
    531     """Check if an installed package is disabled. adb must be ready.
    532 
    533     @param package: Package in request.
    534     """
    535     return _is_in_installed_packages_list(package, '-d')
    536 
    537 
    538 def get_package_install_path(package):
    539     """Returns the apk install location of the given package."""
    540     output = adb_shell('pm path {}'.format(pipes.quote(package)))
    541     return output.split(':')[1]
    542 
    543 
    544 def _before_iteration_hook(obj):
    545     """Executed by parent class before every iteration.
    546 
    547     This function resets the run_once_finished flag before every iteration
    548     so we can detect failure on every single iteration.
    549 
    550     Args:
    551         obj: the test itself
    552     """
    553     obj.run_once_finished = False
    554 
    555 
    556 def _after_iteration_hook(obj):
    557     """Executed by parent class after every iteration.
    558 
    559     The parent class will handle exceptions and failures in the run and will
    560     always call this hook afterwards. Take a screenshot if the run has not
    561     been marked as finished (i.e. there was a failure/exception).
    562 
    563     Args:
    564         obj: the test itself
    565     """
    566     if not obj.run_once_finished:
    567         if is_adb_connected():
    568             logging.debug('Recent activities dump:\n%s',
    569                           adb_shell('dumpsys activity recents'))
    570         if not os.path.exists(_SCREENSHOT_DIR_PATH):
    571             os.mkdir(_SCREENSHOT_DIR_PATH, 0755)
    572         obj.num_screenshots += 1
    573         if obj.num_screenshots <= _MAX_SCREENSHOT_NUM:
    574             logging.warning('Iteration %d failed, taking a screenshot.',
    575                             obj.iteration)
    576             try:
    577                 utils.run('screenshot "{}/{}_iter{}.png"'.format(
    578                     _SCREENSHOT_DIR_PATH, _SCREENSHOT_BASENAME, obj.iteration))
    579             except Exception as e:
    580                 logging.warning('Unable to capture screenshot. %s', e)
    581         else:
    582             logging.warning('Too many failures, no screenshot taken')
    583 
    584 
    585 def send_keycode(keycode):
    586     """Sends the given keycode to the container
    587 
    588     @param keycode: keycode to send.
    589     """
    590     adb_shell('input keyevent {}'.format(keycode))
    591 
    592 
    593 def get_android_sdk_version():
    594     """Returns the Android SDK version.
    595 
    596     This function can be called before Android container boots.
    597     """
    598     with open('/etc/lsb-release') as f:
    599         values = dict(line.split('=', 1) for line in f.read().splitlines())
    600     try:
    601         return int(values['CHROMEOS_ARC_ANDROID_SDK_VERSION'])
    602     except (KeyError, ValueError):
    603         raise error.TestError('Could not determine Android SDK version')
    604 
    605 
    606 def set_device_mode(device_mode, use_fake_sensor_with_lifetime_secs=0):
    607     """Sets the device in either Clamshell or Tablet mode.
    608 
    609     "inject_powerd_input_event" might fail if the DUT does not support Tablet
    610     mode, and it will raise an |error.CmdError| exception. To prevent that, use
    611     the |use_fake_sensor_with_lifetime_secs| parameter.
    612 
    613     @param device_mode: string with either 'clamshell' or 'tablet'
    614     @param use_fake_sensor_with_lifetime_secs: if > 0, it will create the
    615            input device with the given lifetime in seconds
    616     @raise ValueError: if passed invalid parameters
    617     @raise error.CmdError: if inject_powerd_input_event fails
    618     """
    619     valid_value = ('tablet', 'clamshell')
    620     if device_mode not in valid_value:
    621         raise ValueError('Invalid device_mode parameter: %s' % device_mode)
    622 
    623     value = 1 if device_mode == 'tablet' else 0
    624 
    625     args = ['--code=tablet', '--value=%d' % value]
    626 
    627     if use_fake_sensor_with_lifetime_secs > 0:
    628         args.extend(['--create_dev', '--dev_lifetime=%d' %
    629                      use_fake_sensor_with_lifetime_secs])
    630 
    631     try:
    632         utils.run('inject_powerd_input_event', args=args)
    633     except error.CmdError as err:
    634         # TODO: Fragile code ahead. Correct way to do it is to check
    635         # if device is already in desired mode, and do nothing if so.
    636         # ATM we don't have a way to check current device mode.
    637 
    638         # Assuming that CmdError means that device does not support
    639         # --code=tablet parameter, meaning that device only supports clamshell
    640         # mode.
    641         if device_mode == 'clamshell' and \
    642                 use_fake_sensor_with_lifetime_secs == 0:
    643                     return
    644         raise err
    645 
    646 
    647 def wait_for_userspace_ready():
    648     """Waits for userspace apps to be launchable.
    649 
    650     Launches and then closes Android settings as a way to ensure all basic
    651     services are ready. This goes a bit beyond waiting for boot-up to complete,
    652     as being able to launch an activity requires more of the framework to have
    653     started. The boot-complete signal happens fairly early, and the framework
    654     system server is still starting services. By waiting for ActivityManager to
    655     respond, we automatically wait on more services to be ready.
    656     """
    657     output = adb_shell('am start -W -a android.settings.SETTINGS')
    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')
    662 
    663 
    664 class 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 sane 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))
    772                 logging.info('Install apk output: %s', str(out))
    773             # Verify if package(s) are installed correctly
    774             if not full_pkg_names:
    775                 raise error.TestError('Package names of apks expected')
    776             for pkg in full_pkg_names:
    777                 logging.info('Check if %s is installed', pkg)
    778                 if not is_package_installed(pkg):
    779                     raise error.TestError('Package %s not found' % pkg)
    780                 # Make sure full_pkg_names contains installed packages only
    781                 # so arc_teardown() knows what packages to uninstall.
    782                 self.full_pkg_names.append(pkg)
    783 
    784     def _count_nested_array_level(self, array):
    785         """Count the level of a nested array."""
    786         if isinstance(array, list):
    787             return 1 + self._count_nested_array_level(array[0])
    788         return 0
    789 
    790     def _fix_nested_array_level(self, var_name, expected_level, array):
    791         """Enclose array one level deeper if needed."""
    792         level = self._count_nested_array_level(array)
    793         if level == expected_level:
    794             return array
    795         if level == expected_level - 1:
    796             return [array]
    797 
    798         logging.error("Variable %s nested level is not fixable: "
    799                       "Expecting %d, seeing %d",
    800                       var_name, expected_level, level)
    801         raise error.TestError('Format error with variable %s' % var_name)
    802 
    803     def arc_setup(self, dep_packages=None, apks=None, full_pkg_names=None,
    804                   uiautomator=False, block_outbound=False,
    805                   disable_play_store=False):
    806         """ARC test setup: Setup dependencies and install apks.
    807 
    808         This function disables package verification and enables non-market
    809         APK installation. Then, it installs specified APK(s) and uiautomator
    810         package and path if required in a test.
    811 
    812         @param dep_packages: Array of package names of autotest_deps APK
    813                              packages.
    814         @param apks: Array of APK name arrays to be installed in dep_package.
    815         @param full_pkg_names: Array of full package name arrays to be removed
    816                                in teardown.
    817         @param uiautomator: uiautomator python package is required or not.
    818         @param block_outbound: block outbound network traffic during a test.
    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 xrange(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         if block_outbound:
    884             self.block_outbound()
    885 
    886     def arc_teardown(self):
    887         """ARC test teardown.
    888 
    889         This function removes all installed packages in arc_setup stage
    890         first. Then, it restores package verification and disables non-market
    891         APK installation.
    892 
    893         """
    894         if self.full_pkg_names:
    895             for pkg in self.full_pkg_names:
    896                 logging.info('Uninstalling %s', pkg)
    897                 if not is_package_installed(pkg):
    898                     raise error.TestError('Package %s was not installed' % pkg)
    899                 adb_uninstall(pkg)
    900         if self.uiautomator:
    901             logging.info('Uninstalling %s', self._FULL_PKG_NAME_UIAUTOMATOR)
    902             adb_uninstall(self._FULL_PKG_NAME_UIAUTOMATOR)
    903         if self._should_reenable_play_store:
    904             adb_shell('pm enable ' + _PLAY_STORE_PKG)
    905         adb_shell('settings put secure install_non_market_apps 0')
    906         adb_shell('settings put global package_verifier_enable 1')
    907         adb_shell('settings put secure package_verifier_user_consent 0')
    908 
    909         # Remove the adb keys without going through adb. This is because the
    910         # 'rm' tool does not have permissions to remove the keys once they have
    911         # been restorecon(8)ed.
    912         utils.system_output('rm -f %s' %
    913                             pipes.quote(os.path.join(
    914                                 get_android_data_root(),
    915                                 os.path.relpath(_ANDROID_ADB_KEYS_PATH, '/'))))
    916         utils.system_output('adb kill-server')
    917 
    918     def block_outbound(self):
    919         """ Blocks the connection from the container to outer network.
    920 
    921             The iptables settings accept only 100.115.92.2 port 5555 (adb) and
    922             all local connections, e.g. uiautomator.
    923         """
    924         logging.info('Blocking outbound connection')
    925         # ipv6
    926         _android_shell('ip6tables -I OUTPUT -j REJECT')
    927         _android_shell('ip6tables -I OUTPUT -d ip6-localhost -j ACCEPT')
    928         # ipv4
    929         _android_shell('iptables -I OUTPUT -j REJECT')
    930         _android_shell('iptables -I OUTPUT -p tcp -s 100.115.92.2 '
    931                        '--sport 5555 '
    932                        '-j ACCEPT')
    933         _android_shell('iptables -I OUTPUT -d localhost -j ACCEPT')
    934 
    935     def unblock_outbound(self):
    936         """ Unblocks the connection from the container to outer network.
    937 
    938             The iptables settings are not permanent which means they reset on
    939             each instance invocation. But we can still use this function to
    940             unblock the outbound connections during the test if needed.
    941         """
    942         logging.info('Unblocking outbound connection')
    943         # ipv4
    944         _android_shell('iptables -D OUTPUT -d localhost -j ACCEPT')
    945         _android_shell('iptables -D OUTPUT -p tcp -s 100.115.92.2 '
    946                        '--sport 5555 '
    947                        '-j ACCEPT')
    948         _android_shell('iptables -D OUTPUT -j REJECT')
    949         # ipv6
    950         _android_shell('ip6tables -D OUTPUT -d ip6-localhost -j ACCEPT')
    951         _android_shell('ip6tables -D OUTPUT -j REJECT')
    952 
    953     def _add_ui_object_not_found_handler(self):
    954         """Logs the device dump upon uiautomator.UiObjectNotFoundException."""
    955         from uiautomator import device as d
    956         d.handlers.on(lambda d: logging.debug('Device window dump:\n%s',
    957                                               d.dump()))
    958 
    959     def _disable_play_store(self):
    960         """Disables the Google Play Store app."""
    961         if is_package_disabled(_PLAY_STORE_PKG):
    962             return
    963         adb_shell('am force-stop ' + _PLAY_STORE_PKG)
    964         adb_shell('am start -a android.settings.APPLICATION_DETAILS_SETTINGS '
    965                   '-d package:' + _PLAY_STORE_PKG)
    966 
    967         # Note: the straightforward "pm disable <package>" command would be
    968         # better, but that requires root permissions, which aren't available on
    969         # a pre-release image being tested. The only other way is through the
    970         # Settings UI, but which might change.
    971         from uiautomator import device as d
    972         d(textMatches='(?i)DISABLE', packageName=_SETTINGS_PKG).wait.exists()
    973         d(textMatches='(?i)DISABLE', packageName=_SETTINGS_PKG).click.wait()
    974         d(textMatches='(?i)DISABLE APP').click.wait()
    975         ok_button = d(textMatches='(?i)OK')
    976         if ok_button.exists:
    977             ok_button.click.wait()
    978         adb_shell('am force-stop ' + _SETTINGS_PKG)
    979