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