• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 re
11import shutil
12import socket
13import sys
14import tempfile
15import time
16
17from autotest_lib.client.bin import test, utils
18from autotest_lib.client.common_lib import error
19from 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
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 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
77def 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
84def _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
89def get_zygote_type():
90    """Return zygote service type."""
91    return _android_shell('getprop ro.zygote')
92
93
94def get_sdk_version():
95    """Return the SDK level version for Android."""
96    return _android_shell('getprop ro.build.version.sdk')
97
98
99def get_product():
100    """Return the product string used for the Android build."""
101    return _android_shell('getprop ro.build.product')
102
103
104def _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
114def _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
122def 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
200def 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
212def 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
222def 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
234def 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
245def 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
253def 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.
273def adb_root():
274    """Restart adbd with root permission."""
275
276    adb_cmd('root')
277
278
279def get_container_root():
280    """Returns path to Android container root directory."""
281    return _ANDROID_CONTAINER_ROOT_PATH
282
283
284def 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
305def 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
310def 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
320def get_container_pid():
321    """Returns the PID of the container."""
322    return utils.read_one_line(get_container_pid_path())
323
324
325def 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
333def get_sdcard_pid():
334    """Returns the PID of the sdcard container."""
335    return utils.read_one_line(_SDCARD_PID_PATH)
336
337
338def _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
346def 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
364def 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
369def 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
378def 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
387def 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
401def 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
415def _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
426def 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
450def 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
458def 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
466def 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
480def _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
490def 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
500def _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
522def 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
530def 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
538def 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
544def _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
556def _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
585def 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
593def 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
606def 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
647def 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
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 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