1#!/usr/bin/env python 2 3# Copyright 2015 Google Inc. 4# 5# Use of this source code is governed by a BSD-style license that can be 6# found in the LICENSE file. 7 8 9from __future__ import with_statement 10 11# Imports the monkeyrunner modules used by this program 12from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice 13 14import ast 15import os 16import subprocess 17import time 18 19 20# Time to wait between performing UI actions and capturing the SKP. 21WAIT_FOR_SKP_CAPTURE = 1 22 23 24class DragAction: 25 """Action describing a touch drag.""" 26 def __init__(self, start, end, duration, points): 27 self.start = start 28 self.end = end 29 self.duration = duration 30 self.points = points 31 32 def run(self, device): 33 """Perform the action.""" 34 return device.drag(self.start, self.end, self.duration, self.points) 35 36 37class PressAction: 38 """Action describing a button press.""" 39 def __init__(self, button, press_type): 40 self.button = button 41 self.press_type = press_type 42 43 def run(self, device): 44 """Perform the action.""" 45 return device.press(self.button, self.press_type) 46 47 48def parse_action(action_dict): 49 """Parse a dict describing an action and return an Action object.""" 50 if action_dict['type'] == 'drag': 51 return DragAction(tuple(action_dict['start']), 52 tuple(action_dict['end']), 53 action_dict['duration'], 54 action_dict['points']) 55 elif action_dict['type'] == 'press': 56 return PressAction(action_dict['button'], action_dict['press_type']) 57 else: 58 raise TypeError('Unsupported action type: %s' % action_dict['type']) 59 60 61class App: 62 """Class which describes an app to launch and actions to run.""" 63 def __init__(self, name, package, activity, app_launch_delay, actions): 64 self.name = name 65 self.package = package 66 self.activity = activity 67 self.app_launch_delay = app_launch_delay 68 self.run_component = '%s/%s' % (self.package, self.activity) 69 self.actions = [parse_action(a) for a in actions] 70 71 def launch(self, device): 72 """Launch the app on the device.""" 73 device.startActivity(component=self.run_component) 74 time.sleep(self.app_launch_delay) 75 76 def kill(self): 77 """Kill the app.""" 78 adb_shell('am force-stop %s' % self.package) 79 80 81def check_output(cmd): 82 """Convenience implementation of subprocess.check_output.""" 83 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 84 if proc.wait() != 0: 85 raise Exception('Command failed: %s' % ' '.join(cmd)) 86 return proc.communicate()[0] 87 88 89def adb_shell(cmd): 90 """Run the given ADB shell command and emulate the exit code.""" 91 output = check_output(['adb', 'shell', cmd + '; echo $?']).strip() 92 lines = output.splitlines() 93 if lines[-1] != '0': 94 raise Exception('ADB command failed: %s\n\nOutput:\n%s' % (cmd, output)) 95 return '\n'.join(lines[:-1]) 96 97 98def remote_file_exists(filename): 99 """Return True if the given file exists on the device and False otherwise.""" 100 try: 101 adb_shell('test -f %s' % filename) 102 return True 103 except Exception: 104 return False 105 106 107def capture_skp(skp_file, package, device): 108 """Capture an SKP.""" 109 remote_path = '/data/data/%s/cache/%s' % (package, os.path.basename(skp_file)) 110 try: 111 adb_shell('rm %s' % remote_path) 112 except Exception: 113 if remote_file_exists(remote_path): 114 raise 115 116 adb_shell('setprop debug.hwui.capture_frame_as_skp %s' % remote_path) 117 try: 118 # Spin, wait for the SKP to be written. 119 timeout = 10 # Seconds 120 start = time.time() 121 device.drag((300, 300), (300, 350), 1, 10) # Dummy action to force a draw. 122 while not remote_file_exists(remote_path): 123 if time.time() - start > timeout: 124 raise Exception('Timed out waiting for SKP capture.') 125 time.sleep(1) 126 127 # Pull the SKP from the device. 128 cmd = ['adb', 'pull', remote_path, skp_file] 129 check_output(cmd) 130 131 finally: 132 adb_shell('setprop debug.hwui.capture_frame_as_skp ""') 133 134 135def load_app(filename): 136 """Load the JSON file describing an app and return an App instance.""" 137 with open(filename) as f: 138 app_dict = ast.literal_eval(f.read()) 139 return App(app_dict['name'], 140 app_dict['package'], 141 app_dict['activity'], 142 app_dict['app_launch_delay'], 143 app_dict['actions']) 144 145 146def main(): 147 """Capture SKPs for all apps.""" 148 device = MonkeyRunner.waitForConnection() 149 150 # TODO(borenet): Kill all apps. 151 device.wake() 152 device.drag((600, 600), (10, 10), 0.2, 10) 153 154 apps_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'apps') 155 app_files = [os.path.join(apps_dir, app) for app in os.listdir(apps_dir)] 156 157 for app_file in app_files: 158 app = load_app(app_file) 159 print app.name 160 print ' Package %s' % app.package 161 app.launch(device) 162 print ' Launched activity %s' % app.activity 163 164 for action in app.actions: 165 print ' %s' % action.__class__.__name__ 166 action.run(device) 167 168 time.sleep(WAIT_FOR_SKP_CAPTURE) 169 print ' Capturing SKP.' 170 skp_file = '%s.skp' % app.name 171 capture_skp(skp_file, app.package, device) 172 print ' Wrote SKP to %s' % skp_file 173 print 174 app.kill() 175 176 177if __name__ == '__main__': 178 main() 179