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