• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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