1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.monkeyrunner.recorder; 17 18 import com.android.monkeyrunner.MonkeyDevice; 19 import com.android.chimpchat.core.IChimpImage; 20 import com.android.chimpchat.core.IChimpDevice; 21 import com.android.monkeyrunner.recorder.actions.Action; 22 import com.android.monkeyrunner.recorder.actions.DragAction; 23 import com.android.monkeyrunner.recorder.actions.DragAction.Direction; 24 import com.android.monkeyrunner.recorder.actions.PressAction; 25 import com.android.monkeyrunner.recorder.actions.TouchAction; 26 import com.android.monkeyrunner.recorder.actions.TypeAction; 27 import com.android.monkeyrunner.recorder.actions.WaitAction; 28 29 import java.awt.BorderLayout; 30 import java.awt.Dimension; 31 import java.awt.Graphics2D; 32 import java.awt.event.ActionEvent; 33 import java.awt.event.ActionListener; 34 import java.awt.event.MouseAdapter; 35 import java.awt.event.MouseEvent; 36 import java.awt.image.BufferedImage; 37 import java.io.FileNotFoundException; 38 import java.util.Map; 39 import java.util.logging.Level; 40 import java.util.logging.Logger; 41 42 import javax.swing.BoxLayout; 43 import javax.swing.ImageIcon; 44 import javax.swing.JButton; 45 import javax.swing.JComboBox; 46 import javax.swing.JFileChooser; 47 import javax.swing.JFrame; 48 import javax.swing.JLabel; 49 import javax.swing.JList; 50 import javax.swing.JOptionPane; 51 import javax.swing.JPanel; 52 import javax.swing.JScrollPane; 53 import javax.swing.JTextField; 54 import javax.swing.SwingUtilities; 55 import javax.swing.Timer; 56 57 /** 58 * MainFrame for MonkeyRecorder. 59 */ 60 public class MonkeyRecorderFrame extends JFrame { 61 private static final Logger LOG = 62 Logger.getLogger(MonkeyRecorderFrame.class.getName()); 63 64 private final IChimpDevice device; 65 66 private static final long serialVersionUID = 1L; 67 private JPanel jContentPane = null; 68 private JLabel display = null; 69 private JScrollPane historyPanel = null; 70 private JPanel actionPanel = null; 71 private JButton waitButton = null; 72 private JButton pressButton = null; 73 private JButton typeButton = null; 74 private JButton flingButton = null; 75 private JButton exportActionButton = null; 76 77 private JButton refreshButton = null; 78 79 private BufferedImage currentImage; // @jve:decl-index=0: 80 private BufferedImage scaledImage = new BufferedImage(320, 480, 81 BufferedImage.TYPE_INT_ARGB); // @jve:decl-index=0: 82 83 private JList historyList; 84 private ActionListModel actionListModel; 85 86 private final Timer refreshTimer = new Timer(1000, new ActionListener() { 87 @Override 88 public void actionPerformed(ActionEvent e) { 89 refreshDisplay(); // @jve:decl-index=0: 90 } 91 }); 92 93 /** 94 * This is the default constructor 95 */ 96 public MonkeyRecorderFrame(IChimpDevice device) { 97 this.device = device; 98 initialize(); 99 } 100 101 private void initialize() { 102 this.setSize(400, 600); 103 this.setContentPane(getJContentPane()); 104 this.setTitle("MonkeyRecorder"); 105 106 SwingUtilities.invokeLater(new Runnable() { 107 @Override 108 public void run() { 109 refreshDisplay(); 110 }}); 111 refreshTimer.start(); 112 } 113 114 private void refreshDisplay() { 115 IChimpImage snapshot = device.takeSnapshot(); 116 currentImage = snapshot.createBufferedImage(); 117 118 Graphics2D g = scaledImage.createGraphics(); 119 g.drawImage(currentImage, 0, 0, 120 scaledImage.getWidth(), scaledImage.getHeight(), 121 null); 122 g.dispose(); 123 124 display.setIcon(new ImageIcon(scaledImage)); 125 126 pack(); 127 } 128 129 /** 130 * This method initializes jContentPane 131 * 132 * @return javax.swing.JPanel 133 */ 134 private JPanel getJContentPane() { 135 if (jContentPane == null) { 136 display = new JLabel(); 137 jContentPane = new JPanel(); 138 jContentPane.setLayout(new BorderLayout()); 139 jContentPane.add(display, BorderLayout.CENTER); 140 jContentPane.add(getHistoryPanel(), BorderLayout.EAST); 141 jContentPane.add(getActionPanel(), BorderLayout.NORTH); 142 143 display.setPreferredSize(new Dimension(320, 480)); 144 145 display.addMouseListener(new MouseAdapter() { 146 @Override 147 public void mouseClicked(MouseEvent event) { 148 touch(event); 149 } 150 }); 151 } 152 return jContentPane; 153 } 154 155 /** 156 * This method initializes historyPanel 157 * 158 * @return javax.swing.JScrollPane 159 */ 160 private JScrollPane getHistoryPanel() { 161 if (historyPanel == null) { 162 historyPanel = new JScrollPane(); 163 historyPanel.getViewport().setView(getHistoryList()); 164 } 165 return historyPanel; 166 } 167 168 private JList getHistoryList() { 169 if (historyList == null) { 170 actionListModel = new ActionListModel(); 171 historyList = new JList(actionListModel); 172 } 173 return historyList; 174 } 175 176 /** 177 * This method initializes actionPanel 178 * 179 * @return javax.swing.JPanel 180 */ 181 private JPanel getActionPanel() { 182 if (actionPanel == null) { 183 actionPanel = new JPanel(); 184 actionPanel.setLayout(new BoxLayout(getActionPanel(), BoxLayout.X_AXIS)); 185 actionPanel.add(getWaitButton(), null); 186 actionPanel.add(getPressButton(), null); 187 actionPanel.add(getTypeButton(), null); 188 actionPanel.add(getFlingButton(), null); 189 actionPanel.add(getExportActionButton(), null); 190 actionPanel.add(getRefreshButton(), null); 191 } 192 return actionPanel; 193 } 194 195 /** 196 * This method initializes waitButton 197 * 198 * @return javax.swing.JButton 199 */ 200 private JButton getWaitButton() { 201 if (waitButton == null) { 202 waitButton = new JButton(); 203 waitButton.setText("Wait"); 204 waitButton.addActionListener(new java.awt.event.ActionListener() { 205 @Override 206 public void actionPerformed(java.awt.event.ActionEvent e) { 207 String howLongStr = JOptionPane.showInputDialog("How many seconds to wait?"); 208 if (howLongStr != null) { 209 float howLong = Float.parseFloat(howLongStr); 210 addAction(new WaitAction(howLong)); 211 } 212 } 213 }); 214 } 215 return waitButton; 216 } 217 218 /** 219 * This method initializes pressButton 220 * 221 * @return javax.swing.JButton 222 */ 223 private JButton getPressButton() { 224 if (pressButton == null) { 225 pressButton = new JButton(); 226 pressButton.setText("Press a Button"); 227 pressButton.addActionListener(new java.awt.event.ActionListener() { 228 @Override 229 public void actionPerformed(java.awt.event.ActionEvent e) { 230 JPanel panel = new JPanel(); 231 JLabel text = new JLabel("What button to press?"); 232 JComboBox keys = new JComboBox(PressAction.KEYS); 233 keys.setEditable(true); 234 JComboBox direction = new JComboBox(PressAction.DOWNUP_FLAG_MAP.values().toArray()); 235 panel.add(text); 236 panel.add(keys); 237 panel.add(direction); 238 239 int result = JOptionPane.showConfirmDialog(null, panel, "Input", JOptionPane.OK_CANCEL_OPTION); 240 if (result == JOptionPane.OK_OPTION) { 241 // Look up the "flag" value for the press choice 242 Map<String, String> lookupMap = PressAction.DOWNUP_FLAG_MAP.inverse(); 243 String flag = lookupMap.get(direction.getSelectedItem()); 244 addAction(new PressAction((String) keys.getSelectedItem(), flag)); 245 } 246 } 247 }); 248 } 249 return pressButton; 250 } 251 252 /** 253 * This method initializes typeButton 254 * 255 * @return javax.swing.JButton 256 */ 257 private JButton getTypeButton() { 258 if (typeButton == null) { 259 typeButton = new JButton(); 260 typeButton.setText("Type Something"); 261 typeButton.addActionListener(new java.awt.event.ActionListener() { 262 @Override 263 public void actionPerformed(java.awt.event.ActionEvent e) { 264 String whatToType = JOptionPane.showInputDialog("What to type?"); 265 if (whatToType != null) { 266 addAction(new TypeAction(whatToType)); 267 } 268 } 269 }); 270 } 271 return typeButton; 272 } 273 274 /** 275 * This method initializes flingButton 276 * 277 * @return javax.swing.JButton 278 */ 279 private JButton getFlingButton() { 280 if (flingButton == null) { 281 flingButton = new JButton(); 282 flingButton.setText("Fling"); 283 flingButton.addActionListener(new java.awt.event.ActionListener() { 284 @Override 285 public void actionPerformed(java.awt.event.ActionEvent e) { 286 JPanel panel = new JPanel(); 287 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 288 panel.add(new JLabel("Which Direction to fling?")); 289 JComboBox directionChooser = new JComboBox(DragAction.Direction.getNames()); 290 panel.add(directionChooser); 291 panel.add(new JLabel("How long to drag (in ms)?")); 292 JTextField ms = new JTextField(); 293 ms.setText("1000"); 294 panel.add(ms); 295 panel.add(new JLabel("How many steps to do it in?")); 296 JTextField steps = new JTextField(); 297 steps.setText("10"); 298 panel.add(steps); 299 300 301 302 int result = JOptionPane.showConfirmDialog(null, panel, "Input", JOptionPane.OK_CANCEL_OPTION); 303 if (result == JOptionPane.OK_OPTION) { 304 DragAction.Direction dir = 305 DragAction.Direction.valueOf((String) directionChooser.getSelectedItem()); 306 long millis = Long.parseLong(ms.getText()); 307 int numSteps = Integer.parseInt(steps.getText()); 308 309 addAction(newFlingAction(dir, numSteps, millis)); 310 } 311 } 312 }); 313 } 314 return flingButton; 315 } 316 317 private DragAction newFlingAction(Direction dir, int numSteps, long millis) { 318 int width = Integer.parseInt(device.getProperty("display.width")); 319 int height = Integer.parseInt(device.getProperty("display.height")); 320 321 // Adjust the w/h to a pct of the total size, so we don't hit things on the "outside" 322 width = (int) (width * 0.8f); 323 height = (int) (height * 0.8f); 324 int minW = (int) (width * 0.2f); 325 int minH = (int) (height * 0.2f); 326 327 int midWidth = width / 2; 328 int midHeight = height / 2; 329 330 int startx = minW; 331 int starty = minH; 332 int endx = minW; 333 int endy = minH; 334 335 switch (dir) { 336 case NORTH: 337 startx = endx = midWidth; 338 starty = height; 339 break; 340 case SOUTH: 341 startx = endx = midWidth; 342 endy = height; 343 break; 344 case EAST: 345 starty = endy = midHeight; 346 endx = width; 347 break; 348 case WEST: 349 starty = endy = midHeight; 350 startx = width; 351 break; 352 } 353 354 return new DragAction(dir, startx, starty, endx, endy, numSteps, millis); 355 } 356 357 /** 358 * This method initializes exportActionButton 359 * 360 * @return javax.swing.JButton 361 */ 362 private JButton getExportActionButton() { 363 if (exportActionButton == null) { 364 exportActionButton = new JButton(); 365 exportActionButton.setText("Export Actions"); 366 exportActionButton.addActionListener(new java.awt.event.ActionListener() { 367 @Override 368 public void actionPerformed(java.awt.event.ActionEvent ev) { 369 JFileChooser fc = new JFileChooser(); 370 if (fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { 371 try { 372 actionListModel.export(fc.getSelectedFile()); 373 } catch (FileNotFoundException e) { 374 LOG.log(Level.SEVERE, "Unable to save file", e); 375 } 376 } 377 } 378 }); 379 } 380 return exportActionButton; 381 } 382 383 /** 384 * This method initializes refreshButton 385 * 386 * @return javax.swing.JButton 387 */ 388 private JButton getRefreshButton() { 389 if (refreshButton == null) { 390 refreshButton = new JButton(); 391 refreshButton.setText("Refresh Display"); 392 refreshButton.addActionListener(new java.awt.event.ActionListener() { 393 @Override 394 public void actionPerformed(java.awt.event.ActionEvent e) { 395 refreshDisplay(); 396 } 397 }); 398 } 399 return refreshButton; 400 } 401 402 private void touch(MouseEvent event) { 403 int x = event.getX(); 404 int y = event.getY(); 405 406 // Since we scaled the image down, our x/y are scaled as well. 407 double scalex = ((double) currentImage.getWidth()) / ((double) scaledImage.getWidth()); 408 double scaley = ((double) currentImage.getHeight()) / ((double) scaledImage.getHeight()); 409 410 x = (int) (x * scalex); 411 y = (int) (y * scaley); 412 413 switch (event.getID()) { 414 case MouseEvent.MOUSE_CLICKED: 415 addAction(new TouchAction(x, y, MonkeyDevice.DOWN_AND_UP)); 416 break; 417 case MouseEvent.MOUSE_PRESSED: 418 addAction(new TouchAction(x, y, MonkeyDevice.DOWN)); 419 break; 420 case MouseEvent.MOUSE_RELEASED: 421 addAction(new TouchAction(x, y, MonkeyDevice.UP)); 422 break; 423 } 424 } 425 426 public void addAction(Action a) { 427 actionListModel.add(a); 428 try { 429 a.execute(device); 430 } catch (Exception e) { 431 LOG.log(Level.SEVERE, "Unable to execute action!", e); 432 } 433 } 434 } 435