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 subprocess 10import sys 11 12from py_utils import dependency_util 13from devil import base_error 14from devil.android import apk_helper 15 16from telemetry.core import exceptions 17from telemetry.core import platform 18from telemetry.core import util 19from telemetry import decorators 20from telemetry.internal.backends import android_browser_backend_settings 21from telemetry.internal.backends.chrome import android_browser_backend 22from telemetry.internal.browser import browser 23from telemetry.internal.browser import possible_browser 24from telemetry.internal.platform import android_device 25from telemetry.internal.util import binary_manager 26 27 28CHROME_PACKAGE_NAMES = { 29 'android-content-shell': 30 ['org.chromium.content_shell_apk', 31 android_browser_backend_settings.ContentShellBackendSettings, 32 'ContentShell.apk'], 33 'android-webview': 34 ['org.chromium.webview_shell', 35 android_browser_backend_settings.WebviewBackendSettings, 36 None], 37 'android-webview-shell': 38 ['org.chromium.android_webview.shell', 39 android_browser_backend_settings.WebviewShellBackendSettings, 40 'AndroidWebView.apk'], 41 'android-chromium': 42 ['org.chromium.chrome', 43 android_browser_backend_settings.ChromeBackendSettings, 44 'ChromePublic.apk'], 45 'android-chrome': 46 ['com.google.android.apps.chrome', 47 android_browser_backend_settings.ChromeBackendSettings, 48 'Chrome.apk'], 49 'android-chrome-work': 50 ['com.chrome.work', 51 android_browser_backend_settings.ChromeBackendSettings, 52 None], 53 'android-chrome-beta': 54 ['com.chrome.beta', 55 android_browser_backend_settings.ChromeBackendSettings, 56 None], 57 'android-chrome-dev': 58 ['com.chrome.dev', 59 android_browser_backend_settings.ChromeBackendSettings, 60 None], 61 'android-chrome-canary': 62 ['com.chrome.canary', 63 android_browser_backend_settings.ChromeBackendSettings, 64 None], 65 'android-system-chrome': 66 ['com.android.chrome', 67 android_browser_backend_settings.ChromeBackendSettings, 68 None], 69} 70 71 72class PossibleAndroidBrowser(possible_browser.PossibleBrowser): 73 """A launchable android browser instance.""" 74 def __init__(self, browser_type, finder_options, android_platform, 75 backend_settings, apk_name): 76 super(PossibleAndroidBrowser, self).__init__( 77 browser_type, 'android', backend_settings.supports_tab_control) 78 assert browser_type in FindAllBrowserTypes(finder_options), ( 79 'Please add %s to android_browser_finder.FindAllBrowserTypes' % 80 browser_type) 81 self._platform = android_platform 82 self._platform_backend = ( 83 android_platform._platform_backend) # pylint: disable=protected-access 84 self._backend_settings = backend_settings 85 self._local_apk = None 86 87 if browser_type == 'exact': 88 if not os.path.exists(apk_name): 89 raise exceptions.PathMissingError( 90 'Unable to find exact apk %s specified by --browser-executable' % 91 apk_name) 92 self._local_apk = apk_name 93 elif browser_type == 'reference': 94 if not os.path.exists(apk_name): 95 raise exceptions.PathMissingError( 96 'Unable to find reference apk at expected location %s.' % apk_name) 97 self._local_apk = apk_name 98 elif apk_name: 99 assert finder_options.chrome_root, ( 100 'Must specify Chromium source to use apk_name') 101 chrome_root = finder_options.chrome_root 102 candidate_apks = [] 103 for build_path in util.GetBuildDirectories(chrome_root): 104 apk_full_name = os.path.join(build_path, 'apks', apk_name) 105 if os.path.exists(apk_full_name): 106 last_changed = os.path.getmtime(apk_full_name) 107 candidate_apks.append((last_changed, apk_full_name)) 108 109 if candidate_apks: 110 # Find the candidate .apk with the latest modification time. 111 newest_apk_path = sorted(candidate_apks)[-1][1] 112 self._local_apk = newest_apk_path 113 114 def __repr__(self): 115 return 'PossibleAndroidBrowser(browser_type=%s)' % self.browser_type 116 117 def _InitPlatformIfNeeded(self): 118 pass 119 120 def Create(self, finder_options): 121 self._InitPlatformIfNeeded() 122 browser_backend = android_browser_backend.AndroidBrowserBackend( 123 self._platform_backend, 124 finder_options.browser_options, self._backend_settings) 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, browser_options): 139 if len(browser_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 normalized_path = os.path.expanduser(finder_options.browser_executable) 189 exact_package = apk_helper.GetPackageName(normalized_path) 190 package_info = next( 191 (info for info in CHROME_PACKAGE_NAMES.itervalues() 192 if info[0] == exact_package or info[2] == apk_name), None) 193 194 # It is okay if the APK name or package doesn't match any of known chrome 195 # browser APKs, since it may be of a different browser. 196 if package_info: 197 if not exact_package: 198 raise exceptions.PackageDetectionError( 199 'Unable to find package for %s specified by --browser-executable' % 200 normalized_path) 201 202 [package, backend_settings, _] = package_info 203 if package == exact_package: 204 possible_browsers.append(PossibleAndroidBrowser( 205 'exact', 206 finder_options, 207 android_platform, 208 backend_settings(package), 209 normalized_path)) 210 else: 211 raise exceptions.UnknownPackageError( 212 '%s specified by --browser-executable has an unknown package: %s' % 213 (normalized_path, exact_package)) 214 215 # Add the reference build if found. 216 os_version = dependency_util.GetChromeApkOsVersion( 217 android_platform.GetOSVersionName()) 218 arch = android_platform.GetArchName() 219 try: 220 reference_build = binary_manager.FetchPath( 221 'chrome_stable', arch, 'android', os_version) 222 except (binary_manager.NoPathFoundError, 223 binary_manager.CloudStorageError): 224 reference_build = None 225 226 if reference_build and os.path.exists(reference_build): 227 # TODO(aiolos): how do we stably map the android chrome_stable apk to the 228 # correct package name? 229 package, backend_settings, _ = CHROME_PACKAGE_NAMES['android-chrome'] 230 possible_browsers.append(PossibleAndroidBrowser( 231 'reference', 232 finder_options, 233 android_platform, 234 backend_settings(package), 235 reference_build)) 236 237 # Add any known local versions. 238 for name, package_info in CHROME_PACKAGE_NAMES.iteritems(): 239 package, backend_settings, apk_name = package_info 240 if apk_name and not finder_options.chrome_root: 241 continue 242 b = PossibleAndroidBrowser(name, 243 finder_options, 244 android_platform, 245 backend_settings(package), 246 apk_name) 247 if b.platform.CanLaunchApplication(package) or b.HaveLocalAPK(): 248 possible_browsers.append(b) 249 return possible_browsers 250 251 252def FindAllAvailableBrowsers(finder_options, device): 253 """Finds all the possible browsers on one device. 254 255 The device is either the only device on the host platform, 256 or |finder_options| specifies a particular device. 257 """ 258 if not isinstance(device, android_device.AndroidDevice): 259 return [] 260 261 try: 262 android_platform = platform.GetPlatformForDevice(device, finder_options) 263 return _FindAllPossibleBrowsers(finder_options, android_platform) 264 except base_error.BaseError as e: 265 logging.error('Unable to find browsers on %s: %s', device.device_id, str(e)) 266 ps_output = subprocess.check_output(['ps', '-ef']) 267 logging.error('Ongoing processes:\n%s', ps_output) 268 return [] 269