1#!/usr/bin/env python 2# Copyright 2019 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""A script to use a package as the WebView provider while running a command.""" 6 7import argparse 8import contextlib 9import logging 10import os 11import re 12import sys 13 14if __name__ == '__main__': 15 _DEVIL_ROOT_DIR = os.path.abspath( 16 os.path.join(os.path.dirname(__file__), '..', '..', '..')) 17 sys.path.append(_DEVIL_ROOT_DIR) 18 19from devil import devil_env 20from devil.android import apk_helper 21from devil.android import device_errors 22from devil.android.sdk import version_codes 23from devil.android.tools import script_common 24from devil.android.tools import system_app 25from devil.utils import cmd_helper 26from devil.utils import parallelizer 27from devil.utils import run_tests_helper 28 29with devil_env.SysPath(devil_env.PY_UTILS_PATH): 30 from py_utils import tempfile_ext 31 32logger = logging.getLogger(__name__) 33 34_SYSTEM_PATH_RE = re.compile(r'^\s*\/(system|product)\/') 35_WEBVIEW_INSTALL_TIMEOUT = 300 36 37 38@contextlib.contextmanager 39def UseWebViewProvider(device, apk, expected_package=''): 40 """A context manager that uses the apk as the webview provider while in scope. 41 42 Args: 43 device: (device_utils.DeviceUtils) The device for which the webview apk 44 should be used as the provider. 45 apk: (str) The path to the webview APK to use. 46 expected_package: (str) If non-empty, verify apk's package name matches 47 this value. 48 """ 49 package_name = apk_helper.GetPackageName(apk) 50 51 if expected_package: 52 if package_name != expected_package: 53 raise device_errors.CommandFailedError( 54 'WebView Provider package %s does not match expected %s' % 55 (package_name, expected_package), str(device)) 56 57 if (device.build_version_sdk in [ 58 version_codes.NOUGAT, version_codes.NOUGAT_MR1 59 ]): 60 logger.warning('Due to webviewupdate bug in Nougat, WebView Fallback Logic ' 61 'will be disabled and WebView provider may be changed after ' 62 'exit of UseWebViewProvider context manager scope.') 63 64 webview_update = device.GetWebViewUpdateServiceDump() 65 original_fallback_logic = webview_update.get('FallbackLogicEnabled', None) 66 original_provider = webview_update.get('CurrentWebViewPackage', None) 67 68 # This is only necessary if the provider is a fallback provider, but we can't 69 # generally determine this, so we set this just in case. 70 device.SetWebViewFallbackLogic(False) 71 72 try: 73 # If user installed versions of the package is present, they must be 74 # uninstalled first, so that the system version of the package, 75 # if any, can be found by the ReplaceSystemApp context manager 76 with _UninstallNonSystemApp(device, package_name): 77 all_paths = device.GetApplicationPaths(package_name) 78 system_paths = _FilterPaths(all_paths, True) 79 non_system_paths = _FilterPaths(all_paths, False) 80 if non_system_paths: 81 raise device_errors.CommandFailedError( 82 'Non-System application paths found after uninstallation: ', 83 str(non_system_paths)) 84 elif system_paths: 85 # app is system app, use ReplaceSystemApp to install 86 with system_app.ReplaceSystemApp( 87 device, package_name, apk, 88 install_timeout=_WEBVIEW_INSTALL_TIMEOUT): 89 _SetWebViewProvider(device, package_name) 90 yield 91 else: 92 # app is not present on device, can directly install 93 with _InstallApp(device, apk): 94 _SetWebViewProvider(device, package_name) 95 yield 96 finally: 97 # restore the original provider only if it was known and not the current 98 # provider 99 if original_provider is not None: 100 webview_update = device.GetWebViewUpdateServiceDump() 101 new_provider = webview_update.get('CurrentWebViewPackage', None) 102 if new_provider != original_provider: 103 device.SetWebViewImplementation(original_provider) 104 105 # enable the fallback logic only if it was known to be enabled 106 if original_fallback_logic is True: 107 device.SetWebViewFallbackLogic(True) 108 109 110def _SetWebViewProvider(device, package_name): 111 """ Set the WebView provider to the package_name if supported. """ 112 if device.build_version_sdk >= version_codes.NOUGAT: 113 device.SetWebViewImplementation(package_name) 114 115 116def _FilterPaths(path_list, is_system): 117 """ Return paths in the path_list that are/aren't system paths. """ 118 return [ 119 p for p in path_list if is_system == bool(re.match(_SYSTEM_PATH_RE, p)) 120 ] 121 122 123def _RebasePath(new_root, old_root): 124 """ Graft old_root onto new_root and return the result. """ 125 return os.path.join(new_root, os.path.relpath(old_root, '/')) 126 127 128@contextlib.contextmanager 129def _UninstallNonSystemApp(device, package_name): 130 """ Make package un-installed while in scope. """ 131 all_paths = device.GetApplicationPaths(package_name) 132 user_paths = _FilterPaths(all_paths, False) 133 host_paths = [] 134 if user_paths: 135 with tempfile_ext.NamedTemporaryDirectory() as temp_dir: 136 for user_path in user_paths: 137 host_path = _RebasePath(temp_dir, user_path) 138 # PullFile takes care of host_path creation if needed. 139 device.PullFile(user_path, host_path, timeout=_WEBVIEW_INSTALL_TIMEOUT) 140 host_paths.append(host_path) 141 device.Uninstall(package_name) 142 try: 143 yield 144 finally: 145 for host_path in reversed(host_paths): 146 device.Install( 147 host_path, reinstall=True, timeout=_WEBVIEW_INSTALL_TIMEOUT) 148 else: 149 yield 150 151 152@contextlib.contextmanager 153def _InstallApp(device, apk): 154 """ Make apk installed while in scope. """ 155 package_name = apk_helper.GetPackageName(apk) 156 device.Install(apk, reinstall=True, timeout=_WEBVIEW_INSTALL_TIMEOUT) 157 try: 158 yield 159 finally: 160 device.Uninstall(package_name) 161 162 163def main(raw_args): 164 parser = argparse.ArgumentParser() 165 166 def add_common_arguments(p): 167 script_common.AddDeviceArguments(p) 168 script_common.AddEnvironmentArguments(p) 169 p.add_argument( 170 '-v', 171 '--verbose', 172 action='count', 173 default=0, 174 help='Print more information.') 175 p.add_argument('command', nargs='*') 176 177 @contextlib.contextmanager 178 def use_webview_provider(device, args): 179 with UseWebViewProvider(device, args.apk, args.expected_package): 180 yield 181 182 parser.add_argument( 183 '--apk', required=True, help='The apk to use as the provider.') 184 parser.add_argument( 185 '--expected-package', 186 default='', 187 help="Verify apk's package name matches value, disabled by default.") 188 add_common_arguments(parser) 189 parser.set_defaults(func=use_webview_provider) 190 191 args = parser.parse_args(raw_args) 192 193 run_tests_helper.SetLogLevel(args.verbose) 194 script_common.InitializeEnvironment(args) 195 196 devices = script_common.GetDevices(args.devices, args.denylist_file) 197 parallel_devices = parallelizer.SyncParallelizer( 198 [args.func(d, args) for d in devices]) 199 with parallel_devices: 200 if args.command: 201 return cmd_helper.Call(args.command) 202 return 0 203 204 205if __name__ == '__main__': 206 sys.exit(main(sys.argv[1:])) 207