How to simulate a tap at a specific coordinate in an android webview?
Asked Answered
G

4

7

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?

Galahad answered 2/1, 2014 at 16:8 Comment(0)
K
11

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);
}
Kathlenekathlin answered 14/8, 2016 at 11:21 Comment(1)
Love it mate. This is the only thing I have found that works!Damek
L
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.

Larva answered 1/4, 2021 at 7:46 Comment(1)
it's a perfect solution, thank you!Pinchas
D
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)
Damek answered 8/3, 2021 at 16:1 Comment(0)
I
0

Try not recycling the motionEvent before dispatching it

Irremovable answered 14/5, 2014 at 11:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.