// Copyright 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.content.browser.test.util; import android.os.SystemClock; import android.test.ActivityInstrumentationTestCase2; import android.test.TouchUtils; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; /** * Touch-related functionality reused across test cases. */ public class TouchCommon { private ActivityInstrumentationTestCase2 mActivityTestCase; // TODO(leandrogracia): This method should receive and use an activity // instead of the ActivityInstrumentationTestCase2. However this is causing // problems downstream. Any fix for this should be landed downstream first. public TouchCommon(ActivityInstrumentationTestCase2 activityTestCase) { mActivityTestCase = activityTestCase; } /** * Starts (synchronously) a drag motion. Normally followed by dragTo() and dragEnd(). * * @param x * @param y * @param downTime (in ms) * @see TouchUtils */ public void dragStart(float x, float y, long downTime) { MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0); dispatchTouchEvent(event); } /** * Drags / moves (synchronously) to the specified coordinates. Normally preceeded by * dragStart() and followed by dragEnd() * * @param fromX * @param toX * @param fromY * @param toY * @param stepCount * @param downTime (in ms) * @see TouchUtils */ public void dragTo(float fromX, float toX, float fromY, float toY, int stepCount, long downTime) { float x = fromX; float y = fromY; float yStep = (toY - fromY) / stepCount; float xStep = (toX - fromX) / stepCount; for (int i = 0; i < stepCount; ++i) { y += yStep; x += xStep; long eventTime = SystemClock.uptimeMillis(); MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0); dispatchTouchEvent(event); } } /** * Finishes (synchronously) a drag / move at the specified coordinate. * Normally preceeded by dragStart() and dragTo(). * * @param x * @param y * @param downTime (in ms) * @see TouchUtils */ public void dragEnd(float x, float y, long downTime) { long eventTime = SystemClock.uptimeMillis(); MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); dispatchTouchEvent(event); } /** * Sends (synchronously) a single click to an absolute screen coordinates. * * @param x screen absolute * @param y screen absolute * @see TouchUtils */ public void singleClick(float x, float y) { long downTime = SystemClock.uptimeMillis(); long eventTime = SystemClock.uptimeMillis(); MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0); dispatchTouchEvent(event); eventTime = SystemClock.uptimeMillis(); event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); dispatchTouchEvent(event); } /** * Sends (synchronously) a single click to the View at the specified coordinates. * * @param v The view to be clicked. * @param x Relative x location to v * @param y Relative y location to v */ public void singleClickView(View v, int x, int y) { int location[] = getAbsoluteLocationFromRelative(v, x, y); int absoluteX = location[0]; int absoluteY = location[1]; singleClick(absoluteX, absoluteY); } /** * Sends (synchronously) a single click to the center of the View. */ public void singleClickView(View v) { singleClickView(v, v.getWidth() / 2, v.getHeight() / 2); } /** * Sends (synchronously) a single click on the specified relative coordinates inside * a given view. * * @param view The view to be clicked. * @param x screen absolute * @param y screen absolute * @see TouchUtils */ public void singleClickViewRelative(View view, int x, int y) { long downTime = SystemClock.uptimeMillis(); long eventTime = SystemClock.uptimeMillis(); MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0); dispatchTouchEvent(view, event); eventTime = SystemClock.uptimeMillis(); event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); dispatchTouchEvent(view, event); } /** * Sends (synchronously) a long press to an absolute screen coordinates. * * @param x screen absolute * @param y screen absolute * @see TouchUtils */ public void longPress(float x, float y) { long downTime = SystemClock.uptimeMillis(); long eventTime = SystemClock.uptimeMillis(); MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0); dispatchTouchEvent(event); int longPressTimeout = ViewConfiguration.get( mActivityTestCase.getActivity()).getLongPressTimeout(); // Long press is flaky with just longPressTimeout. Doubling the time to be safe. SystemClock.sleep(longPressTimeout * 2); eventTime = SystemClock.uptimeMillis(); event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); dispatchTouchEvent(event); } /** * Sends (synchronously) a long press to the View at the specified coordinates. * * @param v The view to be clicked. * @param x Relative x location to v * @param y Relative y location to v */ public void longPressView(View v, int x, int y) { int location[] = getAbsoluteLocationFromRelative(v, x, y); int absoluteX = location[0]; int absoluteY = location[1]; longPress(absoluteX, absoluteY); } /** * Send a MotionEvent to the root view of the activity. * @param event */ private void dispatchTouchEvent(final MotionEvent event) { View view = mActivityTestCase.getActivity().findViewById(android.R.id.content).getRootView(); dispatchTouchEvent(view, event); } /** * Send a MotionEvent to the specified view instead of the root view. * For example AutofillPopup window that is above the root view. * @param view The view that should receive the event. * @param event The view to be dispatched. */ private void dispatchTouchEvent(final View view, final MotionEvent event) { try { mActivityTestCase.runTestOnUiThread(new Runnable() { @Override public void run() { view.dispatchTouchEvent(event); } }); } catch (Throwable e) { throw new RuntimeException("Dispatching touch event failed", e); } } /** * Returns the absolute location in screen coordinates from location relative * to view. * @param v The view the coordinates are relative to. * @param x Relative x location. * @param y Relative y location. * @return absolute x and y location in an array. */ private static int[] getAbsoluteLocationFromRelative(View v, int x, int y) { int location[] = new int[2]; v.getLocationOnScreen(location); location[0] += x; location[1] += y; return location; } }