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