1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Finds android browsers that can be controlled by telemetry.""" 6 7import logging 8import os 9import sys 10 11from catapult_base import dependency_util 12from devil.android import apk_helper 13 14from telemetry.core import exceptions 15from telemetry.core import platform 16from telemetry.core import util 17from telemetry import decorators 18from telemetry.internal.backends import android_browser_backend_settings 19from telemetry.internal.backends.chrome import android_browser_backend 20from telemetry.internal.browser import browser 21from telemetry.internal.browser import possible_browser 22from telemetry.internal.platform import android_device 23from telemetry.internal.util import binary_manager 24 25 26CHROME_PACKAGE_NAMES = { 27 'android-content-shell': 28 ['org.chromium.content_shell_apk', 29 android_browser_backend_settings.ContentShellBackendSettings, 30 'ContentShell.apk'], 31 'android-webview': 32 ['org.chromium.webview_shell', 33 android_browser_backend_settings.WebviewBackendSettings, 34 None], 35 'android-webview-shell': 36 ['org.chromium.android_webview.shell', 37 android_browser_backend_settings.WebviewShellBackendSettings, 38 'AndroidWebView.apk'], 39 'android-chromium': 40 ['org.chromium.chrome', 41 android_browser_backend_settings.ChromeBackendSettings, 42 'ChromePublic.apk'], 43 'android-chrome': 44 ['com.google.android.apps.chrome', 45 android_browser_backend_settings.ChromeBackendSettings, 46 'Chrome.apk'], 47 'android-chrome-work': 48 ['com.chrome.work', 49 android_browser_backend_settings.ChromeBackendSettings, 50 None], 51 'android-chrome-beta': 52 ['com.chrome.beta', 53 android_browser_backend_settings.ChromeBackendSettings, 54 None], 55 'android-chrome-dev': 56 ['com.chrome.dev', 57 android_browser_backend_settings.ChromeBackendSettings, 58 None], 59 'android-chrome-canary': 60 ['com.chrome.canary', 61 android_browser_backend_settings.ChromeBackendSettings, 62 None], 63 'android-system-chrome': 64 ['com.android.chrome', 65 android_browser_backend_settings.ChromeBackendSettings, 66 None], 67} 68 69 70class PossibleAndroidBrowser(possible_browser.PossibleBrowser): 71 """A launchable android browser instance.""" 72 def __init__(self, browser_type, finder_options, android_platform, 73 backend_settings, apk_name): 74 super(PossibleAndroidBrowser, self).__init__( 75 browser_type, 'android', backend_settings.supports_tab_control) 76 assert browser_type in FindAllBrowserTypes(finder_options), ( 77 'Please add %s to android_browser_finder.FindAllBrowserTypes' % 78 browser_type) 79 self._platform = android_platform 80 self._platform_backend = ( 81 android_platform._platform_backend) # pylint: disable=protected-access 82 self._backend_settings = backend_settings 83 self._local_apk = None 84 85 if browser_type == 'exact': 86 if not os.path.exists(apk_name): 87 raise exceptions.PathMissingError( 88 'Unable to find exact apk %s specified by --browser-executable' % 89 apk_name) 90 self._local_apk = apk_name 91 elif browser_type == 'reference': 92 if not os.path.exists(apk_name): 93 raise exceptions.PathMissingError( 94 'Unable to find reference apk at expected location %s.' % apk_name) 95 self._local_apk = apk_name 96 elif apk_name: 97 assert finder_options.chrome_root, ( 98 'Must specify Chromium source to use apk_name') 99 chrome_root = finder_options.chrome_root 100 candidate_apks = [] 101 for build_path in util.GetBuildDirectories(chrome_root): 102 apk_full_name = os.path.join(build_path, 'apks', apk_name) 103 if os.path.exists(apk_full_name): 104 last_changed = os.path.getmtime(apk_full_name) 105 candidate_apks.append((last_changed, apk_full_name)) 106 107 if candidate_apks: 108 # Find the candidate .apk with the latest modification time. 109 newest_apk_path = sorted(candidate_apks)[-1][1] 110 self._local_apk = newest_apk_path 111 112 def __repr__(self): 113 return 'PossibleAndroidBrowser(browser_type=%s)' % self.browser_type 114 115 def _InitPlatformIfNeeded(self): 116 pass 117 118 def Create(self, finder_options): 119 self._InitPlatformIfNeeded() 120 browser_backend = android_browser_backend.AndroidBrowserBackend( 121 self._platform_backend, 122 finder_options.browser_options, self._backend_settings, 123 output_profile_path=finder_options.output_profile_path, 124 extensions_to_load=finder_options.extensions_to_load) 125 try: 126 return browser.Browser( 127 browser_backend, self._platform_backend, self._credentials_path) 128 except Exception: 129 logging.exception('Failure while creating Android browser.') 130 original_exception = sys.exc_info() 131 try: 132 browser_backend.Close() 133 except Exception: 134 logging.exception('Secondary failure while closing browser backend.') 135 136 raise original_exception[0], original_exception[1], original_exception[2] 137 138 def SupportsOptions(self, finder_options): 139 if len(finder_options.extensions_to_load) != 0: 140 return False 141 return True 142 143 def HaveLocalAPK(self): 144 return self._local_apk and os.path.exists(self._local_apk) 145 146 @decorators.Cache 147 def UpdateExecutableIfNeeded(self): 148 if self.HaveLocalAPK(): 149 logging.warn('Installing %s on device if needed.' % self._local_apk) 150 self.platform.InstallApplication(self._local_apk) 151 152 def last_modification_time(self): 153 if self.HaveLocalAPK(): 154 return os.path.getmtime(self._local_apk) 155 return -1 156 157 158def SelectDefaultBrowser(possible_browsers): 159 """Return the newest possible browser.""" 160 if not possible_browsers: 161 return None 162 return max(possible_browsers, key=lambda b: b.last_modification_time()) 163 164 165def CanFindAvailableBrowsers(): 166 return android_device.CanDiscoverDevices() 167 168 169def CanPossiblyHandlePath(target_path): 170 return os.path.splitext(target_path.lower())[1] == '.apk' 171 172 173def FindAllBrowserTypes(options): 174 del options # unused 175 return CHROME_PACKAGE_NAMES.keys() + ['exact', 'reference'] 176 177 178def _FindAllPossibleBrowsers(finder_options, android_platform): 179 """Testable version of FindAllAvailableBrowsers.""" 180 if not android_platform: 181 return [] 182 possible_browsers = [] 183 184 # Add the exact APK if given. 185 if (finder_options.browser_executable and 186 CanPossiblyHandlePath(finder_options.browser_executable)): 187 apk_name = os.path.basename(finder_options.browser_executable) 188 package_info = next((info for info in CHROME_PACKAGE_NAMES.itervalues() 189 if info[2] == apk_name), None) 190 191 # It is okay if the APK name doesn't match any of known chrome browser APKs, 192 # since it may be of a different browser. 193 if package_info: 194 normalized_path = os.path.expanduser(finder_options.browser_executable) 195 exact_package = apk_helper.GetPackageName(normalized_path) 196 if not exact_package: 197 raise exceptions.PackageDetectionError( 198 'Unable to find package for %s specified by --browser-executable' % 199 normalized_path) 200 201 [package, backend_settings, _] = package_info 202 if package == exact_package: 203 possible_browsers.append(PossibleAndroidBrowser( 204 'exact', 205 finder_options, 206 android_platform, 207 backend_settings(package), 208 normalized_path)) 209 else: 210 raise exceptions.UnknownPackageError( 211 '%s specified by --browser-executable has an unknown package: %s' % 212 (normalized_path, exact_package)) 213 214 # Add the reference build if found. 215 os_version = dependency_util.GetChromeApkOsVersion( 216 android_platform.GetOSVersionName()) 217 arch = android_platform.GetArchName() 218 try: 219 reference_build = binary_manager.FetchPath( 220 'chrome_stable', arch, 'android', os_version) 221 except (binary_manager.NoPathFoundError, 222 binary_manager.CloudStorageError): 223 reference_build = None 224 225 if reference_build and os.path.exists(reference_build): 226 # TODO(aiolos): how do we stably map the android chrome_stable apk to the 227 # correct package name? 228 package, backend_settings, _ = CHROME_PACKAGE_NAMES['android-chrome'] 229 possible_browsers.append(PossibleAndroidBrowser( 230 'reference', 231 finder_options, 232 android_platform, 233 backend_settings(package), 234 reference_build)) 235 236 # Add any known local versions. 237 for name, package_info in CHROME_PACKAGE_NAMES.iteritems(): 238 package, backend_settings, local_apk = package_info 239 b = PossibleAndroidBrowser(name, 240 finder_options, 241 android_platform, 242 backend_settings(package), 243 local_apk) 244 if b.platform.CanLaunchApplication(package) or b.HaveLocalAPK(): 245 possible_browsers.append(b) 246 return possible_browsers 247 248 249def FindAllAvailableBrowsers(finder_options, device): 250 """Finds all the possible browsers on one device. 251 252 The device is either the only device on the host platform, 253 or |finder_options| specifies a particular device. 254 """ 255 if not isinstance(device, android_device.AndroidDevice): 256 return [] 257 android_platform = platform.GetPlatformForDevice(device, finder_options) 258 return _FindAllPossibleBrowsers(finder_options, android_platform) 259