• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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