Chromium Code Reviews| Index: platform_tools/android/skp_gen/android_skp_capture.py |
| diff --git a/platform_tools/android/skp_gen/android_skp_capture.py b/platform_tools/android/skp_gen/android_skp_capture.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..5b1a33ac1a50c22c1fe559db09dcee581d09e550 |
| --- /dev/null |
| +++ b/platform_tools/android/skp_gen/android_skp_capture.py |
| @@ -0,0 +1,169 @@ |
| +#!/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 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 |
| + |
| + |
| +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']), |
|
rmistry
2015/12/02 20:49:18
What does it return if you do not specify tuple he
borenet
2015/12/03 18:14:48
A list. I'm not sure if monkeyrunner really cares
|
| + 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, actions): |
| + self.name = name |
| + self.package = package |
| + self.activity = activity |
| + self.run_component = '/'.join((self.package, self.activity)) |
|
rmistry
2015/12/02 20:49:18
[optional] I believe '%s/%s' % (self.package, self
borenet
2015/12/03 18:14:48
Done.
|
| + 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(3) |
|
rmistry
2015/12/02 20:49:18
Make time to wait a parameter in the constructor.
borenet
2015/12/03 18:14:48
Done.
|
| + |
| + 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() |
| + split = output.splitlines() |
|
rmistry
2015/12/02 20:49:18
Call these lines or tokens or output_tokens instea
borenet
2015/12/03 18:14:48
Done.
|
| + if split[-1] != '0': |
| + raise Exception('ADB command failed: %s\n\nOutput:\n%s' % (cmd, output)) |
| + return '\n'.join(split[:-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(filename, package, device): |
|
rmistry
2015/12/02 20:49:18
call this skp_file instead? that is the argument y
borenet
2015/12/03 18:14:48
Done.
|
| + """Capture an SKP.""" |
| + remote_path = '/data/data/%s/cache/%s' % (package, os.path.basename(filename)) |
| + if remote_file_exists(remote_path): |
| + adb_shell('rm %s' % remote_path) |
|
rmistry
2015/12/02 20:49:18
I am assuming that trying to remove a file that do
borenet
2015/12/03 18:14:47
Yeah, so the problem with that is I'd want to actu
rmistry
2015/12/04 13:47:51
You would be saving an exists call in a few places
borenet
2015/12/04 14:41:44
Done.
|
| + |
| + 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) # Dummy 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, filename] |
| + 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['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(1) |
|
rmistry
2015/12/02 20:49:18
Could also either put this in the JSON or make it
borenet
2015/12/03 18:14:48
Done.
|
| + print ' Capturing SKP.' |
| + skp_file = '%s.skp' % app.name |
| + capture_skp(skp_file, app.package, device) |
| + print ' Wrote SKP to %s' % skp_file |
| + print '' |
|
rmistry
2015/12/02 20:49:18
[optional] print with no arguments should also wor
borenet
2015/12/03 18:14:48
Done.
|
| + app.kill() |
| + |
| + |
| +if __name__ == '__main__': |
| + main() |