| #!/usr/bin/env python |
| |
| # Copyright 2015 Google Inc. |
| # |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| |
| from __future__ import print_function |
| from __future__ import with_statement |
| |
| # Imports the monkeyrunner modules used by this program |
| from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice |
| |
| import ast |
| import os |
| import subprocess |
| import time |
| |
| |
| # Time to wait between performing UI actions and capturing the SKP. |
| WAIT_FOR_SKP_CAPTURE = 1 |
| |
| |
| class DragAction: |
| """Action describing a touch drag.""" |
| def __init__(self, start, end, duration, points): |
| self.start = start |
| self.end = end |
| self.duration = duration |
| self.points = points |
| |
| def run(self, device): |
| """Perform the action.""" |
| return device.drag(self.start, self.end, self.duration, self.points) |
| |
| |
| class PressAction: |
| """Action describing a button press.""" |
| def __init__(self, button, press_type): |
| self.button = button |
| self.press_type = press_type |
| |
| def run(self, device): |
| """Perform the action.""" |
| return device.press(self.button, self.press_type) |
| |
| |
| def parse_action(action_dict): |
| """Parse a dict describing an action and return an Action object.""" |
| if action_dict['type'] == 'drag': |
| return DragAction(tuple(action_dict['start']), |
| tuple(action_dict['end']), |
| action_dict['duration'], |
| action_dict['points']) |
| elif action_dict['type'] == 'press': |
| return PressAction(action_dict['button'], action_dict['press_type']) |
| else: |
| raise TypeError('Unsupported action type: %s' % action_dict['type']) |
| |
| |
| class App: |
| """Class which describes an app to launch and actions to run.""" |
| def __init__(self, name, package, activity, app_launch_delay, actions): |
| self.name = name |
| self.package = package |
| self.activity = activity |
| self.app_launch_delay = app_launch_delay |
| self.run_component = '%s/%s' % (self.package, self.activity) |
| self.actions = [parse_action(a) for a in actions] |
| |
| def launch(self, device): |
| """Launch the app on the device.""" |
| device.startActivity(component=self.run_component) |
| time.sleep(self.app_launch_delay) |
| |
| def kill(self): |
| """Kill the app.""" |
| adb_shell('am force-stop %s' % self.package) |
| |
| |
| def check_output(cmd): |
| """Convenience implementation of subprocess.check_output.""" |
| proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
| if proc.wait() != 0: |
| raise Exception('Command failed: %s' % ' '.join(cmd)) |
| return proc.communicate()[0] |
| |
| |
| def adb_shell(cmd): |
| """Run the given ADB shell command and emulate the exit code.""" |
| output = check_output(['adb', 'shell', cmd + '; echo $?']).strip() |
| lines = output.splitlines() |
| if lines[-1] != '0': |
| raise Exception('ADB command failed: %s\n\nOutput:\n%s' % (cmd, output)) |
| return '\n'.join(lines[:-1]) |
| |
| |
| def remote_file_exists(filename): |
| """Return True if the given file exists on the device and False otherwise.""" |
| try: |
| adb_shell('test -f %s' % filename) |
| return True |
| except Exception: |
| return False |
| |
| |
| def capture_skp(skp_file, package, device): |
| """Capture an SKP.""" |
| remote_path = '/data/data/%s/cache/%s' % (package, os.path.basename(skp_file)) |
| try: |
| adb_shell('rm %s' % remote_path) |
| except Exception: |
| if remote_file_exists(remote_path): |
| raise |
| |
| adb_shell('setprop debug.hwui.capture_frame_as_skp %s' % remote_path) |
| try: |
| # Spin, wait for the SKP to be written. |
| timeout = 10 # Seconds |
| start = time.time() |
| device.drag((300, 300), (300, 350), 1, 10) # Arbitrary action to force a draw. |
| while not remote_file_exists(remote_path): |
| if time.time() - start > timeout: |
| raise Exception('Timed out waiting for SKP capture.') |
| time.sleep(1) |
| |
| # Pull the SKP from the device. |
| cmd = ['adb', 'pull', remote_path, skp_file] |
| check_output(cmd) |
| |
| finally: |
| adb_shell('setprop debug.hwui.capture_frame_as_skp ""') |
| |
| |
| def load_app(filename): |
| """Load the JSON file describing an app and return an App instance.""" |
| with open(filename) as f: |
| app_dict = ast.literal_eval(f.read()) |
| return App(app_dict['name'], |
| app_dict['package'], |
| app_dict['activity'], |
| app_dict['app_launch_delay'], |
| app_dict['actions']) |
| |
| |
| def main(): |
| """Capture SKPs for all apps.""" |
| device = MonkeyRunner.waitForConnection() |
| |
| # TODO(borenet): Kill all apps. |
| device.wake() |
| device.drag((600, 600), (10, 10), 0.2, 10) |
| |
| apps_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'apps') |
| app_files = [os.path.join(apps_dir, app) for app in os.listdir(apps_dir)] |
| |
| for app_file in app_files: |
| app = load_app(app_file) |
| print(app.name) |
| print(' Package %s' % app.package) |
| app.launch(device) |
| print(' Launched activity %s' % app.activity) |
| |
| for action in app.actions: |
| print(' %s' % action.__class__.__name__) |
| action.run(device) |
| |
| time.sleep(WAIT_FOR_SKP_CAPTURE) |
| print(' Capturing SKP.') |
| skp_file = '%s.skp' % app.name |
| capture_skp(skp_file, app.package, device) |
| print(' Wrote SKP to %s' % skp_file) |
| print |
| app.kill() |
| |
| |
| if __name__ == '__main__': |
| main() |