5

I am able to simulate tap events on typical android views (textbox, button, etc.) using MotionEvent.obtain, like this:

 int meta_state = 0;
    MotionEvent motionEvent = MotionEvent.obtain(
            SystemClock.uptimeMillis(),
            SystemClock.uptimeMillis(),
            MotionEvent.ACTION_DOWN,
            x,
            y,
            meta_state
    );
    motionEvent.recycle();

    view_tapped.dispatchTouchEvent(motionEvent);

view_tapped is a reference to the view I want to sent the event to. However, when I call dispatchTouchEvent on a webview, it doesn't seem to tap any element inside.

I've also put an onTouchListener inside the webview, and then dumped all the information related to the motionevent when the view is actually tapped by a finger, and then created a new motionevent with all of the same information and dispatched it to the webview, and it isn't tapping the screen. The onTouchListener is still fired, but the element on the screen isn't tapped.

I also tried doing it with Javascript, by trying to figure out which element was at the specific coordinate and then firing the click event by injecting javascript, but no luck either.

Any ideas as to what I'm doing wrong?

seibelj
  • 890
  • 2
  • 10
  • 22

4 Answers4

8

There is my solution:

private void simulateClick(float x, float y) {
    long downTime = SystemClock.uptimeMillis();
    long eventTime = SystemClock.uptimeMillis();
    MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
    MotionEvent.PointerProperties pp1 = new MotionEvent.PointerProperties();
    pp1.id = 0;
    pp1.toolType = MotionEvent.TOOL_TYPE_FINGER;
    properties[0] = pp1;
    MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1];
    MotionEvent.PointerCoords pc1 = new MotionEvent.PointerCoords();
    pc1.x = x;
    pc1.y = y;
    pc1.pressure = 1;
    pc1.size = 1;
    pointerCoords[0] = pc1;
    MotionEvent motionEvent = MotionEvent.obtain(downTime, eventTime,
            MotionEvent.ACTION_DOWN, 1, properties,
            pointerCoords, 0,  0, 1, 1, 0, 0, 0, 0 );
    dispatchTouchEvent(motionEvent);

    motionEvent = MotionEvent.obtain(downTime, eventTime,
            MotionEvent.ACTION_UP, 1, properties,
            pointerCoords, 0,  0, 1, 1, 0, 0, 0, 0 );
    dispatchTouchEvent(motionEvent);
}
Fedir Tsapana
  • 1,283
  • 16
  • 19
2

I think you just need one line of code. The below command is working fine for me.

Runtime.getRuntime().exec("/system/bin/input tap 100 400"); 

That's a much cleaner solution. Unfortunately, it doesn't have events like touch_down, touch_up, or move_to, etc.

Elizabeth Harper
  • 131
  • 1
  • 12
1

This answer is based on Fedir Tsapana's response. I refactored it to make it more generalized, this is a utility that generates the two motion events needed to simulate a finger touch click event.

It was particularly useful for forwarding an event that was being consumed in another window on top of where I wanted the click to go.

fun simulateClickAtCoordinates(x: Float, y: Float): Pair<MotionEvent, MotionEvent> {
    val downTime = SystemClock.uptimeMillis()
    val eventTime = SystemClock.uptimeMillis() + 100
    val properties = buildPointerProperties()
    val pointerCoords = buildPointerCoords(x, y)
    return buildPressEvent(downTime, eventTime, properties, pointerCoords) to buildReleaseEvent(downTime, eventTime, properties, pointerCoords)
}

private fun buildPointerProperties(): Array<MotionEvent.PointerProperties?> {
    val properties = arrayOfNulls<MotionEvent.PointerProperties>(1)
    properties[0] = MotionEvent.PointerProperties().apply {
        id = 0
        toolType = MotionEvent.TOOL_TYPE_FINGER
    }
    return properties
}

private fun buildPointerCoords(x: Float, y: Float): Array<MotionEvent.PointerCoords?> {
    val pointerCoords = arrayOfNulls<MotionEvent.PointerCoords>(1)
    pointerCoords[0] = MotionEvent.PointerCoords().apply {
        this.x = x
        this.y = y
        pressure = 1f
        size = 1f
    }
    return pointerCoords
}

private fun buildPressEvent(downTime: Long, eventTime: Long, properties: Array<MotionEvent.PointerProperties?>, pointerCoords: Array<MotionEvent.PointerCoords?>) =
        MotionEvent.obtain(downTime, eventTime,
                MotionEvent.ACTION_DOWN, 1, properties,
                pointerCoords, 0, 0, 1f, 1f, 0, 0, 0, 0)

private fun buildReleaseEvent(downTime: Long, eventTime: Long, properties: Array<MotionEvent.PointerProperties?>, pointerCoords: Array<MotionEvent.PointerCoords?>) =
        MotionEvent.obtain(downTime, eventTime,
                MotionEvent.ACTION_UP, 1, properties,
                pointerCoords, 0, 0, 1f, 1f, 0, 0, 0, 0)
Sean Blahovici
  • 5,350
  • 4
  • 28
  • 38
0

Try not recycling the motionEvent before dispatching it

Fernando Gallego
  • 4,064
  • 31
  • 50