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