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 android_command_line_backend 13from telemetry.internal.backends import browser_backend 14from telemetry.internal.backends.chrome import chrome_browser_backend 15from telemetry.internal.browser import user_agent 16 17from devil.android.sdk import intent 18 19 20class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): 21 """The backend for controlling a browser instance running on Android.""" 22 def __init__(self, android_platform_backend, browser_options, 23 backend_settings, output_profile_path, extensions_to_load): 24 assert isinstance(android_platform_backend, 25 android_platform_backend_module.AndroidPlatformBackend) 26 super(AndroidBrowserBackend, self).__init__( 27 android_platform_backend, 28 supports_tab_control=backend_settings.supports_tab_control, 29 supports_extensions=False, browser_options=browser_options, 30 output_profile_path=output_profile_path, 31 extensions_to_load=extensions_to_load) 32 33 self._port_keeper = util.PortKeeper() 34 # Use the port hold by _port_keeper by default. 35 self._port = self._port_keeper.port 36 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 # Kill old browser. 47 self._KillBrowser() 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 _KillBrowser(self): 71 if self.device.IsUserBuild(): 72 self.platform_backend.StopApplication(self._backend_settings.package) 73 else: 74 self.platform_backend.KillApplication(self._backend_settings.package) 75 76 def Start(self): 77 self.device.RunShellCommand('logcat -c') 78 if self.browser_options.startup_url: 79 url = self.browser_options.startup_url 80 elif self.browser_options.profile_dir: 81 url = None 82 else: 83 # If we have no existing tabs start with a blank page since default 84 # startup with the NTP can lead to race conditions with Telemetry 85 url = 'about:blank' 86 87 self.platform_backend.DismissCrashDialogIfNeeded() 88 89 user_agent_dict = user_agent.GetChromeUserAgentDictFromType( 90 self.browser_options.browser_user_agent_type) 91 92 browser_startup_args = self.GetBrowserStartupArgs() 93 with android_command_line_backend.SetUpCommandLineFlags( 94 self.device, self._backend_settings, 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 GetBrowserStartupArgs(self): 158 args = super(AndroidBrowserBackend, self).GetBrowserStartupArgs() 159 args.append('--enable-remote-debugging') 160 args.append('--disable-fre') 161 args.append('--disable-external-intent-requests') 162 return args 163 164 @property 165 def pid(self): 166 pids = self.device.GetPids(self._backend_settings.package) 167 if not pids or self._backend_settings.package not in pids: 168 raise exceptions.BrowserGoneException(self.browser) 169 if len(pids[self._backend_settings.package]) > 1: 170 raise Exception( 171 'At most one instance of process %s expected but found pids: ' 172 '%s' % (self._backend_settings.package, pids)) 173 return int(pids[self._backend_settings.package][0]) 174 175 @property 176 def browser_directory(self): 177 return None 178 179 @property 180 def profile_directory(self): 181 return self._backend_settings.profile_dir 182 183 @property 184 def package(self): 185 return self._backend_settings.package 186 187 @property 188 def activity(self): 189 return self._backend_settings.activity 190 191 def __del__(self): 192 self.Close() 193 194 def Close(self): 195 super(AndroidBrowserBackend, self).Close() 196 197 self._KillBrowser() 198 199 self.platform_backend.StopForwardingHost(self._port) 200 201 if self._output_profile_path: 202 self.platform_backend.PullProfile( 203 self._backend_settings.package, self._output_profile_path) 204 205 def IsBrowserRunning(self): 206 return self.platform_backend.IsAppRunning(self._backend_settings.package) 207 208 def GetStandardOutput(self): 209 return self.platform_backend.GetStandardOutput() 210 211 def GetStackTrace(self): 212 return self.platform_backend.GetStackTrace() 213