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 |