blob: d4c7493bd7adc881189f7a68aca33057300a629f [file] [log] [blame]
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import subprocess
from telemetry.core import exceptions
from telemetry.internal.platform import android_platform_backend as \
android_platform_backend_module
from telemetry.core import util
from telemetry.internal.backends import android_command_line_backend
from telemetry.internal.backends import browser_backend
from telemetry.internal.backends.chrome import chrome_browser_backend
from telemetry.internal.browser import user_agent
from devil.android import app_ui
from devil.android.sdk import intent
class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
"""The backend for controlling a browser instance running on Android."""
def __init__(self, android_platform_backend, browser_options,
backend_settings):
assert isinstance(android_platform_backend,
android_platform_backend_module.AndroidPlatformBackend)
super(AndroidBrowserBackend, self).__init__(
android_platform_backend,
supports_tab_control=backend_settings.supports_tab_control,
supports_extensions=False, browser_options=browser_options)
self._port_keeper = util.PortKeeper()
# Use the port hold by _port_keeper by default.
self._port = self._port_keeper.port
extensions_to_load = browser_options.extensions_to_load
if len(extensions_to_load) > 0:
raise browser_backend.ExtensionsNotSupportedException(
'Android browser does not support extensions.')
# Initialize fields so that an explosion during init doesn't break in Close.
self._backend_settings = backend_settings
self._saved_sslflag = ''
# Stop old browser, if any.
self._StopBrowser()
if self.device.HasRoot() or self.device.NeedsSU():
if self.browser_options.profile_dir:
self.platform_backend.PushProfile(
self._backend_settings.package,
self.browser_options.profile_dir)
elif not self.browser_options.dont_override_profile:
self.platform_backend.RemoveProfile(
self._backend_settings.package,
self._backend_settings.profile_ignore_list)
# Set the debug app if needed.
self.platform_backend.SetDebugApp(self._backend_settings.package)
@property
def log_file_path(self):
return None
@property
def device(self):
return self.platform_backend.device
def _StopBrowser(self):
# Note: it's important to stop and _not_ kill the browser app, since
# stopping also clears the app state in Android's activity manager.
self.platform_backend.StopApplication(self._backend_settings.package)
def Start(self):
self.device.RunShellCommand('logcat -c')
if self.browser_options.startup_url:
url = self.browser_options.startup_url
elif self.browser_options.profile_dir:
url = None
else:
# If we have no existing tabs start with a blank page since default
# startup with the NTP can lead to race conditions with Telemetry
url = 'about:blank'
self.platform_backend.DismissCrashDialogIfNeeded()
user_agent_dict = user_agent.GetChromeUserAgentDictFromType(
self.browser_options.browser_user_agent_type)
browser_startup_args = self.GetBrowserStartupArgs()
with android_command_line_backend.SetUpCommandLineFlags(
self.device, self._backend_settings, browser_startup_args):
self.device.StartActivity(
intent.Intent(package=self._backend_settings.package,
activity=self._backend_settings.activity,
action=None, data=url, category=None,
extras=user_agent_dict),
blocking=True)
# TODO(crbug.com/404771): Move port forwarding to network_controller.
remote_devtools_port = self._backend_settings.GetDevtoolsRemotePort(
self.device)
try:
# Release reserved port right before forwarding host to device.
self._port_keeper.Release()
assert self._port == self._port_keeper.port, (
'Android browser backend must use reserved port by _port_keeper')
self.platform_backend.ForwardHostToDevice(
self._port, remote_devtools_port)
except Exception:
logging.exception('Failed to forward %s to %s.',
str(self._port), str(remote_devtools_port))
logging.warning('Currently forwarding:')
try:
for line in self.device.adb.ForwardList().splitlines():
logging.warning(' %s', line)
except Exception:
logging.warning('Exception raised while listing forwarded '
'connections.')
logging.warning('Host tcp ports in use:')
try:
for line in subprocess.check_output(['netstat', '-t']).splitlines():
logging.warning(' %s', line)
except Exception:
logging.warning('Exception raised while listing tcp ports.')
logging.warning('Device unix domain sockets in use:')
try:
for line in self.device.ReadFile('/proc/net/unix', as_root=True,
force_pull=True).splitlines():
logging.warning(' %s', line)
except Exception:
logging.warning('Exception raised while listing unix domain sockets.')
raise
try:
self._WaitForBrowserToComeUp()
self._InitDevtoolsClientBackend(remote_devtools_port)
except exceptions.BrowserGoneException:
logging.critical('Failed to connect to browser.')
if not (self.device.HasRoot() or self.device.NeedsSU()):
logging.critical(
'Resolve this by either: '
'(1) Flashing to a userdebug build OR '
'(2) Manually enabling web debugging in Chrome at '
'Settings > Developer tools > Enable USB Web debugging.')
self.Close()
raise
except:
self.Close()
raise
def Foreground(self):
package = self._backend_settings.package
activity = self._backend_settings.activity
self.device.StartActivity(
intent.Intent(package=package,
activity=activity,
action=None,
flags=[intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED]),
blocking=False)
# TODO(crbug.com/601052): The following waits for any UI node for the
# package launched to appear on the screen. When the referenced bug is
# fixed, remove this workaround and just switch blocking above to True.
try:
app_ui.AppUi(self.device).WaitForUiNode(package=package)
except Exception:
raise exceptions.BrowserGoneException(self.browser,
'Timed out waiting for browser to come back foreground.')
def Background(self):
package = 'org.chromium.push_apps_to_background'
activity = package + '.PushAppsToBackgroundActivity'
self.device.StartActivity(
intent.Intent(
package=package,
activity=activity,
action=None,
flags=[intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED]),
blocking=True)
def GetBrowserStartupArgs(self):
args = super(AndroidBrowserBackend, self).GetBrowserStartupArgs()
args.append('--enable-remote-debugging')
args.append('--disable-fre')
args.append('--disable-external-intent-requests')
return args
@property
def pid(self):
pids = self.device.GetPids(self._backend_settings.package)
if not pids or self._backend_settings.package not in pids:
raise exceptions.BrowserGoneException(self.browser)
if len(pids[self._backend_settings.package]) > 1:
raise Exception(
'At most one instance of process %s expected but found pids: '
'%s' % (self._backend_settings.package, pids))
return int(pids[self._backend_settings.package][0])
@property
def browser_directory(self):
return None
@property
def profile_directory(self):
return self._backend_settings.profile_dir
@property
def package(self):
return self._backend_settings.package
@property
def activity(self):
return self._backend_settings.activity
def __del__(self):
self.Close()
def Close(self):
super(AndroidBrowserBackend, self).Close()
self._StopBrowser()
self.platform_backend.StopForwardingHost(self._port)
if self._output_profile_path:
self.platform_backend.PullProfile(
self._backend_settings.package, self._output_profile_path)
def IsBrowserRunning(self):
return self.platform_backend.IsAppRunning(self._backend_settings.package)
def GetStandardOutput(self):
return self.platform_backend.GetStandardOutput()
def GetStackTrace(self):
return self.platform_backend.GetStackTrace()
def GetMostRecentMinidumpPath(self):
return None
def GetAllMinidumpPaths(self):
return None
def GetAllUnsymbolizedMinidumpPaths(self):
return None
def SymbolizeMinidump(self, minidump_path):
return None