# Copyright 2013 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. """Finds android browsers that can be controlled by telemetry.""" import logging import os import sys from py_utils import dependency_util from devil import base_error from devil.android import apk_helper from telemetry.core import exceptions from telemetry.core import platform from telemetry.core import util from telemetry import decorators from telemetry.internal.backends import android_browser_backend_settings from telemetry.internal.backends.chrome import android_browser_backend from telemetry.internal.browser import browser from telemetry.internal.browser import possible_browser from telemetry.internal.platform import android_device from telemetry.internal.util import binary_manager CHROME_PACKAGE_NAMES = { 'android-content-shell': ['org.chromium.content_shell_apk', android_browser_backend_settings.ContentShellBackendSettings, 'ContentShell.apk'], 'android-webview': ['org.chromium.webview_shell', android_browser_backend_settings.WebviewBackendSettings, None], 'android-webview-shell': ['org.chromium.android_webview.shell', android_browser_backend_settings.WebviewShellBackendSettings, 'AndroidWebView.apk'], 'android-chromium': ['org.chromium.chrome', android_browser_backend_settings.ChromeBackendSettings, 'ChromePublic.apk'], 'android-chrome': ['com.google.android.apps.chrome', android_browser_backend_settings.ChromeBackendSettings, 'Chrome.apk'], 'android-chrome-work': ['com.chrome.work', android_browser_backend_settings.ChromeBackendSettings, None], 'android-chrome-beta': ['com.chrome.beta', android_browser_backend_settings.ChromeBackendSettings, None], 'android-chrome-dev': ['com.chrome.dev', android_browser_backend_settings.ChromeBackendSettings, None], 'android-chrome-canary': ['com.chrome.canary', android_browser_backend_settings.ChromeBackendSettings, None], 'android-system-chrome': ['com.android.chrome', android_browser_backend_settings.ChromeBackendSettings, None], } class PossibleAndroidBrowser(possible_browser.PossibleBrowser): """A launchable android browser instance.""" def __init__(self, browser_type, finder_options, android_platform, backend_settings, apk_name): super(PossibleAndroidBrowser, self).__init__( browser_type, 'android', backend_settings.supports_tab_control) assert browser_type in FindAllBrowserTypes(finder_options), ( 'Please add %s to android_browser_finder.FindAllBrowserTypes' % browser_type) self._platform = android_platform self._platform_backend = ( android_platform._platform_backend) # pylint: disable=protected-access self._backend_settings = backend_settings self._local_apk = None if browser_type == 'exact': if not os.path.exists(apk_name): raise exceptions.PathMissingError( 'Unable to find exact apk %s specified by --browser-executable' % apk_name) self._local_apk = apk_name elif browser_type == 'reference': if not os.path.exists(apk_name): raise exceptions.PathMissingError( 'Unable to find reference apk at expected location %s.' % apk_name) self._local_apk = apk_name elif apk_name: assert finder_options.chrome_root, ( 'Must specify Chromium source to use apk_name') chrome_root = finder_options.chrome_root candidate_apks = [] for build_path in util.GetBuildDirectories(chrome_root): apk_full_name = os.path.join(build_path, 'apks', apk_name) if os.path.exists(apk_full_name): last_changed = os.path.getmtime(apk_full_name) candidate_apks.append((last_changed, apk_full_name)) if candidate_apks: # Find the candidate .apk with the latest modification time. newest_apk_path = sorted(candidate_apks)[-1][1] self._local_apk = newest_apk_path def __repr__(self): return 'PossibleAndroidBrowser(browser_type=%s)' % self.browser_type def _InitPlatformIfNeeded(self): pass def Create(self, finder_options): self._InitPlatformIfNeeded() browser_backend = android_browser_backend.AndroidBrowserBackend( self._platform_backend, finder_options.browser_options, self._backend_settings) try: return browser.Browser( browser_backend, self._platform_backend, self._credentials_path) except Exception: logging.exception('Failure while creating Android browser.') original_exception = sys.exc_info() try: browser_backend.Close() except Exception: logging.exception('Secondary failure while closing browser backend.') raise original_exception[0], original_exception[1], original_exception[2] def SupportsOptions(self, browser_options): if len(browser_options.extensions_to_load) != 0: return False return True def HaveLocalAPK(self): return self._local_apk and os.path.exists(self._local_apk) @decorators.Cache def UpdateExecutableIfNeeded(self): if self.HaveLocalAPK(): logging.warn('Installing %s on device if needed.' % self._local_apk) self.platform.InstallApplication(self._local_apk) def last_modification_time(self): if self.HaveLocalAPK(): return os.path.getmtime(self._local_apk) return -1 def SelectDefaultBrowser(possible_browsers): """Return the newest possible browser.""" if not possible_browsers: return None return max(possible_browsers, key=lambda b: b.last_modification_time()) def CanFindAvailableBrowsers(): return android_device.CanDiscoverDevices() def CanPossiblyHandlePath(target_path): return os.path.splitext(target_path.lower())[1] == '.apk' def FindAllBrowserTypes(options): del options # unused return CHROME_PACKAGE_NAMES.keys() + ['exact', 'reference'] def _FindAllPossibleBrowsers(finder_options, android_platform): """Testable version of FindAllAvailableBrowsers.""" if not android_platform: return [] possible_browsers = [] # Add the exact APK if given. if (finder_options.browser_executable and CanPossiblyHandlePath(finder_options.browser_executable)): apk_name = os.path.basename(finder_options.browser_executable) normalized_path = os.path.expanduser(finder_options.browser_executable) exact_package = apk_helper.GetPackageName(normalized_path) package_info = next( (info for info in CHROME_PACKAGE_NAMES.itervalues() if info[0] == exact_package or info[2] == apk_name), None) # It is okay if the APK name or package doesn't match any of known chrome # browser APKs, since it may be of a different browser. if package_info: if not exact_package: raise exceptions.PackageDetectionError( 'Unable to find package for %s specified by --browser-executable' % normalized_path) [package, backend_settings, _] = package_info if package == exact_package: possible_browsers.append(PossibleAndroidBrowser( 'exact', finder_options, android_platform, backend_settings(package), normalized_path)) else: raise exceptions.UnknownPackageError( '%s specified by --browser-executable has an unknown package: %s' % (normalized_path, exact_package)) # Add the reference build if found. os_version = dependency_util.GetChromeApkOsVersion( android_platform.GetOSVersionName()) arch = android_platform.GetArchName() try: reference_build = binary_manager.FetchPath( 'chrome_stable', arch, 'android', os_version) except (binary_manager.NoPathFoundError, binary_manager.CloudStorageError): reference_build = None if reference_build and os.path.exists(reference_build): # TODO(aiolos): how do we stably map the android chrome_stable apk to the # correct package name? package, backend_settings, _ = CHROME_PACKAGE_NAMES['android-chrome'] possible_browsers.append(PossibleAndroidBrowser( 'reference', finder_options, android_platform, backend_settings(package), reference_build)) # Add any known local versions. for name, package_info in CHROME_PACKAGE_NAMES.iteritems(): package, backend_settings, apk_name = package_info if apk_name and not finder_options.chrome_root: continue b = PossibleAndroidBrowser(name, finder_options, android_platform, backend_settings(package), apk_name) if b.platform.CanLaunchApplication(package) or b.HaveLocalAPK(): possible_browsers.append(b) return possible_browsers def FindAllAvailableBrowsers(finder_options, device): """Finds all the possible browsers on one device. The device is either the only device on the host platform, or |finder_options| specifies a particular device. """ if not isinstance(device, android_device.AndroidDevice): return [] try: android_platform = platform.GetPlatformForDevice(device, finder_options) return _FindAllPossibleBrowsers(finder_options, android_platform) except base_error.BaseError as e: logging.error('Unable to find browsers on %s: %s', device.device_id, str(e)) return []