Index: tools/telemetry/telemetry/internal/backends/mandoline/android_mandoline_backend.py |
diff --git a/tools/telemetry/telemetry/internal/backends/chrome/android_browser_backend.py b/tools/telemetry/telemetry/internal/backends/mandoline/android_mandoline_backend.py |
similarity index 27% |
copy from tools/telemetry/telemetry/internal/backends/chrome/android_browser_backend.py |
copy to tools/telemetry/telemetry/internal/backends/mandoline/android_mandoline_backend.py |
index 37874d484c44982d6ded459f255c6e9d27b092ab..3aadab34a9284d721dedbf361d605f7d39e7cd2a 100644 |
--- a/tools/telemetry/telemetry/internal/backends/chrome/android_browser_backend.py |
+++ b/tools/telemetry/telemetry/internal/backends/mandoline/android_mandoline_backend.py |
@@ -1,48 +1,45 @@ |
-# Copyright 2013 The Chromium Authors. All rights reserved. |
+# Copyright 2015 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 os |
+import random |
+import re |
import sys |
from telemetry.core import exceptions |
+from telemetry.core import util |
+from telemetry.internal.backends.mandoline import android |
+from telemetry.internal.backends.mandoline import config |
+from telemetry.internal.backends.mandoline import mandoline_browser_backend |
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 import forwarders |
util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android') |
-from pylib.device 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, output_profile_path, extensions_to_load, |
- target_arch): |
+try: |
+ from pylib import constants |
+except ImportError: |
+ pass |
+ |
+class AndroidMandolineBackend( |
+ mandoline_browser_backend.MandolineBrowserBackend): |
+ """The backend for controlling a mandoline browser instance running on |
+ Android.""" |
+ |
+ def __init__(self, android_platform_backend, browser_options, target_arch, |
+ browser_type, build_path, package): |
assert isinstance(android_platform_backend, |
android_platform_backend_module.AndroidPlatformBackend) |
- super(AndroidBrowserBackend, self).__init__( |
+ super(AndroidMandolineBackend, self).__init__( |
android_platform_backend, |
- supports_tab_control=backend_settings.supports_tab_control, |
- supports_extensions=False, browser_options=browser_options, |
- output_profile_path=output_profile_path, |
- extensions_to_load=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._target_arch = target_arch |
- self._saved_sslflag = '' |
+ browser_options=browser_options) |
- # TODO(tonyg): This is flaky because it doesn't reserve the port that it |
- # allocates. Need to fix this. |
- self._port = util.GetUnreservedAvailableLocalPort() |
+ self._target_arch = target_arch |
+ self._browser_type = browser_type |
+ self._build_path = build_path |
+ self._package = package |
+ self._device_port = None |
# TODO(wuhu): Move to network controller backend. |
self.platform_backend.InstallTestCa() |
@@ -50,98 +47,88 @@ class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): |
# Kill old browser. |
self._KillBrowser() |
- 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) |
- |
- if self.browser_options.netsim: |
- assert self.platform_backend.use_rndis_forwarder, ( |
- 'Netsim requires RNDIS forwarding.') |
- self.wpr_port_pairs = forwarders.PortPairs( |
- http=forwarders.PortPair(0, 80), |
- https=forwarders.PortPair(0, 443), |
- dns=forwarders.PortPair(0, 53)) |
- |
- # Set the debug app if needed. |
- self.platform_backend.SetDebugApp(self._backend_settings.package) |
- |
@property |
def device(self): |
return self.platform_backend.device |
def _KillBrowser(self): |
if self.device.IsUserBuild(): |
- self.platform_backend.StopApplication(self._backend_settings.package) |
+ self.platform_backend.StopApplication(self._package) |
else: |
- self.platform_backend.KillApplication(self._backend_settings.package) |
+ self.platform_backend.KillApplication(self._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() |
- 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), |
- blocking=True) |
- |
- remote_devtools_port = self._backend_settings.GetDevtoolsRemotePort( |
- self.device) |
- self.platform_backend.ForwardHostToDevice(self._port, |
- remote_devtools_port) |
- 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.') |
- sys.exit(1) |
- except: |
- import traceback |
- traceback.print_exc() |
- self.Close() |
- raise |
+ self._port = util.GetUnreservedAvailableLocalPort() |
+ self._device_port = self._GetAvailableDevicePort() |
+ |
+ self.device.adb.Forward('tcp:%d' % self._port, 'tcp:%d' % self._device_port) |
+ logging.info('Forwarded host port %d to device port %d.' % |
+ (self._port, self._device_port)) |
+ |
+ args = self.GetBrowserStartupArgs() |
+ |
+ if self.browser_options.startup_url: |
+ # TODO(yzshen): For now "about:blank" is not supported yet. |
+ if self.browser_options.startup_url != "about:blank": |
+ args.append(self.browser_options.startup_url) |
+ else: |
+ logging.warning('Ignoring startup URL about:blank.') |
+ |
+ logging.debug('Starting Mandoline %s', args) |
+ |
+ is_debug = 'debug' in self._browser_type |
+ mandoline_config = config.Config(build_dir=self._build_path, |
+ target_os=config.Config.OS_ANDROID, |
+ target_cpu=self._target_arch, |
+ is_debug=is_debug, |
+ apk_name='Mandoline.apk') |
+ shell = android.AndroidShell(mandoline_config) |
+ shell.InitShell(self.device) |
+ |
+ output = sys.stdout |
+ if not self.browser_options.show_stdout: |
+ output = open(os.devnull, 'w') |
+ logging_process = shell.ShowLogs(output) |
+ |
+ # Unlock device screen. |
+ self.device.SendKeyEvent(constants.keyevent.KEYCODE_MENU) |
+ shell.StartActivity(self.activity, args, output, logging_process.terminate) |
+ |
+ try: |
+ self._WaitForBrowserToComeUp() |
+ self._InitDevtoolsClientBackend(self._port) |
+ except exceptions.BrowserGoneException: |
+ logging.critical('Failed to connect to browser.') |
+ # TODO(yzshen): It seems wrong to catch exception and then exit, but this |
+ # matches the behavior of AndroidBrowserBackend. Consider fixing both. |
+ sys.exit(1) |
+ except: |
+ import traceback |
+ traceback.print_exc() |
+ self.Close() |
+ raise |
def GetBrowserStartupArgs(self): |
- args = super(AndroidBrowserBackend, self).GetBrowserStartupArgs() |
- args.append('--enable-remote-debugging') |
- args.append('--disable-fre') |
- args.append('--disable-external-intent-requests') |
+ args = super(AndroidMandolineBackend, self).GetBrowserStartupArgs() |
+ # TODO(yzshen): Mandoline on Android doesn't support excessively long |
+ # command line yet. Remove fieldtrial-related flags as a temp fix because |
+ # they are long and not processed by Mandoline anyway. |
+ # http://crbug.com/514285 |
+ args = [arg for arg in args if arg.find('--force-fieldtrials=') == -1 and |
+ arg.find('--force-fieldtrial-params=') == -1] |
+ args.append('--remote-debugging-port=%i' % self._device_port) |
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: |
+ pids = self.device.GetPids(self._package) |
+ if not pids or self._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]) |
+ return int(pids[self._package]) |
@property |
def browser_directory(self): |
@@ -149,32 +136,34 @@ class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): |
@property |
def profile_directory(self): |
- return self._backend_settings.profile_dir |
+ raise NotImplementedError() |
@property |
def package(self): |
- return self._backend_settings.package |
+ return self._package |
@property |
def activity(self): |
- return self._backend_settings.activity |
+ return 'MandolineActivity' |
def __del__(self): |
self.Close() |
def Close(self): |
- super(AndroidBrowserBackend, self).Close() |
+ super(AndroidMandolineBackend, self).Close() |
self.platform_backend.RemoveTestCa() |
self._KillBrowser() |
- if self._output_profile_path: |
- self.platform_backend.PullProfile( |
- self._backend_settings.package, self._output_profile_path) |
+ if self._port: |
+ # TODO(yzshen): Consider remove the port forwarding. Currently AdbWrapper |
+ # doesn't expose necessary API. |
+ self._port = None |
+ self._device_port = None |
def IsBrowserRunning(self): |
- return self.platform_backend.IsAppRunning(self._backend_settings.package) |
+ return self.platform_backend.IsAppRunning(self._package) |
def GetStandardOutput(self): |
return self.platform_backend.GetStandardOutput() |
@@ -185,3 +174,31 @@ class AndroidBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): |
@property |
def should_ignore_certificate_errors(self): |
return not self.platform_backend.is_test_ca_installed |
+ |
+ def _GetAvailableDevicePort(self): |
+ used_ports = [] |
+ netstat_results = self.device.RunShellCommand( |
+ ['netstat', '-a'], check_return=True, large_output=True) |
+ pattern = re.compile(r"^(127\.0\.0\.1:|localhost:)(\d+)$") |
+ for single_connect in netstat_results: |
+ # Column 3 is the local address which we want to check with. |
+ connect_results = single_connect.split() |
+ if connect_results[0] != 'tcp': |
+ continue |
+ if len(connect_results) < 6: |
+ raise Exception('Unexpected format while parsing netstat line: ' + |
+ single_connect) |
+ result = re.match(pattern, connect_results[3]) |
+ if result: |
+ used_ports.append(int(result.group(2))) |
+ |
+ # The range of ephemeral ports on Android. |
+ dynamic_port_begin = 32768 |
+ dynamic_port_end = 61000 |
+ assert len(used_ports) < dynamic_port_end - dynamic_port_begin + 1 |
+ |
+ available_port = random.randint(dynamic_port_begin, dynamic_port_end) |
+ while available_port in used_ports: |
+ available_port = random.randint(dynamic_port_begin, dynamic_port_end) |
+ |
+ return available_port |