Create touch events programmatically in Android
Creating a touch event in Android is pretty easy. Although, is the easy way the best? Let’s find out!
The simplest way is to use the “input” binary which you can use to create touch events all over the screen (via the shell user). The command to create a simple tap event is as simple as – adb shell input tap x y
.
However, there’s a latency of more than 1 second while executing this command. If we’re making some automation related app, we definitely wouldn’t want this.
Another approach is to write values directly to the device node file available inside /dev
for input. This approach has slightly lesser latency, but file writing operations are anyways expensive. On top of that, some devices/OEMs might have different locations/names for the device nodes which makes this approach a nightmare to implement.
Can we do better both in terms of consistency and latency? Definitely!
Luckily, there’s a class called InputManager
in Android which has some interesting methods, although the methods of our interest are either private or hidden.
Now’s when Java’s Reflection API comes to the rescue. We’ll follow a series of hacky reflection API calls to get our input injection working.
First of all, we need the instance of InputManager class. For that, we’ll just invoke the getInstance
method via reflection.
1 2 3 4 |
String methodName = "getInstance"; Object[] objArr = new Object[0]; im = (InputManager) InputManager.class.getDeclaredMethod(methodName, new Class[0]) .invoke(null, objArr); |
Next, we need to make obtain
method of MotionEvent
class accessible. Then, we will get the reflection reference of injectInputEvent
method.
1 2 3 4 5 6 7 |
//Make MotionEvent.obtain() method accessible methodName = "obtain"; MotionEvent.class.getDeclaredMethod(methodName, new Class[0]).setAccessible(true); //Get the reference to injectInputEvent method methodName = "injectInputEvent"; injectInputEventMethod = InputManager.class.getMethod(methodName, new Class[]{InputEvent.class, Integer.TYPE}); |
We’re all set now, and just need to write the code to actually pass the touch events to the Android system. The code involves creating a MotionEvent
object, and calling the injectInputEvent
via reflection.
1 2 3 4 5 6 7 |
when = SystemClock.uptimeMillis(); source = InputDeviceCompat.SOURCE_TOUCHSCREEN; pressure = 1.0f action = 0; MotionEvent event = MotionEvent.obtain(when, when, action, x, y, pressure, 1.0f, 0, 1.0f, 1.0f, 0, 0); event.setSource(source); injectInputEventMethod.invoke(im, new Object[]{event, Integer.valueOf(0)}); |
Another awesome thing with this approach is that, the framework will itself figure out if the current event will be a normal tap, swipe or a long press.
The full implementation of this approach is present in RemoteDroid – https://github.com/omerjerk/RemoteDroid/blob/master/app/src/main/java/in/omerjerk/remotedroid/app/EventInput.java.
Where can you create touch events?
Of course, a random user (every app is run by its own user) can’t just create touch events on anywhere on the screen just like that. A particular app can create touch events on those views only which are owned by the same app.
But there’s a catch, shell user (uid 200) can create touch events all over the screen.
Now the question becomes, how would you start your app so that the app’s code is executed by the shell user. Either figure this out yourself or search my blog for something close to that. 😉