• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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