• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 /*
3  * Copyright (C) 2011 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package com.android.chimpchat;
18 
19 import com.android.chimpchat.core.IChimpView;
20 import com.android.chimpchat.core.PhysicalButton;
21 import com.android.chimpchat.core.ChimpException;
22 import com.android.chimpchat.core.ChimpRect;
23 import com.android.chimpchat.core.ChimpView;
24 
25 import com.google.common.collect.Lists;
26 
27 import java.io.BufferedReader;
28 import java.io.BufferedWriter;
29 import java.io.IOException;
30 import java.io.InputStreamReader;
31 import java.io.OutputStreamWriter;
32 import java.net.Socket;
33 import java.net.SocketException;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.StringTokenizer;
39 import java.util.logging.Level;
40 import java.util.logging.Logger;
41 
42 /**
43  * Provides a nicer interface to interacting with the low-level network access protocol for talking
44  * to the monkey.
45  *
46  * This class is thread-safe and can handle being called from multiple threads.
47  */
48 public class ChimpManager {
49     private static Logger LOG = Logger.getLogger(ChimpManager.class.getName());
50 
51     private Socket monkeySocket;
52     private BufferedWriter monkeyWriter;
53     private BufferedReader monkeyReader;
54 
55     /**
56      * Create a new ChimpMananger to talk to the specified device.
57      *
58      * @param monkeySocket the already connected socket on which to send protocol messages.
59      * @throws IOException if there is an issue setting up the sockets
60      */
ChimpManager(Socket monkeySocket)61     public ChimpManager(Socket monkeySocket) throws IOException {
62         this.monkeySocket = monkeySocket;
63         monkeyWriter =
64                 new BufferedWriter(new OutputStreamWriter(monkeySocket.getOutputStream()));
65         monkeyReader = new BufferedReader(new InputStreamReader(monkeySocket.getInputStream()));
66     }
67 
68     /* Ensure that everything gets shutdown properly */
finalize()69     protected void finalize() throws Throwable {
70         try {
71             quit();
72         } finally {
73             close();
74             super.finalize();
75         }
76     }
77 
78     /**
79      * Send a touch down event at the specified location.
80      *
81      * @param x the x coordinate of where to click
82      * @param y the y coordinate of where to click
83      * @return success or not
84      * @throws IOException on error communicating with the device
85      */
touchDown(int x, int y)86     public boolean touchDown(int x, int y) throws IOException {
87         return sendMonkeyEvent("touch down " + x + " " + y);
88     }
89 
90     /**
91      * Send a touch down event at the specified location.
92      *
93      * @param x the x coordinate of where to click
94      * @param y the y coordinate of where to click
95      * @return success or not
96      * @throws IOException on error communicating with the device
97      */
touchUp(int x, int y)98     public boolean touchUp(int x, int y) throws IOException {
99         return sendMonkeyEvent("touch up " + x + " " + y);
100     }
101 
102     /**
103      * Send a touch move event at the specified location.
104      *
105      * @param x the x coordinate of where to click
106      * @param y the y coordinate of where to click
107      * @return success or not
108      * @throws IOException on error communicating with the device
109      */
touchMove(int x, int y)110     public boolean touchMove(int x, int y) throws IOException {
111         return sendMonkeyEvent("touch move " + x + " " + y);
112     }
113 
114     /**
115      * Send a touch (down and then up) event at the specified location.
116      *
117      * @param x the x coordinate of where to click
118      * @param y the y coordinate of where to click
119      * @return success or not
120      * @throws IOException on error communicating with the device
121      */
touch(int x, int y)122     public boolean touch(int x, int y) throws IOException {
123         return sendMonkeyEvent("tap " + x + " " + y);
124     }
125 
126     /**
127      * Press a physical button on the device.
128      *
129      * @param name the name of the button (As specified in the protocol)
130      * @return success or not
131      * @throws IOException on error communicating with the device
132      */
press(String name)133     public boolean press(String name) throws IOException {
134         return sendMonkeyEvent("press " + name);
135     }
136 
137     /**
138      * Send a Key Down event for the specified button.
139      *
140      * @param name the name of the button (As specified in the protocol)
141      * @return success or not
142      * @throws IOException on error communicating with the device
143      */
keyDown(String name)144     public boolean keyDown(String name) throws IOException {
145         return sendMonkeyEvent("key down " + name);
146     }
147 
148     /**
149      * Send a Key Up event for the specified button.
150      *
151      * @param name the name of the button (As specified in the protocol)
152      * @return success or not
153      * @throws IOException on error communicating with the device
154      */
keyUp(String name)155     public boolean keyUp(String name) throws IOException {
156         return sendMonkeyEvent("key up " + name);
157     }
158 
159     /**
160      * Press a physical button on the device.
161      *
162      * @param button the button to press
163      * @return success or not
164      * @throws IOException on error communicating with the device
165      */
press(PhysicalButton button)166     public boolean press(PhysicalButton button) throws IOException {
167         return press(button.getKeyName());
168     }
169 
170     /**
171      * This function allows the communication bridge between the host and the device
172      * to be invisible to the script for internal needs.
173      * It splits a command into monkey events and waits for responses for each over an adb tcp socket.
174      * Returns on an error, else continues and sets up last response.
175      *
176      * @param command the monkey command to send to the device
177      * @return the (unparsed) response returned from the monkey.
178      * @throws IOException on error communicating with the device
179      */
sendMonkeyEventAndGetResponse(String command)180     private String sendMonkeyEventAndGetResponse(String command) throws IOException {
181         command = command.trim();
182         LOG.info("Monkey Command: " + command + ".");
183 
184         // send a single command and get the response
185         monkeyWriter.write(command + "\n");
186         monkeyWriter.flush();
187         return monkeyReader.readLine();
188     }
189 
190     /**
191      * Parse a monkey response string to see if the command succeeded or not.
192      *
193      * @param monkeyResponse the response
194      * @return true if response code indicated success.
195      */
parseResponseForSuccess(String monkeyResponse)196     private boolean parseResponseForSuccess(String monkeyResponse) {
197         if (monkeyResponse == null) {
198             return false;
199         }
200         // return on ok
201         if(monkeyResponse.startsWith("OK")) {
202             return true;
203         }
204 
205         return false;
206     }
207 
208     /**
209      * Parse a monkey response string to get the extra data returned.
210      *
211      * @param monkeyResponse the response
212      * @return any extra data that was returned, or empty string if there was nothing.
213      */
parseResponseForExtra(String monkeyResponse)214     private String parseResponseForExtra(String monkeyResponse) {
215         int offset = monkeyResponse.indexOf(':');
216         if (offset < 0) {
217             return "";
218         }
219         return monkeyResponse.substring(offset + 1);
220     }
221 
222     /**
223      * This function allows the communication bridge between the host and the device
224      * to be invisible to the script for internal needs.
225      * It splits a command into monkey events and waits for responses for each over an
226      * adb tcp socket.
227      *
228      * @param command the monkey command to send to the device
229      * @return true on success.
230      * @throws IOException on error communicating with the device
231      */
sendMonkeyEvent(String command)232     private boolean sendMonkeyEvent(String command) throws IOException {
233         synchronized (this) {
234             String monkeyResponse = sendMonkeyEventAndGetResponse(command);
235             return parseResponseForSuccess(monkeyResponse);
236         }
237     }
238 
239     /**
240      * Close all open resources related to this device.
241      */
close()242     public void close() {
243         try {
244             monkeySocket.close();
245         } catch (IOException e) {
246             LOG.log(Level.SEVERE, "Unable to close monkeySocket", e);
247         }
248         try {
249             monkeyReader.close();
250         } catch (IOException e) {
251             LOG.log(Level.SEVERE, "Unable to close monkeyReader", e);
252         }
253         try {
254             monkeyWriter.close();
255         } catch (IOException e) {
256             LOG.log(Level.SEVERE, "Unable to close monkeyWriter", e);
257         }
258     }
259 
260     /**
261      * Function to get a static variable from the device.
262      *
263      * @param name name of static variable to get
264      * @return the value of the variable, or null if there was an error
265      * @throws IOException on error communicating with the device
266      */
getVariable(String name)267     public String getVariable(String name) throws IOException {
268         synchronized (this) {
269             String response = sendMonkeyEventAndGetResponse("getvar " + name);
270             if (!parseResponseForSuccess(response)) {
271                 return null;
272             }
273             return parseResponseForExtra(response);
274         }
275     }
276 
277     /**
278      * Function to get the list of variables from the device.
279      * @return the list of variables as a collection of strings
280      * @throws IOException on error communicating with the device
281      */
listVariable()282     public Collection<String> listVariable() throws IOException {
283         synchronized (this) {
284             String response = sendMonkeyEventAndGetResponse("listvar");
285             if (!parseResponseForSuccess(response)) {
286                 Collections.emptyList();
287             }
288             String extras = parseResponseForExtra(response);
289             return Lists.newArrayList(extras.split(" "));
290         }
291     }
292 
293     /**
294      * Tells the monkey that we are done for this session.
295      * @throws IOException on error communicating with the device
296      */
done()297     public void done() throws IOException {
298         // this command just drops the connection, so handle it here
299         synchronized (this) {
300             sendMonkeyEventAndGetResponse("done");
301         }
302     }
303 
304     /**
305      * Tells the monkey that we are done forever.
306      * @throws IOException on error communicating with the device
307      */
quit()308     public void quit() throws IOException {
309         // this command drops the connection, so handle it here
310         synchronized (this) {
311             try {
312                 sendMonkeyEventAndGetResponse("quit");
313             } catch (SocketException e) {
314                 // flush was called after the call had been written, so it tried flushing to a
315                 // broken pipe.
316             }
317         }
318     }
319 
320     /**
321      * Send a tap event at the specified location.
322      *
323      * @param x the x coordinate of where to click
324      * @param y the y coordinate of where to click
325      * @return success or not
326      * @throws IOException on error communicating with the device
327      */
tap(int x, int y)328     public boolean tap(int x, int y) throws IOException {
329         return sendMonkeyEvent("tap " + x + " " + y);
330     }
331 
332     /**
333      * Type the following string to the monkey.
334      *
335      * @param text the string to type
336      * @return success
337      * @throws IOException on error communicating with the device
338      */
type(String text)339     public boolean type(String text) throws IOException {
340         // The network protocol can't handle embedded line breaks, so we have to handle it
341         // here instead
342         StringTokenizer tok = new StringTokenizer(text, "\n", true);
343         while (tok.hasMoreTokens()) {
344             String line = tok.nextToken();
345             if ("\n".equals(line)) {
346                 boolean success = press(PhysicalButton.ENTER);
347                 if (!success) {
348                     return false;
349                 }
350             } else {
351                 boolean success = sendMonkeyEvent("type " + line);
352                 if (!success) {
353                     return false;
354                 }
355             }
356         }
357         return true;
358     }
359 
360     /**
361      * Type the character to the monkey.
362      *
363      * @param keyChar the character to type.
364      * @return success
365      * @throws IOException on error communicating with the device
366      */
type(char keyChar)367     public boolean type(char keyChar) throws IOException {
368         return type(Character.toString(keyChar));
369     }
370 
371     /**
372      * Wake the device up from sleep.
373      * @throws IOException on error communicating with the device
374      */
wake()375     public void wake() throws IOException {
376         sendMonkeyEvent("wake");
377     }
378 
379 
380     /**
381      * Retrieves the list of view ids from the current application.
382      * @return the list of view ids as a collection of strings
383      * @throws IOException on error communicating with the device
384      */
listViewIds()385     public Collection<String> listViewIds() throws IOException {
386         synchronized (this) {
387             String response = sendMonkeyEventAndGetResponse("listviews");
388             if (!parseResponseForSuccess(response)) {
389                 Collections.emptyList();
390             }
391             String extras = parseResponseForExtra(response);
392             return Lists.newArrayList(extras.split(" "));
393         }
394     }
395 
396     /**
397      * Queries the on-screen view with the given id and returns the response.
398      * It's up to the calling method to parse the returned String.
399      * @param idType The type of ID to query the view by
400      * @param id The view id of the view
401      * @param query the query
402      * @return the response from the query
403      * @throws IOException on error communicating with the device
404      */
queryView(String idType, List<String> ids, String query)405     public String queryView(String idType, List<String> ids, String query) throws IOException {
406         StringBuilder monkeyCommand = new StringBuilder("queryview " + idType + " ");
407         for(String id : ids) {
408             monkeyCommand.append(id).append(" ");
409         }
410         monkeyCommand.append(query);
411         synchronized (this) {
412             String response = sendMonkeyEventAndGetResponse(monkeyCommand.toString());
413             if (!parseResponseForSuccess(response)) {
414                 throw new ChimpException(parseResponseForExtra(response));
415             }
416             return parseResponseForExtra(response);
417         }
418     }
419 
420     /**
421      * Returns the current root view of the device.
422      * @return the root view of the device
423      */
getRootView()424     public IChimpView getRootView() throws IOException {
425         synchronized(this) {
426             String response = sendMonkeyEventAndGetResponse("getrootview");
427             String extra = parseResponseForExtra(response);
428             List<String> ids = Arrays.asList(extra.split(" "));
429             if (!parseResponseForSuccess(response) || ids.size() != 2) {
430                 throw new ChimpException(extra);
431             }
432             ChimpView root = new ChimpView(ChimpView.ACCESSIBILITY_IDS, ids);
433             root.setManager(this);
434             return root;
435         }
436     }
437 
438     /**
439      * Queries the device for a list of views with the given
440      * @return A string containing the accessibility ids of the views with the given text
441      */
getViewsWithText(String text)442     public String getViewsWithText(String text) throws IOException {
443         synchronized(this) {
444             // Monkey has trouble parsing a single word in quotes
445             if (text.split(" ").length > 1) {
446                 text = "\"" + text + "\"";
447             }
448             String response = sendMonkeyEventAndGetResponse("getviewswithtext " + text);
449             if (!parseResponseForSuccess(response)) {
450                 throw new ChimpException(parseResponseForExtra(response));
451             }
452             return parseResponseForExtra(response);
453         }
454     }
455 }
456