• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2012 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 re
6
7from telemetry.core import util
8from telemetry.internal.app import android_process
9from telemetry.internal.backends import android_browser_backend_settings
10from telemetry.internal.backends import android_command_line_backend
11from telemetry.internal.backends import app_backend
12
13from devil.android import app_ui
14from devil.android.sdk import intent
15
16
17class AndroidAppBackend(app_backend.AppBackend):
18
19  def __init__(self, android_platform_backend, start_intent,
20               is_app_ready_predicate=None, app_has_webviews=True):
21    super(AndroidAppBackend, self).__init__(
22        start_intent.package, android_platform_backend)
23    self._default_process_name = start_intent.package
24    self._start_intent = start_intent
25    self._is_app_ready_predicate = is_app_ready_predicate
26    self._is_running = False
27    self._app_has_webviews = app_has_webviews
28    self._existing_processes_by_pid = {}
29    self._app_ui = None
30
31  @property
32  def device(self):
33    return self.platform_backend.device
34
35  def GetAppUi(self):
36    if self._app_ui is None:
37      self._app_ui = app_ui.AppUi(self.device, self._start_intent.package)
38    return self._app_ui
39
40  def _LaunchAndWaitForApplication(self):
41    """Launch the app and wait for it to be ready."""
42    def is_app_ready():
43      return self._is_app_ready_predicate(self.app)
44
45    # When "is_app_ready_predicate" is provided, we use it to wait for the
46    # app to become ready, otherwise "blocking=True" is used as a fall back.
47    # TODO(slamm): check if the wait for "ps" check is really needed, or
48    # whether the "blocking=True" fall back is sufficient.
49    has_ready_predicate = self._is_app_ready_predicate is not None
50    self.device.StartActivity(
51        self._start_intent,
52        blocking=not has_ready_predicate,
53        force_stop=True,  # Ensure app was not running.
54    )
55    if has_ready_predicate:
56      util.WaitFor(is_app_ready, timeout=60)
57
58  def Start(self):
59    """Start an Android app and wait for it to finish launching.
60
61    If the app has webviews, the app is launched with the suitable
62    command line arguments.
63
64    AppStory derivations can customize the wait-for-ready-state to wait
65    for a more specific event if needed.
66    """
67    if self._app_has_webviews:
68      webview_startup_args = self.GetWebviewStartupArgs()
69      backend_settings = (
70          android_browser_backend_settings.WebviewBackendSettings(
71              'android-webview'))
72      with android_command_line_backend.SetUpCommandLineFlags(
73          self.device, backend_settings, webview_startup_args):
74        self._LaunchAndWaitForApplication()
75    else:
76      self._LaunchAndWaitForApplication()
77    self._is_running = True
78
79  def Foreground(self):
80    self.device.StartActivity(
81        intent.Intent(package=self._start_intent.package,
82                      activity=self._start_intent.activity,
83                      action=None,
84                      flags=[intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED]),
85        blocking=True)
86
87  def Close(self):
88    self._is_running = False
89    self.platform_backend.KillApplication(self._start_intent.package)
90
91  def IsAppRunning(self):
92    return self._is_running
93
94  def GetStandardOutput(self):
95    raise NotImplementedError
96
97  def GetStackTrace(self):
98    raise NotImplementedError
99
100  def GetProcesses(self, process_filter=None):
101    if process_filter is None:
102      # Match process names of the form: 'process_name[:subprocess]'.
103      process_filter = re.compile(
104          '^%s(:|$)' % re.escape(self._default_process_name)).match
105
106    processes = set()
107    ps_output = self.platform_backend.GetPsOutput(['pid', 'name'])
108    for pid, name in ps_output:
109      if not process_filter(name):
110        continue
111
112      if pid not in self._existing_processes_by_pid:
113        self._existing_processes_by_pid[pid] = android_process.AndroidProcess(
114            self, pid, name)
115      processes.add(self._existing_processes_by_pid[pid])
116    return processes
117
118  def GetProcess(self, subprocess_name):
119    assert subprocess_name.startswith(':')
120    process_name = self._default_process_name + subprocess_name
121    return self.GetProcesses(lambda n: n == process_name).pop()
122
123  def GetWebViews(self):
124    assert self._app_has_webviews
125    webviews = set()
126    for process in self.GetProcesses():
127      webviews.update(process.GetWebViews())
128    return webviews
129
130  def GetWebviewStartupArgs(self):
131    assert self._app_has_webviews
132    args = []
133
134    # Turn on GPU benchmarking extension for all runs. The only side effect of
135    # the extension being on is that render stats are tracked. This is believed
136    # to be effectively free. And, by doing so here, it avoids us having to
137    # programmatically inspect a pageset's actions in order to determine if it
138    # might eventually scroll.
139    args.append('--enable-gpu-benchmarking')
140
141    return args
142