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