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.PowerManager; 21 import android.os.RemoteException; 22 import android.os.ServiceManager; 23 import android.os.SystemClock; 24 import android.util.Log; 25 import android.view.KeyCharacterMap; 26 import android.view.KeyEvent; 27 import android.view.MotionEvent; 28 29 import java.io.BufferedReader; 30 import java.io.IOException; 31 import java.io.InputStreamReader; 32 import java.io.PrintWriter; 33 import java.lang.Integer; 34 import java.lang.NumberFormatException; 35 import java.net.InetAddress; 36 import java.net.ServerSocket; 37 import java.net.Socket; 38 import java.util.ArrayList; 39 import java.util.HashMap; 40 import java.util.Map; 41 import java.util.LinkedList; 42 import java.util.List; 43 import java.util.Queue; 44 import java.util.StringTokenizer; 45 46 /** 47 * An Event source for getting Monkey Network Script commands from 48 * over the network. 49 */ 50 public class MonkeySourceNetwork implements MonkeyEventSource { 51 private static final String TAG = "MonkeyStub"; 52 /* The version of the monkey network protocol */ 53 public static final int MONKEY_NETWORK_VERSION = 2; 54 private static DeferredReturn deferredReturn; 55 56 /** 57 * ReturnValue from the MonkeyCommand that indicates whether the 58 * command was sucessful or not. 59 */ 60 public static class MonkeyCommandReturn { 61 private final boolean success; 62 private final String message; 63 MonkeyCommandReturn(boolean success)64 public MonkeyCommandReturn(boolean success) { 65 this.success = success; 66 this.message = null; 67 } 68 MonkeyCommandReturn(boolean success, String message)69 public MonkeyCommandReturn(boolean success, 70 String message) { 71 this.success = success; 72 this.message = message; 73 } 74 hasMessage()75 boolean hasMessage() { 76 return message != null; 77 } 78 getMessage()79 String getMessage() { 80 return message; 81 } 82 wasSuccessful()83 boolean wasSuccessful() { 84 return success; 85 } 86 } 87 88 public final static MonkeyCommandReturn OK = new MonkeyCommandReturn(true); 89 public final static MonkeyCommandReturn ERROR = new MonkeyCommandReturn(false); 90 public final static MonkeyCommandReturn EARG = new MonkeyCommandReturn(false, 91 "Invalid Argument"); 92 93 /** 94 * Interface that MonkeyCommands must implement. 95 */ 96 public interface MonkeyCommand { 97 /** 98 * Translate the command line into a sequence of MonkeyEvents. 99 * 100 * @param command the command line. 101 * @param queue the command queue. 102 * @return MonkeyCommandReturn indicating what happened. 103 */ translateCommand(List<String> command, CommandQueue queue)104 MonkeyCommandReturn translateCommand(List<String> command, CommandQueue queue); 105 } 106 107 /** 108 * Command to simulate closing and opening the keyboard. 109 */ 110 private static class FlipCommand implements MonkeyCommand { 111 // flip open 112 // flip closed translateCommand(List<String> command, CommandQueue queue)113 public MonkeyCommandReturn translateCommand(List<String> command, 114 CommandQueue queue) { 115 if (command.size() > 1) { 116 String direction = command.get(1); 117 if ("open".equals(direction)) { 118 queue.enqueueEvent(new MonkeyFlipEvent(true)); 119 return OK; 120 } else if ("close".equals(direction)) { 121 queue.enqueueEvent(new MonkeyFlipEvent(false)); 122 return OK; 123 } 124 } 125 return EARG; 126 } 127 } 128 129 /** 130 * Command to send touch events to the input system. 131 */ 132 private static class TouchCommand implements MonkeyCommand { 133 // touch [down|up|move] [x] [y] 134 // touch down 120 120 135 // touch move 140 140 136 // touch up 140 140 translateCommand(List<String> command, CommandQueue queue)137 public MonkeyCommandReturn translateCommand(List<String> command, 138 CommandQueue queue) { 139 if (command.size() == 4) { 140 String actionName = command.get(1); 141 int x = 0; 142 int y = 0; 143 try { 144 x = Integer.parseInt(command.get(2)); 145 y = Integer.parseInt(command.get(3)); 146 } catch (NumberFormatException e) { 147 // Ok, it wasn't a number 148 Log.e(TAG, "Got something that wasn't a number", e); 149 return EARG; 150 } 151 152 // figure out the action 153 int action = -1; 154 if ("down".equals(actionName)) { 155 action = MotionEvent.ACTION_DOWN; 156 } else if ("up".equals(actionName)) { 157 action = MotionEvent.ACTION_UP; 158 } else if ("move".equals(actionName)) { 159 action = MotionEvent.ACTION_MOVE; 160 } 161 if (action == -1) { 162 Log.e(TAG, "Got a bad action: " + actionName); 163 return EARG; 164 } 165 166 queue.enqueueEvent(new MonkeyTouchEvent(action) 167 .addPointer(0, x, y)); 168 return OK; 169 } 170 return EARG; 171 } 172 } 173 174 /** 175 * Command to send Trackball events to the input system. 176 */ 177 private static class TrackballCommand implements MonkeyCommand { 178 // trackball [dx] [dy] 179 // trackball 1 0 -- move right 180 // trackball -1 0 -- move left translateCommand(List<String> command, CommandQueue queue)181 public MonkeyCommandReturn translateCommand(List<String> command, 182 CommandQueue queue) { 183 if (command.size() == 3) { 184 int dx = 0; 185 int dy = 0; 186 try { 187 dx = Integer.parseInt(command.get(1)); 188 dy = Integer.parseInt(command.get(2)); 189 } catch (NumberFormatException e) { 190 // Ok, it wasn't a number 191 Log.e(TAG, "Got something that wasn't a number", e); 192 return EARG; 193 } 194 queue.enqueueEvent(new MonkeyTrackballEvent(MotionEvent.ACTION_MOVE) 195 .addPointer(0, dx, dy)); 196 return OK; 197 198 } 199 return EARG; 200 } 201 } 202 203 /** 204 * Command to send Key events to the input system. 205 */ 206 private static class KeyCommand implements MonkeyCommand { 207 // key [down|up] [keycode] 208 // key down 82 209 // key up 82 translateCommand(List<String> command, CommandQueue queue)210 public MonkeyCommandReturn translateCommand(List<String> command, 211 CommandQueue queue) { 212 if (command.size() == 3) { 213 int keyCode = getKeyCode(command.get(2)); 214 if (keyCode < 0) { 215 // Ok, you gave us something bad. 216 Log.e(TAG, "Can't find keyname: " + command.get(2)); 217 return EARG; 218 } 219 Log.d(TAG, "keycode: " + keyCode); 220 int action = -1; 221 if ("down".equals(command.get(1))) { 222 action = KeyEvent.ACTION_DOWN; 223 } else if ("up".equals(command.get(1))) { 224 action = KeyEvent.ACTION_UP; 225 } 226 if (action == -1) { 227 Log.e(TAG, "got unknown action."); 228 return EARG; 229 } 230 queue.enqueueEvent(new MonkeyKeyEvent(action, keyCode)); 231 return OK; 232 } 233 return EARG; 234 } 235 } 236 237 /** 238 * Get an integer keycode value from a given keyname. 239 * 240 * @param keyName the key name to get the code for 241 * @return the integer keycode value, or -1 on error. 242 */ getKeyCode(String keyName)243 private static int getKeyCode(String keyName) { 244 int keyCode = -1; 245 try { 246 keyCode = Integer.parseInt(keyName); 247 } catch (NumberFormatException e) { 248 // Ok, it wasn't a number, see if we have a 249 // keycode name for it 250 keyCode = MonkeySourceRandom.getKeyCode(keyName); 251 if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { 252 // OK, one last ditch effort to find a match. 253 // Build the KEYCODE_STRING from the string 254 // we've been given and see if that key 255 // exists. This would allow you to do "key 256 // down menu", for example. 257 keyCode = MonkeySourceRandom.getKeyCode("KEYCODE_" + keyName.toUpperCase()); 258 if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { 259 // Still unknown 260 return -1; 261 } 262 } 263 } 264 return keyCode; 265 } 266 267 /** 268 * Command to put the Monkey to sleep. 269 */ 270 private static class SleepCommand implements MonkeyCommand { 271 // sleep 2000 translateCommand(List<String> command, CommandQueue queue)272 public MonkeyCommandReturn translateCommand(List<String> command, 273 CommandQueue queue) { 274 if (command.size() == 2) { 275 int sleep = -1; 276 String sleepStr = command.get(1); 277 try { 278 sleep = Integer.parseInt(sleepStr); 279 } catch (NumberFormatException e) { 280 Log.e(TAG, "Not a number: " + sleepStr, e); 281 return EARG; 282 } 283 queue.enqueueEvent(new MonkeyThrottleEvent(sleep)); 284 return OK; 285 } 286 return EARG; 287 } 288 } 289 290 /** 291 * Command to type a string 292 */ 293 private static class TypeCommand implements MonkeyCommand { 294 // wake translateCommand(List<String> command, CommandQueue queue)295 public MonkeyCommandReturn translateCommand(List<String> command, 296 CommandQueue queue) { 297 if (command.size() == 2) { 298 String str = command.get(1); 299 300 char[] chars = str.toString().toCharArray(); 301 302 // Convert the string to an array of KeyEvent's for 303 // the built in keymap. 304 KeyCharacterMap keyCharacterMap = KeyCharacterMap. 305 load(KeyCharacterMap.VIRTUAL_KEYBOARD); 306 KeyEvent[] events = keyCharacterMap.getEvents(chars); 307 308 // enqueue all the events we just got. 309 for (KeyEvent event : events) { 310 queue.enqueueEvent(new MonkeyKeyEvent(event)); 311 } 312 return OK; 313 } 314 return EARG; 315 } 316 } 317 318 /** 319 * Command to wake the device up 320 */ 321 private static class WakeCommand implements MonkeyCommand { 322 // wake translateCommand(List<String> command, CommandQueue queue)323 public MonkeyCommandReturn translateCommand(List<String> command, 324 CommandQueue queue) { 325 if (!wake()) { 326 return ERROR; 327 } 328 return OK; 329 } 330 } 331 332 /** 333 * Command to "tap" at a location (Sends a down and up touch 334 * event). 335 */ 336 private static class TapCommand implements MonkeyCommand { 337 // tap x y translateCommand(List<String> command, CommandQueue queue)338 public MonkeyCommandReturn translateCommand(List<String> command, 339 CommandQueue queue) { 340 if (command.size() == 3) { 341 int x = 0; 342 int y = 0; 343 try { 344 x = Integer.parseInt(command.get(1)); 345 y = Integer.parseInt(command.get(2)); 346 } catch (NumberFormatException e) { 347 // Ok, it wasn't a number 348 Log.e(TAG, "Got something that wasn't a number", e); 349 return EARG; 350 } 351 352 queue.enqueueEvent(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN) 353 .addPointer(0, x, y)); 354 queue.enqueueEvent(new MonkeyTouchEvent(MotionEvent.ACTION_UP) 355 .addPointer(0, x, y)); 356 return OK; 357 } 358 return EARG; 359 } 360 } 361 362 /** 363 * Command to "press" a buttons (Sends an up and down key event.) 364 */ 365 private static class PressCommand implements MonkeyCommand { 366 // press keycode translateCommand(List<String> command, CommandQueue queue)367 public MonkeyCommandReturn translateCommand(List<String> command, 368 CommandQueue queue) { 369 if (command.size() == 2) { 370 int keyCode = getKeyCode(command.get(1)); 371 if (keyCode < 0) { 372 // Ok, you gave us something bad. 373 Log.e(TAG, "Can't find keyname: " + command.get(1)); 374 return EARG; 375 } 376 377 queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, keyCode)); 378 queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_UP, keyCode)); 379 return OK; 380 381 } 382 return EARG; 383 } 384 } 385 386 /** 387 * Command to defer the return of another command until the given event occurs. 388 * deferreturn takes three arguments. It takes an event to wait for (e.g. waiting for the 389 * device to display a different activity would the "screenchange" event), a 390 * timeout, which is the number of microseconds to wait for the event to occur, and it takes 391 * a command. The command can be any other Monkey command that can be issued over the network 392 * (e.g. press KEYCODE_HOME). deferreturn will then run this command, return an OK, wait for 393 * the event to occur and return the deferred return value when either the event occurs or 394 * when the timeout is reached (whichever occurs first). Note that there is no difference 395 * between an event occurring and the timeout being reached; the client will have to verify 396 * that the change actually occured. 397 * 398 * Example: 399 * deferreturn screenchange 1000 press KEYCODE_HOME 400 * This command will press the home key on the device and then wait for the screen to change 401 * for up to one second. Either the screen will change, and the results fo the key press will 402 * be returned to the client, or the timeout will be reached, and the results for the key 403 * press will be returned to the client. 404 */ 405 private static class DeferReturnCommand implements MonkeyCommand { 406 // deferreturn [event] [timeout (ms)] [command] 407 // deferreturn screenchange 100 tap 10 10 translateCommand(List<String> command, CommandQueue queue)408 public MonkeyCommandReturn translateCommand(List<String> command, 409 CommandQueue queue) { 410 if (command.size() > 3) { 411 String event = command.get(1); 412 int eventId; 413 if (event.equals("screenchange")) { 414 eventId = DeferredReturn.ON_WINDOW_STATE_CHANGE; 415 } else { 416 return EARG; 417 } 418 long timeout = Long.parseLong(command.get(2)); 419 MonkeyCommand deferredCommand = COMMAND_MAP.get(command.get(3)); 420 if (deferredCommand != null) { 421 List<String> parts = command.subList(3, command.size()); 422 MonkeyCommandReturn ret = deferredCommand.translateCommand(parts, queue); 423 deferredReturn = new DeferredReturn(eventId, ret, timeout); 424 return OK; 425 } 426 } 427 return EARG; 428 } 429 } 430 431 432 /** 433 * Force the device to wake up. 434 * 435 * @return true if woken up OK. 436 */ wake()437 private static final boolean wake() { 438 IPowerManager pm = 439 IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE)); 440 try { 441 pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_UNKNOWN, 442 "Monkey", null); 443 } catch (RemoteException e) { 444 Log.e(TAG, "Got remote exception", e); 445 return false; 446 } 447 return true; 448 } 449 450 // This maps from command names to command implementations. 451 private static final Map<String, MonkeyCommand> COMMAND_MAP = new HashMap<String, MonkeyCommand>(); 452 453 static { 454 // Add in all the commands we support 455 COMMAND_MAP.put("flip", new FlipCommand()); 456 COMMAND_MAP.put("touch", new TouchCommand()); 457 COMMAND_MAP.put("trackball", new TrackballCommand()); 458 COMMAND_MAP.put("key", new KeyCommand()); 459 COMMAND_MAP.put("sleep", new SleepCommand()); 460 COMMAND_MAP.put("wake", new WakeCommand()); 461 COMMAND_MAP.put("tap", new TapCommand()); 462 COMMAND_MAP.put("press", new PressCommand()); 463 COMMAND_MAP.put("type", new TypeCommand()); 464 COMMAND_MAP.put("listvar", new MonkeySourceNetworkVars.ListVarCommand()); 465 COMMAND_MAP.put("getvar", new MonkeySourceNetworkVars.GetVarCommand()); 466 COMMAND_MAP.put("listviews", new MonkeySourceNetworkViews.ListViewsCommand()); 467 COMMAND_MAP.put("queryview", new MonkeySourceNetworkViews.QueryViewCommand()); 468 COMMAND_MAP.put("getrootview", new MonkeySourceNetworkViews.GetRootViewCommand()); 469 COMMAND_MAP.put("getviewswithtext", 470 new MonkeySourceNetworkViews.GetViewsWithTextCommand()); 471 COMMAND_MAP.put("deferreturn", new DeferReturnCommand()); 472 } 473 474 // QUIT command 475 private static final String QUIT = "quit"; 476 // DONE command 477 private static final String DONE = "done"; 478 479 // command response strings 480 private static final String OK_STR = "OK"; 481 private static final String ERROR_STR = "ERROR"; 482 483 public static interface CommandQueue { 484 /** 485 * Enqueue an event to be returned later. This allows a 486 * command to return multiple events. Commands using the 487 * command queue still have to return a valid event from their 488 * translateCommand method. The returned command will be 489 * executed before anything put into the queue. 490 * 491 * @param e the event to be enqueued. 492 */ enqueueEvent(MonkeyEvent e)493 public void enqueueEvent(MonkeyEvent e); 494 }; 495 496 // Queue of Events to be processed. This allows commands to push 497 // multiple events into the queue to be processed. 498 private static class CommandQueueImpl implements CommandQueue{ 499 private final Queue<MonkeyEvent> queuedEvents = new LinkedList<MonkeyEvent>(); 500 enqueueEvent(MonkeyEvent e)501 public void enqueueEvent(MonkeyEvent e) { 502 queuedEvents.offer(e); 503 } 504 505 /** 506 * Get the next queued event to excecute. 507 * 508 * @return the next event, or null if there aren't any more. 509 */ getNextQueuedEvent()510 public MonkeyEvent getNextQueuedEvent() { 511 return queuedEvents.poll(); 512 } 513 }; 514 515 // A holder class for a deferred return value. This allows us to defer returning the success of 516 // a call until a given event has occurred. 517 private static class DeferredReturn { 518 public static final int ON_WINDOW_STATE_CHANGE = 1; 519 520 private int event; 521 private MonkeyCommandReturn deferredReturn; 522 private long timeout; 523 DeferredReturn(int event, MonkeyCommandReturn deferredReturn, long timeout)524 public DeferredReturn(int event, MonkeyCommandReturn deferredReturn, long timeout) { 525 this.event = event; 526 this.deferredReturn = deferredReturn; 527 this.timeout = timeout; 528 } 529 530 /** 531 * Wait until the given event has occurred before returning the value. 532 * @return The MonkeyCommandReturn from the command that was deferred. 533 */ waitForEvent()534 public MonkeyCommandReturn waitForEvent() { 535 switch(event) { 536 case ON_WINDOW_STATE_CHANGE: 537 try { 538 synchronized(MonkeySourceNetworkViews.class) { 539 MonkeySourceNetworkViews.class.wait(timeout); 540 } 541 } catch(InterruptedException e) { 542 Log.d(TAG, "Deferral interrupted: " + e.getMessage()); 543 } 544 } 545 return deferredReturn; 546 } 547 }; 548 549 private final CommandQueueImpl commandQueue = new CommandQueueImpl(); 550 551 private BufferedReader input; 552 private PrintWriter output; 553 private boolean started = false; 554 555 private ServerSocket serverSocket; 556 private Socket clientSocket; 557 MonkeySourceNetwork(int port)558 public MonkeySourceNetwork(int port) throws IOException { 559 // Only bind this to local host. This means that you can only 560 // talk to the monkey locally, or though adb port forwarding. 561 serverSocket = new ServerSocket(port, 562 0, // default backlog 563 InetAddress.getLocalHost()); 564 } 565 566 /** 567 * Start a network server listening on the specified port. The 568 * network protocol is a line oriented protocol, where each line 569 * is a different command that can be run. 570 * 571 * @param port the port to listen on 572 */ startServer()573 private void startServer() throws IOException { 574 clientSocket = serverSocket.accept(); 575 // At this point, we have a client connected. 576 // Attach the accessibility listeners so that we can start receiving 577 // view events. Do this before wake so we can catch the wake event 578 // if possible. 579 MonkeySourceNetworkViews.setup(); 580 // Wake the device up in preparation for doing some commands. 581 wake(); 582 583 input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); 584 // auto-flush 585 output = new PrintWriter(clientSocket.getOutputStream(), true); 586 } 587 588 /** 589 * Stop the server from running so it can reconnect a new client. 590 */ stopServer()591 private void stopServer() throws IOException { 592 clientSocket.close(); 593 MonkeySourceNetworkViews.teardown(); 594 input.close(); 595 output.close(); 596 started = false; 597 } 598 599 /** 600 * Helper function for commandLineSplit that replaces quoted 601 * charaters with their real values. 602 * 603 * @param input the string to do replacement on. 604 * @return the results with the characters replaced. 605 */ replaceQuotedChars(String input)606 private static String replaceQuotedChars(String input) { 607 return input.replace("\\\"", "\""); 608 } 609 610 /** 611 * This function splits the given line into String parts. It obey's quoted 612 * strings and returns them as a single part. 613 * 614 * "This is a test" -> returns only one element 615 * This is a test -> returns four elements 616 * 617 * @param line the line to parse 618 * @return the List of elements 619 */ commandLineSplit(String line)620 private static List<String> commandLineSplit(String line) { 621 ArrayList<String> result = new ArrayList<String>(); 622 StringTokenizer tok = new StringTokenizer(line); 623 624 boolean insideQuote = false; 625 StringBuffer quotedWord = new StringBuffer(); 626 while (tok.hasMoreTokens()) { 627 String cur = tok.nextToken(); 628 if (!insideQuote && cur.startsWith("\"")) { 629 // begin quote 630 quotedWord.append(replaceQuotedChars(cur)); 631 insideQuote = true; 632 } else if (insideQuote) { 633 // end quote 634 if (cur.endsWith("\"")) { 635 insideQuote = false; 636 quotedWord.append(" ").append(replaceQuotedChars(cur)); 637 String word = quotedWord.toString(); 638 639 // trim off the quotes 640 result.add(word.substring(1, word.length() - 1)); 641 } else { 642 quotedWord.append(" ").append(replaceQuotedChars(cur)); 643 } 644 } else { 645 result.add(replaceQuotedChars(cur)); 646 } 647 } 648 return result; 649 } 650 651 /** 652 * Translate the given command line into a MonkeyEvent. 653 * 654 * @param commandLine the full command line given. 655 */ translateCommand(String commandLine)656 private void translateCommand(String commandLine) { 657 Log.d(TAG, "translateCommand: " + commandLine); 658 List<String> parts = commandLineSplit(commandLine); 659 if (parts.size() > 0) { 660 MonkeyCommand command = COMMAND_MAP.get(parts.get(0)); 661 if (command != null) { 662 MonkeyCommandReturn ret = command.translateCommand(parts, commandQueue); 663 handleReturn(ret); 664 } 665 } 666 } 667 handleReturn(MonkeyCommandReturn ret)668 private void handleReturn(MonkeyCommandReturn ret) { 669 if (ret.wasSuccessful()) { 670 if (ret.hasMessage()) { 671 returnOk(ret.getMessage()); 672 } else { 673 returnOk(); 674 } 675 } else { 676 if (ret.hasMessage()) { 677 returnError(ret.getMessage()); 678 } else { 679 returnError(); 680 } 681 } 682 } 683 684 getNextEvent()685 public MonkeyEvent getNextEvent() { 686 if (!started) { 687 try { 688 startServer(); 689 } catch (IOException e) { 690 Log.e(TAG, "Got IOException from server", e); 691 return null; 692 } 693 started = true; 694 } 695 696 // Now, get the next command. This call may block, but that's OK 697 try { 698 while (true) { 699 // Check to see if we have any events queued up. If 700 // we do, use those until we have no more. Then get 701 // more input from the user. 702 MonkeyEvent queuedEvent = commandQueue.getNextQueuedEvent(); 703 if (queuedEvent != null) { 704 // dispatch the event 705 return queuedEvent; 706 } 707 708 // Check to see if we have any returns that have been deferred. If so, now that 709 // we've run the queued commands, wait for the given event to happen (or the timeout 710 // to be reached), and handle the deferred MonkeyCommandReturn. 711 if (deferredReturn != null) { 712 Log.d(TAG, "Waiting for event"); 713 MonkeyCommandReturn ret = deferredReturn.waitForEvent(); 714 deferredReturn = null; 715 handleReturn(ret); 716 } 717 718 String command = input.readLine(); 719 if (command == null) { 720 Log.d(TAG, "Connection dropped."); 721 // Treat this exactly the same as if the user had 722 // ended the session cleanly with a done commant. 723 command = DONE; 724 } 725 726 if (DONE.equals(command)) { 727 // stop the server so it can accept new connections 728 try { 729 stopServer(); 730 } catch (IOException e) { 731 Log.e(TAG, "Got IOException shutting down!", e); 732 return null; 733 } 734 // return a noop event so we keep executing the main 735 // loop 736 return new MonkeyNoopEvent(); 737 } 738 739 // Do quit checking here 740 if (QUIT.equals(command)) { 741 // then we're done 742 Log.d(TAG, "Quit requested"); 743 // let the host know the command ran OK 744 returnOk(); 745 return null; 746 } 747 748 // Do comment checking here. Comments aren't a 749 // command, so we don't echo anything back to the 750 // user. 751 if (command.startsWith("#")) { 752 // keep going 753 continue; 754 } 755 756 // Translate the command line. This will handle returning error/ok to the user 757 translateCommand(command); 758 } 759 } catch (IOException e) { 760 Log.e(TAG, "Exception: ", e); 761 return null; 762 } 763 } 764 765 /** 766 * Returns ERROR to the user. 767 */ returnError()768 private void returnError() { 769 output.println(ERROR_STR); 770 } 771 772 /** 773 * Returns ERROR to the user. 774 * 775 * @param msg the error message to include 776 */ returnError(String msg)777 private void returnError(String msg) { 778 output.print(ERROR_STR); 779 output.print(":"); 780 output.println(msg); 781 } 782 783 /** 784 * Returns OK to the user. 785 */ returnOk()786 private void returnOk() { 787 output.println(OK_STR); 788 } 789 790 /** 791 * Returns OK to the user. 792 * 793 * @param returnValue the value to return from this command. 794 */ returnOk(String returnValue)795 private void returnOk(String returnValue) { 796 output.print(OK_STR); 797 output.print(":"); 798 output.println(returnValue); 799 } 800 setVerbose(int verbose)801 public void setVerbose(int verbose) { 802 // We're not particualy verbose 803 } 804 validate()805 public boolean validate() { 806 // we have no pre-conditions to validate 807 return true; 808 } 809 } 810