Home | History | Annotate | Download | only in skp_gen
      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 
      9 from __future__ import with_statement
     10 
     11 # Imports the monkeyrunner modules used by this program
     12 from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice
     13 
     14 import ast
     15 import os
     16 import subprocess
     17 import time
     18 
     19 
     20 # Time to wait between performing UI actions and capturing the SKP.
     21 WAIT_FOR_SKP_CAPTURE = 1
     22 
     23 
     24 class 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 
     37 class 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 
     48 def 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 
     61 class 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 
     81 def 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 
     89 def 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 
     98 def 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 
    107 def 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 
    135 def 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 
    146 def 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 
    177 if __name__ == '__main__':
    178   main()
    179