OSDN Git Service

Add "type" command to allow strings to be more easily typed.
[android-x86/development.git] / cmds / monkey / src / com / android / commands / monkey / MonkeySourceNetwork.java
1 /*
2  * Copyright 2009, 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.commands.monkey;
17
18 import android.content.Context;
19 import android.os.IPowerManager;
20 import android.os.RemoteException;
21 import android.os.ServiceManager;
22 import android.os.SystemClock;
23 import android.util.Log;
24 import android.view.KeyCharacterMap;
25 import android.view.KeyEvent;
26 import android.view.MotionEvent;
27
28 import java.io.BufferedReader;
29 import java.io.IOException;
30 import java.io.InputStreamReader;
31 import java.io.PrintWriter;
32 import java.lang.Integer;
33 import java.lang.NumberFormatException;
34 import java.net.InetAddress;
35 import java.net.ServerSocket;
36 import java.net.Socket;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.Map;
40 import java.util.LinkedList;
41 import java.util.List;
42 import java.util.Queue;
43 import java.util.StringTokenizer;
44
45 /**
46  * An Event source for getting Monkey Network Script commands from
47  * over the network.
48  */
49 public class MonkeySourceNetwork implements MonkeyEventSource {
50     private static final String TAG = "MonkeyStub";
51
52     private interface MonkeyCommand {
53         MonkeyEvent translateCommand(List<String> command, CommandQueue queue);
54     }
55
56     /**
57      * Command to simulate closing and opening the keyboard.
58      */
59     private static class FlipCommand implements MonkeyCommand {
60         // flip open
61         // flip closed
62         public MonkeyEvent translateCommand(List<String> command,
63                                             CommandQueue queue) {
64             if (command.size() > 1) {
65                 String direction = command.get(1);
66                 if ("open".equals(direction)) {
67                     return new MonkeyFlipEvent(true);
68                 } else if ("close".equals(direction)) {
69                     return new MonkeyFlipEvent(false);
70                 }
71             }
72             return null;
73         }
74     }
75
76     /**
77      * Command to send touch events to the input system.
78      */
79     private static class TouchCommand implements MonkeyCommand {
80         // touch [down|up|move] [x] [y]
81         // touch down 120 120
82         // touch move 140 140
83         // touch up 140 140
84         public MonkeyEvent translateCommand(List<String> command,
85                                             CommandQueue queue) {
86             if (command.size() == 4) {
87                 String actionName = command.get(1);
88                 int x = 0;
89                 int y = 0;
90                 try {
91                     x = Integer.parseInt(command.get(2));
92                     y = Integer.parseInt(command.get(3));
93                 } catch (NumberFormatException e) {
94                     // Ok, it wasn't a number
95                     Log.e(TAG, "Got something that wasn't a number", e);
96                     return null;
97                 }
98
99                 // figure out the action
100                 int action = -1;
101                 if ("down".equals(actionName)) {
102                     action = MotionEvent.ACTION_DOWN;
103                 } else if ("up".equals(actionName)) {
104                     action = MotionEvent.ACTION_UP;
105                 } else if ("move".equals(actionName)) {
106                     action = MotionEvent.ACTION_MOVE;
107                 }
108                 if (action == -1) {
109                     Log.e(TAG, "Got a bad action: " + actionName);
110                     return null;
111                 }
112
113                 return new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
114                                              -1, action, x, y, 0);
115             }
116             return null;
117
118         }
119     }
120
121     /**
122      * Command to send Trackball events to the input system.
123      */
124     private static class TrackballCommand implements MonkeyCommand {
125         // trackball [dx] [dy]
126         // trackball 1 0 -- move right
127         // trackball -1 0 -- move left
128         public MonkeyEvent translateCommand(List<String> command,
129                                             CommandQueue queue) {
130             if (command.size() == 3) {
131                 int dx = 0;
132                 int dy = 0;
133                 try {
134                     dx = Integer.parseInt(command.get(1));
135                     dy = Integer.parseInt(command.get(2));
136                 } catch (NumberFormatException e) {
137                     // Ok, it wasn't a number
138                     Log.e(TAG, "Got something that wasn't a number", e);
139                     return null;
140                 }
141                 return new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, -1,
142                     MotionEvent.ACTION_MOVE, dx, dy, 0);
143
144             }
145             return null;
146         }
147     }
148
149     /**
150      * Command to send Key events to the input system.
151      */
152     private static class KeyCommand implements MonkeyCommand {
153         // key [down|up] [keycode]
154         // key down 82
155         // key up 82
156         public MonkeyEvent translateCommand(List<String> command,
157                                             CommandQueue queue) {
158             if (command.size() == 3) {
159                 int keyCode = getKeyCode(command.get(2));
160                 if (keyCode < 0) {
161                     // Ok, you gave us something bad.
162                     Log.e(TAG, "Can't find keyname: " + command.get(2));
163                     return null;
164                 }
165                 Log.d(TAG, "keycode: " + keyCode);
166                 int action = -1;
167                 if ("down".equals(command.get(1))) {
168                     action = KeyEvent.ACTION_DOWN;
169                 } else if ("up".equals(command.get(1))) {
170                     action = KeyEvent.ACTION_UP;
171                 }
172                 if (action == -1) {
173                     Log.e(TAG, "got unknown action.");
174                     return null;
175                 }
176                 return new MonkeyKeyEvent(action, keyCode);
177             }
178             return null;
179         }
180     }
181
182     /**
183      * Get an integer keycode value from a given keyname.
184      *
185      * @param keyName the key name to get the code for
186      * @returns the integer keycode value, or -1 on error.
187      */
188     private static int getKeyCode(String keyName) {
189         int keyCode = -1;
190         try {
191             keyCode = Integer.parseInt(keyName);
192         } catch (NumberFormatException e) {
193             // Ok, it wasn't a number, see if we have a
194             // keycode name for it
195             keyCode = MonkeySourceRandom.getKeyCode(keyName);
196             if (keyCode == -1) {
197                 // OK, one last ditch effort to find a match.
198                 // Build the KEYCODE_STRING from the string
199                 // we've been given and see if that key
200                 // exists.  This would allow you to do "key
201                 // down menu", for example.
202                 keyCode = MonkeySourceRandom.getKeyCode("KEYCODE_" + keyName.toUpperCase());
203             }
204         }
205         return keyCode;
206     }
207
208     /**
209      * Command to put the Monkey to sleep.
210      */
211     private static class SleepCommand implements MonkeyCommand {
212         // sleep 2000
213         public MonkeyEvent translateCommand(List<String> command,
214                                             CommandQueue queue) {
215             if (command.size() == 2) {
216                 int sleep = -1;
217                 String sleepStr = command.get(1);
218                 try {
219                     sleep = Integer.parseInt(sleepStr);
220                 } catch (NumberFormatException e) {
221                   Log.e(TAG, "Not a number: " + sleepStr, e);
222                 }
223                 return new MonkeyThrottleEvent(sleep);
224             }
225             return null;
226         }
227     }
228
229     /**
230      * Command to type a string
231      */
232     private static class TypeCommand implements MonkeyCommand {
233         // wake
234         public MonkeyEvent translateCommand(List<String> command,
235                                             CommandQueue queue) {
236             if (command.size() == 2) {
237                 String str = command.get(1);
238
239                 char[] chars = str.toString().toCharArray();
240
241                 // Convert the string to an array of KeyEvent's for
242                 // the built in keymap.
243                 KeyCharacterMap keyCharacterMap = KeyCharacterMap.
244                         load(KeyCharacterMap.BUILT_IN_KEYBOARD);
245                 KeyEvent[] events = keyCharacterMap.getEvents(chars);
246
247                 // enqueue all the events we just got.
248                 for (KeyEvent event : events) {
249                     queue.enqueueEvent(new MonkeyKeyEvent(event));
250                 }
251                 return new MonkeyNoopEvent();
252             }
253             return null;
254         }
255     }
256
257     /**
258      * Command to wake the device up
259      */
260     private static class WakeCommand implements MonkeyCommand {
261         // wake
262         public MonkeyEvent translateCommand(List<String> command,
263                                             CommandQueue queue) {
264             if (!wake()) {
265                 return null;
266             }
267             return new MonkeyNoopEvent();
268         }
269     }
270
271     /**
272      * Command to "tap" at a location (Sends a down and up touch
273      * event).
274      */
275     private static class TapCommand implements MonkeyCommand {
276         // tap x y
277         public MonkeyEvent translateCommand(List<String> command,
278                                             CommandQueue queue) {
279             if (command.size() == 3) {
280                 int x = 0;
281                 int y = 0;
282                 try {
283                     x = Integer.parseInt(command.get(1));
284                     y = Integer.parseInt(command.get(2));
285                 } catch (NumberFormatException e) {
286                     // Ok, it wasn't a number
287                     Log.e(TAG, "Got something that wasn't a number", e);
288                     return null;
289                 }
290
291                 queue.enqueueEvent(new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
292                                                          -1, MotionEvent.ACTION_DOWN,
293                                                          x, y, 0));
294                 queue.enqueueEvent(new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
295                                                          -1, MotionEvent.ACTION_UP,
296                                                          x, y, 0));
297                 return new MonkeyNoopEvent();
298             }
299             return null;
300         }
301     }
302
303     /**
304      * Command to "press" a buttons (Sends an up and down key event.)
305      */
306     private static class PressCommand implements MonkeyCommand {
307         // press keycode
308         public MonkeyEvent translateCommand(List<String> command,
309                                             CommandQueue queue) {
310             if (command.size() == 2) {
311                 int keyCode = getKeyCode(command.get(1));
312                 if (keyCode < 0) {
313                     // Ok, you gave us something bad.
314                     Log.e(TAG, "Can't find keyname: " + command.get(1));
315                     return null;
316                 }
317
318                 queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, keyCode));
319                 queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_UP, keyCode));
320                 return new MonkeyNoopEvent();
321
322             }
323             return null;
324         }
325     }
326
327     /**
328      * Force the device to wake up.
329      *
330      * @return true if woken up OK.
331      */
332     private static final boolean wake() {
333         IPowerManager pm =
334                 IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
335         try {
336             pm.userActivityWithForce(SystemClock.uptimeMillis(), true, true);
337         } catch (RemoteException e) {
338             Log.e(TAG, "Got remote exception", e);
339             return false;
340         }
341         return true;
342     }
343
344     // This maps from command names to command implementations.
345     private static final Map<String, MonkeyCommand> COMMAND_MAP = new HashMap<String, MonkeyCommand>();
346
347     static {
348         // Add in all the commands we support
349         COMMAND_MAP.put("flip", new FlipCommand());
350         COMMAND_MAP.put("touch", new TouchCommand());
351         COMMAND_MAP.put("trackball", new TrackballCommand());
352         COMMAND_MAP.put("key", new KeyCommand());
353         COMMAND_MAP.put("sleep", new SleepCommand());
354         COMMAND_MAP.put("wake", new WakeCommand());
355         COMMAND_MAP.put("tap", new TapCommand());
356         COMMAND_MAP.put("press", new PressCommand());
357         COMMAND_MAP.put("type", new TypeCommand());
358     }
359
360     // QUIT command
361     private static final String QUIT = "quit";
362
363     // command response strings
364     private static final String OK = "OK";
365     private static final String ERROR = "ERROR";
366
367     private static interface CommandQueue {
368         /**
369          * Enqueue an event to be returned later.  This allows a
370          * command to return multiple events.  Commands using the
371          * command queue still have to return a valid event from their
372          * translateCommand method.  The returned command will be
373          * executed before anything put into the queue.
374          *
375          * @param e the event to be enqueued.
376          */
377         public void enqueueEvent(MonkeyEvent e);
378     };
379
380     // Queue of Events to be processed.  This allows commands to push
381     // multiple events into the queue to be processed.
382     private static class CommandQueueImpl implements CommandQueue{
383         private final Queue<MonkeyEvent> queuedEvents = new LinkedList<MonkeyEvent>();
384
385         public void enqueueEvent(MonkeyEvent e) {
386             queuedEvents.offer(e);
387         }
388
389         /**
390          * Get the next queued event to excecute.
391          *
392          * @returns the next event, or null if there aren't any more.
393          */
394         public MonkeyEvent getNextQueuedEvent() {
395             return queuedEvents.poll();
396         }
397     };
398
399     private final CommandQueueImpl commandQueue = new CommandQueueImpl();
400
401     private final int port;
402     private BufferedReader input;
403     private PrintWriter output;
404     private boolean started = false;
405
406     public MonkeySourceNetwork(int port) {
407         this.port = port;
408     }
409
410     /**
411      * Start a network server listening on the specified port.  The
412      * network protocol is a line oriented protocol, where each line
413      * is a different command that can be run.
414      *
415      * @param port the port to listen on
416      */
417     private void startServer() throws IOException {
418         // Only bind this to local host.  This means that you can only
419         // talk to the monkey locally, or though adb port forwarding.
420         ServerSocket server = new ServerSocket(port,
421                                                0, // default backlog
422                                                InetAddress.getLocalHost());
423         Socket s = server.accept();
424         // At this point, we have a client connected.  Wake the device
425         // up in preparation for doing some commands.
426         wake();
427
428         input = new BufferedReader(new InputStreamReader(s.getInputStream()));
429         // auto-flush
430         output = new PrintWriter(s.getOutputStream(), true);
431     }
432
433     /**
434      * Helper function for commandLineSplit that replaces quoted
435      * charaters with their real values.
436      *
437      * @param input the string to do replacement on.
438      * @returns the results with the characters replaced.
439      */
440     private static String replaceQuotedChars(String input) {
441         return input.replace("\\\"", "\"");
442     }
443
444     /**
445      * This function splits the given line into String parts.  It obey's quoted
446      * strings and returns them as a single part.
447      *
448      * "This is a test" -> returns only one element
449      * This is a test -> returns four elements
450      *
451      * @param line the line to parse
452      * @return the List of elements
453      */
454     private static List<String> commandLineSplit(String line) {
455         ArrayList<String> result = new ArrayList<String>();
456         StringTokenizer tok = new StringTokenizer(line);
457
458         boolean insideQuote = false;
459         StringBuffer quotedWord = new StringBuffer();
460         while (tok.hasMoreTokens()) {
461             String cur = tok.nextToken();
462             if (!insideQuote && cur.startsWith("\"")) {
463                 // begin quote
464                 quotedWord.append(replaceQuotedChars(cur));
465                 insideQuote = true;
466             } else if (insideQuote) {
467                 // end quote
468                 if (cur.endsWith("\"")) {
469                     insideQuote = false;
470                     quotedWord.append(" ").append(replaceQuotedChars(cur));
471                     String word = quotedWord.toString();
472
473                     // trim off the quotes
474                     result.add(word.substring(1, word.length() - 1));
475                 } else {
476                     quotedWord.append(" ").append(replaceQuotedChars(cur));
477                 }
478             } else {
479                 result.add(replaceQuotedChars(cur));
480             }
481         }
482         return result;
483     }
484
485     /**
486      * Translate the given command line into a MonkeyEvent.
487      *
488      * @param commandLine the full command line given.
489      * @returns the MonkeyEvent corresponding to the command, or null
490      * if there was an issue.
491      */
492     private MonkeyEvent translateCommand(String commandLine) {
493         Log.d(TAG, "translateCommand: " + commandLine);
494         List<String> parts = commandLineSplit(commandLine);
495         if (parts.size() > 0) {
496             MonkeyCommand command = COMMAND_MAP.get(parts.get(0));
497             if (command != null) {
498                 return command.translateCommand(parts,
499                                                 commandQueue);
500             }
501             return null;
502         }
503         return null;
504     }
505
506     public MonkeyEvent getNextEvent() {
507         if (!started) {
508             try {
509                 startServer();
510             } catch (IOException e) {
511                 Log.e(TAG, "Got IOException from server", e);
512                 return null;
513             }
514             started = true;
515         }
516
517         // Now, get the next command.  This call may block, but that's OK
518         try {
519             while (true) {
520                 // Check to see if we have any events queued up.  If
521                 // we do, use those until we have no more.  Then get
522                 // more input from the user.
523                 MonkeyEvent queuedEvent = commandQueue.getNextQueuedEvent();
524                 if (queuedEvent != null) {
525                     // dispatch the event
526                     return queuedEvent;
527                 }
528
529                 String command = input.readLine();
530                 if (command == null) {
531                     Log.d(TAG, "Connection dropped.");
532                     return null;
533                 }
534                 // Do quit checking here
535                 if (QUIT.equals(command)) {
536                     // then we're done
537                     Log.d(TAG, "Quit requested");
538                     // let the host know the command ran OK
539                     output.println(OK);
540                     return null;
541                 }
542
543                 // Do comment checking here.  Comments aren't a
544                 // command, so we don't echo anything back to the
545                 // user.
546                 if (command.startsWith("#")) {
547                   // keep going
548                   continue;
549                 }
550
551                 // Translate the command line
552                 MonkeyEvent event = translateCommand(command);
553                 if (event != null) {
554                     // let the host know the command ran OK
555                     output.println(OK);
556                     return event;
557                 }
558                 // keep going.  maybe the next command will make more sense
559                 Log.e(TAG, "Got unknown command! \"" + command + "\"");
560                 output.println(ERROR);
561             }
562         } catch (IOException e) {
563             Log.e(TAG, "Exception: ", e);
564             return null;
565         }
566     }
567
568     public void setVerbose(int verbose) {
569         // We're not particualy verbose
570     }
571
572     public boolean validate() {
573         // we have no pre-conditions to validate
574         return true;
575     }
576 }