blob: 5a19bf2029a56d50cc648a131780b2a6e4577a0d [file] [log] [blame]
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import collections
import glob
import logging
import os
import pipes
import re
import shutil
import subprocess
import sys
import tempfile
from autotest_lib.client.bin import test, utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import chrome, arc_common
_ADB_KEYS_PATH = '/tmp/adb_keys'
_ADB_VENDOR_KEYS = 'ADB_VENDOR_KEYS'
_ANDROID_CONTAINER_PID_PATH = '/run/containers/android*/container.pid'
_ANDROID_DATA_ROOT_PATH = '/opt/google/containers/android/rootfs/android-data'
_ANDROID_CONTAINER_ROOT_PATH = '/opt/google/containers/android/rootfs'
_SCREENSHOT_DIR_PATH = '/var/log/arc-screenshots'
_SCREENSHOT_BASENAME = 'arc-screenshot'
_MAX_SCREENSHOT_NUM = 10
_ADBD_PID_PATH = '/run/arc/adbd.pid'
_SDCARD_PID_PATH = '/run/arc/sdcard.pid'
_ANDROID_ADB_KEYS_PATH = '/data/misc/adb/adb_keys'
_PROCESS_CHECK_INTERVAL_SECONDS = 1
_WAIT_FOR_ADB_READY = 60
_WAIT_FOR_ANDROID_PROCESS_SECONDS = 60
_WAIT_FOR_DATA_MOUNTED_SECONDS = 60
_VAR_LOGCAT_PATH = '/var/log/logcat'
_PLAY_STORE_PKG = 'com.android.vending'
_SETTINGS_PKG = 'com.android.settings'
def setup_adb_host():
"""Setup ADB host keys.
This sets up the files and environment variables that wait_for_adb_ready()
needs"""
if _ADB_VENDOR_KEYS in os.environ:
return
if not os.path.exists(_ADB_KEYS_PATH):
os.mkdir(_ADB_KEYS_PATH)
# adb expects $HOME to be writable.
os.environ['HOME'] = _ADB_KEYS_PATH
# Generate and save keys for adb if needed
key_path = os.path.join(_ADB_KEYS_PATH, 'test_key')
if not os.path.exists(key_path):
utils.system('adb keygen ' + pipes.quote(key_path))
os.environ[_ADB_VENDOR_KEYS] = key_path
def adb_connect(attempts=1):
"""Attempt to connect ADB to the Android container.
Returns true if successful. Do not call this function directly. Call
wait_for_adb_ready() instead.
"""
# Kill existing adb server every other invocation to ensure that a full
# reconnect is performed.
if attempts % 2 == 1:
utils.system('adb kill-server', ignore_status=True)
if utils.system('adb connect localhost:22', ignore_status=True) != 0:
return False
return is_adb_connected()
def is_adb_connected():
"""Return true if adb is connected to the container."""
output = utils.system_output('adb get-state', ignore_status=True)
logging.debug('adb get-state: %s', output)
return output.strip() == 'device'
def _is_android_data_mounted():
"""Return true if Android's /data is mounted with partial boot enabled."""
return _android_shell('getprop ro.data_mounted') == '1'
def get_zygote_type():
"""Return zygote service type."""
return _android_shell('getprop ro.zygote')
def get_sdk_version():
"""Return the SDK level version for Android."""
return _android_shell('getprop ro.build.version.sdk')
def get_product():
"""Return the product string used for the Android build."""
return _android_shell('getprop ro.build.product')
def _wait_for_data_mounted(timeout=_WAIT_FOR_DATA_MOUNTED_SECONDS):
utils.poll_for_condition(
condition=_is_android_data_mounted,
desc='Wait for /data mounted',
timeout=timeout,
sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS)
def wait_for_adb_ready(timeout=_WAIT_FOR_ADB_READY):
"""Wait for the ADB client to connect to the ARC container.
@param timeout: Timeout in seconds.
"""
# Although adbd is started at login screen, we still need /data to be
# mounted to set up key-based authentication. /data should be mounted
# once the user has logged in.
_wait_for_data_mounted()
setup_adb_host()
if is_adb_connected():
return
# Push keys for adb.
pubkey_path = os.environ[_ADB_VENDOR_KEYS] + '.pub'
with open(pubkey_path, 'r') as f:
_write_android_file(_ANDROID_ADB_KEYS_PATH, f.read())
_android_shell('chown shell ' + pipes.quote(_ANDROID_ADB_KEYS_PATH))
_android_shell('restorecon ' + pipes.quote(_ANDROID_ADB_KEYS_PATH))
# This starts adbd, restarting it if needed so it can read the updated key.
_android_shell('setprop sys.usb.config mtp')
_android_shell('setprop sys.usb.config mtp,adb')
exception = error.TestFail('adb is not ready in %d seconds.' % timeout)
# Keeps track of how many times adb has attempted to establish a
# connection.
def _adb_connect_wrapper():
_adb_connect_wrapper.attempts += 1
return adb_connect(_adb_connect_wrapper.attempts)
_adb_connect_wrapper.attempts = 0
try:
utils.poll_for_condition(_adb_connect_wrapper,
exception,
timeout)
except utils.TimeoutError:
# The operation has failed, but let's grab some logs.
logging.error('ARC booted: %s',
_android_shell('getprop sys.boot_completed',
ignore_status=True))
logging.error('ARC system events: %s',
_android_shell('logcat -d -b events *:S arc_system_event',
ignore_status=True))
logging.error('adbd process: %s', _android_shell('pidof adbd'))
logging.error('adb state: %s',
utils.system_output('adb get-state', ignore_status=True))
raise
def grant_permissions(package, permissions):
"""Grants permissions to a package.
@param package: Package name.
@param permissions: A list of permissions.
"""
for permission in permissions:
adb_shell('pm grant %s android.permission.%s' % (
pipes.quote(package), pipes.quote(permission)))
def adb_cmd(cmd, **kwargs):
"""Executed cmd using adb. Must wait for adb ready.
@param cmd: Command to run.
"""
# TODO(b/79122489) - Assert if cmd == 'root'
wait_for_adb_ready()
return utils.system_output('adb %s' % cmd, **kwargs)
def adb_shell(cmd, **kwargs):
"""Executed shell command with adb.
@param cmd: Command to run.
"""
output = adb_cmd('shell %s' % pipes.quote(cmd), **kwargs)
# Some android commands include a trailing CRLF in their output.
if kwargs.pop('strip_trailing_whitespace', True):
output = output.rstrip()
return output
def adb_install(apk, auto_grant_permissions=True):
"""Install an apk into container. You must connect first.
@param apk: Package to install.
@param auto_grant_permissions: Set to false to not automatically grant all
permissions. Most tests should not care.
"""
flags = '-g' if auto_grant_permissions else ''
return adb_cmd('install -r %s %s' % (flags, apk), timeout=60*5)
def adb_uninstall(apk):
"""Remove an apk from container. You must connect first.
@param apk: Package to uninstall.
"""
return adb_cmd('uninstall %s' % apk)
def adb_reboot():
"""Reboots the container and block until container pid is gone.
You must connect first.
"""
old_pid = get_container_pid()
logging.info('Trying to reboot PID:%s' % old_pid)
adb_cmd('reboot', ignore_status=True)
# Ensure that the old container is no longer booted
utils.poll_for_condition(
lambda: not utils.pid_is_alive(int(old_pid)), timeout=10)
# This adb_root() function is deceiving in that it works just fine on debug
# builds of ARC (user-debug, eng). However "adb root" does not work on user
# builds as run by the autotest machines when testing prerelease images. In fact
# it will silently fail. You will need to find another way to do do what you
# need to do as root.
#
# TODO(b/79122489) - Remove this function.
def adb_root():
"""Restart adbd with root permission."""
adb_cmd('root')
def get_container_root():
"""Returns path to Android container root directory."""
return _ANDROID_CONTAINER_ROOT_PATH
def get_container_pid_path():
"""Returns the container's PID file path.
Raises:
TestError if no PID file is found, or more than one files are found.
"""
# Find the PID file rather than the android-XXXXXX/ directory to ignore
# stale and empty android-XXXXXX/ directories when they exist.
arc_container_pid_files = glob.glob(_ANDROID_CONTAINER_PID_PATH)
if len(arc_container_pid_files) == 0:
raise error.TestError('Android container.pid not available')
if len(arc_container_pid_files) > 1:
raise error.TestError(
'Multiple Android container.pid files found: %r. '
'Reboot your DUT to recover.' % (arc_container_pid_files))
return arc_container_pid_files[0]
def get_android_data_root():
"""Returns path to Chrome OS directory that bind-mounts Android's /data."""
return _ANDROID_DATA_ROOT_PATH
def get_job_pid(job_name):
"""Returns the PID of an upstart job."""
status = utils.system_output('status %s' % job_name)
match = re.match(r'^%s start/running, process (\d+)$' % job_name,
status)
if not match:
raise error.TestError('Unexpected status: "%s"' % status)
return match.group(1)
def get_container_pid():
"""Returns the PID of the container."""
return utils.read_one_line(get_container_pid_path())
def get_adbd_pid():
"""Returns the PID of the adbd proxy container."""
if not os.path.exists(_ADBD_PID_PATH):
# The adbd proxy does not run on all boards.
return None
return utils.read_one_line(_ADBD_PID_PATH)
def get_sdcard_pid():
"""Returns the PID of the sdcard container."""
return utils.read_one_line(_SDCARD_PID_PATH)
def get_removable_media_pid():
"""Returns the PID of the arc-removable-media FUSE daemon."""
job_pid = get_job_pid('arc-removable-media')
# |job_pid| is the minijail process, obtain the PID of the process running
# inside the mount namespace.
# FUSE process is the only process running as chronos in the process group.
return utils.system_output('pgrep -u chronos -g %s' % job_pid)
def get_obb_mounter_pid():
"""Returns the PID of the OBB mounter."""
return utils.system_output('pgrep -f -u root ^/usr/bin/arc-obb-mounter')
def _is_android_booted():
"""Return whether Android has completed booting."""
return adb_shell('getprop sys.boot_completed', ignore_status=True) == '1'
def wait_for_boot_completed(timeout=60, sleep=1):
"""Waits until sys.boot_completed becomes 1."""
utils.poll_for_condition(
condition=_is_android_booted,
desc='Wait for Android boot',
timeout=timeout, # sec
sleep_interval=sleep) # sec
def is_android_process_running(process_name):
"""Return whether Android has completed booting.
@param process_name: Process name.
"""
output = adb_shell('pgrep -c -f %s' % pipes.quote(process_name))
return int(output) > 0
def check_android_file_exists(filename):
"""Checks whether the given file exists in the Android filesystem
@param filename: File to check.
"""
return adb_shell('test -e {} && echo FileExists'.format(
pipes.quote(filename))).find("FileExists") >= 0
def read_android_file(filename):
"""Reads a file in Android filesystem.
@param filename: File to read.
"""
with tempfile.NamedTemporaryFile() as tmpfile:
adb_cmd('pull %s %s' % (pipes.quote(filename),
pipes.quote(tmpfile.name)))
with open(tmpfile.name) as f:
return f.read()
return None
def write_android_file(filename, data):
"""Writes to a file in Android filesystem.
@param filename: File to write.
@param data: Data to write.
"""
with tempfile.NamedTemporaryFile() as tmpfile:
tmpfile.write(data)
tmpfile.flush()
adb_cmd('push %s %s' % (pipes.quote(tmpfile.name),
pipes.quote(filename)))
def _write_android_file(filename, data):
"""Writes to a file in Android filesystem.
This is an internal function used to bootstrap adb.
Tests should use write_android_file instead.
"""
android_cmd = 'cat > %s' % pipes.quote(filename)
cros_cmd = 'android-sh -c %s' % pipes.quote(android_cmd)
utils.run(cros_cmd, stdin=data)
def get_android_file_stats(filename):
"""Returns an object of file stats for an Android file.
The returned object supported limited attributes, but can be easily extended
if needed. Note that the value are all string.
This uses _android_shell to run as root, so that it can access to all files
inside the container. On non-debuggable build, adb shell is not rootable.
"""
mapping = {
'%a': 'mode',
'%g': 'gid',
'%h': 'nlink',
'%u': 'uid',
}
output = _android_shell(
'stat -c "%s" %s' % (' '.join(mapping.keys()), pipes.quote(filename)))
stats = output.split(' ')
if len(stats) != len(mapping):
raise error.TestError('Unexpected output from stat: %s' % output)
_Stats = collections.namedtuple('_Stats', mapping.values())
return _Stats(*stats)
def remove_android_file(filename):
"""Removes a file in Android filesystem.
@param filename: File to remove.
"""
adb_shell('rm -f %s' % pipes.quote(filename))
def wait_for_android_boot(timeout=None):
"""Sleep until Android has completed booting or timeout occurs.
@param timeout: Timeout in seconds.
"""
arc_common.wait_for_android_boot(timeout)
def wait_for_android_process(process_name,
timeout=_WAIT_FOR_ANDROID_PROCESS_SECONDS):
"""Sleep until an Android process is running or timeout occurs.
@param process_name: Process name.
@param timeout: Timeout in seconds.
"""
condition = lambda: is_android_process_running(process_name)
utils.poll_for_condition(condition=condition,
desc='%s is running' % process_name,
timeout=timeout,
sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS)
def _android_shell(cmd, **kwargs):
"""Execute cmd instead the Android container.
This function is strictly for internal use only, as commands do not run in
a fully consistent Android environment. Prefer adb_shell instead.
"""
return utils.system_output('android-sh -c {}'.format(pipes.quote(cmd)),
**kwargs)
def is_android_container_alive():
"""Check if android container is alive."""
try:
container_pid = get_container_pid()
except Exception, e:
logging.error('is_android_container_alive failed: %r', e)
return False
return utils.pid_is_alive(int(container_pid))
def _is_in_installed_packages_list(package, option=None):
"""Check if a package is in the list returned by pm list packages.
adb must be ready.
@param package: Package in request.
@param option: An option for the command adb shell pm list packages.
Valid values include '-s', '-3', '-d', and '-e'.
"""
command = 'pm list packages'
if option:
command += ' ' + option
packages = adb_shell(command).splitlines()
package_entry = 'package:' + package
ret = package_entry in packages
if not ret:
logging.info('Could not find "%s" in %s' %
(package_entry, str(packages)))
return ret
def is_package_installed(package):
"""Check if a package is installed. adb must be ready.
@param package: Package in request.
"""
return _is_in_installed_packages_list(package)
def is_package_disabled(package):
"""Check if an installed package is disabled. adb must be ready.
@param package: Package in request.
"""
return _is_in_installed_packages_list(package, '-d')
def get_package_install_path(package):
"""Returns the apk install location of the given package."""
output = adb_shell('pm path {}'.format(pipes.quote(package)))
return output.split(':')[1]
def _before_iteration_hook(obj):
"""Executed by parent class before every iteration.
This function resets the run_once_finished flag before every iteration
so we can detect failure on every single iteration.
Args:
obj: the test itself
"""
obj.run_once_finished = False
def _after_iteration_hook(obj):
"""Executed by parent class after every iteration.
The parent class will handle exceptions and failures in the run and will
always call this hook afterwards. Take a screenshot if the run has not
been marked as finished (i.e. there was a failure/exception).
Args:
obj: the test itself
"""
if not obj.run_once_finished:
if is_adb_connected():
logging.debug('Recent activities dump:\n%s',
adb_shell('dumpsys activity recents'))
if not os.path.exists(_SCREENSHOT_DIR_PATH):
os.mkdir(_SCREENSHOT_DIR_PATH, 0755)
obj.num_screenshots += 1
if obj.num_screenshots <= _MAX_SCREENSHOT_NUM:
logging.warning('Iteration %d failed, taking a screenshot.',
obj.iteration)
from cros.graphics.gbm import crtcScreenshot
try:
image = crtcScreenshot()
image.save('{}/{}_iter{}.png'.format(_SCREENSHOT_DIR_PATH,
_SCREENSHOT_BASENAME,
obj.iteration))
except Exception as e:
logging.warning('Unable to capture screenshot. %s', e)
else:
logging.warning('Too many failures, no screenshot taken')
def send_keycode(keycode):
"""Sends the given keycode to the container
@param keycode: keycode to send.
"""
adb_shell('input keyevent {}'.format(keycode))
def get_android_sdk_version():
"""Returns the Android SDK version.
This function can be called before Android container boots.
"""
with open('/etc/lsb-release') as f:
values = dict(line.split('=', 1) for line in f.read().splitlines())
try:
return int(values['CHROMEOS_ARC_ANDROID_SDK_VERSION'])
except (KeyError, ValueError):
raise error.TestError('Could not determine Android SDK version')
def set_device_mode(device_mode, use_fake_sensor_with_lifetime_secs=0):
"""Sets the device in either Clamshell or Tablet mode.
"inject_powerd_input_event" might fail if the DUT does not support Tablet
mode, and it will raise an |error.CmdError| exception. To prevent that, use
the |use_fake_sensor_with_lifetime_secs| parameter.
@param device_mode: string with either 'clamshell' or 'tablet'
@param use_fake_sensor_with_lifetime_secs: if > 0, it will create the
input device with the given lifetime in seconds
@raise ValueError: if passed invalid parameters
@raise error.CmdError: if inject_powerd_input_event fails
"""
valid_value = ('tablet', 'clamshell')
if device_mode not in valid_value:
raise ValueError('Invalid device_mode parameter: %s' % device_mode)
value = 1 if device_mode == 'tablet' else 0
args = ['--code=tablet', '--value=%d' % value]
if use_fake_sensor_with_lifetime_secs > 0:
args.extend(['--create_dev', '--dev_lifetime=%d' %
use_fake_sensor_with_lifetime_secs])
utils.run('inject_powerd_input_event', args=args)
class Logcat(object):
"""Saves the output of logcat to a file."""
def __init__(self, path=_VAR_LOGCAT_PATH):
with open(path, 'w') as f:
self._proc = subprocess.Popen(
['android-sh', '-c', 'logcat'],
stdout=f,
stderr=subprocess.STDOUT,
close_fds=True)
def __enter__(self):
"""Support for context manager."""
return self
def __exit__(self, *args):
"""Support for context manager.
Calls close().
"""
self.close()
def close(self):
"""Stop the logcat process gracefully."""
if not self._proc:
return
self._proc.terminate()
class TimeoutException(Exception):
"""Termination timeout timed out."""
try:
utils.poll_for_condition(
condition=lambda: self._proc.poll() is not None,
exception=TimeoutException,
timeout=10,
sleep_interval=0.1,
desc='Waiting for logcat to terminate')
except TimeoutException:
logging.info('Killing logcat due to timeout')
self._proc.kill()
self._proc.wait()
finally:
self._proc = None
class ArcTest(test.test):
""" Base class of ARC Test.
This class could be used as super class of an ARC test for saving
redundant codes for container bringup, autotest-dep package(s) including
uiautomator setup if required, and apks install/remove during
arc_setup/arc_teardown, respectively. By default arc_setup() is called in
initialize() after Android have been brought up. It could also be
overridden to perform non-default tasks. For example, a simple
ArcHelloWorldTest can be just implemented with print 'HelloWorld' in its
run_once() and no other functions are required. We could expect
ArcHelloWorldTest would bring up browser and wait for container up, then
print 'Hello World', and shutdown browser after. As a precaution, if you
overwrite initialize(), arc_setup(), or cleanup() function(s) in ARC test,
remember to call the corresponding function(s) in this base class as well.
"""
version = 1
_PKG_UIAUTOMATOR = 'uiautomator'
_FULL_PKG_NAME_UIAUTOMATOR = 'com.github.uiautomator'
def __init__(self, *args, **kwargs):
"""Initialize flag setting."""
super(ArcTest, self).__init__(*args, **kwargs)
self.initialized = False
# Set the flag run_once_finished to detect if a test is executed
# successfully without any exception thrown. Otherwise, generate
# a screenshot in /var/log for debugging.
self.run_once_finished = False
self.logcat_proc = None
self.dep_package = None
self.apks = None
self.full_pkg_names = []
self.uiautomator = False
self._should_reenable_play_store = False
self._chrome = None
if os.path.exists(_SCREENSHOT_DIR_PATH):
shutil.rmtree(_SCREENSHOT_DIR_PATH)
self.register_before_iteration_hook(_before_iteration_hook)
self.register_after_iteration_hook(_after_iteration_hook)
# Keep track of the number of debug screenshots taken and keep the
# total number sane to avoid issues.
self.num_screenshots = 0
def initialize(self, extension_path=None, username=None, password=None,
arc_mode=arc_common.ARC_MODE_ENABLED, **chrome_kargs):
"""Log in to a test account."""
extension_paths = [extension_path] if extension_path else []
self._chrome = chrome.Chrome(extension_paths=extension_paths,
username=username,
password=password,
arc_mode=arc_mode,
**chrome_kargs)
if extension_path:
self._extension = self._chrome.get_extension(extension_path)
else:
self._extension = None
# With ARC enabled, Chrome will wait until container to boot up
# before returning here, see chrome.py.
self.initialized = True
try:
if is_android_container_alive():
self.arc_setup()
else:
logging.error('Container is alive?')
except Exception as err:
raise error.TestFail(err)
def after_run_once(self):
"""Executed after run_once() only if there were no errors.
This function marks the run as finished with a flag. If there was a
failure the flag won't be set and the failure can then be detected by
testing the run_once_finished flag.
"""
logging.info('After run_once')
self.run_once_finished = True
def cleanup(self):
"""Log out of Chrome."""
if not self.initialized:
logging.info('Skipping ARC cleanup: not initialized')
return
logging.info('Starting ARC cleanup')
try:
if is_android_container_alive():
self.arc_teardown()
except Exception as err:
raise error.TestFail(err)
finally:
try:
if self.logcat_proc:
self.logcat_proc.close()
finally:
if self._chrome is not None:
self._chrome.close()
def _install_apks(self, dep_package, apks, full_pkg_names):
""""Install apks fetched from the specified package folder.
@param dep_package: A dependent package directory
@param apks: List of apk names to be installed
@param full_pkg_names: List of packages to be uninstalled at teardown
"""
apk_path = os.path.join(self.autodir, 'deps', dep_package)
if apks:
for apk in apks:
logging.info('Installing %s', apk)
out = adb_install('%s/%s' % (apk_path, apk))
logging.info('Install apk output: %s' % str(out))
# Verify if package(s) are installed correctly
if not full_pkg_names:
raise error.TestError('Package names of apks expected')
for pkg in full_pkg_names:
logging.info('Check if %s is installed', pkg)
if not is_package_installed(pkg):
raise error.TestError('Package %s not found' % pkg)
# Make sure full_pkg_names contains installed packages only
# so arc_teardown() knows what packages to uninstall.
self.full_pkg_names.append(pkg)
def _count_nested_array_level(self, array):
"""Count the level of a nested array."""
if isinstance(array, list):
return 1 + self._count_nested_array_level(array[0])
return 0
def _fix_nested_array_level(self, var_name, expected_level, array):
"""Enclose array one level deeper if needed."""
level = self._count_nested_array_level(array)
if level == expected_level:
return array
if level == expected_level - 1:
return [array]
logging.error("Variable %s nested level is not fixable: "
"Expecting %d, seeing %d",
var_name, expected_level, level)
raise error.TestError('Format error with variable %s' % var_name)
def arc_setup(self, dep_packages=None, apks=None, full_pkg_names=None,
uiautomator=False, block_outbound=False,
disable_play_store=False):
"""ARC test setup: Setup dependencies and install apks.
This function disables package verification and enables non-market
APK installation. Then, it installs specified APK(s) and uiautomator
package and path if required in a test.
@param dep_packages: Array of package names of autotest_deps APK
packages.
@param apks: Array of APK name arrays to be installed in dep_package.
@param full_pkg_names: Array of full package name arrays to be removed
in teardown.
@param uiautomator: uiautomator python package is required or not.
@param block_outbound: block outbound network traffic during a test.
@param disable_play_store: Set this to True if you want to prevent
GMS Core from updating.
"""
if not self.initialized:
logging.info('Skipping ARC setup: not initialized')
return
logging.info('Starting ARC setup')
# Sample parameters for multi-deps setup after fixup (if needed):
# dep_packages: ['Dep1-apk', 'Dep2-apk']
# apks: [['com.dep1.arch1.apk', 'com.dep2.arch2.apk'], ['com.dep2.apk']
# full_pkg_nmes: [['com.dep1.app'], ['com.dep2.app']]
# TODO(crbug/777787): once the parameters of all callers of arc_setup
# are refactored, we can delete the safety net here.
if dep_packages:
dep_packages = self._fix_nested_array_level(
'dep_packages', 1, dep_packages)
apks = self._fix_nested_array_level('apks', 2, apks)
full_pkg_names = self._fix_nested_array_level(
'full_pkg_names', 2, full_pkg_names)
if (len(dep_packages) != len(apks) or
len(apks) != len(full_pkg_names)):
logging.info('dep_packages length is %d', len(dep_packages))
logging.info('apks length is %d', len(apks))
logging.info('full_pkg_names length is %d',
len(full_pkg_names))
raise error.TestFail(
'dep_packages/apks/full_pkg_names format error')
self.dep_packages = dep_packages
self.apks = apks
self.uiautomator = uiautomator or disable_play_store
# Setup dependent packages if required
packages = []
if dep_packages:
packages = dep_packages[:]
if self.uiautomator:
packages.append(self._PKG_UIAUTOMATOR)
if packages:
logging.info('Setting up dependent package(s) %s', packages)
self.job.setup_dep(packages)
self.logcat_proc = Logcat()
wait_for_adb_ready()
# Setting verifier_verify_adb_installs to zero suppresses a dialog box
# that can appear asking for the user to consent to the install.
adb_shell('settings put global verifier_verify_adb_installs 0')
# Install apks based on dep_packages/apks/full_pkg_names tuples
if dep_packages:
for i in xrange(len(dep_packages)):
self._install_apks(dep_packages[i], apks[i], full_pkg_names[i])
if self.uiautomator:
path = os.path.join(self.autodir, 'deps', self._PKG_UIAUTOMATOR)
sys.path.append(path)
self._add_ui_object_not_found_handler()
if disable_play_store and not is_package_disabled(_PLAY_STORE_PKG):
self._disable_play_store()
if not is_package_disabled(_PLAY_STORE_PKG):
raise error.TestFail('Failed to disable Google Play Store.')
self._should_reenable_play_store = True
if block_outbound:
self.block_outbound()
def arc_teardown(self):
"""ARC test teardown.
This function removes all installed packages in arc_setup stage
first. Then, it restores package verification and disables non-market
APK installation.
"""
if self.full_pkg_names:
for pkg in self.full_pkg_names:
logging.info('Uninstalling %s', pkg)
if not is_package_installed(pkg):
raise error.TestError('Package %s was not installed' % pkg)
adb_uninstall(pkg)
if self.uiautomator:
logging.info('Uninstalling %s', self._FULL_PKG_NAME_UIAUTOMATOR)
adb_uninstall(self._FULL_PKG_NAME_UIAUTOMATOR)
if self._should_reenable_play_store:
adb_shell('pm enable ' + _PLAY_STORE_PKG)
adb_shell('settings put secure install_non_market_apps 0')
adb_shell('settings put global package_verifier_enable 1')
adb_shell('settings put secure package_verifier_user_consent 0')
# Remove the adb keys without going through adb. This is because the
# 'rm' tool does not have permissions to remove the keys once they have
# been restorecon(8)ed.
utils.system_output('rm -f %s' %
pipes.quote(os.path.join(
get_android_data_root(),
os.path.relpath(_ANDROID_ADB_KEYS_PATH, '/'))))
utils.system_output('adb kill-server')
def block_outbound(self):
""" Blocks the connection from the container to outer network.
The iptables settings accept only 100.115.92.2 port 5555 (adb) and
all local connections, e.g. uiautomator.
"""
logging.info('Blocking outbound connection')
_android_shell('iptables -I OUTPUT -j REJECT')
_android_shell('iptables -I OUTPUT -p tcp -s 100.115.92.2 '
'--sport 5555 '
'-j ACCEPT')
_android_shell('iptables -I OUTPUT -p tcp -d localhost -j ACCEPT')
def unblock_outbound(self):
""" Unblocks the connection from the container to outer network.
The iptables settings are not permanent which means they reset on
each instance invocation. But we can still use this function to
unblock the outbound connections during the test if needed.
"""
logging.info('Unblocking outbound connection')
_android_shell('iptables -D OUTPUT -p tcp -d localhost -j ACCEPT')
_android_shell('iptables -D OUTPUT -p tcp -s 100.115.92.2 '
'--sport 5555 '
'-j ACCEPT')
_android_shell('iptables -D OUTPUT -j REJECT')
def _add_ui_object_not_found_handler(self):
"""Logs the device dump upon uiautomator.UiObjectNotFoundException."""
from uiautomator import device as d
d.handlers.on(lambda d: logging.debug('Device window dump:\n%s',
d.dump()))
def _disable_play_store(self):
"""Disables the Google Play Store app."""
if is_package_disabled(_PLAY_STORE_PKG):
return
adb_shell('am force-stop ' + _PLAY_STORE_PKG)
adb_shell('am start -a android.settings.APPLICATION_DETAILS_SETTINGS '
'-d package:' + _PLAY_STORE_PKG)
# Note: the straightforward "pm disable <package>" command would be
# better, but that requires root permissions, which aren't available on
# a pre-release image being tested. The only other way is through the
# Settings UI, but which might change.
from uiautomator import device as d
d(textMatches='(?i)DISABLE', packageName=_SETTINGS_PKG).wait.exists()
d(textMatches='(?i)DISABLE', packageName=_SETTINGS_PKG).click.wait()
d(textMatches='(?i)DISABLE APP').click.wait()
ok_button = d(textMatches='(?i)OK')
if ok_button.exists:
ok_button.click.wait()
adb_shell('am force-stop ' + _SETTINGS_PKG)