|  | #!/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() |