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 5import logging 6import subprocess 7 8from telemetry.core import exceptions 9from telemetry.internal.platform import android_platform_backend as \ 10 android_platform_backend_module 11from telemetry.core import util 12from telemetry.internal.backends import browser_backend 13from telemetry.internal.backends.chrome import chrome_browser_backend 14from telemetry.internal.browser import user_agent 15 16from devil.android import app_ui 17from devil.android import flag_changer 18from devil.android.sdk import intent 19 20 21class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): 22 """The backend for controlling a browser instance running on Android.""" 23 def __init__(self, android_platform_backend, browser_options, 24 backend_settings): 25 assert isinstance(android_platform_backend, 26 android_platform_backend_module.AndroidPlatformBackend) 27 super(AndroidBrowserBackend, self).__init__( 28 android_platform_backend, 29 supports_tab_control=backend_settings.supports_tab_control, 30 supports_extensions=False, browser_options=browser_options) 31 32 self._port_keeper = util.PortKeeper() 33 # Use the port hold by _port_keeper by default. 34 self._port = self._port_keeper.port 35 36 extensions_to_load = browser_options.extensions_to_load 37 38 if len(extensions_to_load) > 0: 39 raise browser_backend.ExtensionsNotSupportedException( 40 'Android browser does not support extensions.') 41 42 # Initialize fields so that an explosion during init doesn't break in Close. 43 self._backend_settings = backend_settings 44 self._saved_sslflag = '' 45 46 # Stop old browser, if any. 47 self._StopBrowser() 48 49 if self.device.HasRoot() or self.device.NeedsSU(): 50 if self.browser_options.profile_dir: 51 self.platform_backend.PushProfile( 52 self._backend_settings.package, 53 self.browser_options.profile_dir) 54 elif not self.browser_options.dont_override_profile: 55 self.platform_backend.RemoveProfile( 56 self._backend_settings.package, 57 self._backend_settings.profile_ignore_list) 58 59 # Set the debug app if needed. 60 self.platform_backend.SetDebugApp(self._backend_settings.package) 61 62 @property 63 def log_file_path(self): 64 return None 65 66 @property 67 def device(self): 68 return self.platform_backend.device 69 70 def _StopBrowser(self): 71 # Note: it's important to stop and _not_ kill the browser app, since 72 # stopping also clears the app state in Android's activity manager. 73 self.platform_backend.StopApplication(self._backend_settings.package) 74 75 def Start(self): 76 self.device.adb.Logcat(clear=True) 77 if self.browser_options.startup_url: 78 url = self.browser_options.startup_url 79 elif self.browser_options.profile_dir: 80 url = None 81 else: 82 # If we have no existing tabs start with a blank page since default 83 # startup with the NTP can lead to race conditions with Telemetry 84 url = 'about:blank' 85 86 self.platform_backend.DismissCrashDialogIfNeeded() 87 88 user_agent_dict = user_agent.GetChromeUserAgentDictFromType( 89 self.browser_options.browser_user_agent_type) 90 91 browser_startup_args = self.GetBrowserStartupArgs() 92 command_line_name = self._backend_settings.command_line_name 93 with flag_changer.CustomCommandLineFlags( 94 self.device, command_line_name, browser_startup_args): 95 self.device.StartActivity( 96 intent.Intent(package=self._backend_settings.package, 97 activity=self._backend_settings.activity, 98 action=None, data=url, category=None, 99 extras=user_agent_dict), 100 blocking=True) 101 102 # TODO(crbug.com/404771): Move port forwarding to network_controller. 103 remote_devtools_port = self._backend_settings.GetDevtoolsRemotePort( 104 self.device) 105 try: 106 # Release reserved port right before forwarding host to device. 107 self._port_keeper.Release() 108 assert self._port == self._port_keeper.port, ( 109 'Android browser backend must use reserved port by _port_keeper') 110 self.platform_backend.ForwardHostToDevice( 111 self._port, remote_devtools_port) 112 except Exception: 113 logging.exception('Failed to forward %s to %s.', 114 str(self._port), str(remote_devtools_port)) 115 logging.warning('Currently forwarding:') 116 try: 117 for line in self.device.adb.ForwardList().splitlines(): 118 logging.warning(' %s', line) 119 except Exception: 120 logging.warning('Exception raised while listing forwarded ' 121 'connections.') 122 123 logging.warning('Host tcp ports in use:') 124 try: 125 for line in subprocess.check_output(['netstat', '-t']).splitlines(): 126 logging.warning(' %s', line) 127 except Exception: 128 logging.warning('Exception raised while listing tcp ports.') 129 130 logging.warning('Device unix domain sockets in use:') 131 try: 132 for line in self.device.ReadFile('/proc/net/unix', as_root=True, 133 force_pull=True).splitlines(): 134 logging.warning(' %s', line) 135 except Exception: 136 logging.warning('Exception raised while listing unix domain sockets.') 137 138 raise 139 140 try: 141 self._WaitForBrowserToComeUp() 142 self._InitDevtoolsClientBackend(remote_devtools_port) 143 except exceptions.BrowserGoneException: 144 logging.critical('Failed to connect to browser.') 145 if not (self.device.HasRoot() or self.device.NeedsSU()): 146 logging.critical( 147 'Resolve this by either: ' 148 '(1) Flashing to a userdebug build OR ' 149 '(2) Manually enabling web debugging in Chrome at ' 150 'Settings > Developer tools > Enable USB Web debugging.') 151 self.Close() 152 raise 153 except: 154 self.Close() 155 raise 156 157 def Foreground(self): 158 package = self._backend_settings.package 159 activity = self._backend_settings.activity 160 self.device.StartActivity( 161 intent.Intent(package=package, 162 activity=activity, 163 action=None, 164 flags=[intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED]), 165 blocking=False) 166 # TODO(crbug.com/601052): The following waits for any UI node for the 167 # package launched to appear on the screen. When the referenced bug is 168 # fixed, remove this workaround and just switch blocking above to True. 169 try: 170 app_ui.AppUi(self.device).WaitForUiNode(package=package) 171 except Exception: 172 raise exceptions.BrowserGoneException(self.browser, 173 'Timed out waiting for browser to come back foreground.') 174 175 def Background(self): 176 package = 'org.chromium.push_apps_to_background' 177 activity = package + '.PushAppsToBackgroundActivity' 178 self.device.StartActivity( 179 intent.Intent( 180 package=package, 181 activity=activity, 182 action=None, 183 flags=[intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED]), 184 blocking=True) 185 186 def GetBrowserStartupArgs(self): 187 args = super(AndroidBrowserBackend, self).GetBrowserStartupArgs() 188 args.append('--enable-remote-debugging') 189 args.append('--disable-fre') 190 args.append('--disable-external-intent-requests') 191 return args 192 193 @property 194 def pid(self): 195 pids = self.device.GetPids(self._backend_settings.package) 196 if not pids or self._backend_settings.package not in pids: 197 raise exceptions.BrowserGoneException(self.browser) 198 if len(pids[self._backend_settings.package]) > 1: 199 raise Exception( 200 'At most one instance of process %s expected but found pids: ' 201 '%s' % (self._backend_settings.package, pids)) 202 return int(pids[self._backend_settings.package][0]) 203 204 @property 205 def browser_directory(self): 206 return None 207 208 @property 209 def profile_directory(self): 210 return self._backend_settings.profile_dir 211 212 @property 213 def package(self): 214 return self._backend_settings.package 215 216 @property 217 def activity(self): 218 return self._backend_settings.activity 219 220 def __del__(self): 221 self.Close() 222 223 def Close(self): 224 super(AndroidBrowserBackend, self).Close() 225 226 self._StopBrowser() 227 228 self.platform_backend.StopForwardingHost(self._port) 229 230 if self._output_profile_path: 231 self.platform_backend.PullProfile( 232 self._backend_settings.package, self._output_profile_path) 233 234 def IsBrowserRunning(self): 235 return self.platform_backend.IsAppRunning(self._backend_settings.package) 236 237 def GetStandardOutput(self): 238 return self.platform_backend.GetStandardOutput() 239 240 def GetStackTrace(self): 241 return self.platform_backend.GetStackTrace() 242 243 def GetMostRecentMinidumpPath(self): 244 return None 245 246 def GetAllMinidumpPaths(self): 247 return None 248 249 def GetAllUnsymbolizedMinidumpPaths(self): 250 return None 251 252 def SymbolizeMinidump(self, minidump_path): 253 return None 254