2011年6月17日 星期五

[Android] Generating multitouch MotionEvents for testing

For testing the behaviour of the GestureDetector, i want to send MotionEvents to check action result.


Above android 2.3 we can use the method 'obtain' to get multitouch event.

public static MotionEvent obtain (long downTime, long eventTime, int action, int pointers, int[] pointerIds, PointerCoords[] pointerCoords, int metaState, float xPrecision, float yPrecision, int deviceId, int edgeFlags, int source, int flags)
            //action is an int variable indicate what event type to send. (DOWN, UP, POINIT_DOWN....)
            //point is an PointF array to indecate all coordinates touch on screen.
            int[] pointCoordsID = new int[point.length];
            PointerCoords[] pointCoords = new PointerCoords[point.length];
            for (int i = 0; i < point.length; i++) 
            {
                pointerIds[i] = i;
                pointerCoords[i] = new PointerCoords();
                pointerCoords[i].x = point[i].x;
                pointerCoords[i].y = point[i].y;
            }            
            long downTime = SystemClock.uptimeMillis();
            long eventTime = SystemClock.uptimeMillis();

            MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, point.lenth, pointCoordsID, pointCoords, 0, 1, 1, 0, 0, 0, 0);



#########################################################


If your device or sdk is android 2.2 or lower, you have to create MotionEvent from Parcel as below to perform it.


            //action is an int variable indicate what event type to send. (DOWN, UP, POINIT_DOWN....)
            //point is an PointF array to indecate all coordinates touch on screen.

            Parcel inParcel = Parcel.obtain();

            long downTime = SystemClock.uptimeMillis();
            long eventTime = SystemClock.uptimeMillis();
            inParcel.writeLong(downTime); //DownTime
            inParcel.writeLong(eventTime); //EventTime
            inParcel.writeInt(action);         //Action
            inParcel.writeInt(0);         //MetaState
            inParcel.writeFloat(point[0].x); //RawX
            inParcel.writeFloat(point[0].y); //RawY
            final int NP = m_point.length; //NumPointers
            inParcel.writeInt(NP);
            final int NS = m_point.length; //NumSamples
            inParcel.writeInt(NS);
            final int NI = NP*NS;
            if (NI > 0) 
            {
                int i;               
                //set point index
                for (i=0; i<NP; i++) 
                {
                inParcel.writeInt(i);
                }
                // set location, pressure, size for each sampleData
                int NUM_SAMPLE_DATA = 4;
                final int ND = NI*NUM_SAMPLE_DATA;
                float[] history = new float[ND];;
                for (i = 0; i < m_point.length; i++) 
                {
                history[i*NUM_SAMPLE_DATA] = point[i].x;
                history[i*NUM_SAMPLE_DATA + 1] = point[i].y;
                history[i*NUM_SAMPLE_DATA + 2] = 0;
                history[i*NUM_SAMPLE_DATA + 3] = 0;
                }
                for (i=0; i<ND; i++) 
                {
                inParcel.writeFloat(history[i]);
                }
                // set event time for each sampleData
                for (i=0; i<NS; i++) 
                {
                inParcel.writeLong(eventTime);
                }
            }
            inParcel.writeFloat(1); //PrecisionX
            inParcel.writeFloat(1); //PrecisionY
            inParcel.writeInt(0); //DeviceID
            inParcel.writeInt(0); //EdgeFlags
            
            inParcel.setDataPosition(0);
            
            MotionEvent event = MotionEvent.CREATOR.createFromParcel(inParcel);


###########################################


Reference:
http://stackoverflow.com/questions/3637044/generating-multitouch-motionevents-for-testing

4 則留言:

  1. Hi,
    I reference your code to generate multitouch event for 2.3, but it still can't be generate. Here is my code.

    PointerCoords[] coords = new PointerCoords[2];
    int[] pointerIds = new int[2];
    pointerIds[0] = 0;
    pointerIds[1] = 1;
    coords[0] = new PointerCoords();
    coords[1] = new PointerCoords();
    coords[0].x = 400;
    coords[0].y = 200;
    coords[1].x = 400;
    coords[1].y = 380;

    MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(),eventTime,MotionEvent. ACTION_POINTER_DOWN,2,pointerIds,coords,0,1,1,0,0,0,0);
    inst.sendPointerSync(me);

    eventTime+=500;
    coords[0].y = 0;
    coords[1].y += 120;
    me = MotionEvent.obtain(SystemClock.uptimeMillis(),eventTime,MotionEvent.ACTION_MOVE,2,pointerIds,coords,0,1,1,0,0,0,0);
    inst.sendPointerSync(me);

    eventTime+=100;
    me = MotionEvent.obtain(SystemClock.uptimeMillis(),eventTime,MotionEvent.ACTION_UP,2,pointerIds,coords,0,1,1,0,0,0,0);
    inst.sendPointerSync(me);

    回覆刪除
  2. I believe the problem is wrong action type.
    I simulate the pinch gesture for zoom-in in turn as below.

    ACTION_DOWN: the first finger down.
    ACTION_POINTER_2_DOWN: the second finger down.
    ACTION_MOVE: two fingers move to more closed.
    ACTION_POINTER_1_UP: two fingers up simultaneously.

    Hope it works for you.

    回覆刪除
  3. hi , my sdk is 2.2, and my code like that, but it doesn't work
    public MotionEvent constructMotionEvent(int action, Point[] point){

    Parcel inParcel = Parcel.obtain();

    long downTime = SystemClock.uptimeMillis();
    long eventTime = SystemClock.uptimeMillis();
    inParcel.writeLong(downTime); //DownTime
    inParcel.writeLong(eventTime); //EventTime
    inParcel.writeInt(action); //Action
    inParcel.writeInt(0); //MetaState
    inParcel.writeFloat(0); //RawX
    inParcel.writeFloat(0); //RawY
    final int NP = point.length; //NumPointers
    inParcel.writeInt(NP);
    final int NS = point.length; //NumSamples
    inParcel.writeInt(NS);
    final int NI = NP*NS;
    if (NI > 0)
    {
    int i;
    //set point index
    for (i=0; i<NP; i++)
    {
    inParcel.writeInt(i);
    }
    // set location, pressure, size for each sampleData
    int NUM_SAMPLE_DATA = 4;
    final int ND = NI*NUM_SAMPLE_DATA;
    float[] history = new float[ND];;
    for (i = 0; i < point.length; i++)
    {
    history[i*NUM_SAMPLE_DATA] = point[i].x;
    history[i*NUM_SAMPLE_DATA + 1] = point[i].y;
    history[i*NUM_SAMPLE_DATA + 2] = 0;
    history[i*NUM_SAMPLE_DATA + 3] = 0;
    }
    for (i=0; i<ND; i++)
    {
    inParcel.writeFloat(history[i]);
    }
    // set event time for each sampleData
    for (i=0; i<NS; i++)
    {
    inParcel.writeLong(eventTime);
    }
    }
    inParcel.writeFloat(1); //PrecisionX
    inParcel.writeFloat(1); //PrecisionY
    inParcel.writeInt(0); //DeviceID
    inParcel.writeInt(0); //EdgeFlags

    inParcel.setDataPosition(0);

    MotionEvent event = MotionEvent.CREATOR.createFromParcel(inParcel);
    return event;
    }

    MotionEvent event = constructMotionEvent(MotionEvent.ACTION_DOWN, ps);
    inst.waitForIdleSync();
    inst.sendPointerSync(event);

    event = constructMotionEvent(MotionEvent.ACTION_UP, ps);
    inst.waitForIdleSync();
    inst.sendPointerSync(event);

    i wanna click one point down, then second point down, release point_1, release point_2, but the code above only press the first point, i'm sure, the value points are both ok

    回覆刪除
    回覆
    1. I'm not sure, but try it.

      MotionEvent event = constructMotionEvent(MotionEvent.ACTION_DOWN, ps);
      TheObjectActivity.dispatchTouchEvent(event);
      inst.waitForIdleSync();

      MotionEvent event = constructMotionEvent(MotionEvent.ACTION_POINTER_2_DOWN, ps);
      TheObjectActivity.dispatchTouchEvent(event);
      inst.waitForIdleSync();

      MotionEvent event = constructMotionEvent(MotionEvent.ACTION_POINTER_1_UP, ps);
      TheObjectActivity.dispatchTouchEvent(event);
      inst.waitForIdleSync();

      刪除