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