| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import logging | 5 import logging |
| 6 import pipes | 6 import pipes |
| 7 import sys | 7 import sys |
| 8 import time | 8 import time |
| 9 | 9 |
| 10 from telemetry.core import exceptions | 10 from telemetry.core import exceptions |
| 11 from telemetry.core import forwarders | 11 from telemetry.core import forwarders |
| 12 from telemetry.core import util | 12 from telemetry.core import util |
| 13 from telemetry.core.backends import adb_commands | 13 from telemetry.core.backends import adb_commands |
| 14 from telemetry.core.backends import android_command_line_backend |
| 14 from telemetry.core.backends import browser_backend | 15 from telemetry.core.backends import browser_backend |
| 15 from telemetry.core.backends.chrome import chrome_browser_backend | 16 from telemetry.core.backends.chrome import chrome_browser_backend |
| 16 from telemetry.core.platform import android_platform_backend as \ | 17 from telemetry.core.platform import android_platform_backend as \ |
| 17 android_platform_backend_module | 18 android_platform_backend_module |
| 18 from telemetry.core.forwarders import android_forwarder | 19 from telemetry.core.forwarders import android_forwarder |
| 19 | 20 |
| 20 util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android') | 21 util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android') |
| 21 from pylib.device import device_errors # pylint: disable=F0401 | 22 from pylib.device import device_errors # pylint: disable=F0401 |
| 22 from pylib.device import intent # pylint: disable=F0401 | 23 from pylib.device import intent # pylint: disable=F0401 |
| 23 | 24 |
| 24 | 25 |
| 25 class AndroidBrowserBackendSettings(object): | |
| 26 | |
| 27 def __init__(self, activity, cmdline_file, package, pseudo_exec_name, | |
| 28 supports_tab_control): | |
| 29 self.activity = activity | |
| 30 self._cmdline_file = cmdline_file | |
| 31 self.package = package | |
| 32 self.pseudo_exec_name = pseudo_exec_name | |
| 33 self.supports_tab_control = supports_tab_control | |
| 34 | |
| 35 def GetCommandLineFile(self, is_user_debug_build): # pylint: disable=W0613 | |
| 36 return self._cmdline_file | |
| 37 | |
| 38 def GetDevtoolsRemotePort(self, adb): | |
| 39 raise NotImplementedError() | |
| 40 | |
| 41 @property | |
| 42 def profile_ignore_list(self): | |
| 43 # Don't delete lib, since it is created by the installer. | |
| 44 return ['lib'] | |
| 45 | |
| 46 | |
| 47 class ChromeBackendSettings(AndroidBrowserBackendSettings): | |
| 48 # Stores a default Preferences file, re-used to speed up "--page-repeat". | |
| 49 _default_preferences_file = None | |
| 50 | |
| 51 def GetCommandLineFile(self, is_user_debug_build): | |
| 52 if is_user_debug_build: | |
| 53 return '/data/local/tmp/chrome-command-line' | |
| 54 else: | |
| 55 return '/data/local/chrome-command-line' | |
| 56 | |
| 57 def __init__(self, package): | |
| 58 super(ChromeBackendSettings, self).__init__( | |
| 59 activity='com.google.android.apps.chrome.Main', | |
| 60 cmdline_file=None, | |
| 61 package=package, | |
| 62 pseudo_exec_name='chrome', | |
| 63 supports_tab_control=True) | |
| 64 | |
| 65 def GetDevtoolsRemotePort(self, adb): | |
| 66 return 'localabstract:chrome_devtools_remote' | |
| 67 | |
| 68 | |
| 69 class ContentShellBackendSettings(AndroidBrowserBackendSettings): | |
| 70 def __init__(self, package): | |
| 71 super(ContentShellBackendSettings, self).__init__( | |
| 72 activity='org.chromium.content_shell_apk.ContentShellActivity', | |
| 73 cmdline_file='/data/local/tmp/content-shell-command-line', | |
| 74 package=package, | |
| 75 pseudo_exec_name='content_shell', | |
| 76 supports_tab_control=False) | |
| 77 | |
| 78 def GetDevtoolsRemotePort(self, adb): | |
| 79 return 'localabstract:content_shell_devtools_remote' | |
| 80 | |
| 81 | |
| 82 class ChromeShellBackendSettings(AndroidBrowserBackendSettings): | |
| 83 def __init__(self, package): | |
| 84 super(ChromeShellBackendSettings, self).__init__( | |
| 85 activity='org.chromium.chrome.shell.ChromeShellActivity', | |
| 86 cmdline_file='/data/local/tmp/chrome-shell-command-line', | |
| 87 package=package, | |
| 88 pseudo_exec_name='chrome_shell', | |
| 89 supports_tab_control=False) | |
| 90 | |
| 91 def GetDevtoolsRemotePort(self, adb): | |
| 92 return 'localabstract:chrome_shell_devtools_remote' | |
| 93 | |
| 94 | |
| 95 class WebviewBackendSettings(AndroidBrowserBackendSettings): | |
| 96 def __init__(self, package, | |
| 97 activity='org.chromium.telemetry_shell.TelemetryActivity', | |
| 98 cmdline_file='/data/local/tmp/webview-command-line'): | |
| 99 super(WebviewBackendSettings, self).__init__( | |
| 100 activity=activity, | |
| 101 cmdline_file=cmdline_file, | |
| 102 package=package, | |
| 103 pseudo_exec_name='webview', | |
| 104 supports_tab_control=False) | |
| 105 | |
| 106 def GetDevtoolsRemotePort(self, adb): | |
| 107 # The DevTools socket name for WebView depends on the activity PID's. | |
| 108 retries = 0 | |
| 109 timeout = 1 | |
| 110 pid = None | |
| 111 while True: | |
| 112 pids = adb.ExtractPid(self.package) | |
| 113 if len(pids) > 0: | |
| 114 pid = pids[-1] | |
| 115 break | |
| 116 time.sleep(timeout) | |
| 117 retries += 1 | |
| 118 timeout *= 2 | |
| 119 if retries == 4: | |
| 120 logging.critical('android_browser_backend: Timeout while waiting for ' | |
| 121 'activity %s:%s to come up', | |
| 122 self.package, | |
| 123 self.activity) | |
| 124 raise exceptions.BrowserGoneException(self.browser, | |
| 125 'Timeout waiting for PID.') | |
| 126 return 'localabstract:webview_devtools_remote_%s' % str(pid) | |
| 127 | |
| 128 | |
| 129 class WebviewShellBackendSettings(WebviewBackendSettings): | |
| 130 def __init__(self, package): | |
| 131 super(WebviewShellBackendSettings, self).__init__( | |
| 132 activity='org.chromium.android_webview.shell.AwShellActivity', | |
| 133 cmdline_file='/data/local/tmp/android-webview-command-line', | |
| 134 package=package) | |
| 135 | |
| 136 | |
| 137 class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): | 26 class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): |
| 138 """The backend for controlling a browser instance running on Android.""" | 27 """The backend for controlling a browser instance running on Android.""" |
| 139 def __init__(self, android_platform_backend, browser_options, | 28 def __init__(self, android_platform_backend, browser_options, |
| 140 backend_settings, use_rndis_forwarder, output_profile_path, | 29 backend_settings, use_rndis_forwarder, output_profile_path, |
| 141 extensions_to_load, target_arch): | 30 extensions_to_load, target_arch): |
| 142 assert isinstance(android_platform_backend, | 31 assert isinstance(android_platform_backend, |
| 143 android_platform_backend_module.AndroidPlatformBackend) | 32 android_platform_backend_module.AndroidPlatformBackend) |
| 144 super(AndroidBrowserBackend, self).__init__( | 33 super(AndroidBrowserBackend, self).__init__( |
| 145 android_platform_backend, | 34 android_platform_backend, |
| 146 supports_tab_control=backend_settings.supports_tab_control, | 35 supports_tab_control=backend_settings.supports_tab_control, |
| 147 supports_extensions=False, browser_options=browser_options, | 36 supports_extensions=False, browser_options=browser_options, |
| 148 output_profile_path=output_profile_path, | 37 output_profile_path=output_profile_path, |
| 149 extensions_to_load=extensions_to_load) | 38 extensions_to_load=extensions_to_load) |
| 150 if len(extensions_to_load) > 0: | 39 if len(extensions_to_load) > 0: |
| 151 raise browser_backend.ExtensionsNotSupportedException( | 40 raise browser_backend.ExtensionsNotSupportedException( |
| 152 'Android browser does not support extensions.') | 41 'Android browser does not support extensions.') |
| 153 | 42 |
| 154 # Initialize fields so that an explosion during init doesn't break in Close. | 43 # Initialize fields so that an explosion during init doesn't break in Close. |
| 155 self._backend_settings = backend_settings | 44 self._backend_settings = backend_settings |
| 156 self._saved_cmdline = '' | |
| 157 self._target_arch = target_arch | 45 self._target_arch = target_arch |
| 158 self._saved_sslflag = '' | 46 self._saved_sslflag = '' |
| 159 | 47 |
| 160 # TODO(tonyg): This is flaky because it doesn't reserve the port that it | 48 # TODO(tonyg): This is flaky because it doesn't reserve the port that it |
| 161 # allocates. Need to fix this. | 49 # allocates. Need to fix this. |
| 162 self._port = adb_commands.AllocateTestServerPort() | 50 self._port = adb_commands.AllocateTestServerPort() |
| 163 | 51 |
| 164 # TODO(wuhu): Move to network controller backend. | 52 # TODO(wuhu): Move to network controller backend. |
| 165 self.platform_backend.InstallTestCa() | 53 self.platform_backend.InstallTestCa() |
| 166 | 54 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 190 # Set the debug app if needed. | 78 # Set the debug app if needed. |
| 191 self.platform_backend.SetDebugApp(self._backend_settings.package) | 79 self.platform_backend.SetDebugApp(self._backend_settings.package) |
| 192 | 80 |
| 193 @property | 81 @property |
| 194 def _adb(self): | 82 def _adb(self): |
| 195 return self.platform_backend.adb | 83 return self.platform_backend.adb |
| 196 | 84 |
| 197 def _KillBrowser(self): | 85 def _KillBrowser(self): |
| 198 self.platform_backend.KillApplication(self._backend_settings.package) | 86 self.platform_backend.KillApplication(self._backend_settings.package) |
| 199 | 87 |
| 200 def _SetUpCommandLine(self): | |
| 201 def QuoteIfNeeded(arg): | |
| 202 # Properly escape "key=valueA valueB" to "key='valueA valueB'" | |
| 203 # Values without spaces, or that seem to be quoted are left untouched. | |
| 204 # This is required so CommandLine.java can parse valueB correctly rather | |
| 205 # than as a separate switch. | |
| 206 params = arg.split('=', 1) | |
| 207 if len(params) != 2: | |
| 208 return arg | |
| 209 key, values = params | |
| 210 if ' ' not in values: | |
| 211 return arg | |
| 212 if values[0] in '"\'' and values[-1] == values[0]: | |
| 213 return arg | |
| 214 return '%s=%s' % (key, pipes.quote(values)) | |
| 215 args = [self._backend_settings.pseudo_exec_name] | |
| 216 args.extend(self.GetBrowserStartupArgs()) | |
| 217 content = ' '.join(QuoteIfNeeded(arg) for arg in args) | |
| 218 cmdline_file = self._backend_settings.GetCommandLineFile( | |
| 219 self._adb.IsUserBuild()) | |
| 220 | |
| 221 try: | |
| 222 # Save the current command line to restore later, except if it appears to | |
| 223 # be a Telemetry created one. This is to prevent a common bug where | |
| 224 # --host-resolver-rules borks people's browsers if something goes wrong | |
| 225 # with Telemetry. | |
| 226 self._saved_cmdline = ''.join(self._adb.device().ReadFile(cmdline_file)) | |
| 227 if '--host-resolver-rules' in self._saved_cmdline: | |
| 228 self._saved_cmdline = '' | |
| 229 self._adb.device().WriteFile(cmdline_file, content, as_root=True) | |
| 230 except device_errors.CommandFailedError: | |
| 231 logging.critical('Cannot set Chrome command line. ' | |
| 232 'Fix this by flashing to a userdebug build.') | |
| 233 sys.exit(1) | |
| 234 | |
| 235 def _RestoreCommandLine(self): | |
| 236 cmdline_file = self._backend_settings.GetCommandLineFile( | |
| 237 self._adb.IsUserBuild()) | |
| 238 self._adb.device().WriteFile(cmdline_file, self._saved_cmdline, | |
| 239 as_root=True) | |
| 240 | |
| 241 def Start(self): | 88 def Start(self): |
| 242 self._SetUpCommandLine() | |
| 243 | |
| 244 self._adb.device().RunShellCommand('logcat -c') | 89 self._adb.device().RunShellCommand('logcat -c') |
| 245 if self.browser_options.startup_url: | 90 if self.browser_options.startup_url: |
| 246 url = self.browser_options.startup_url | 91 url = self.browser_options.startup_url |
| 247 elif self.browser_options.profile_dir: | 92 elif self.browser_options.profile_dir: |
| 248 url = None | 93 url = None |
| 249 else: | 94 else: |
| 250 # If we have no existing tabs start with a blank page since default | 95 # If we have no existing tabs start with a blank page since default |
| 251 # startup with the NTP can lead to race conditions with Telemetry | 96 # startup with the NTP can lead to race conditions with Telemetry |
| 252 url = 'about:blank' | 97 url = 'about:blank' |
| 253 | 98 |
| 254 self.platform_backend.DismissCrashDialogIfNeeded() | 99 self.platform_backend.DismissCrashDialogIfNeeded() |
| 255 | 100 |
| 256 self._adb.device().StartActivity( | |
| 257 intent.Intent(package=self._backend_settings.package, | |
| 258 activity=self._backend_settings.activity, | |
| 259 action=None, data=url, category=None), | |
| 260 blocking=True) | |
| 261 | |
| 262 remote_devtools_port = self._backend_settings.GetDevtoolsRemotePort( | 101 remote_devtools_port = self._backend_settings.GetDevtoolsRemotePort( |
| 263 self._adb) | 102 self._adb) |
| 264 self.platform_backend.ForwardHostToDevice(self._port, remote_devtools_port) | 103 self.platform_backend.ForwardHostToDevice(self._port, remote_devtools_port) |
| 265 | 104 |
| 266 try: | 105 browser_startup_args = self.GetBrowserStartupArgs() |
| 267 self._WaitForBrowserToComeUp(remote_devtools_port=remote_devtools_port) | 106 with android_command_line_backend.SetUpCommandLineFlags( |
| 268 except exceptions.BrowserGoneException: | 107 self._adb, self._backend_settings, browser_startup_args): |
| 269 logging.critical('Failed to connect to browser.') | 108 self._adb.device().StartActivity( |
| 270 if not self._adb.device().old_interface.CanAccessProtectedFileContents(): | 109 intent.Intent(package=self._backend_settings.package, |
| 271 logging.critical( | 110 activity=self._backend_settings.activity, |
| 272 'Resolve this by either: ' | 111 action=None, data=url, category=None), |
| 273 '(1) Flashing to a userdebug build OR ' | 112 blocking=True) |
| 274 '(2) Manually enabling web debugging in Chrome at ' | 113 |
| 275 'Settings > Developer tools > Enable USB Web debugging.') | 114 try: |
| 276 sys.exit(1) | 115 self._WaitForBrowserToComeUp(remote_devtools_port=remote_devtools_port) |
| 277 except: | 116 except exceptions.BrowserGoneException: |
| 278 import traceback | 117 logging.critical('Failed to connect to browser.') |
| 279 traceback.print_exc() | 118 device = self._adb.device() |
| 280 self.Close() | 119 if not device.old_interface.CanAccessProtectedFileContents(): |
| 281 raise | 120 logging.critical( |
| 282 finally: | 121 'Resolve this by either: ' |
| 283 self._RestoreCommandLine() | 122 '(1) Flashing to a userdebug build OR ' |
| 123 '(2) Manually enabling web debugging in Chrome at ' |
| 124 'Settings > Developer tools > Enable USB Web debugging.') |
| 125 sys.exit(1) |
| 126 except: |
| 127 import traceback |
| 128 traceback.print_exc() |
| 129 self.Close() |
| 130 raise |
| 284 | 131 |
| 285 def GetBrowserStartupArgs(self): | 132 def GetBrowserStartupArgs(self): |
| 286 args = super(AndroidBrowserBackend, self).GetBrowserStartupArgs() | 133 args = super(AndroidBrowserBackend, self).GetBrowserStartupArgs() |
| 287 args.append('--enable-remote-debugging') | 134 args.append('--enable-remote-debugging') |
| 288 args.append('--disable-fre') | 135 args.append('--disable-fre') |
| 289 args.append('--disable-external-intent-requests') | 136 args.append('--disable-external-intent-requests') |
| 290 return args | 137 return args |
| 291 | 138 |
| 292 @property | 139 @property |
| 293 def forwarder_factory(self): | 140 def forwarder_factory(self): |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 342 | 189 |
| 343 def GetStandardOutput(self): | 190 def GetStandardOutput(self): |
| 344 return self.platform_backend.GetStandardOutput() | 191 return self.platform_backend.GetStandardOutput() |
| 345 | 192 |
| 346 def GetStackTrace(self): | 193 def GetStackTrace(self): |
| 347 return self.platform_backend.GetStackTrace(self._target_arch) | 194 return self.platform_backend.GetStackTrace(self._target_arch) |
| 348 | 195 |
| 349 @property | 196 @property |
| 350 def should_ignore_certificate_errors(self): | 197 def should_ignore_certificate_errors(self): |
| 351 return not self.platform_backend.is_test_ca_installed | 198 return not self.platform_backend.is_test_ca_installed |
| OLD | NEW |