| #!/usr/bin/env vpython3 | 
 | # | 
 | # Copyright 2018 The Chromium Authors | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 |  | 
 | """Removes the preinstalled WebView on a device to avoid signature mismatches. | 
 |  | 
 | This should only be used by developers. This script will fail on actual user | 
 | devices (and this configuration is not recommended for user devices). | 
 |  | 
 | The recommended development configuration for Googlers is to satisfy all of the | 
 | below: | 
 |   1. The device has a Google-provided image. | 
 |   2. The device does not have an image based on AOSP. | 
 |   3. Set `use_signing_keys = true` in GN args. | 
 |  | 
 | If any of the above are not satisfied (or if you're external to Google), you can | 
 | use this script to remove the system-image WebView on your device, which will | 
 | allow you to install a local WebView build without triggering signature | 
 | mismatches (which would otherwise block installing the APK). | 
 |  | 
 | After running this script, you should be able to build and install | 
 | system_webview_apk. | 
 |   * If your device does *not* have an AOSP-based image, you will need to set | 
 |     `system_webview_package_name = "com.google.android.webview"` in GN args. | 
 | """ | 
 |  | 
 | from __future__ import print_function | 
 |  | 
 | import argparse | 
 | import logging | 
 | import os | 
 | import sys | 
 |  | 
 | sys.path.append(os.path.join( | 
 |     os.path.dirname(__file__), os.pardir, os.pardir, 'build', 'android')) | 
 | # pylint: disable=wrong-import-position,import-error | 
 | import devil_chromium | 
 | from devil.android import device_errors | 
 | from devil.android import device_utils | 
 | 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 logging_common | 
 |  | 
 | WEBVIEW_PACKAGES = ['com.android.webview', 'com.google.android.webview'] | 
 |  | 
 | TRICHROME_WEBVIEW_PACKAGE = 'com.google.android.webview' | 
 | TRICHROME_CHROME_PACKAGE = 'com.android.chrome' | 
 | TRICHROME_LIBRARY_PACKAGE = 'com.google.android.trichromelibrary' | 
 |  | 
 | ALREADY_UNINSTALLED_ERROR_MESSAGE = "DELETE_FAILED_INTERNAL_ERROR" | 
 |  | 
 |  | 
 | def FindFilePath(device, file_name): | 
 |   paths = device.RunShellCommand(['find', '/product', '-iname', file_name], | 
 |                                  check_return=True) | 
 |   assert len(paths) <= 1, ('Found multiple paths %s for %s' % | 
 |                            (str(paths), file_name)) | 
 |   return paths | 
 |  | 
 |  | 
 | def FindSystemAPKFiles(device, apk_name): | 
 |   """The expected structure of WebViewGoogle system APK is one of the following: | 
 |     On most Q+ devices and emulators: | 
 |     /product/app/WebViewGoogle/WebViewGoogle.apk.gz | 
 |     /product/app/WebViewGoogle-Stub/WebViewGoogle-Stub.apk | 
 |     On Q and R emulators: | 
 |     /product/app/WebViewGoogle/WebViewGoogle.apk | 
 |  | 
 |     Others Trichrome system APKs follow a similar structure. | 
 |   """ | 
 |   paths = [] | 
 |   paths.extend(FindFilePath(device, apk_name + '.apk.gz')) | 
 |   paths.extend(FindFilePath(device, apk_name + '-Stub.apk')) | 
 |   paths.extend(FindFilePath(device, apk_name + '.apk')) | 
 |   if len(paths) == 0: | 
 |     logging.info('%s system APK not found or already removed', apk_name) | 
 |   return paths | 
 |  | 
 |  | 
 | def RemoveTrichromeSystemAPKs(device): | 
 |   """Removes Trichrome system APKs.""" | 
 |   logging.info('Removing Trichrome system APKs from %s...', device.serial) | 
 |   paths = [] | 
 |   with system_app.EnableSystemAppModification(device): | 
 |     for apk in ['WebViewGoogle', 'Chrome', 'TrichromeLibrary']: | 
 |       paths.extend(FindSystemAPKFiles(device, apk)) | 
 |     device.RemovePath(paths, force=True, recursive=True) | 
 |  | 
 |  | 
 | def UninstallTrichromePackages(device): | 
 |   """Uninstalls Trichrome packages.""" | 
 |   logging.info('Uninstalling Trichrome packages from %s...', device.serial) | 
 |   device.Uninstall(TRICHROME_WEBVIEW_PACKAGE) | 
 |   device.Uninstall(TRICHROME_CHROME_PACKAGE) | 
 |   # Keep uninstalling TRICHROME_LIBRARY_PACKAGE until we get | 
 |   # device_errors.AdbCommandFailedError as multiple versions maybe installed. | 
 |   # device.Uninstall doesn't work on shared libraries. We need to call Uninstall | 
 |   # on AdbWrapper directly. | 
 |   is_trichrome_library_installed = True | 
 |   try: | 
 |     # Limiting uninstalling to 10 times as a precaution. | 
 |     for _ in range(10): | 
 |       device.adb.Uninstall(TRICHROME_LIBRARY_PACKAGE) | 
 |   except device_errors.AdbCommandFailedError as e: | 
 |     if e.output and ALREADY_UNINSTALLED_ERROR_MESSAGE in e.output: | 
 |       # Trichrome library is already uninstalled. | 
 |       is_trichrome_library_installed = False | 
 |   if is_trichrome_library_installed: | 
 |     raise device_errors.CommandFailedError( | 
 |         '{} is still installed on the device'.format(TRICHROME_LIBRARY_PACKAGE), | 
 |         device) | 
 |  | 
 |  | 
 | def UninstallWebViewSystemImages(device): | 
 |   """Uninstalls system images for known WebView packages.""" | 
 |   logging.info('Removing system images from %s...', device.serial) | 
 |   system_app.RemoveSystemApps(device, WEBVIEW_PACKAGES) | 
 |   # Removing system apps will reboot the device, so we try to unlock the device | 
 |   # once that's done. | 
 |   device.Unlock() | 
 |  | 
 |  | 
 | def UninstallWebViewUpdates(device): | 
 |   """Uninstalls updates for WebView packages, if updates exist.""" | 
 |   logging.info('Uninstalling updates from %s...', device.serial) | 
 |   for webview_package in WEBVIEW_PACKAGES: | 
 |     try: | 
 |       device.Uninstall(webview_package) | 
 |     except device_errors.AdbCommandFailedError: | 
 |       # This can happen if the app is on the system image but there are no | 
 |       # updates installed on top of that. | 
 |       logging.info('No update to uninstall for %s on %s', webview_package, | 
 |                    device.serial) | 
 |  | 
 |  | 
 | def CheckWebViewIsUninstalled(device): | 
 |   """Throws if WebView is still installed.""" | 
 |   for webview_package in WEBVIEW_PACKAGES: | 
 |     if device.IsApplicationInstalled(webview_package): | 
 |       raise device_errors.CommandFailedError( | 
 |           '{} is still installed on the device'.format(webview_package), | 
 |           device) | 
 |  | 
 |  | 
 | def RemovePreinstalledWebViews(device): | 
 |   device.EnableRoot() | 
 |   try: | 
 |     if device.build_version_sdk >= version_codes.Q: | 
 |       logging.warning('This is a Q+ device, so both WebView and Chrome will be ' | 
 |                       'removed.') | 
 |       RemoveTrichromeSystemAPKs(device) | 
 |       UninstallTrichromePackages(device) | 
 |     else: | 
 |       UninstallWebViewUpdates(device) | 
 |       UninstallWebViewSystemImages(device) | 
 |     CheckWebViewIsUninstalled(device) | 
 |   except device_errors.CommandFailedError: | 
 |     if device.is_emulator: | 
 |       # Point the user to documentation, since there's a good chance they can | 
 |       # workaround this. Use lots of newlines to make sure this message doesn't | 
 |       # get lost. | 
 |       logging.error('Did you start the emulator with "-writable-system?"\n' | 
 |                     'See https://chromium.googlesource.com/chromium/src/+/' | 
 |                     'main/docs/android_emulator.md#writable-system-partition' | 
 |                     '\n') | 
 |     raise | 
 |   device.SetWebViewFallbackLogic(False)  # Allow standalone WebView on N+ | 
 |  | 
 | def main(): | 
 |   parser = argparse.ArgumentParser(description=""" | 
 | Removes the preinstalled WebView APKs to avoid signature mismatches during | 
 | development. | 
 | """) | 
 |  | 
 |   script_common.AddEnvironmentArguments(parser) | 
 |   script_common.AddDeviceArguments(parser) | 
 |   logging_common.AddLoggingArguments(parser) | 
 |  | 
 |   args = parser.parse_args() | 
 |   logging_common.InitializeLogging(args) | 
 |   devil_chromium.Initialize(adb_path=args.adb_path) | 
 |  | 
 |   devices = device_utils.DeviceUtils.HealthyDevices(device_arg=args.devices) | 
 |   device_utils.DeviceUtils.parallel(devices).pMap(RemovePreinstalledWebViews) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |   main() |