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