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