blob: 36b7039138ccc4732059c084e25005396896f0db [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A script to use a package as the WebView provider while running a command."""
import argparse
import contextlib
import logging
import os
import re
import sys
if __name__ == '__main__':
sys.path.append(
os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', '..', '..')))
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', '..', '..', '..', 'common', 'py_utils')))
from devil.android import apk_helper
from devil.android import device_errors
from devil.android.sdk import version_codes
from devil.android.tools import script_common
from devil.android.tools import system_app
from devil.utils import cmd_helper
from devil.utils import parallelizer
from devil.utils import run_tests_helper
from py_utils import tempfile_ext
logger = logging.getLogger(__name__)
_SYSTEM_PATH_RE = re.compile(r'^\s*\/system\/')
_WEBVIEW_INSTALL_TIMEOUT = 300
@contextlib.contextmanager
def UseWebViewProvider(device, apk, expected_package=''):
"""A context manager that uses the apk as the webview provider while in scope.
Args:
device: (device_utils.DeviceUtils) The device for which the webview apk
should be used as the provider.
apk: (str) The path to the webview APK to use.
expected_package: (str) If non-empty, verify apk's package name matches
this value.
"""
package_name = apk_helper.GetPackageName(apk)
if expected_package:
if package_name != expected_package:
raise device_errors.CommandFailedError(
'WebView Provider package %s does not match expected %s' %
(package_name, expected_package), str(device))
if (device.build_version_sdk in
[version_codes.NOUGAT, version_codes.NOUGAT_MR1]):
logger.warning('Due to webviewupdate bug in Nougat, WebView Fallback Logic '
'will be disabled and WebView provider may be changed after '
'exit of UseWebViewProvider context manager scope.')
webview_update = device.GetWebViewUpdateServiceDump()
original_fallback_logic = webview_update.get('FallbackLogicEnabled', None)
original_provider = webview_update.get('CurrentWebViewPackage', None)
# This is only necessary if the provider is a fallback provider, but we can't
# generally determine this, so we set this just in case.
device.SetWebViewFallbackLogic(False)
try:
# If user installed versions of the package is present, they must be
# uninstalled first, so that the system version of the package,
# if any, can be found by the ReplaceSystemApp context manager
with _UninstallNonSystemApp(device, package_name):
all_paths = device.GetApplicationPaths(package_name)
system_paths = _FilterPaths(all_paths, True)
non_system_paths = _FilterPaths(all_paths, False)
if non_system_paths:
raise device_errors.CommandFailedError(
'Non-System application paths found after uninstallation: ',
str(non_system_paths))
elif system_paths:
# app is system app, use ReplaceSystemApp to install
with system_app.ReplaceSystemApp(
device,
package_name,
apk,
install_timeout=_WEBVIEW_INSTALL_TIMEOUT):
_SetWebViewProvider(device, package_name)
yield
else:
# app is not present on device, can directly install
with _InstallApp(device, apk):
_SetWebViewProvider(device, package_name)
yield
finally:
# restore the original provider only if it was known and not the current
# provider
if original_provider is not None:
webview_update = device.GetWebViewUpdateServiceDump()
new_provider = webview_update.get('CurrentWebViewPackage', None)
if new_provider != original_provider:
device.SetWebViewImplementation(original_provider)
# enable the fallback logic only if it was known to be enabled
if original_fallback_logic is True:
device.SetWebViewFallbackLogic(True)
def _SetWebViewProvider(device, package_name):
""" Set the WebView provider to the package_name if supported. """
if device.build_version_sdk >= version_codes.NOUGAT:
device.SetWebViewImplementation(package_name)
def _FilterPaths(path_list, is_system):
""" Return paths in the path_list that are/aren't system paths. """
return [
p for p in path_list if is_system == bool(re.match(_SYSTEM_PATH_RE, p))
]
def _RebasePath(new_root, old_root):
""" Graft old_root onto new_root and return the result. """
return os.path.join(new_root, os.path.relpath(old_root, '/'))
@contextlib.contextmanager
def _UninstallNonSystemApp(device, package_name):
""" Make package un-installed while in scope. """
all_paths = device.GetApplicationPaths(package_name)
user_paths = _FilterPaths(all_paths, False)
host_paths = []
if user_paths:
with tempfile_ext.NamedTemporaryDirectory() as temp_dir:
for user_path in user_paths:
host_path = _RebasePath(temp_dir, user_path)
# PullFile takes care of host_path creation if needed.
device.PullFile(user_path, host_path)
host_paths.append(host_path)
device.Uninstall(package_name)
try:
yield
finally:
for host_path in reversed(host_paths):
device.Install(host_path, reinstall=True,
timeout=_WEBVIEW_INSTALL_TIMEOUT)
else:
yield
@contextlib.contextmanager
def _InstallApp(device, apk):
""" Make apk installed while in scope. """
package_name = apk_helper.GetPackageName(apk)
device.Install(apk, reinstall=True, timeout=_WEBVIEW_INSTALL_TIMEOUT)
try:
yield
finally:
device.Uninstall(package_name)
def main(raw_args):
parser = argparse.ArgumentParser()
def add_common_arguments(p):
script_common.AddDeviceArguments(p)
script_common.AddEnvironmentArguments(p)
p.add_argument(
'-v', '--verbose', action='count', default=0,
help='Print more information.')
p.add_argument('command', nargs='*')
@contextlib.contextmanager
def use_webview_provider(device, args):
with UseWebViewProvider(device, args.apk, args.expected_package):
yield
parser.add_argument(
'--apk', required=True,
help='The apk to use as the provider.')
parser.add_argument(
'--expected-package', default='',
help="Verify apk's package name matches value, disabled by default.")
add_common_arguments(parser)
parser.set_defaults(func=use_webview_provider)
args = parser.parse_args(raw_args)
run_tests_helper.SetLogLevel(args.verbose)
script_common.InitializeEnvironment(args)
devices = script_common.GetDevices(args.devices, args.blacklist_file)
parallel_devices = parallelizer.SyncParallelizer(
[args.func(d, args) for d in devices])
with parallel_devices:
if args.command:
return cmd_helper.Call(args.command)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))