diff options
author | Bill Napier <napier@google.com> | 2010-10-18 00:00:20 -0700 |
---|---|---|
committer | Bill Napier <napier@google.com> | 2010-10-18 13:54:38 -0700 |
commit | 6db57208c8fb964bba0bc6da098e8aac94ea6b93 (patch) | |
tree | 0e61f339dda319b881e2adccf24945c247a29777 /monkeyrunner/src | |
parent | 7564d1720b5505dde7a9a9a6ec000757a8e42cbf (diff) | |
download | sdk-6db57208c8fb964bba0bc6da098e8aac94ea6b93.zip sdk-6db57208c8fb964bba0bc6da098e8aac94ea6b93.tar.gz sdk-6db57208c8fb964bba0bc6da098e8aac94ea6b93.tar.bz2 |
Initial cut at MonkeyRecorder.
MonkeyRecorder (and MonkeyPlayback) are a set of tools for using MonkeyRunner to record and playback actions. The current implementation is not very sophisticated, but it works.
Please don't review yet. Needs a lot of style cleanup.
Change-Id: Id300a27294b5dc13a842fade900e8b9916b8a17b
Diffstat (limited to 'monkeyrunner/src')
14 files changed, 1037 insertions, 38 deletions
diff --git a/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java b/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java index 8d25dd9..864441e 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java +++ b/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java @@ -38,6 +38,7 @@ import org.python.core.PyInteger; import org.python.core.PyList; import org.python.core.PyNone; import org.python.core.PyObject; +import org.python.core.PyReflectedField; import org.python.core.PyReflectedFunction; import org.python.core.PyString; import org.python.core.PyStringMap; @@ -302,9 +303,26 @@ public final class JythonUtils { } } + // Also look at all the fields (both static and instance). + for (Field f : clz.getFields()) { + if (f.isAnnotationPresent(MonkeyRunnerExported.class)) { + String fieldName = f.getName(); + PyObject pyField = dict.__finditem__(fieldName); + if (pyField != null && pyField instanceof PyReflectedField) { + PyReflectedField realPyfield = (PyReflectedField) pyField; + MonkeyRunnerExported doc = f.getAnnotation(MonkeyRunnerExported.class); + + // TODO: figure out how to set field documentation. __doc__ is Read Only + // in this context. + // realPyfield.__setattr__("__doc__", new PyString(buildDoc(doc))); + functions.remove(fieldName); + } + } + } + // Now remove any elements left from the functions collection for (String name : functions) { - dict.__delitem__(name); + dict.__delitem__(name); } } diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java b/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java index 2d120f5..0f4362a 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java +++ b/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java @@ -55,12 +55,11 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { @MonkeyRunnerExported(doc = "Sends a DOWN event, immediately followed by an UP event when used with touch() or press()") public static final String DOWN_AND_UP = "downAndUp"; - // Visible to subclasses - protected enum TouchPressType { + public enum TouchPressType { DOWN, UP, DOWN_AND_UP, } - private static final Map<String, TouchPressType> TOUCH_NAME_TO_ENUM = + public static final Map<String, TouchPressType> TOUCH_NAME_TO_ENUM = ImmutableMap.of(MonkeyDevice.DOWN, TouchPressType.DOWN, MonkeyDevice.UP, TouchPressType.UP, MonkeyDevice.DOWN_AND_UP, TouchPressType.DOWN_AND_UP); @@ -126,7 +125,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { // Default String type = MonkeyDevice.DOWN_AND_UP; try { - String tmpType = ap.getString(2); + String tmpType = ap.getString(1); if (VALID_DOWN_UP_TYPES.contains(tmpType)) { type = tmpType; } else { @@ -189,7 +188,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { // Default String type = MonkeyDevice.DOWN_AND_UP; try { - String tmpType = ap.getString(2); + String tmpType = ap.getString(1); if (VALID_DOWN_UP_TYPES.contains(tmpType)) { type = tmpType; } else { @@ -372,26 +371,26 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { * * @param into which bootloader to boot into. Null means default reboot. */ - protected abstract void reboot(@Nullable String into); - - protected abstract String getProperty(String key); - protected abstract String getSystemProperty(String key); - protected abstract void touch(int x, int y, TouchPressType type); - protected abstract void press(String keyName, TouchPressType type); - protected abstract void drag(int startx, int starty, int endx, int endy, int steps, long ms); - protected abstract void type(String string); - protected abstract String shell(String cmd); - protected abstract boolean installPackage(String path); - protected abstract boolean removePackage(String packageName); - protected abstract void startActivity(@Nullable String uri, @Nullable String action, + public abstract void reboot(@Nullable String into); + + public abstract String getProperty(String key); + public abstract String getSystemProperty(String key); + public abstract void touch(int x, int y, TouchPressType type); + public abstract void press(String keyName, TouchPressType type); + public abstract void drag(int startx, int starty, int endx, int endy, int steps, long ms); + public abstract void type(String string); + public abstract String shell(String cmd); + public abstract boolean installPackage(String path); + public abstract boolean removePackage(String packageName); + public abstract void startActivity(@Nullable String uri, @Nullable String action, @Nullable String data, @Nullable String mimetype, Collection<String> categories, Map<String, Object> extras, @Nullable String component, int flags); - protected abstract void broadcastIntent(@Nullable String uri, @Nullable String action, + public abstract void broadcastIntent(@Nullable String uri, @Nullable String action, @Nullable String data, @Nullable String mimetype, Collection<String> categories, Map<String, Object> extras, @Nullable String component, int flags); - protected abstract Map<String, Object> instrument(String packageName, + public abstract Map<String, Object> instrument(String packageName, Map<String, Object> args); - protected abstract void wake(); + public abstract void wake(); } diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java b/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java index 648843d..8480223 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java +++ b/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java @@ -181,7 +181,7 @@ public class MonkeyRunner extends PyObject implements ClassDictInit { * @param title the title of the dialog box. * @param okTitle the title of the button. */ - private static void alert(String message, String title, String okTitle) { + public static void alert(String message, String title, String okTitle) { Object[] options = { okTitle }; JOptionPane.showOptionDialog(null, message, title, JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, options, options[0]); @@ -195,7 +195,7 @@ public class MonkeyRunner extends PyObject implements ClassDictInit { * @param choices the list of the choices to display. * @return the index of the selected choice, or -1 if nothing was chosen. */ - private static int choice(String message, String title, Collection<String> choices) { + public static int choice(String message, String title, Collection<String> choices) { Object[] possibleValues = choices.toArray(); Object selectedValue = JOptionPane.showInputDialog(null, message, title, JOptionPane.QUESTION_MESSAGE, null, possibleValues, possibleValues[0]); @@ -217,7 +217,7 @@ public class MonkeyRunner extends PyObject implements ClassDictInit { * @param title the title of the dialog box. * @return the entered string, or null if cancelled */ - private static String input(String message, String initialValue, String title) { + public static String input(String message, String initialValue, String title) { return (String) JOptionPane.showInputDialog(null, message, title, JOptionPane.QUESTION_MESSAGE, null, null, initialValue); } diff --git a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java index b180ccd..7130019 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java +++ b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java @@ -199,12 +199,12 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected String getSystemProperty(String key) { + public String getSystemProperty(String key) { return device.getProperty(key); } @Override - protected String getProperty(String key) { + public String getProperty(String key) { try { return manager.getVariable(key); } catch (IOException e) { @@ -214,7 +214,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected void wake() { + public void wake() { try { manager.wake(); } catch (IOException e) { @@ -231,7 +231,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected String shell(String cmd) { + public String shell(String cmd) { CommandOutputCapture capture = new CommandOutputCapture(); try { device.executeShellCommand(cmd, capture); @@ -252,7 +252,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected boolean installPackage(String path) { + public boolean installPackage(String path) { try { String result = device.installPackage(path, true); if (result != null) { @@ -267,7 +267,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected boolean removePackage(String packageName) { + public boolean removePackage(String packageName) { try { String result = device.uninstallPackage(packageName); if (result != null) { @@ -283,7 +283,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected void press(String keyName, TouchPressType type) { + public void press(String keyName, TouchPressType type) { try { switch (type) { case DOWN_AND_UP: @@ -302,7 +302,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected void type(String string) { + public void type(String string) { try { manager.type(string); } catch (IOException e) { @@ -311,7 +311,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected void touch(int x, int y, TouchPressType type) { + public void touch(int x, int y, TouchPressType type) { try { switch (type) { case DOWN: @@ -330,7 +330,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected void reboot(String into) { + public void reboot(String into) { try { device.reboot(into); } catch (TimeoutException e) { @@ -343,7 +343,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected void startActivity(String uri, String action, String data, String mimetype, + public void startActivity(String uri, String action, String data, String mimetype, Collection<String> categories, Map<String, Object> extras, String component, int flags) { List<String> intentArgs = buildIntentArgString(uri, action, data, mimetype, categories, @@ -353,7 +353,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected void broadcastIntent(String uri, String action, String data, String mimetype, + public void broadcastIntent(String uri, String action, String data, String mimetype, Collection<String> categories, Map<String, Object> extras, String component, int flags) { List<String> intentArgs = buildIntentArgString(uri, action, data, mimetype, categories, @@ -442,7 +442,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected Map<String, Object> instrument(String packageName, Map<String, Object> args) { + public Map<String, Object> instrument(String packageName, Map<String, Object> args) { List<String> shellCmd = Lists.newArrayList("am", "instrument", "-w", "-r", packageName); String result = shell(shellCmd.toArray(ZERO_LENGTH_STRING_ARRAY)); return convertInstrumentResult(result); @@ -490,7 +490,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - protected void drag(int startx, int starty, int endx, int endy, int steps, long ms) { + public void drag(int startx, int starty, int endx, int endy, int steps, long ms) { final long iterationTime = ms / steps; LinearInterpolator lerp = new LinearInterpolator(steps); diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/ActionListModel.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/ActionListModel.java new file mode 100644 index 0000000..5e0b7e7 --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/ActionListModel.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.monkeyrunner.recorder; + +import com.google.common.collect.Lists; + +import com.android.monkeyrunner.recorder.actions.Action; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.List; + +import javax.swing.AbstractListModel; + +/** + * List model for managing actions. + */ +public class ActionListModel extends AbstractListModel { + private List<Action> actionList = Lists.newArrayList(); + + /** + * Add the specified action to the end of the list + * @param a the action to add. + */ + public void add(Action a) { + actionList.add(a); + int newIndex = actionList.size() - 1; + this.fireIntervalAdded(this, newIndex, newIndex); + } + + @Override + public Object getElementAt(int arg0) { + return actionList.get(arg0).getDisplayName(); + } + + + @Override + public int getSize() { + return actionList.size(); + } + + /** + * Serialize all the stored actions to the specified file. + * + * @param selectedFile the file to write to + * @throws FileNotFoundException if the file can't be created. + */ + public void export(File selectedFile) throws FileNotFoundException { + PrintWriter out = new PrintWriter(selectedFile); + for (Action a : actionList) { + out.println(a.serialize()); + } + out.close(); + } +}
\ No newline at end of file diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java new file mode 100644 index 0000000..c1a8f7f --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.monkeyrunner.recorder; + +import com.android.monkeyrunner.MonkeyDevice; +import com.android.monkeyrunner.adb.AdbBackend; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.WindowConstants; + +/** + * Helper entry point for MonkeyRecorder. + */ +public class MonkeyRecorder { + private static final Logger LOG = Logger.getLogger(MonkeyRecorder.class.getName()); + // This lock is used to keep the python process blocked while the frame is runing. + private static final Object LOCK = new Object(); + + /** + * Jython entry point for MonkeyRecorder. Meant to be called like this: + * + * <code> + * from com.android.monkeyrunner import MonkeyRunner as mr + * from com.android.monkeyrunner import MonkeyRecorder + * MonkeyRecorder.start(mr.waitForConnection()) + * </code> + * + * @param device + */ + public static void start(final MonkeyDevice device) { + MonkeyRecorderFrame frame = new MonkeyRecorderFrame(device); + // TODO: this is a hack until the window listener works. + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + device.dispose(); + synchronized (LOCK) { + LOCK.notifyAll(); + } + } + }); + + frame.setVisible(true); + synchronized (LOCK) { + try { + LOCK.wait(); + } catch (InterruptedException e) { + LOG.log(Level.SEVERE, "Unexpected Exception", e); + } + } + } + + public static void main(String[] args) { + AdbBackend adb = new AdbBackend(); + MonkeyRecorder.start(adb.waitForConnection()); + } +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java new file mode 100644 index 0000000..b6c1f78 --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.monkeyrunner.recorder; + +import com.android.monkeyrunner.MonkeyDevice; +import com.android.monkeyrunner.MonkeyImage; +import com.android.monkeyrunner.recorder.actions.Action; +import com.android.monkeyrunner.recorder.actions.DragAction; +import com.android.monkeyrunner.recorder.actions.DragAction.Direction; +import com.android.monkeyrunner.recorder.actions.PressAction; +import com.android.monkeyrunner.recorder.actions.TouchAction; +import com.android.monkeyrunner.recorder.actions.TypeAction; +import com.android.monkeyrunner.recorder.actions.WaitAction; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.io.FileNotFoundException; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.Timer; + +/** + * MainFrame for MonkeyRecorder. + */ +public class MonkeyRecorderFrame extends JFrame { + private static final Logger LOG = + Logger.getLogger(MonkeyRecorderFrame.class.getName()); + + private final MonkeyDevice device; + + private static final long serialVersionUID = 1L; + private JPanel jContentPane = null; + private JLabel display = null; + private JScrollPane historyPanel = null; + private JPanel actionPanel = null; + private JButton waitButton = null; + private JButton pressButton = null; + private JButton typeButton = null; + private JButton flingButton = null; + private JButton exportActionButton = null; + + private JButton refreshButton = null; + + private BufferedImage currentImage; // @jve:decl-index=0: + private BufferedImage scaledImage = new BufferedImage(320, 480, + BufferedImage.TYPE_INT_ARGB); // @jve:decl-index=0: + + private JList historyList; + private ActionListModel actionListModel; + + private final Timer refreshTimer = new Timer(1000, new ActionListener() { + public void actionPerformed(ActionEvent e) { + refreshDisplay(); // @jve:decl-index=0: + } + }); + + /** + * This is the default constructor + */ + public MonkeyRecorderFrame(MonkeyDevice device) { + this.device = device; + initialize(); + } + + private void initialize() { + this.setSize(400, 600); + this.setContentPane(getJContentPane()); + this.setTitle("MonkeyRecorder"); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + refreshDisplay(); + }}); + refreshTimer.start(); + } + + private void refreshDisplay() { + MonkeyImage snapshot = device.takeSnapshot(); + currentImage = snapshot.createBufferedImage(); + + Graphics2D g = scaledImage.createGraphics(); + g.drawImage(currentImage, 0, 0, + scaledImage.getWidth(), scaledImage.getHeight(), + null); + g.dispose(); + + display.setIcon(new ImageIcon(scaledImage)); + + pack(); + } + + /** + * This method initializes jContentPane + * + * @return javax.swing.JPanel + */ + private JPanel getJContentPane() { + if (jContentPane == null) { + display = new JLabel(); + jContentPane = new JPanel(); + jContentPane.setLayout(new BorderLayout()); + jContentPane.add(display, BorderLayout.CENTER); + jContentPane.add(getHistoryPanel(), BorderLayout.EAST); + jContentPane.add(getActionPanel(), BorderLayout.NORTH); + + display.setPreferredSize(new Dimension(320, 480)); + + display.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + touch(event); + } + }); + } + return jContentPane; + } + + /** + * This method initializes historyPanel + * + * @return javax.swing.JScrollPane + */ + private JScrollPane getHistoryPanel() { + if (historyPanel == null) { + historyPanel = new JScrollPane(); + historyPanel.getViewport().setView(getHistoryList()); + } + return historyPanel; + } + + private JList getHistoryList() { + if (historyList == null) { + actionListModel = new ActionListModel(); + historyList = new JList(actionListModel); + } + return historyList; + } + + /** + * This method initializes actionPanel + * + * @return javax.swing.JPanel + */ + private JPanel getActionPanel() { + if (actionPanel == null) { + actionPanel = new JPanel(); + actionPanel.setLayout(new BoxLayout(getActionPanel(), BoxLayout.X_AXIS)); + actionPanel.add(getWaitButton(), null); + actionPanel.add(getPressButton(), null); + actionPanel.add(getTypeButton(), null); + actionPanel.add(getFlingButton(), null); + actionPanel.add(getExportActionButton(), null); + actionPanel.add(getRefreshButton(), null); + } + return actionPanel; + } + + /** + * This method initializes waitButton + * + * @return javax.swing.JButton + */ + private JButton getWaitButton() { + if (waitButton == null) { + waitButton = new JButton(); + waitButton.setText("Wait"); + waitButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + String howLongStr = JOptionPane.showInputDialog("How many seconds to wait?"); + if (howLongStr != null) { + float howLong = Float.parseFloat(howLongStr); + addAction(new WaitAction(howLong)); + } + } + }); + } + return waitButton; + } + + /** + * This method initializes pressButton + * + * @return javax.swing.JButton + */ + private JButton getPressButton() { + if (pressButton == null) { + pressButton = new JButton(); + pressButton.setText("Press a Button"); + pressButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + JPanel panel = new JPanel(); + JLabel text = new JLabel("What button to press?"); + JComboBox keys = new JComboBox(PressAction.KEYS); + keys.setEditable(true); + JComboBox direction = new JComboBox(PressAction.DOWNUP_FLAG_MAP.values().toArray()); + panel.add(text); + panel.add(keys); + panel.add(direction); + + int result = JOptionPane.showConfirmDialog(null, panel, "Input", JOptionPane.OK_CANCEL_OPTION); + if (result == JOptionPane.OK_OPTION) { + // Look up the "flag" value for the press choice + Map<String, String> lookupMap = PressAction.DOWNUP_FLAG_MAP.inverse(); + String flag = lookupMap.get(direction.getSelectedItem()); + addAction(new PressAction((String) keys.getSelectedItem(), flag)); + } + } + }); + } + return pressButton; + } + + /** + * This method initializes typeButton + * + * @return javax.swing.JButton + */ + private JButton getTypeButton() { + if (typeButton == null) { + typeButton = new JButton(); + typeButton.setText("Type Something"); + typeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + String whatToType = JOptionPane.showInputDialog("What to type?"); + if (whatToType != null) { + addAction(new TypeAction(whatToType)); + } + } + }); + } + return typeButton; + } + + /** + * This method initializes flingButton + * + * @return javax.swing.JButton + */ + private JButton getFlingButton() { + if (flingButton == null) { + flingButton = new JButton(); + flingButton.setText("Fling"); + flingButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + panel.add(new JLabel("Which Direction to fling?")); + JComboBox directionChooser = new JComboBox(DragAction.Direction.getNames()); + panel.add(directionChooser); + panel.add(new JLabel("How long to drag (in ms)?")); + JTextField ms = new JTextField(); + ms.setText("1000"); + panel.add(ms); + panel.add(new JLabel("How many steps to do it in?")); + JTextField steps = new JTextField(); + steps.setText("10"); + panel.add(steps); + + + + int result = JOptionPane.showConfirmDialog(null, panel, "Input", JOptionPane.OK_CANCEL_OPTION); + if (result == JOptionPane.OK_OPTION) { + DragAction.Direction dir = + DragAction.Direction.valueOf((String) directionChooser.getSelectedItem()); + long millis = Long.parseLong(ms.getText()); + int numSteps = Integer.parseInt(steps.getText()); + + addAction(newFlingAction(dir, numSteps, millis)); + } + } + }); + } + return flingButton; + } + + private DragAction newFlingAction(Direction dir, int numSteps, long millis) { + int width = Integer.parseInt(device.getProperty("display.width")); + int height = Integer.parseInt(device.getProperty("display.height")); + + // Adjust the w/h to a pct of the total size, so we don't hit things on the "outside" + width = (int) (width * 0.8f); + height = (int) (height * 0.8f); + int minW = (int) (width * 0.2f); + int minH = (int) (height * 0.2f); + + int midWidth = width / 2; + int midHeight = height / 2; + + int startx = minW; + int starty = minH; + int endx = minW; + int endy = minH; + + switch (dir) { + case NORTH: + startx = endx = midWidth; + starty = height; + break; + case SOUTH: + startx = endx = midWidth; + endy = height; + break; + case EAST: + starty = endy = midHeight; + endx = width; + break; + case WEST: + starty = endy = midHeight; + startx = width; + break; + } + + return new DragAction(dir, startx, starty, endx, endy, numSteps, millis); + } + + /** + * This method initializes exportActionButton + * + * @return javax.swing.JButton + */ + private JButton getExportActionButton() { + if (exportActionButton == null) { + exportActionButton = new JButton(); + exportActionButton.setText("Export Actions"); + exportActionButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent ev) { + JFileChooser fc = new JFileChooser(); + if (fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { + try { + actionListModel.export(fc.getSelectedFile()); + } catch (FileNotFoundException e) { + LOG.log(Level.SEVERE, "Unable to save file", e); + } + } + } + }); + } + return exportActionButton; + } + + /** + * This method initializes refreshButton + * + * @return javax.swing.JButton + */ + private JButton getRefreshButton() { + if (refreshButton == null) { + refreshButton = new JButton(); + refreshButton.setText("Refresh Display"); + refreshButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent e) { + refreshDisplay(); + } + }); + } + return refreshButton; + } + + private void touch(MouseEvent event) { + int x = event.getX(); + int y = event.getY(); + + // Since we scaled the image down, our x/y are scaled as well. + double scalex = ((double) currentImage.getWidth()) / ((double) scaledImage.getWidth()); + double scaley = ((double) currentImage.getHeight()) / ((double) scaledImage.getHeight()); + + x = (int) (x * scalex); + y = (int) (y * scaley); + + switch (event.getID()) { + case MouseEvent.MOUSE_CLICKED: + addAction(new TouchAction(x, y, MonkeyDevice.DOWN_AND_UP)); + break; + case MouseEvent.MOUSE_PRESSED: + addAction(new TouchAction(x, y, MonkeyDevice.DOWN)); + break; + case MouseEvent.MOUSE_RELEASED: + addAction(new TouchAction(x, y, MonkeyDevice.UP)); + break; + } + } + + public void addAction(Action a) { + actionListModel.add(a); + try { + a.execute(device); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Unable to execute action!", e); + } + } +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java new file mode 100644 index 0000000..d582aa4 --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.monkeyrunner.recorder.actions; + +import com.android.monkeyrunner.MonkeyDevice; + +/** + * All actions that can be recorded must implement this interface. + */ +public interface Action { + /** + * Serialize this action into a string. This method is called to put the list of actions into + * a file. + * + * @return the serialized string + */ + String serialize(); + + /** + * Get the printable name for this action. This method is used to show the Action in the UI. + * + * @return the display name + */ + String getDisplayName(); + + /** + * Execute the given action. + * + * @param device the device to execute the action on. + */ + void execute(MonkeyDevice device) throws Exception; +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java new file mode 100644 index 0000000..082bfe4 --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.monkeyrunner.recorder.actions; + +import com.android.monkeyrunner.MonkeyDevice; + +/** + * Action to drag the "finger" across the device. + */ +public class DragAction implements Action { + private final long timeMs; + private final int steps; + private final int startx; + private final int starty; + private final int endx; + private final int endy; + private final Direction dir; + + public enum Direction { + NORTH, SOUTH, EAST, WEST; + + private static String[] names; + static { + Direction[] values = Direction.values(); + names = new String[values.length]; + for (int x = 0; x < values.length; x++) { + names[x] = values[x].name(); + } + } + + public static String[] getNames() { + return names; + } + } + + public DragAction(Direction dir, + int startx, int starty, int endx, int endy, + int numSteps, long millis) { + this.dir = dir; + this.startx = startx; + this.starty = starty; + this.endx = endx; + this.endy = endy; + steps = numSteps; + timeMs = millis; + } + + @Override + public String getDisplayName() { + return String.format("Fling %s", dir.name().toLowerCase()); + } + + @Override + public String serialize() { + float duration = timeMs / 1000.0f; + + String pydict = PyDictUtilBuilder.newBuilder(). + addTuple("start", startx, starty). + addTuple("end", endx, endy). + add("duration", duration). + add("steps", steps). + build(); + return "DRAG|" + pydict; + } + + @Override + public void execute(MonkeyDevice device) { + device.drag(startx, starty, endx, endy, steps, timeMs); + } +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java new file mode 100644 index 0000000..a0d9e0e --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.monkeyrunner.recorder.actions; + +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; + +import com.android.monkeyrunner.MonkeyDevice; + +/** + * Action to press a certain button. + */ +public class PressAction implements Action { + public static String[] KEYS = { + "MENU", "HOME", "SEARCH", + }; + + public static final BiMap<String, String> DOWNUP_FLAG_MAP = + ImmutableBiMap.of(MonkeyDevice.DOWN_AND_UP, "Press", + MonkeyDevice.DOWN, "Down", + MonkeyDevice.UP, "Up"); + + private final String key; + private final String downUpFlag; + + public PressAction(String key, String downUpFlag) { + this.key = key; + this.downUpFlag = downUpFlag; + } + + public PressAction(String key) { + this(key, MonkeyDevice.DOWN_AND_UP); + } + + @Override + public String getDisplayName() { + return String.format("%s button %s", + DOWNUP_FLAG_MAP.get(downUpFlag), key); + } + + @Override + public String serialize() { + String pydict = PyDictUtilBuilder.newBuilder(). + add("name", key). + add("type", downUpFlag).build(); + return "PRESS|" + pydict; + } + + @Override + public void execute(MonkeyDevice device) { + device.press(key, + MonkeyDevice.TOUCH_NAME_TO_ENUM.get(downUpFlag)); + } +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PyDictUtilBuilder.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PyDictUtilBuilder.java new file mode 100644 index 0000000..0cfbabe --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PyDictUtilBuilder.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.monkeyrunner.recorder.actions; + +/** + * Utility class to create Python Dictionary Strings. + * + * {'key': 'value'} + */ +public class PyDictUtilBuilder { + private StringBuilder sb = new StringBuilder(); + + public PyDictUtilBuilder() { + sb.append("{"); + } + + public static PyDictUtilBuilder newBuilder() { + return new PyDictUtilBuilder(); + } + + private void addHelper(String key, String value) { + sb.append("'").append(key).append("'"); + sb.append(":").append(value).append(","); + } + + public PyDictUtilBuilder add(String key, int value) { + addHelper(key, Integer.toString(value)); + return this; + } + + public PyDictUtilBuilder add(String key, float value) { + addHelper(key, Float.toString(value)); + return this; + } + + public PyDictUtilBuilder add(String key, String value) { + addHelper(key, "'" + value + "'"); + return this; + } + + public String build() { + sb.append("}"); + return sb.toString(); + } + + public PyDictUtilBuilder addTuple(String key, int x, int y) { + String valuestr = new StringBuilder().append("(").append(x).append(",").append(y).append(")").toString(); + addHelper(key, valuestr); + return this; + } +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java new file mode 100644 index 0000000..4633edb --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.monkeyrunner.recorder.actions; + +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; + +import com.android.monkeyrunner.MonkeyDevice; + +/** + * Action to touch the touchscreen at a certain location. + */ +public class TouchAction implements Action { + public static final BiMap<String, String> DOWNUP_FLAG_MAP = + ImmutableBiMap.of(MonkeyDevice.DOWN_AND_UP, "Tap", + MonkeyDevice.DOWN, "Down", + MonkeyDevice.UP, "Up"); + + private final int x; + private final int y; + private final String direction; + + public TouchAction(int x, int y, String direction) { + this.x = x; + this.y = y; + this.direction = direction; + } + + @Override + public String getDisplayName() { + return String.format("%s touchscreen at (%d, %d)", + DOWNUP_FLAG_MAP.get(direction), x, y); + } + + @Override + public void execute(MonkeyDevice device) throws Exception { + device.touch(x, y, + MonkeyDevice.TOUCH_NAME_TO_ENUM.get(direction)); + } + + @Override + public String serialize() { + String pydict = PyDictUtilBuilder.newBuilder(). + add("x", x). + add("y", y). + add("type", direction).build(); + return "TOUCH|" + pydict; + } +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java new file mode 100644 index 0000000..1bfb9e9 --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.monkeyrunner.recorder.actions; + +import com.android.monkeyrunner.MonkeyDevice; + +/** + * Action to type in a string on the device. + */ +public class TypeAction implements Action { + private final String whatToType; + + public TypeAction(String whatToType) { + this.whatToType = whatToType; + } + + @Override + public String getDisplayName() { + return String.format("Type \"%s\"", whatToType); + } + + @Override + public String serialize() { + String pydict = PyDictUtilBuilder.newBuilder().add("message", whatToType).build(); + return "TYPE|" + pydict; + } + + @Override + public void execute(MonkeyDevice device) { + device.type(whatToType); + } +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java new file mode 100644 index 0000000..9115f9a --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.monkeyrunner.recorder.actions; + +import com.android.monkeyrunner.MonkeyDevice; + +/** + * Action that specifies to wait for a certain amount of time. + */ +public class WaitAction implements Action { + private final float howLongSeconds; + + public WaitAction(float howLongSeconds) { + this.howLongSeconds = howLongSeconds; + } + + @Override + public String getDisplayName() { + return String.format("Wait for %g seconds", this.howLongSeconds); + } + + @Override + public String serialize() { + String pydict = PyDictUtilBuilder.newBuilder().add("seconds", howLongSeconds).build(); + return "WAIT|" + pydict; + } + + @Override + public void execute(MonkeyDevice device) throws Exception { + long ms = (long) (1000.0f * howLongSeconds); + Thread.sleep(ms); + } +} |