12

I am doing a generic automation script.

I need to send complex swipe events to the android screen without specifically having access to the focused application(s)

Best way I figured so far is to use adb, create a file with sendevent commands, push it on the device and run it from there. Even that, it is painfully slow (much slower compared to if I record it with getevent and pipe it back in).

I managed to optimize the file since I figured out that each sendevent block does not specifically require both X and Y, but it is still a few orders of magnitude slower

Example of part of the file (I'm trying on a HTC One):

sendevent /dev/input/event5 3 57 49
sendevent /dev/input/event5 3 53 942
sendevent /dev/input/event5 3 54 2747
sendevent /dev/input/event5 0 0 0

sendevent /dev/input/event5 3 53 1207
sendevent /dev/input/event5 3 54 2483
sendevent /dev/input/event5 0 0 0

sendevent /dev/input/event5 3 53 1472
sendevent /dev/input/event5 0 0 0

sendevent /dev/input/event5 3 54 2218
sendevent /dev/input/event5 0 0 0

sendevent /dev/input/event5 3 53 1207
sendevent /dev/input/event5 3 54 2483
sendevent /dev/input/event5 0 0 0

sendevent /dev/input/event5 3 53 1472

So my focus is to optimize the speed of single long-complex swipes, not of multiple small ones.

Anyone know of a better way to do this?


So, Chris Stratton's idea worked in principle (re-piping the cat-ed output generates the same swipe successfully), but I can't be able to create my own code to pipe it back in. I'm guessing it's something to do with the separators between send event commands... but I still can't get it to work

I used a modification of the sendevent.c file to get a file with triples per line and output to another file. Do you happen to know what could be the issue? Conversion looks good ...


SOLLUTION: I managed to solve it, mostly thanks to the answers bellow. Here is a C script that takes a file with HEX values and outputs the appropriate binary file.

Usage: (for me the touch driver file is /dev/input/event5 - HTC One - for other devices it might be a different file !!!)

 $> adb shell getevent > tmp.in
 $> ./sendevent tmp.in tmp.out
 $> adb shell push tmp.out /mnt/sdcard/
 $> adb shell "cd /mnt/sdcard/ && cat tmp.out > /dev/input/event5"

and the source:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>

typedef uint32_t        __u32;
typedef uint16_t        __u16;
typedef __signed__ int  __s32;

__attribute__((aligned(1),packed)) struct input_event {
    __u32 time_dummy_1;
    __u32 time_dummy_2;
    __u16 type;
    __u16 code;
    __s32 value;
};

int convert (char * str) {
    return (int) strtol(str, NULL, 16);
}

#define S_ALL (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH)

int main (int argc, char *argv[]) {
    int i;
    int fd;
    int ret;

    if(argc < 3) {
        fprintf(stderr, "use: %s in-file out-file\n", argv[0]);
        return 1;
    }

    fd = open(argv[2], O_CREAT | O_WRONLY, S_ALL);
    if(fd < 0) {
        fprintf(stderr, "could not open %s, %s\n", argv[2], strerror(errno));
        return 1;
    }

    FILE * fd_in = fopen(argv[1], "r");
    if (fd_in == NULL) {
        fprintf(stderr, "Can't open input file: %s\n", argv[1]);
        return 1;
    }

    struct input_event event;
    char type[32];
    char code[32];
    char value[32];
    int count = 0;
    while (fscanf(fd_in, "%s %s %s", type, code, value) != EOF) {
        memset(&event, 0, sizeof(event));
        // printf("%d) %s %s %s\n", ++count, type, code, value);
        event.type = convert(type);
        event.code = convert(code);
        event.value = convert(value);
        memset(type, 0, sizeof(type));
        memset(code, 0, sizeof(code));
        memset(value, 0, sizeof(value));
        ret = write(fd, &event, sizeof(event));
        if(ret < sizeof(event)) {
            fprintf(stderr, "write event failed, %s\n", strerror(errno));
            return -1;
        }
    }

    return 0;
}
Stefan
  • 3,962
  • 4
  • 34
  • 39
  • I know this is an old question, but would you be able to explain how to get this new sendevents.c file onto the android device, i presume you would use adb push but dont know what directory i can place the file on so that it is an executable. – Michael Kent Apr 17 '14 at 10:35
  • I have now created a question as that is more appropriate. your help would be appreciated. http://stackoverflow.com/questions/23131081/adding-a-custom-tool-to-the-android-adb-shell – Michael Kent Apr 17 '14 at 10:48
  • I posted an answer to your question. Hope it helps: http://stackoverflow.com/questions/23131081/adding-a-custom-tool-to-the-android-adb-shell/23135779#23135779 – Stefan Apr 17 '14 at 14:25

2 Answers2

13

Please note that this answer pertains to circa-2013 versions of Android and may not apply to current ones. Jellybean was contemporary at the time, Kitkat came out a few weeks after the question was asked

Your delay is likely a result of inefficiently having to repeatedly launch a new sendevent process, parse the textual event record, and open the device node - for each individual event. If you instead do everything from within a single process, opening the device file only once, it will be much more efficient.

If we look at the source for sendevent in toolbox contemporary with the date of the question (for example, https://android.googlesource.com/platform/system/core/+/jb-release/toolbox/sendevent.c ) we see that the core of what it is doing is encoding the events into binary records

struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};

and writing them to the appropriate device

memset(&event, 0, sizeof(event));
event.type = atoi(argv[2]);
event.code = atoi(argv[3]);
event.value = atoi(argv[4]);
ret = write(fd, &event, sizeof(event));

Provided that you are executing something as the shell userid or another in the input unix group, you should be able to accomplish the same thing that sendevent does from your own custom program, or using other command line tools like cat, thus efficiently pushing a binary file of event records.

For example

adb shell
cd /mnt/sdcard
cat /dev/input/event2 > events

Do a few touch screen events, then ctrl-C to kill cat

Now you can play back the captured file of binary events:

cat events > /dev/input/event2 

(Note: sendevent is zeroing the timeval part of each record; recording and playback may not do that; you'll have to see, and if it matters zero those portions of each record from the file before you write it back)

Chris Stratton
  • 39,853
  • 6
  • 84
  • 117
  • that's seems like a great idea. lemme try it out and I'll get back to you – Stefan Oct 02 '13 at 16:44
  • it almost works, can you please look over my updated post, maybe you know what's wrong – Stefan Oct 02 '13 at 19:04
  • @tak3r - while I don't know if it's your only problem, it looks like you are using format specifiers for an `int` ie "%d" but passing a pointer to an __u16 (which is effectively an `unsigned short`) to fscanf. You probably want "%hu", or else read it into a temporary unsigned int and then copy it over. – Chris Stratton Oct 02 '13 at 19:16
  • ok, changed that but still no effect. Not sure how significant it is, but for a random complex swipe if I record it with cat ... | wc it gives: 52 301 8640 in parallel I also register the adb getevent output, convert the values to base 10 and run ./sendevent on it after which the result is: 0 11 7704 The number of events recorded with sendevent is 321 which is quite close to the number of words reported by wc thats why (and also the fact that mine has no lines) i think it mainly has to do with some sort of separator as well – Stefan Oct 02 '13 at 20:02
  • Try running both on your dev machine against a regular file target and hexdumping the outputs to compare. Or just compare your input to the hexdump of the output. – Chris Stratton Oct 02 '13 at 20:05
  • Ok they are considerably different. https://www.dropbox.com/s/dronz6ruvhgk0ae/adb-swipe-diff.html – Stefan Oct 02 '13 at 20:28
  • I see three differences: your "sendevent" program (is that one you modified or the original?) is outputting a null record before and after each real one, it's timeval stuct is zeros (expected), and the bytes within the record are in reverse order. Or it could be related to struct packing, and some interpretation by endian value vs blind copy between the memory version of the struct and file. What is sizeof(event) ? – Chris Stratton Oct 02 '13 at 20:39
  • I used the code you mentioned, stripped up the stuff it didn't use and made it read from a file instead of argv (updated in post) sizeof(event) = 24 Everything I try seems to not produce the same thing. I tried adding all the lines to an array of structs and then just printing that but it is still different. – Stefan Oct 02 '13 at 21:32
  • I believe the size of the event record should be 16, not 24. I think the problem is either padding / alignment in the code, or in the definition of timeval being pulled in. My suggestion is first `__attribute__((aligned(1),packed)) struct input_event {` and second replace the timeval member therein with a pair of __32 dummy members since you don't use that anyway. – Chris Stratton Oct 02 '13 at 22:01
  • not there yet, but this made significant progress!!! The first line of the hexdumps are almost the same and it draws some random lines on the screen before failing - it is progress still. I updated the code and the hexdumps at the dropbox link (take note the columns are swapped for easier editing, my hexdump is the first one) https://www.dropbox.com/s/dronz6ruvhgk0ae/adb-swipe-diff.html – Stefan Oct 02 '13 at 22:30
  • It was looking to me before like the byte order or element order was backwards, can you check that, maybe in your textual source data? – Chris Stratton Oct 03 '13 at 00:58
  • nope they are in the right order... it's exactly the code from your source. Does it work correctly if you try it? – Stefan Oct 03 '13 at 06:13
  • @tak3r you don't push any executable to the phone right? you just use cat, echo? – auselen Oct 03 '13 at 07:36
  • i write all the converted binary code to a file, push the file to the phone and use cat from the phone to the /dev/input/event – Stefan Oct 03 '13 at 13:26
  • @tak3r because you have not specified O_TRUNC and at some point in your experiments the output file was longer, you have garbage at the end of your generated file in your dropbox posting. However, I don't think that's the only remaining issue. – Chris Stratton Oct 03 '13 at 14:49
  • @tak3r - I converted your captured data from the 2nd column back to binary and pushed that to the device node; achieving varying results. I suspect perhaps there's a limit to how fast events can be accepted. – Chris Stratton Oct 03 '13 at 15:19
  • that shounds like a valid statement... although do you have any idea how is the garbage generated? all i do is to take the stuff from getevent and convert it to decimal and pipe it back in. It's a 1 to 1 mapping and it works (I tested that) ? – Stefan Oct 03 '13 at 22:21
  • You got garbage by once having had a longer file, and now overwriting only the start of it with your improved data, because you don't use the O_TRUNC flag to open() – Chris Stratton Oct 03 '13 at 22:25
  • I have now created a question as that is related to this. your help would be appreciated. http://stackoverflow.com/questions/23131081/adding-a-custom-tool-to-the-android-adb-shell I am posting this s i dont want to ask on here. – Michael Kent Apr 17 '14 at 10:49
10

If you just want to produce linear swipes, you can use input swipe command on shell.

$ adb shell input
usage: input ...
       input text <string>
       input keyevent <key code number or name>
       input [touchscreen|touchpad|touchnavigation] tap <x> <y>
       input [touchscreen|touchpad|touchnavigation] swipe <x1> <y1> <x2> <y2> [duration(ms)]
       input trackball press
       input trackball roll <dx> <dy>

Command below draws a nice line for me in a drawing application

$ adb shell input swipe 300 300 500 1000

and a quicker one

$ adb shell input touchscreen swipe 300 300 500 1000 100
auselen
  • 27,577
  • 7
  • 73
  • 114
  • i need complex swipes unfortunately or if there are linear swipes then I need them to not have a keyup event at the end so i can compose them in a single complex swipe – Stefan Oct 02 '13 at 16:41
  • @tak3r do you have root access? – auselen Oct 02 '13 at 16:43
  • 1
    I don't think root access will be required. This appears to be usable from the adb shell, meaning the `shell` userid has permission to do whatever is needed. The input command is a script which uses app-process to launch input.java (unofficial repo https://github.com/android/platform_frameworks_base/blob/master/cmds/input/src/com/android/commands/input/Input.java ) which could presumably be replaced with a custom version similarly compiled and stored. And similarly invoked *from the adb shell* - you would not be able to do this from an app with having an end-run around the security model. – Chris Stratton Oct 02 '13 at 16:52
  • @ChrisStratton Unfortunately no. Being under system directory make that application talk to components otherwise not possible. You can't even build that app outside of an Android repository, since it uses hidden APIs. – auselen Oct 02 '13 at 17:41
  • While it may use hidden APIs and be tricky to build, I don't believe the permission situation is as you think. For one **it is not an "app" at all** - it's bare dalvik/java code run by the `shell` user via the `app_process` tool – Chris Stratton Oct 02 '13 at 17:43
  • Again, this code **is not contained in an app**, system or otherwise. So that does not apply. – Chris Stratton Oct 02 '13 at 17:44
  • @ChrisStratton I think your suggestion might be more fruitful in case you want to build your application, however you can't just push that app to the phone and execute. I just suggested this solution because it exists for linear swipes. – auselen Oct 02 '13 at 17:44
  • Again, **this is not about an app or apk**. It's been pointed out that your solution won't meet the needs of the question, what I'm suggesting in comments is that the mechanism behind your solution can probably be adapted to do what is needed. But you are hung up on app permissions, when this, not being an app, depends on the permissions associated with the unix user id `shell` rather than those of an app userid. – Chris Stratton Oct 02 '13 at 17:46
  • @ChrisStratton let me investigate that. – auselen Oct 02 '13 at 18:06
  • 1
    Hey guys. To answer a previous question: I don't have root yet (I don't think there is one out yet for HTC One). Currently I am only running this from an app on my mac, I push the script file with all the send events to the device and I run it from there and it works fine. I tried using adb shell and cat-ing into /dev/input/event5 (this is my touch events) and it worked. Check the updated post ^^ – Stefan Oct 02 '13 at 19:07
  • @ChrisStratton I made a few tries, not successful. Simply you end up with a jar and if you try to run it under AndroidRuntime (app_process) it needs to become an ODEX but that's not possible with shell user. If you want to just run a standalone jar via dalvikvm, then you are cut out from runtime. - Please educate me if you think there is some otherway. – auselen Oct 03 '13 at 09:43
  • Hey this looks promising. Please answer this: How would you record what the user does on the screen (meaning the opposite of what is shown here) ? – android developer Mar 01 '14 at 00:15
  • If using mac terminal and firing command with touchscreen then it won't fire. Error "Error: Unknown command: touchscreen ". I need to simulate pinch zoom using terminal along with adb ,with provided coordinates. – nikhil84 Aug 20 '14 at 10:10
  • @auselen can I generate a long touch using this method? I think I can use the swipe command with the same source and destination coordinates, is this possible? Also, what is the proper duration for normal touch and long one? Thanks. – Dania Mar 17 '16 at 17:46