Thursday, November 10, 2011

Android: Using monkey from Java


The latest version of the Android SDK and tools include chimpchat, a library that facilitates the use of monkey from Java. This is equivalent to monkeyrunner, which is the bridge between monkey and the Python scripting language.
While Python is an incredibly powerful and expressive scripting language and will permit you creating tests with just a few statements, there are some occasions when you don't want to introduce a new language to the project leaving your Java confort zone or you prefer to leverage the use of previously created libraries instead of writing new ones.
In such cases, you can now have the same access to monkey running on the device with the help of chimpchat, as we are going to demonstrate.


Creating a Java project
Our first step will be to create a new Java project and we will add the required libraries to the Java Build Path as External Jars.
We are naming the project JavaMonkey, for obvious reasons.




We are adding these libraries from Android SDK, which are used directly or indirectly by our project, to the Java Build Path:

  • chimpchat.jar
  • ddmlib.jar
  • guavalib.jar
  • sdklib.jar


JavaMonkey.java
Our intention is to create a simple class, serving the purpose of a simple example to get as started. We will be simply:

  1. Creating a JavaMonkey object
  2. initializing it, this implies creating the connection with any emulator or device found or throwing an exception is not connection was made before the timeout expires
  3. listing all the properties in the device or emulator
  4. shutting down the connection

Following, is the JavaMonkey class: 



/**
 * Copyright (C) 2011  Diego Torres Milano
 */
package com.example.javamonkey;

import java.util.TreeMap;

import com.android.chimpchat.ChimpChat;
import com.android.chimpchat.core.IChimpDevice;

/**
 * @author diego
 *
 */
public class JavaMonkey {

        private static final String ADB = "/Users/diego/opt/android-sdk/platform-tools/adb";
        private static final long TIMEOUT = 5000;
        private ChimpChat mChimpchat;
        private IChimpDevice mDevice;

        /**
         * Constructor
         */
        public JavaMonkey() {
                super();
        TreeMap<String, String> options = new TreeMap<String, String>();
        options.put("backend", "adb");
        options.put("adbLocation", ADB);
        mChimpchat = ChimpChat.getInstance(options);
        }

        /**
         * Initializes the JavaMonkey.
         */
        private void init() {
                mDevice = mChimpchat.waitForConnection(TIMEOUT, ".*");
                if ( mDevice == null ) {
                        throw new RuntimeException("Couldn't connect.");
                }
                mDevice.wake();
        }

        /**
         * List all properties.
         */
        private void listProperties() {
                if ( mDevice == null ) {
                        throw new IllegalStateException("init() must be called first.");
                }
                for (String prop: mDevice.getPropertyList()) {
                        System.out.println(prop + ": " + mDevice.getProperty(prop));
                }
        }

        /**
         * Terminates this JavaMonkey.
         */
        private void shutdown() {
                mChimpchat.shutdown();
                mDevice = null;
        }

        /**
         * @param args
         */
        public static void main(String[] args) {
                final JavaMonkey javaMonkey = new JavaMonkey();
                javaMonkey.init();
                javaMonkey.listProperties();
                javaMonkey.shutdown();
        }

}


Configuration
One of the important things you have to adapt to your environment is the location of the adb command. Otherwise if you don't set it you will receive:

E/adb: Failed to get the adb version: Cannot run program "adb": error=2, No such file or directory


Hope this helps you get started with chimpchat. As always, comments and questions are always welcome.

37 comments:

obartley said...

Hi Diego, great post, very helpful, but I'm having an issue, whenever I try to run my java code against a physical device, I inevitably encounter an error:

INFO: Error starting command: monkey --port 12345
com.android.ddmlib.ShellCommandUnresponsiveException
at com.android.ddmlib.AdbHelper.executeRemoteCommand(AdbHelper.java:408)
at com.android.ddmlib.Device.executeShellCommand(Device.java:311)
at com.android.chimpchat.adb.AdbChimpDevice$1.run(AdbChimpDevice.java:105)
.
.

I don't see this against an emulator, so I was wondering if you had encountered this situation before? It seems like the default constructor for AdbChimpDevice asynchronously issues the shell command 'monkey --port 12345' which hangs, but I haven't been able to determine why it only affects physical devices.

Diego Torres Milano said...

Perhaps money is not installed on the device you are using for testing. I've run it on my Nexus One with no problems.
Try opening a shell and running monkey from the command line to see if it's there.

Rahul Parekh said...

Thanks Diego for the great post. This got me started quickly and am able to install a package.

However I am unable to figure out how to start an activity. I was able to do this using the example you have for Python (MonkeyRunner)

runComponent = package + '/' + activity

device.startActivity(component=runComponent)

But in chimpChat startActivity() seem to need a lot more parameters and I am unable to figure out what to pass.

I was wondering if you could help me with this.

Thanks!

dami fernández said...

Hi Diego, thanks for your posts. I have a problem when i call startActivity method, like Rahul Parekh said. There are a lot of arguments and i can't understand some of them. I'd be pleased if you had some advice for that.

Diego Torres Milano said...

@Rahul Parekh,
Thanks for your comments.
Unfortunately chimpchat is not very clever nor careful about the parameters you pass to some methods and just crashes. So be cautious.
This is an example that works, I hope you can understand is as it will be squished by blogger. I hope you can reconstruct it by Eclipse:

if ( START_ACTIVITY ) {
IChimpDevice device = javaMonkey.getDevice();
String uri = null;
String action = "android.intent.action.MAIN";
String data = null;
String mimeType = null;
Collection categories = new ArrayList();
Map extras = new HashMap();
String pkg = "com.example.i2at.tc";
String activity = "TemperatureConverterActivity";
String component = pkg + "/." + activity;
int flags = 0;
/**
* Start an activity.
*
* @param uri the URI for the Intent
* @param action the action for the Intent
* @param data the data URI for the Intent
* @param mimeType the mime type for the Intent
* @param categories the category names for the Intent
* @param extras the extras to add to the Intent
* @param component the component of the Intent
* @param flags the flags for the Intent
*
* void startActivity(@Nullable String uri, @Nullable String action,
* @Nullable String data, @Nullable String mimeType,
* Collection categories, Map extras, @Nullable String component,
* int flags);
*/
device.startActivity(uri, action, data, mimeType, categories, extras, component, flags);
}


You can try setting some of the parameters to null (i.e. categories) and see how it collapses and give you the completely misleading error:

INFO: Error starting command: monkey --port 12345

Enjoy.

Diego Torres Milano said...

@Rahul Parekh,
Thanks for your comments.
Unfortunately chimpchat is not very clever nor careful about the parameters you pass to some methods and just crashes. So be cautious.
This is an example that works, I hope you can understand is as it will be squished by blogger. I hope you can reconstruct it by Eclipse:

if ( START_ACTIVITY ) {
IChimpDevice device = javaMonkey.getDevice();
String uri = null;
String action = "android.intent.action.MAIN";
String data = null;
String mimeType = null;
Collection categories = new ArrayList();
Map extras = new HashMap();
String pkg = "com.example.i2at.tc";
String activity = "TemperatureConverterActivity";
String component = pkg + "/." + activity;
int flags = 0;
/**
* Start an activity.
*
* @param uri the URI for the Intent
* @param action the action for the Intent
* @param data the data URI for the Intent
* @param mimeType the mime type for the Intent
* @param categories the category names for the Intent
* @param extras the extras to add to the Intent
* @param component the component of the Intent
* @param flags the flags for the Intent
*
* void startActivity(@Nullable String uri, @Nullable String action,
* @Nullable String data, @Nullable String mimeType,
* Collection categories, Map extras, @Nullable String component,
* int flags);
*/
device.startActivity(uri, action, data, mimeType, categories, extras, component, flags);
}


You can try setting some of the parameters to null (i.e. categories) and see how it collapses and give you the completely misleading error:

INFO: Error starting command: monkey --port 12345

Enjoy.

Diego Torres Milano said...

@LH,
Thanks for your comments. Last comment should solve your problem too.

Rahul Parekh said...

Thanks Diego. I was able to do something similar to what you have mentioned and that resolved my issue.

Anonymous said...

Hi, when I include this statement-
for (String prop: mDevice.getViewIdList()) {
System.out.println(prop + ": " + mDevice.getProperty(prop));
}

I get following exception:

Apr 14, 2012 6:42:21 PM com.android.chimpchat.ChimpManager sendMonkeyEventAndGetResponse
INFO: Monkey Command: listviews.
Apr 14, 2012 6:42:24 PM com.android.chimpchat.adb.AdbChimpDevice$1 run
INFO: Error starting command: monkey --port 12345
com.android.ddmlib.ShellCommandUnresponsiveException
at com.android.ddmlib.AdbHelper.executeRemoteCommand(AdbHelper.java:408)
at com.android.ddmlib.Device.executeShellCommand(Device.java:388)
at com.android.chimpchat.adb.AdbChimpDevice$1.run(AdbChimpDevice.java:105)
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

Exception in thread "main" java.lang.NullPointerException
at com.android.chimpchat.ChimpManager.parseResponseForExtra(ChimpManager.java:215)
at com.android.chimpchat.ChimpManager.listViewIds(ChimpManager.java:391)
at com.android.chimpchat.adb.AdbChimpDevice.getViewIdList(AdbChimpDevice.java:585)
at com.example.javamonkey.JavaMonkey.listProperties(JavaMonkey.java:59)
at com.example.javamonkey.JavaMonkey.main(JavaMonkey.java:78)

Anand P said...

@Rahul parekh, @Diego

In previous comment you have told the issue is resolved regarding the chimpchat startactivity. Could please help me.

Anand P said...

I am not able to compile as it shows compiler error:


The method startActivity(String, String, String, String, Collection, Map, String, int) in the type
IChimpDevice is not applicable for the arguments (String, String, null, null, Collection, Map, String, String)

Diego Torres Milano said...

Flags (last argument for startActivity()) is int, you are passing a String.

Anand P said...

@Diego
Thanks, a silly mistake it is working.

Is there a way to do screenshot comparision using chimpchat?

Anand P said...
This comment has been removed by the author.
Anand P said...

@Diego

Can i click on a view with the help of the text on view with Chimpchat.

Diego Torres Milano said...

@Anand P,
This articles may help you:

monkeyrunner: testing Views properties

AndroidViewClient: Q&A

David M said...

This example works wonderfully for me when created in a java project, but when I move the class into an android project and run it, this line...
mChimpchat = ChimpChat.getInstance(options);
seems to cause the android tablet to produce an error reading "unfortunately javamonkey has stopped". Am I missing something I need to add or is this command in some way incompatible with Android projects? Any help is appreciated, thanks.

Diego Torres Milano said...

Well, chimpchat is intended to be used by Java apps connecting to android devices.
What do you want to achieve using it in an Android app ?

David M said...

I am creating a project that is basically like a mouse for android that can connect to a tablet and move around a cursor on the screen over the other applications and can click on and open other applications. I'm somewhat new to android developing so I assumed that I would need to create an android app instead of a java project to make the program available in an app store and to take advantage of android's other features, like views. Is it possible to do all of this with just a java project? Or is there another software library better suited for what I want to do?

Ashwani Kumar said...
This comment has been removed by the author.
Ashwani Kumar said...

Hi, I am getting the following error:
Traceback (most recent call last):
File "C:\Documents and Settings\gur31265\workspace\MonkeyRunnerForSmartRecorder\com\test\Runner.py", line 23, in
from com.dtmilano.android.viewclient import ViewClient
ImportError: No module named dtmilano.android.viewclient

I had added a source folderpath in PYTHONPATH property of pyDev. I am using eclipse. I had also set ANDROID_VIEW_CLIENT_HOME in system path but with that i get Unresolved import: ViewClient error. I am using Windows 7 OS. please help.

Diego Torres Milano said...

@Ashwani Kumar,
I think the post monkeyrunner: importing from PYTHONPATH may answer you question.

Ashwani Kumar said...

Hi Diego,
The problem is still there. Even the script mentioned on the page you referenced too have unresolved import errors. What i want to know is what all i have to add to sys.path in order to make these scripts working. And the mechanism through which i need to add those libraries.
Thanks

Diego Torres Milano said...

@Ashwani Kumar,
Post some detail on what you did and the error messages you received if any.

Ashwani Kumar said...

Error:
Traceback (most recent call last):
File "C:\Users\gur31265\workspace\MonkeyRunnerForSmartRecorder\com\test\Runner.py", line 23, in
from com.dtmilano.android.viewclient import ViewClient
ImportError: No module named dtmilano.android.viewclient

PythonPath:
D:\eclipse-jee-juno-win32\eclipse\plugins\org.python.pydev_2.6.0.2012062818\pysrc\pydev_sitecustomize;
C:\Users\gur31265\workspace\MonkeyRunnerForSmartRecorder;
D:\E & F Drive Back up\Program Files\Android\android-sdk\tools\lib\monkeyrunner.jar;
C:\Program Files\Python32\DLLs;
C:\Program Files\Python32\lib;
C:\Program Files\Python32;
C:\Program Files\Python32\lib\site-packages

sys.path:
C:\\Users\\gur31265\\workspace\\MonkeyRunnerForSmartRecorder\\com\\test
C:\\Users\\gur31265\\workspace\\MonkeyRunnerForSmartRecorder
D:\\E & F Drive Back up\\Program Files\\Android\\android-sdk\\tools\\lib\\monkeyrunner.jar
C:\\Program Files\\Python32\\DLLs
C:\\Program Files\\Python32\\lib
C:\\Program Files\\Python32
C:\\Program Files\\Python32\\lib\\site-packages
C:\\Windows\\system32\\python32.zip
C:\\Users\\gur31265\\Downloads\\dtmilano-AndroidViewClient-bd98f63\\AndroidViewClient/src

I used the same script given at:
http://dtmilano.blogspot.ca/2012/02/monkeyrunner-interacting-with-views.html

Ashwani Kumar said...

if i comment the following line:

from com.dtmilano.android.viewclient import ViewClient

then i get this error:

Traceback (most recent call last):
File "C:\Users\gur31265\workspace\MonkeyRunner\com\htc\monkey\runner\MonkeyRunner.py", line 21, in
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice
ImportError: No module named android.monkeyrunner

Unknown said...

INFO: Error starting command: monkey --port 12345
com.android.ddmlib.ShellCommandUnresponsiveException

This error occur when you run the multiple instance of above code. To avoid this please use object lock or boolean flag.

Diego Torres Milano said...

@Manish Singla,
They will be running on different JVMs so simple object lock or boolean flag won't work.
Are you suggesting something different?

Mphozor said...

Hi @Diego by adapting your environment what excatly do you mean ? I can run adb from the terminal just by typing adb but here on the chimpchat I get "E/adb: Failed to get the adb version: Cannot run program "adb": error=2, No such file or directory from 'adb' - exists=false"
I am using a Mac

Diego Torres Milano said...

@Mphozor,
Check how the constant ADB is set and adapt it to your installation, full path to adb. My example was run on OS X so you will have no problems.

Anonymous said...

@Mphozor

set your adb location

options = new TreeMap();
options.put("adbLocation", "/opt/android-sdk-macosx/platform-tools/adb");
options.put("backend", "adb");
chimpchat = ChimpChat.getInstance(options);

Tariq Ghalib said...

Hey Diego,

When I try to run this program, I am getting following error.

Exception in thread "main" java.lang.NullPointerException
at automate.init(automate. java:26)
at automate.main(automate. java:57)

Its causing issues on lines where I am doing mChimchat.waitForConnection(TIMEOUT, "Device ID")

Also, when we use .* for device ID what does that imply?

Diego Torres Milano said...

@Rah-e-Huda,
The program works "as is", try to run an unmodified version first to check that you don't have any problems in your setup in general.

Serial number can be specified as a regex, then .* matches anything.

Unknown said...

I keep getting this dreaded error:
Cannot run program "/home/asco/adt-bundle-linux-x86_64/sdk/platform-tools": error=13, Permission denied' while attempting to get adb version from '/home/asco/adt-bundle-linux-x86_64/sdk/platform-tools'

both on a device and an emulator, i chmod777 ed the adb program and the containing directory, tried running 'adb root', added adb to my bashrc. Does anyone have a suggestion what the problem could be here?

Diego Torres Milano said...

@florian weigl,
Do you have 32bit libs installed?

$ sudo apt-get install ia32-libs

Does adb run from your command line?

$ /path/to/adb

Unknown said...
This comment has been removed by the author.
Unknown said...

Hi Diego, thanks for your reply. I have the ia32-libs installed, I even installed a 32bit linux to see if that's the problem, same thing. Adb runs fine from the command line as a normal user. I read about that error on SO and the likes and tried every proposed solution, to no avail :(
Will your program work from Windows? I guess I'll try that next ...