michael@0: package org.mozilla.gecko.tests; michael@0: michael@0: import android.app.Instrumentation; michael@0: import android.os.SystemClock; michael@0: import android.util.FloatMath; michael@0: import android.util.Log; michael@0: import android.view.MotionEvent; michael@0: michael@0: class MotionEventHelper { michael@0: private static final String LOGTAG = "RobocopMotionEventHelper"; michael@0: michael@0: private static final long DRAG_EVENTS_PER_SECOND = 20; // 20 move events per second when doing a drag michael@0: michael@0: private final Instrumentation mInstrumentation; michael@0: private final int mSurfaceOffsetX; michael@0: private final int mSurfaceOffsetY; michael@0: michael@0: public MotionEventHelper(Instrumentation inst, int surfaceOffsetX, int surfaceOffsetY) { michael@0: mInstrumentation = inst; michael@0: mSurfaceOffsetX = surfaceOffsetX; michael@0: mSurfaceOffsetY = surfaceOffsetY; michael@0: Log.i(LOGTAG, "Initialized using offset (" + mSurfaceOffsetX + "," + mSurfaceOffsetY + ")"); michael@0: } michael@0: michael@0: public long down(float x, float y) { michael@0: Log.d(LOGTAG, "Triggering down at (" + x + "," + y + ")"); michael@0: long downTime = SystemClock.uptimeMillis(); michael@0: MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0); michael@0: try { michael@0: mInstrumentation.sendPointerSync(event); michael@0: } finally { michael@0: event.recycle(); michael@0: event = null; michael@0: } michael@0: return downTime; michael@0: } michael@0: michael@0: public long move(long downTime, float x, float y) { michael@0: return move(downTime, SystemClock.uptimeMillis(), x, y); michael@0: } michael@0: michael@0: public long move(long downTime, long moveTime, float x, float y) { michael@0: Log.d(LOGTAG, "Triggering move to (" + x + "," + y + ")"); michael@0: MotionEvent event = MotionEvent.obtain(downTime, moveTime, MotionEvent.ACTION_MOVE, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0); michael@0: try { michael@0: mInstrumentation.sendPointerSync(event); michael@0: } finally { michael@0: event.recycle(); michael@0: event = null; michael@0: } michael@0: return downTime; michael@0: } michael@0: michael@0: public long up(long downTime, float x, float y) { michael@0: return up(downTime, SystemClock.uptimeMillis(), x, y); michael@0: } michael@0: michael@0: public long up(long downTime, long upTime, float x, float y) { michael@0: Log.d(LOGTAG, "Triggering up at (" + x + "," + y + ")"); michael@0: MotionEvent event = MotionEvent.obtain(downTime, upTime, MotionEvent.ACTION_UP, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0); michael@0: try { michael@0: mInstrumentation.sendPointerSync(event); michael@0: } finally { michael@0: event.recycle(); michael@0: event = null; michael@0: } michael@0: return -1L; michael@0: } michael@0: michael@0: public Thread dragAsync(final float startX, final float startY, final float endX, final float endY, final long durationMillis) { michael@0: Thread t = new Thread() { michael@0: @Override michael@0: public void run() { michael@0: int numEvents = (int)(durationMillis * DRAG_EVENTS_PER_SECOND / 1000); michael@0: float eventDx = (endX - startX) / numEvents; michael@0: float eventDy = (endY - startY) / numEvents; michael@0: long downTime = down(startX, startY); michael@0: for (int i = 0; i < numEvents - 1; i++) { michael@0: downTime = move(downTime, startX + (eventDx * i), startY + (eventDy * i)); michael@0: try { michael@0: Thread.sleep(1000L / DRAG_EVENTS_PER_SECOND); michael@0: } catch (InterruptedException ie) { michael@0: ie.printStackTrace(); michael@0: } michael@0: } michael@0: // sleep a bit before sending the last move so that the calculated michael@0: // fling velocity is low and we don't end up doing a fling afterwards. michael@0: try { michael@0: Thread.sleep(1000L); michael@0: } catch (InterruptedException ie) { michael@0: ie.printStackTrace(); michael@0: } michael@0: // do the last one using endX/endY directly to avoid rounding errors michael@0: downTime = move(downTime, endX, endY); michael@0: downTime = up(downTime, endX, endY); michael@0: } michael@0: }; michael@0: t.start(); michael@0: return t; michael@0: } michael@0: michael@0: public void dragSync(float startX, float startY, float endX, float endY, long durationMillis) { michael@0: try { michael@0: dragAsync(startX, startY, endX, endY, durationMillis).join(); michael@0: mInstrumentation.waitForIdleSync(); michael@0: } catch (InterruptedException ie) { michael@0: ie.printStackTrace(); michael@0: } michael@0: } michael@0: michael@0: public void dragSync(float startX, float startY, float endX, float endY) { michael@0: dragSync(startX, startY, endX, endY, 1000); michael@0: } michael@0: michael@0: public Thread flingAsync(final float startX, final float startY, final float endX, final float endY, final float velocity) { michael@0: // note that the first move after the touch-down is used to get over the panning threshold, and michael@0: // is basically cancelled out. this means we need to generate (at least) two move events, with michael@0: // the last move event hitting the target velocity. to do this we just slice the total distance michael@0: // in half, assuming the first half will get us over the panning threshold and the second half michael@0: // will trigger the fling. michael@0: final float dx = (endX - startX) / 2; michael@0: final float dy = (endY - startY) / 2; michael@0: float distance = FloatMath.sqrt((dx * dx) + (dy * dy)); michael@0: final long time = (long)(distance / velocity); michael@0: if (time <= 0) { michael@0: throw new IllegalArgumentException( "Fling parameters require too small a time period" ); michael@0: } michael@0: Thread t = new Thread() { michael@0: @Override michael@0: public void run() { michael@0: long downTime = down(startX, startY); michael@0: downTime = move(downTime, downTime + time, startX + dx, startY + dy); michael@0: downTime = move(downTime, downTime + time + time, endX, endY); michael@0: downTime = up(downTime, downTime + time, endX, endY); michael@0: } michael@0: }; michael@0: t.start(); michael@0: return t; michael@0: } michael@0: michael@0: public void flingSync(float startX, float startY, float endX, float endY, float velocity) { michael@0: try { michael@0: flingAsync(startX, startY, endX, endY, velocity).join(); michael@0: mInstrumentation.waitForIdleSync(); michael@0: } catch (InterruptedException ie) { michael@0: ie.printStackTrace(); michael@0: } michael@0: } michael@0: michael@0: public void tap(float x, float y) { michael@0: long downTime = down(x, y); michael@0: downTime = up(downTime, x, y); michael@0: } michael@0: michael@0: public void doubleTap(float x, float y) { michael@0: tap(x, y); michael@0: tap(x, y); michael@0: } michael@0: }