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.

38 comments:

  1. 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.

    ReplyDelete
  2. 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.

    ReplyDelete
  3. 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!

    ReplyDelete
  4. 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.

    ReplyDelete
  5. @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.

    ReplyDelete
  6. @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.

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

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

    ReplyDelete
  9. 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)

    ReplyDelete
  10. @Rahul parekh, @Diego

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

    ReplyDelete
  11. 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)

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

    ReplyDelete
  13. @Diego
    Thanks, a silly mistake it is working.

    Is there a way to do screenshot comparision using chimpchat?

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete
  15. @Diego

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

    ReplyDelete
  16. 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.

    ReplyDelete
  17. 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 ?

    ReplyDelete
  18. 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?

    ReplyDelete
  19. This comment has been removed by the author.

    ReplyDelete
  20. 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.

    ReplyDelete
  21. 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

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

    ReplyDelete
  23. 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

    ReplyDelete
  24. 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

    ReplyDelete
  25. 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.

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

    ReplyDelete
  27. 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

    ReplyDelete
  28. @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.

    ReplyDelete
  29. @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);

    ReplyDelete
  30. 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?

    ReplyDelete
  31. @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.

    ReplyDelete
  32. 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?

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

    $ sudo apt-get install ia32-libs

    Does adb run from your command line?

    $ /path/to/adb

    ReplyDelete
  34. This comment has been removed by the author.

    ReplyDelete
  35. 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 ...

    ReplyDelete
  36. Erfahren Sie, warum die Einstellung von IT-Spezialisten aus der Ukraine eine kluge Wahl ist. Die Ukraine hat sich als globaler Hotspot für talentierte und erfahrene Fachkräfte etabliert, insbesondere im Bereich der IT. Die Zusammenarbeit mit ukrainischen Experten ermöglicht es Unternehmen, hochqualifiziertes Personal zu wettbewerbsfähigen Preisen einzustellen. Von Laravel-Entwicklern über Machine Learning-Experten bis hin zu Microsoft Dynamics-Entwicklern bieten ukrainische IT-Spezialisten eine breite Palette von Fähigkeiten und Erfahrungen. Nutzen Sie diese Möglichkeit, um Ihr Team zu erweitern und den Erfolg Ihres Unternehmens voranzutreiben. Dieser Artikel

    ReplyDelete