/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.monkeyrunner; import com.google.common.collect.Lists; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; import java.util.Collection; import java.util.Collections; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; /** * Provides a nicer interface to interacting with the low-level network access protocol for talking * to the monkey. * * This class is thread-safe and can handle being called from multiple threads. */ public class MonkeyManager { private static Logger LOG = Logger.getLogger(MonkeyManager.class.getName()); private Socket monkeySocket; private BufferedWriter monkeyWriter; private BufferedReader monkeyReader; /** * Create a new MonkeyMananger to talk to the specified device. * * @param monkeySocket the already connected socket on which to send protocol messages. */ public MonkeyManager(Socket monkeySocket) { try { this.monkeySocket = monkeySocket; monkeyWriter = new BufferedWriter(new OutputStreamWriter(monkeySocket.getOutputStream())); monkeyReader = new BufferedReader(new InputStreamReader(monkeySocket.getInputStream())); } catch(IOException e) { throw new RuntimeException(e); } } /** * Send a touch down event at the specified location. * * @param x the x coordinate of where to click * @param y the y coordinate of where to click * @return success or not * @throws IOException on error communicating with the device */ public boolean touchDown(int x, int y) throws IOException { return sendMonkeyEvent("touch down " + x + " " + y); } /** * Send a touch down event at the specified location. * * @param x the x coordinate of where to click * @param y the y coordinate of where to click * @return success or not * @throws IOException on error communicating with the device */ public boolean touchUp(int x, int y) throws IOException { return sendMonkeyEvent("touch up " + x + " " + y); } /** * Send a touch move event at the specified location. * * @param x the x coordinate of where to click * @param y the y coordinate of where to click * @return success or not * @throws IOException on error communicating with the device */ public boolean touchMove(int x, int y) throws IOException { return sendMonkeyEvent("touch move " + x + " " + y); } /** * Send a touch (down and then up) event at the specified location. * * @param x the x coordinate of where to click * @param y the y coordinate of where to click * @return success or not * @throws IOException on error communicating with the device */ public boolean touch(int x, int y) throws IOException { return sendMonkeyEvent("tap " + x + " " + y); } /** * Press a physical button on the device. * * @param name the name of the button (As specified in the protocol) * @return success or not * @throws IOException on error communicating with the device */ public boolean press(String name) throws IOException { return sendMonkeyEvent("press " + name); } /** * Send a Key Down event for the specified button. * * @param name the name of the button (As specified in the protocol) * @return success or not * @throws IOException on error communicating with the device */ public boolean keyDown(String name) throws IOException { return sendMonkeyEvent("key down " + name); } /** * Send a Key Up event for the specified button. * * @param name the name of the button (As specified in the protocol) * @return success or not * @throws IOException on error communicating with the device */ public boolean keyUp(String name) throws IOException { return sendMonkeyEvent("key up " + name); } /** * Press a physical button on the device. * * @param button the button to press * @return success or not * @throws IOException on error communicating with the device */ public boolean press(PhysicalButton button) throws IOException { return press(button.getKeyName()); } /** * This function allows the communication bridge between the host and the device * to be invisible to the script for internal needs. * It splits a command into monkey events and waits for responses for each over an adb tcp socket. * Returns on an error, else continues and sets up last response. * * @param command the monkey command to send to the device * @return the (unparsed) response returned from the monkey. */ private String sendMonkeyEventAndGetResponse(String command) throws IOException { command = command.trim(); LOG.info("Monkey Command: " + command + "."); // send a single command and get the response monkeyWriter.write(command + "\n"); monkeyWriter.flush(); return monkeyReader.readLine(); } /** * Parse a monkey response string to see if the command succeeded or not. * * @param monkeyResponse the response * @return true if response code indicated success. */ private boolean parseResponseForSuccess(String monkeyResponse) { if (monkeyResponse == null) { return false; } // return on ok if(monkeyResponse.startsWith("OK")) { return true; } return false; } /** * Parse a monkey response string to get the extra data returned. * * @param monkeyResponse the response * @return any extra data that was returned, or empty string if there was nothing. */ private String parseResponseForExtra(String monkeyResponse) { int offset = monkeyResponse.indexOf(':'); if (offset < 0) { return ""; } return monkeyResponse.substring(offset + 1); } /** * This function allows the communication bridge between the host and the device * to be invisible to the script for internal needs. * It splits a command into monkey events and waits for responses for each over an * adb tcp socket. * * @param command the monkey command to send to the device * @return true on success. */ private boolean sendMonkeyEvent(String command) throws IOException { synchronized (this) { String monkeyResponse = sendMonkeyEventAndGetResponse(command); return parseResponseForSuccess(monkeyResponse); } } /** * Close all open resources related to this device. */ public void close() { try { monkeySocket.close(); } catch (IOException e) { LOG.log(Level.SEVERE, "Unable to close monkeySocket", e); } try { monkeyReader.close(); } catch (IOException e) { LOG.log(Level.SEVERE, "Unable to close monkeyReader", e); } try { monkeyWriter.close(); } catch (IOException e) { LOG.log(Level.SEVERE, "Unable to close monkeyWriter", e); } } /** * Function to get a static variable from the device. * * @param name name of static variable to get * @return the value of the variable, or null if there was an error */ public String getVariable(String name) throws IOException { synchronized (this) { String response = sendMonkeyEventAndGetResponse("getvar " + name); if (!parseResponseForSuccess(response)) { return null; } return parseResponseForExtra(response); } } /** * Function to get the list of static variables from the device. */ public Collection<String> listVariable() throws IOException { synchronized (this) { String response = sendMonkeyEventAndGetResponse("listvar"); if (!parseResponseForSuccess(response)) { Collections.emptyList(); } String extras = parseResponseForExtra(response); return Lists.newArrayList(extras.split(" ")); } } /** * Tells the monkey that we are done for this session. * @throws IOException */ public void done() throws IOException { // this command just drops the connection, so handle it here synchronized (this) { sendMonkeyEventAndGetResponse("done"); } } /** * Tells the monkey that we are done forever. * @throws IOException */ public void quit() throws IOException { // this command drops the connection, so handle it here synchronized (this) { sendMonkeyEventAndGetResponse("quit"); } } /** * Send a tap event at the specified location. * * @param x the x coordinate of where to click * @param y the y coordinate of where to click * @return success or not * @throws IOException * @throws IOException on error communicating with the device */ public boolean tap(int x, int y) throws IOException { return sendMonkeyEvent("tap " + x + " " + y); } /** * Type the following string to the monkey. * * @param text the string to type * @return success * @throws IOException */ public boolean type(String text) throws IOException { // The network protocol can't handle embedded line breaks, so we have to handle it // here instead StringTokenizer tok = new StringTokenizer(text, "\n", true); while (tok.hasMoreTokens()) { String line = tok.nextToken(); if ("\n".equals(line)) { boolean success = press(PhysicalButton.ENTER); if (!success) { return false; } } else { boolean success = sendMonkeyEvent("type " + line); if (!success) { return false; } } } return true; } /** * Type the character to the monkey. * * @param keyChar the character to type. * @return success * @throws IOException */ public boolean type(char keyChar) throws IOException { return type(Character.toString(keyChar)); } /** * Wake the device up from sleep. * @throws IOException */ public void wake() throws IOException { sendMonkeyEvent("wake"); } }