| # 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 |
| |
| from telemetry.core import exceptions |
| from telemetry.internal.platform import android_platform_backend as \ |
| android_platform_backend_module |
| 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 import device_signal |
| 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, |
| browser_directory, profile_directory, backend_settings): |
| assert isinstance(android_platform_backend, |
| android_platform_backend_module.AndroidPlatformBackend) |
| super(AndroidBrowserBackend, self).__init__( |
| android_platform_backend, |
| browser_options=browser_options, |
| browser_directory=browser_directory, |
| profile_directory=profile_directory, |
| supports_extensions=False, |
| supports_tab_control=backend_settings.supports_tab_control) |
| self._backend_settings = backend_settings |
| |
| # Initialize fields so that an explosion during init doesn't break in Close. |
| self._saved_sslflag = '' |
| self._app_ui = None |
| |
| # 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 |
| |
| @property |
| def supports_app_ui_interactions(self): |
| return True |
| |
| def GetAppUi(self): |
| if self._app_ui is None: |
| self._app_ui = app_ui.AppUi(self.device, package=self.package) |
| return self._app_ui |
| |
| 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, startup_args, startup_url=None): |
| assert not startup_args, ( |
| 'Startup arguments for Android should be set during ' |
| 'possible_browser.SetUpEnvironment') |
| user_agent_dict = user_agent.GetChromeUserAgentDictFromType( |
| self.browser_options.browser_user_agent_type) |
| self.device.StartActivity( |
| intent.Intent(package=self._backend_settings.package, |
| activity=self._backend_settings.activity, |
| action=None, data=startup_url, category=None, |
| extras=user_agent_dict), |
| blocking=True) |
| try: |
| self.BindDevToolsClient() |
| except: |
| self.Close() |
| raise |
| |
| def BindDevToolsClient(self): |
| super(AndroidBrowserBackend, self).BindDevToolsClient() |
| package = self.devtools_client.GetVersion().get('Android-Package') |
| if package is None: |
| logging.warning('Could not determine package name from DevTools client.') |
| elif package == self._backend_settings.package: |
| logging.info('Successfully connected to %s DevTools client', package) |
| else: |
| raise exceptions.BrowserGoneException( |
| self.browser, 'Expected connection to %s but got %s.' % ( |
| self._backend_settings.package, package)) |
| |
| def _FindDevToolsPortAndTarget(self): |
| devtools_port = self._backend_settings.GetDevtoolsRemotePort(self.device) |
| browser_target = None # Use default |
| return devtools_port, browser_target |
| |
| 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 ForceJavaHeapGarbageCollection(self): |
| # Send USR1 signal to force GC on Chrome processes forked from Zygote. |
| # (c.f. crbug.com/724032) |
| self.device.KillAll( |
| self._backend_settings.package, |
| exact=False, # Send signal to children too. |
| signum=device_signal.SIGUSR1, |
| as_root=True) |
| |
| @property |
| def processes(self): |
| try: |
| zygotes = self.device.ListProcesses('zygote') |
| zygote_pids = set(p.pid for p in zygotes) |
| assert zygote_pids, 'No Android zygote found' |
| processes = self.device.ListProcesses(self._backend_settings.package) |
| return [p for p in processes if p.ppid in zygote_pids] |
| except Exception as exc: |
| # Re-raise as an AppCrashException to get further diagnostic information. |
| # In particular we also get the values of all local variables above. |
| raise exceptions.AppCrashException( |
| self.browser, 'Error getting browser PIDs: %s' % exc) |
| |
| def GetPid(self): |
| browser_processes = self._GetBrowserProcesses() |
| assert len(browser_processes) <= 1, ( |
| 'Found too many browsers: %r' % browser_processes) |
| if not browser_processes: |
| raise exceptions.BrowserGoneException(self.browser) |
| return browser_processes[0].pid |
| |
| def _GetBrowserProcesses(self): |
| """Return all possible browser processes.""" |
| package = self._backend_settings.package |
| return [p for p in self.processes if p.name == package] |
| |
| @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() |
| |
| def IsBrowserRunning(self): |
| return len(self._GetBrowserProcesses()) > 0 |
| |
| 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 |