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 glob
6import logging
7import os
8import pipes
9import re
10import shutil
11import subprocess
12import sys
13import tempfile
14
15from autotest_lib.client.bin import test, utils
16from autotest_lib.client.common_lib import error
17from autotest_lib.client.common_lib.cros import chrome, arc_common
18
19_ADB_KEYS_PATH = '/tmp/adb_keys'
20_ADB_VENDOR_KEYS = 'ADB_VENDOR_KEYS'
21_ANDROID_CONTAINER_PATH = '/var/run/containers/android_*'
22_SCREENSHOT_DIR_PATH = '/var/log/arc-screenshots'
23_SCREENSHOT_BASENAME = 'arc-screenshot'
24_MAX_SCREENSHOT_NUM = 10
25_SDCARD_PID_PATH = '/var/run/arc/sdcard.pid'
26_ANDROID_ADB_KEYS_PATH = '/data/misc/adb/adb_keys'
27_PROCESS_CHECK_INTERVAL_SECONDS = 1
28_WAIT_FOR_ADB_READY = 60
29_WAIT_FOR_ANDROID_PROCESS_SECONDS = 60
30_VAR_LOGCAT_PATH = '/var/log/logcat'
31
32
33def setup_adb_host():
34    """Setup ADB host keys.
35
36    This sets up the files and environment variables that wait_for_adb_ready() needs"""
37    if _ADB_VENDOR_KEYS in os.environ:
38        return
39    if not os.path.exists(_ADB_KEYS_PATH):
40        os.mkdir(_ADB_KEYS_PATH)
41    # adb expects $HOME to be writable.
42    os.environ['HOME'] = _ADB_KEYS_PATH
43
44    # Generate and save keys for adb if needed
45    key_path = os.path.join(_ADB_KEYS_PATH, 'test_key')
46    if not os.path.exists(key_path):
47        utils.system('adb keygen ' + pipes.quote(key_path))
48    os.environ[_ADB_VENDOR_KEYS] = key_path
49
50
51def adb_connect():
52    """Attempt to connect ADB to the Android container.
53
54    Returns true if successful. Do not call this function directly. Call
55    wait_for_adb_ready() instead."""
56    if not is_android_booted():
57        return False
58    if utils.system('adb connect localhost:22', ignore_status=True) != 0:
59        return False
60    return is_adb_connected()
61
62
63def is_adb_connected():
64    """Return true if adb is connected to the container."""
65    output = utils.system_output('adb get-state', ignore_status=True)
66    logging.debug('adb get-state: %s', output)
67    return output.strip() == 'device'
68
69
70def wait_for_adb_ready(timeout=_WAIT_FOR_ADB_READY):
71    """Wait for the ADB client to connect to the ARC container.
72
73    @param timeout: Timeout in seconds.
74    """
75    setup_adb_host()
76    if is_adb_connected():
77      return
78
79    # Push keys for adb.
80    pubkey_path = os.environ[_ADB_VENDOR_KEYS] + '.pub'
81    with open(pubkey_path, 'r') as f:
82        _write_android_file(_ANDROID_ADB_KEYS_PATH, f.read())
83    _android_shell('restorecon ' + pipes.quote(_ANDROID_ADB_KEYS_PATH))
84
85    # This starts adbd.
86    _android_shell('setprop sys.usb.config mtp,adb')
87
88    # Kill existing adb server to ensure that a full reconnect is performed.
89    utils.system('adb kill-server', ignore_status=True)
90
91    exception = error.TestFail('adb is not ready in %d seconds.' % timeout)
92    utils.poll_for_condition(adb_connect,
93                             exception,
94                             timeout)
95
96
97def grant_permissions(package, permissions):
98    """Grants permissions to a package.
99
100    @param package: Package name.
101    @param permissions: A list of permissions.
102
103    """
104    for permission in permissions:
105        adb_shell('pm grant %s android.permission.%s' % (
106                  pipes.quote(package), pipes.quote(permission)))
107
108
109def adb_cmd(cmd, **kwargs):
110    """Executed cmd using adb. Must wait for adb ready.
111
112    @param cmd: Command to run.
113    """
114    wait_for_adb_ready()
115    return utils.system_output('adb %s' % cmd, **kwargs)
116
117
118def adb_shell(cmd, **kwargs):
119    """Executed shell command with adb.
120
121    @param cmd: Command to run.
122    """
123    output = adb_cmd('shell %s' % pipes.quote(cmd), **kwargs)
124    # Some android commands include a trailing CRLF in their output.
125    if kwargs.pop('strip_trailing_whitespace', True):
126      output = output.rstrip()
127    return output
128
129
130def adb_install(apk):
131    """Install an apk into container. You must connect first.
132
133    @param apk: Package to install.
134    """
135    return adb_cmd('install -r %s' % apk)
136
137
138def adb_uninstall(apk):
139    """Remove an apk from container. You must connect first.
140
141    @param apk: Package to uninstall.
142    """
143    return adb_cmd('uninstall %s' % apk)
144
145
146def adb_reboot():
147    """Reboots the container. You must connect first."""
148    adb_root()
149    return adb_cmd('reboot', ignore_status=True)
150
151
152def adb_root():
153    """Restart adbd with root permission."""
154    adb_cmd('root')
155
156
157def get_container_root():
158    """Returns path to Android container root directory.
159
160    Raises:
161      TestError if no container root directory is found, or
162      more than one container root directories are found.
163    """
164    arc_container_roots = glob.glob(_ANDROID_CONTAINER_PATH)
165    if len(arc_container_roots) != 1:
166        raise error.TestError(
167            'Android container not available: %r' % arc_container_roots)
168    return arc_container_roots[0]
169
170
171def get_job_pid(job_name):
172    """Returns the PID of an upstart job."""
173    status = utils.system_output('status %s' % job_name)
174    match = re.match(r'^%s start/running, process (\d+)$' % job_name,
175                     status)
176    if not match:
177        raise error.TestError('Unexpected status: "%s"' % status)
178    return match.group(1)
179
180
181def get_container_pid():
182    """Returns the PID of the container."""
183    container_root = get_container_root()
184    pid_path = os.path.join(container_root, 'container.pid')
185    return utils.read_one_line(pid_path)
186
187
188def get_sdcard_pid():
189    """Returns the PID of the sdcard container."""
190    return utils.read_one_line(_SDCARD_PID_PATH)
191
192
193def get_removable_media_pid():
194    """Returns the PID of the arc-removable-media FUSE daemon."""
195    job_pid = get_job_pid('arc-removable-media')
196    # |job_pid| is the minijail process, obtain the PID of the process running
197    # inside the mount namespace.
198    # FUSE process is the only process running as chronos in the process group.
199    return utils.system_output('pgrep -u chronos -g %s' % job_pid)
200
201
202def get_obb_mounter_pid():
203    """Returns the PID of the OBB mounter."""
204    return utils.system_output('pgrep -f -u root ^/usr/bin/arc-obb-mounter')
205
206
207def is_android_booted():
208    """Return whether Android has completed booting."""
209    # We used to check sys.boot_completed system property to detect Android has
210    # booted in Android M, but in Android N it is set long before BOOT_COMPLETED
211    # intent is broadcast. So we read event logs instead.
212    log = _android_shell(
213        'logcat -d -b events *:S arc_system_event', ignore_status=True)
214    return 'ArcAppLauncher:started' in log
215
216
217def is_android_process_running(process_name):
218    """Return whether Android has completed booting.
219
220    @param process_name: Process name.
221    """
222    output = adb_shell('ps | grep %s' % pipes.quote(' %s$' % process_name))
223    return bool(output)
224
225
226def check_android_file_exists(filename):
227    """Checks whether the given file exists in the Android filesystem
228
229    @param filename: File to check.
230    """
231    return adb_shell('test -e {} && echo FileExists'.format(
232            pipes.quote(filename))).find("FileExists") >= 0
233
234
235def read_android_file(filename):
236    """Reads a file in Android filesystem.
237
238    @param filename: File to read.
239    """
240    with tempfile.NamedTemporaryFile() as tmpfile:
241        adb_cmd('pull %s %s' % (pipes.quote(filename), pipes.quote(tmpfile.name)))
242        with open(tmpfile.name) as f:
243            return f.read()
244
245    return None
246
247
248def write_android_file(filename, data):
249    """Writes to a file in Android filesystem.
250
251    @param filename: File to write.
252    @param data: Data to write.
253    """
254    with tempfile.NamedTemporaryFile() as tmpfile:
255        tmpfile.write(data)
256        tmpfile.flush()
257
258        adb_cmd('push %s %s' % (pipes.quote(tmpfile.name), pipes.quote(filename)))
259
260
261def _write_android_file(filename, data):
262    """Writes to a file in Android filesystem.
263
264    This is an internal function used to bootstrap adb.
265    Tests should use write_android_file instead.
266    """
267    android_cmd = 'cat > %s' % pipes.quote(filename)
268    cros_cmd = 'android-sh -c %s' % pipes.quote(android_cmd)
269    utils.run(cros_cmd, stdin=data)
270
271
272def remove_android_file(filename):
273    """Removes a file in Android filesystem.
274
275    @param filename: File to remove.
276    """
277    adb_shell('rm -f %s' % pipes.quote(filename))
278
279
280def wait_for_android_boot(timeout=None):
281    """Sleep until Android has completed booting or timeout occurs.
282
283    @param timeout: Timeout in seconds.
284    """
285    arc_common.wait_for_android_boot(timeout)
286
287
288def wait_for_android_process(process_name,
289                             timeout=_WAIT_FOR_ANDROID_PROCESS_SECONDS):
290    """Sleep until an Android process is running or timeout occurs.
291
292    @param process_name: Process name.
293    @param timeout: Timeout in seconds.
294    """
295    condition = lambda: is_android_process_running(process_name)
296    utils.poll_for_condition(condition=condition,
297                             desc='%s is running' % process_name,
298                             timeout=timeout,
299                             sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS)
300
301
302def _android_shell(cmd, **kwargs):
303    """Execute cmd instead the Android container.
304
305    This function is strictly for internal use only, as commands do not run in a
306    fully consistent Android environment. Prefer adb_shell instead.
307    """
308    return utils.system_output('android-sh -c {}'.format(pipes.quote(cmd)),
309                               **kwargs)
310
311
312def is_android_container_alive():
313    """Check if android container is alive."""
314    try:
315        container_pid = get_container_pid()
316    except Exception:
317        return False
318    return utils.pid_is_alive(int(container_pid))
319
320
321def is_package_installed(package):
322    """Check if a package is installed. adb must be ready.
323
324    @param package: Package in request.
325    """
326    packages = adb_shell('pm list packages').splitlines()
327    package_entry = 'package:{}'.format(package)
328    return package_entry in packages
329
330
331def _before_iteration_hook(obj):
332    """Executed by parent class before every iteration.
333
334    This function resets the run_once_finished flag before every iteration
335    so we can detect failure on every single iteration.
336
337    Args:
338        obj: the test itself
339    """
340    obj.run_once_finished = False
341
342
343def _after_iteration_hook(obj):
344    """Executed by parent class after every iteration.
345
346    The parent class will handle exceptions and failures in the run and will
347    always call this hook afterwards. Take a screenshot if the run has not
348    been marked as finished (i.e. there was a failure/exception).
349
350    Args:
351        obj: the test itself
352    """
353    if not obj.run_once_finished:
354        if not os.path.exists(_SCREENSHOT_DIR_PATH):
355            os.mkdir(_SCREENSHOT_DIR_PATH, 0755)
356        obj.num_screenshots += 1
357        if obj.num_screenshots <= _MAX_SCREENSHOT_NUM:
358            logging.warning('Iteration %d failed, taking a screenshot.',
359                            obj.iteration)
360            from cros.graphics.drm import crtcScreenshot
361            image = crtcScreenshot()
362            image.save('{}/{}_iter{}.png'.format(_SCREENSHOT_DIR_PATH,
363                                                 _SCREENSHOT_BASENAME,
364                                                 obj.iteration))
365        else:
366            logging.warning('Too many failures, no screenshot taken')
367
368
369def send_keycode(keycode):
370    """Sends the given keycode to the container
371
372    @param keycode: keycode to send.
373    """
374    adb_shell('input keyevent {}'.format(keycode))
375
376
377def get_android_sdk_version():
378    """Returns the Android SDK version.
379
380    This function can be called before Android container boots.
381    """
382    with open('/etc/lsb-release') as f:
383        values = dict(line.split('=', 1) for line in f.read().splitlines())
384    try:
385        return int(values['CHROMEOS_ARC_ANDROID_SDK_VERSION'])
386    except (KeyError, ValueError):
387        raise error.TestError('Could not determine Android SDK version')
388
389
390class ArcTest(test.test):
391    """ Base class of ARC Test.
392
393    This class could be used as super class of an ARC test for saving
394    redundant codes for container bringup, autotest-dep package(s) including
395    uiautomator setup if required, and apks install/remove during
396    arc_setup/arc_teardown, respectively. By default arc_setup() is called in
397    initialize() after Android have been brought up. It could also be overridden
398    to perform non-default tasks. For example, a simple ArcHelloWorldTest can be
399    just implemented with print 'HelloWorld' in its run_once() and no other
400    functions are required. We could expect ArcHelloWorldTest would bring up
401    browser and  wait for container up, then print 'Hello World', and shutdown
402    browser after. As a precaution, if you overwrite initialize(), arc_setup(),
403    or cleanup() function(s) in ARC test, remember to call the corresponding
404    function(s) in this base class as well.
405
406    """
407    version = 1
408    _PKG_UIAUTOMATOR = 'uiautomator'
409    _FULL_PKG_NAME_UIAUTOMATOR = 'com.github.uiautomator'
410
411    def __init__(self, *args, **kwargs):
412        """Initialize flag setting."""
413        super(ArcTest, self).__init__(*args, **kwargs)
414        self.initialized = False
415        # Set the flag run_once_finished to detect if a test is executed
416        # successfully without any exception thrown. Otherwise, generate
417        # a screenshot in /var/log for debugging.
418        self.run_once_finished = False
419        self.logcat_proc = None
420        self.dep_package = None
421        self.apks = None
422        self.full_pkg_names = []
423        self.uiautomator = False
424        self.email_id = None
425        self.password = None
426        self._chrome = None
427        if os.path.exists(_SCREENSHOT_DIR_PATH):
428            shutil.rmtree(_SCREENSHOT_DIR_PATH)
429        self.register_before_iteration_hook(_before_iteration_hook)
430        self.register_after_iteration_hook(_after_iteration_hook)
431        # Keep track of the number of debug screenshots taken and keep the
432        # total number sane to avoid issues.
433        self.num_screenshots = 0
434
435    def initialize(self, extension_path=None,
436                   arc_mode=arc_common.ARC_MODE_ENABLED, **chrome_kargs):
437        """Log in to a test account."""
438        extension_paths = [extension_path] if extension_path else []
439        self._chrome = chrome.Chrome(extension_paths=extension_paths,
440                                     arc_mode=arc_mode,
441                                     **chrome_kargs)
442        if extension_path:
443            self._extension = self._chrome.get_extension(extension_path)
444        else:
445            self._extension = None
446        # With ARC enabled, Chrome will wait until container to boot up
447        # before returning here, see chrome.py.
448        self.initialized = True
449        try:
450            if is_android_container_alive():
451                self.arc_setup()
452            else:
453                logging.error('Container is alive?')
454        except Exception as err:
455            self.cleanup()
456            raise error.TestFail(err)
457
458    def after_run_once(self):
459        """Executed after run_once() only if there were no errors.
460
461        This function marks the run as finished with a flag. If there was a
462        failure the flag won't be set and the failure can then be detected by
463        testing the run_once_finished flag.
464        """
465        logging.info('After run_once')
466        self.run_once_finished = True
467
468    def cleanup(self):
469        """Log out of Chrome."""
470        if not self.initialized:
471            logging.info('Skipping ARC cleanup: not initialized')
472            return
473        logging.info('Starting ARC cleanup')
474        try:
475            if is_android_container_alive():
476                self.arc_teardown()
477        except Exception as err:
478            raise error.TestFail(err)
479        finally:
480            try:
481                self._stop_logcat()
482            finally:
483                if self._chrome is not None:
484                    self._chrome.close()
485
486    def arc_setup(self, dep_package=None, apks=None, full_pkg_names=[],
487                  uiautomator=False, email_id=None, password=None):
488        """ARC test setup: Setup dependencies and install apks.
489
490        This function disables package verification and enables non-market
491        APK installation. Then, it installs specified APK(s) and uiautomator
492        package and path if required in a test.
493
494        @param dep_package: Package name of autotest_deps APK package.
495        @param apks: Array of APK names to be installed in dep_package.
496        @param full_pkg_names: Array of full package names to be removed
497                               in teardown.
498        @param uiautomator: uiautomator python package is required or not.
499
500        @param email_id: email id to be attached to the android. Only used
501                         when  account_util is set to true.
502        @param password: password related to the email_id.
503        """
504        if not self.initialized:
505            logging.info('Skipping ARC setup: not initialized')
506            return
507        logging.info('Starting ARC setup')
508        self.dep_package = dep_package
509        self.apks = apks
510        self.uiautomator = uiautomator
511        self.email_id = email_id
512        self.password = password
513        # Setup dependent packages if required
514        packages = []
515        if dep_package:
516            packages.append(dep_package)
517        if self.uiautomator:
518            packages.append(self._PKG_UIAUTOMATOR)
519        if packages:
520            logging.info('Setting up dependent package(s) %s', packages)
521            self.job.setup_dep(packages)
522
523        # TODO(b/29341443): Run logcat on non ArcTest test cases too.
524        with open(_VAR_LOGCAT_PATH, 'w') as f:
525            self.logcat_proc = subprocess.Popen(
526                ['android-sh', '-c', 'logcat -v threadtime'],
527                stdout=f,
528                stderr=subprocess.STDOUT,
529                close_fds=True)
530
531        wait_for_adb_ready()
532
533        # package_verifier_user_consent == -1 means to reject Google's
534        # verification on the server side through Play Store.  This suppress a
535        # consent dialog from the system.
536        adb_shell('settings put secure package_verifier_user_consent -1')
537        adb_shell('settings put global package_verifier_enable 0')
538        adb_shell('settings put secure install_non_market_apps 1')
539
540        if self.dep_package:
541            apk_path = os.path.join(self.autodir, 'deps', self.dep_package)
542            if self.apks:
543                for apk in self.apks:
544                    logging.info('Installing %s', apk)
545                    adb_install('%s/%s' % (apk_path, apk))
546                # Verify if package(s) are installed correctly
547                if not full_pkg_names:
548                    raise error.TestError('Package names of apks expected')
549                for pkg in full_pkg_names:
550                    logging.info('Check if %s is installed', pkg)
551                    if not is_package_installed(pkg):
552                        raise error.TestError('Package %s not found' % pkg)
553                    # Make sure full_pkg_names contains installed packages only
554                    # so arc_teardown() knows what packages to uninstall.
555                    self.full_pkg_names.append(pkg)
556
557        if self.uiautomator:
558            path = os.path.join(self.autodir, 'deps', self._PKG_UIAUTOMATOR)
559            sys.path.append(path)
560
561    def _stop_logcat(self):
562        """Stop the adb logcat process gracefully."""
563        if not self.logcat_proc:
564            return
565        # Running `adb kill-server` should have killed `adb logcat`
566        # process, but just in case also send termination signal.
567        self.logcat_proc.terminate()
568
569        class TimeoutException(Exception):
570            """Termination timeout timed out."""
571
572        try:
573            utils.poll_for_condition(
574                condition=lambda: self.logcat_proc.poll() is not None,
575                exception=TimeoutException,
576                timeout=10,
577                sleep_interval=0.1,
578                desc='Waiting for adb logcat to terminate')
579        except TimeoutException:
580            logging.info('Killing adb logcat due to timeout')
581            self.logcat_proc.kill()
582            self.logcat_proc.wait()
583
584    def arc_teardown(self):
585        """ARC test teardown.
586
587        This function removes all installed packages in arc_setup stage
588        first. Then, it restores package verification and disables non-market
589        APK installation.
590
591        """
592        if self.full_pkg_names:
593            for pkg in self.full_pkg_names:
594                logging.info('Uninstalling %s', pkg)
595                if not is_package_installed(pkg):
596                    raise error.TestError('Package %s was not installed' % pkg)
597                adb_uninstall(pkg)
598        if self.uiautomator:
599            logging.info('Uninstalling %s', self._FULL_PKG_NAME_UIAUTOMATOR)
600            adb_uninstall(self._FULL_PKG_NAME_UIAUTOMATOR)
601        adb_shell('settings put secure install_non_market_apps 0')
602        adb_shell('settings put global package_verifier_enable 1')
603        adb_shell('settings put secure package_verifier_user_consent 0')
604
605        remove_android_file(_ANDROID_ADB_KEYS_PATH)
606        utils.system_output('adb kill-server')
607
608    def block_outbound(self):
609        """ Blocks the connection from the container to outer network.
610
611            The iptables settings accept only 192.168.254.2 port 5555 (adb) and
612            localhost port 9008 (uiautomator)
613        """
614        logging.info('Blocking outbound connection')
615        _android_shell('iptables -I OUTPUT -j REJECT')
616        _android_shell('iptables -I OUTPUT -p tcp -s 192.168.254.2 --sport 5555 -j ACCEPT')
617        _android_shell('iptables -I OUTPUT -p tcp -d localhost --dport 9008 -j ACCEPT')
618        _android_shell('iptables -I OUTPUT -p tcp -s localhost --sport 9008 -j ACCEPT')
619
620
621    def unblock_outbound(self):
622        """ Unblocks the connection from the container to outer network.
623
624            The iptables settings are not permanent which means they reset on
625            each instance invocation. But we can still use this function to
626            unblock the outbound connections during the test if needed.
627        """
628        logging.info('Unblocking outbound connection')
629        _android_shell('iptables -D OUTPUT -p tcp -s localhost --sport 9008 -j ACCEPT')
630        _android_shell('iptables -D OUTPUT -p tcp -d localhost --dport 9008 -j ACCEPT')
631        _android_shell('iptables -D OUTPUT -p tcp -s 192.168.254.2 --sport 5555 -j ACCEPT')
632        _android_shell('iptables -D OUTPUT -j REJECT')
633