Created
November 26, 2014 15:35
-
-
Save thu2004/130ac80c4fe29505b8d9 to your computer and use it in GitHub Desktop.
AndroidTouchScreen change for multiple tounch not working
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2011 Selenium committers | |
* | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | |
* in compliance with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software distributed under the License | |
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | |
* or implied. See the License for the specific language governing permissions and limitations under | |
* the License. | |
*/ | |
package io.selendroid.android; | |
import android.app.Instrumentation; | |
import android.content.Context; | |
import android.os.PowerManager; | |
import android.os.SystemClock; | |
import android.view.InputDevice; | |
import android.view.MotionEvent; | |
import android.view.MotionEvent.PointerProperties; | |
import android.view.View; | |
import android.view.ViewConfiguration; | |
import android.view.Window; | |
import android.view.WindowManager; | |
import android.view.MotionEvent.PointerCoords; | |
import android.webkit.WebView; | |
import android.widget.AbsListView; | |
import android.widget.ScrollView; | |
import io.selendroid.ServerInstrumentation; | |
import io.selendroid.android.internal.Point; | |
import io.selendroid.exceptions.SelendroidException; | |
import io.selendroid.server.model.TouchScreen; | |
import io.selendroid.server.model.interactions.Coordinates; | |
import io.selendroid.server.action.touch.FlickDirection; | |
import io.selendroid.util.SelendroidLogger; | |
import java.util.ArrayList; | |
import java.util.List; | |
/** | |
* Implements touch capabilities of a device. | |
* | |
*/ | |
public class AndroidTouchScreen implements TouchScreen { | |
private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5; | |
private final ServerInstrumentation instrumentation; | |
private final MotionSender motions; | |
private int down_touch_counter; | |
private PointerCoords[] pointerCoords; | |
public AndroidTouchScreen(ServerInstrumentation instrumentation, MotionSender motions) { | |
this.instrumentation = instrumentation; | |
this.motions = motions; | |
this.down_touch_counter = 0; | |
this.pointerCoords = new PointerCoords[10]; | |
this.pointerCoords[0] = new PointerCoords(); | |
this.pointerCoords[1] = new PointerCoords(); | |
this.pointerCoords[2] = new PointerCoords(); | |
this.pointerCoords[3] = new PointerCoords(); | |
this.pointerCoords[4] = new PointerCoords(); | |
this.pointerCoords[5] = new PointerCoords(); | |
this.pointerCoords[6] = new PointerCoords(); | |
this.pointerCoords[7] = new PointerCoords(); | |
this.pointerCoords[8] = new PointerCoords(); | |
this.pointerCoords[9] = new PointerCoords(); | |
} | |
public void singleTap(Coordinates where) { | |
Point toTap = where.getLocationOnScreen(); | |
List<MotionEvent> motionEvents = new ArrayList<MotionEvent>(); | |
long downTime = SystemClock.uptimeMillis(); | |
motionEvents.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, toTap)); | |
motionEvents.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_UP, toTap)); | |
motions.send(motionEvents); | |
} | |
public void down(int x, int y) { | |
List<MotionEvent> events = new ArrayList<MotionEvent>(); | |
long downTime = SystemClock.uptimeMillis(); | |
Point coords = new Point(x, y); | |
if (this.down_touch_counter < 1) { | |
SelendroidLogger.info("--> action down x=" + x + " , y="+y+" time = "+downTime); | |
// events.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, coords)); | |
// motions.send(events); | |
MotionEvent event = getMultipleTouchMotionEvent(downTime, downTime, | |
getPointerAction(MotionEvent.ACTION_DOWN, this.down_touch_counter ), this.down_touch_counter , coords); | |
injectEventSync(event); | |
} else { | |
SelendroidLogger.info("--> action pointer down ("+ this.down_touch_counter + ") x=" + x + " , y="+y+" time = "+downTime); | |
MotionEvent event = getMultipleTouchMotionEvent(downTime, downTime, | |
getPointerAction(MotionEvent.ACTION_POINTER_DOWN, this.down_touch_counter ), this.down_touch_counter , coords); | |
injectEventSync(event); | |
// event.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_POINTER_DOWN, coords)); | |
} | |
this.down_touch_counter++; | |
} | |
public void up(int x, int y) { | |
List<MotionEvent> events = new ArrayList<MotionEvent>(); | |
long downTime = SystemClock.uptimeMillis(); | |
Point coords = new Point(x, y); | |
this.down_touch_counter--; | |
if (this.down_touch_counter < 1) { | |
SelendroidLogger.info("--> action up x=" + x + " , y="+y+" time = "+downTime); | |
// events.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_UP, coords)); | |
// motions.send(events); | |
MotionEvent event = getMultipleTouchMotionEvent(downTime, downTime, | |
getPointerAction(MotionEvent.ACTION_UP, this.down_touch_counter ), this.down_touch_counter , coords); | |
injectEventSync(event); | |
} else { | |
SelendroidLogger.info("--> action pointer up ("+ this.down_touch_counter + ") x=" + x + " , y="+y+" time = "+downTime); | |
MotionEvent event = getMultipleTouchMotionEvent(downTime, downTime, | |
getPointerAction(MotionEvent.ACTION_POINTER_UP, this.down_touch_counter), this.down_touch_counter , coords); | |
injectEventSync(event); | |
// event.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_POINTER_UP, coords)); | |
} | |
} | |
public void move(int x, int y) { | |
List<MotionEvent> event = new ArrayList<MotionEvent>(); | |
long downTime = SystemClock.uptimeMillis(); | |
SelendroidLogger.info("--> move x=" + x + " , y="+y+" time = "+downTime); | |
Point coords = new Point(x, y); | |
event.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_MOVE, coords)); | |
motions.send(event); | |
} | |
public void scroll(Coordinates where, int xOffset, int yOffset) { | |
long downTime = SystemClock.uptimeMillis(); | |
List<MotionEvent> motionEvents = new ArrayList<MotionEvent>(); | |
Point origin = where.getLocationOnScreen(); | |
Point destination = new Point(origin.x + xOffset, origin.y + yOffset); | |
motionEvents.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, origin)); | |
Scroll scroll = new Scroll(origin, destination, downTime); | |
// Initial acceleration from origin to reference point | |
motionEvents.add(getBatchedMotionEvent(downTime, downTime, origin, scroll.getDecelerationPoint(), | |
Scroll.INITIAL_STEPS, Scroll.TIME_BETWEEN_EVENTS)); | |
// Deceleration phase from reference point to destination | |
motionEvents.add(getBatchedMotionEvent(downTime, scroll.getEventTimeForReferencePoint(), | |
scroll.getDecelerationPoint(), destination, Scroll.DECELERATION_STEPS, | |
Scroll.TIME_BETWEEN_EVENTS)); | |
motionEvents.add(getMotionEvent(downTime, | |
(downTime + scroll.getEventTimeForDestinationPoint()), MotionEvent.ACTION_UP, destination)); | |
motions.send(motionEvents); | |
} | |
public void doubleTap(Coordinates where) { | |
Point toDoubleTap = where.getLocationOnScreen(); | |
List<MotionEvent> motionEvents = new ArrayList<MotionEvent>(); | |
long downTime = SystemClock.uptimeMillis(); | |
motionEvents.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, toDoubleTap)); | |
motionEvents.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_UP, toDoubleTap)); | |
motionEvents.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, toDoubleTap)); | |
motionEvents.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_UP, toDoubleTap)); | |
motions.send(motionEvents); | |
} | |
public void longPress(Coordinates where) { | |
long downTime = SystemClock.uptimeMillis(); | |
long eventTime = SystemClock.uptimeMillis(); | |
Point point = where.getLocationOnScreen(); | |
// List<MotionEvent> motionEvents = new ArrayList<MotionEvent>(); | |
// | |
// motionEvents.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, point)); | |
// motionEvents.add(getMotionEvent(downTime, (downTime + 3000), MotionEvent.ACTION_UP, point)); | |
// sendMotionEvents(motionEvents); | |
Instrumentation inst = instrumentation; | |
MotionEvent event = null; | |
boolean successfull = false; | |
int retry = 0; | |
while (!successfull && retry < 10) { | |
try { | |
if (event == null) { | |
event = | |
MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, point.x, point.y, 0); | |
} | |
SelendroidLogger.debug("trying to send pointer"); | |
inst.sendPointerSync(event); | |
successfull = true; | |
} catch (SecurityException e) { | |
SelendroidLogger.error("failed: " + retry); | |
// activityUtils.hideSoftKeyboard(null, false, true); | |
retry++; | |
} | |
} | |
if (!successfull) { | |
throw new SelendroidException("Click can not be completed!"); | |
} | |
inst.sendPointerSync(event); | |
inst.waitForIdleSync(); | |
eventTime = SystemClock.uptimeMillis(); | |
final int touchSlop = ViewConfiguration.get(inst.getTargetContext()).getScaledTouchSlop(); | |
event = | |
MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, point.x + touchSlop / 2, | |
point.y + touchSlop / 2, 0); | |
inst.sendPointerSync(event); | |
inst.waitForIdleSync(); | |
try { | |
Thread.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f)); | |
} catch (InterruptedException e) { | |
Thread.currentThread().interrupt(); | |
} | |
eventTime = SystemClock.uptimeMillis(); | |
event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, point.x, point.y, 0); | |
inst.sendPointerSync(event); | |
inst.waitForIdleSync(); | |
} | |
public void scroll(final int xOffset, final int yOffset) { | |
List<View> scrollableContainer = | |
ViewHierarchyAnalyzer.getDefaultInstance().findScrollableContainer(); | |
if (scrollableContainer == null) { | |
// nothing to do | |
return; | |
} | |
for (View view : scrollableContainer) { | |
if (view instanceof AbsListView) { | |
final AbsListView absListView = (AbsListView) view; | |
instrumentation.getCurrentActivity().runOnUiThread(new Runnable() { | |
public void run() { | |
absListView.scrollBy(xOffset, yOffset); | |
} | |
}); | |
} else if (view instanceof ScrollView) { | |
final ScrollView scrollView = (ScrollView) view; | |
instrumentation.getCurrentActivity().runOnUiThread(new Runnable() { | |
public void run() { | |
scrollView.scrollBy(xOffset, yOffset); | |
} | |
}); | |
} else if (view instanceof WebView) { | |
final WebView webView = (WebView) view; | |
instrumentation.getCurrentActivity().runOnUiThread(new Runnable() { | |
public void run() { | |
webView.scrollBy(xOffset, yOffset); | |
} | |
}); | |
} | |
} | |
} | |
public void flick(final int speedX, final int speedY) { | |
List<View> scrollableContainer = | |
ViewHierarchyAnalyzer.getDefaultInstance().findScrollableContainer(); | |
if (scrollableContainer == null) { | |
// nothing to do | |
return; | |
} | |
for (View view : scrollableContainer) { | |
if (view instanceof AbsListView) { | |
// ignore | |
} else if (view instanceof ScrollView) { | |
final ScrollView scrollView = (ScrollView) view; | |
instrumentation.getCurrentActivity().runOnUiThread(new Runnable() { | |
public void run() { | |
scrollView.fling(speedY); | |
} | |
}); | |
} else if (view instanceof WebView) { | |
final WebView webView = (WebView) view; | |
instrumentation.getCurrentActivity().runOnUiThread(new Runnable() { | |
public void run() { | |
webView.flingScroll(speedX, speedY); | |
} | |
}); | |
} | |
} | |
} | |
public void flick(Coordinates where, int xOffset, int yOffset, int speed) { | |
long downTime = SystemClock.uptimeMillis(); | |
List<MotionEvent> motionEvents = new ArrayList<MotionEvent>(); | |
Point origin = where.getLocationOnScreen(); | |
Point destination = new Point(origin.x + xOffset, origin.y + yOffset); | |
DynamicIntervalFlick flick = new DynamicIntervalFlick(speed); | |
motionEvents.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, origin)); | |
motionEvents.add(getBatchedMotionEvent(downTime, downTime, origin, destination, flick.getNumberOfSteps(), | |
flick.getTimeBetweenEvents())); | |
motionEvents.add(getMotionEvent(downTime, flick.getTimeForDestinationPoint(downTime), | |
MotionEvent.ACTION_UP, destination)); | |
motions.send(motionEvents); | |
} | |
public void flick(Point origin, FlickDirection direction, int distance, int duration) { | |
int xOffset = distance * direction.getxMultiplier(); | |
int yOffset = distance * direction.getyMultiplier(); | |
Point destination = new Point(origin.x + xOffset, origin.y + yOffset); | |
generateFlickMotions(origin, destination, new FixedIntervalFlick(duration)); | |
} | |
private void generateFlickMotions(Point origin, Point destination, Flick flick) { | |
long downTime = SystemClock.uptimeMillis(); | |
List<MotionEvent> motionEvents = new ArrayList<MotionEvent>(); | |
motionEvents.add(getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, origin)); | |
motionEvents.add(getBatchedMotionEvent( | |
downTime, downTime, origin, destination, flick.getNumberOfSteps(), | |
flick.getTimeBetweenEvents())); | |
motionEvents.add(getMotionEvent(downTime, flick.getTimeForDestinationPoint(downTime), | |
MotionEvent.ACTION_UP, destination)); | |
motions.send(motionEvents); | |
} | |
private MotionEvent getMotionEvent(long start, long eventTime, int action, Point coords) { | |
return MotionEvent.obtain(start, eventTime, action, coords.x, coords.y, 0); | |
} | |
private MotionEvent getMultipleTouchMotionEvent(long start, long eventTime, int action, int pointerCount, Point coords) { | |
// specify the properties for each pointer as finger touch | |
PointerProperties[] properties = new PointerProperties[2]; | |
// PointerCoords[] pointerCoords = new PointerCoords[2]; | |
PointerProperties prop = new PointerProperties(); | |
prop.id = 0; | |
prop.toolType = MotionEvent.TOOL_TYPE_FINGER; | |
properties[0] = prop; | |
PointerProperties prop2 = new PointerProperties(); | |
prop2.id = 1; | |
prop2.toolType = MotionEvent.TOOL_TYPE_FINGER; | |
properties[1] = prop2; | |
PointerCoords p = new PointerCoords(); | |
p.x = coords.x; | |
p.y = coords.y; | |
p.pressure = 1; | |
p.size = 1; | |
this.pointerCoords[pointerCount] = p; | |
return MotionEvent.obtain(start, eventTime, | |
action, 2, properties, | |
this.pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); | |
// MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), | |
// getPointerAction(MotionEvent.ACTION_POINTER_DOWN, x), x + 1, properties, | |
// pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); | |
} | |
private MotionEvent getBatchedMotionEvent(long downTime, long startingEventTime, Point origin, | |
Point destination, int steps, long timeBetweenEvents) { | |
float xStep = (destination.x - origin.x) / steps; | |
float yStep = (destination.y - origin.y) / steps; | |
float x = origin.x; | |
float y = origin.y; | |
long eventTime = startingEventTime; | |
x += xStep; | |
y += yStep; | |
MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0); | |
for (int i = 0; i < steps - 1; i++) { | |
x += xStep; | |
y += yStep; | |
eventTime += timeBetweenEvents; | |
event.addBatch(eventTime, x, y, 1.0f, 1.0f, 0); | |
} | |
eventTime += timeBetweenEvents; | |
event.addBatch(eventTime, destination.getX(), destination.getY(), 1.0f, 1.0f, 0); | |
return event; | |
} | |
@Override | |
public float getBrightness() { | |
PowerManager powerManager = (PowerManager) instrumentation.getContext().getSystemService(Context.POWER_SERVICE); | |
if (!powerManager.isScreenOn()) { | |
return 0f; | |
} else { | |
WindowManager.LayoutParams attributes = instrumentation.getCurrentActivity().getWindow().getAttributes(); | |
return attributes.screenBrightness; | |
} | |
} | |
@Override | |
public void setBrightness(float brightness) { | |
if (brightness < 0) { | |
brightness = 0; | |
} | |
if (brightness > 1) { | |
brightness = 1; | |
} | |
PowerManager powerManager = (PowerManager) instrumentation.getContext().getSystemService(Context.POWER_SERVICE); | |
final Window window = instrumentation.getCurrentActivity().getWindow(); | |
final WindowManager.LayoutParams attributes = window.getAttributes(); | |
PowerManager.WakeLock wakeLock = null; | |
if (brightness != 0) { | |
// Turn on display | |
if (!powerManager.isScreenOn()) { | |
wakeLock = powerManager.newWakeLock( | |
PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "Selendroid screen wake"); | |
} | |
// Now set the brightness | |
attributes.screenBrightness = brightness; | |
} else { | |
// Turn off the display. Oh boy. This is derived from a reading of the PowerManager SDK docs. | |
// http://developer.android.com/reference/android/os/PowerManager.html | |
attributes.screenBrightness = 0; | |
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Selendroid screen sleep"); | |
} | |
instrumentation.getCurrentActivity().runOnUiThread( | |
new Runnable() { | |
@Override public void run() { | |
window.setAttributes(attributes); | |
} | |
} | |
); | |
instrumentation.waitForIdleSync(); | |
if (wakeLock != null) { | |
try { | |
wakeLock.acquire(); | |
wakeLock.release(); | |
} catch (SecurityException ignored) { | |
// We can only turn off the screen if the AUT has the android.permission.WAKE_LOCK permission. | |
} | |
} | |
} | |
final class Scroll { | |
private Point origin; | |
private Point destination; | |
private long downTime; | |
// A regular scroll usually has 15 gestures, where the last 5 are used for deceleration | |
final static int INITIAL_STEPS = 10; | |
final static int DECELERATION_STEPS = 5; | |
final int TOTAL_STEPS = INITIAL_STEPS + DECELERATION_STEPS; | |
// Time in milliseconds to provide a speed similar to scroll | |
final static long TIME_BETWEEN_EVENTS = 50; | |
public Scroll(Point origin, Point destination, long downTime) { | |
this.origin = origin; | |
this.destination = destination; | |
this.downTime = downTime; | |
} | |
// This method is used to calculate the point where the deceleration will start at 20% of the | |
// distance to the destination point | |
private Point getDecelerationPoint() { | |
int deltaX = (destination.x - origin.x); | |
int deltaY = (destination.y - origin.y); | |
// Coordinates of reference point where deceleration should start for scroll gesture, on the | |
// last 20% of the total distance to scroll | |
int xRef = (int) (deltaX * 0.8); | |
int yRef = (int) (deltaY * 0.8); | |
return new Point(origin.x + xRef, origin.y + yRef); | |
} | |
private long getEventTimeForReferencePoint() { | |
return (downTime + INITIAL_STEPS * TIME_BETWEEN_EVENTS); | |
} | |
private long getEventTimeForDestinationPoint() { | |
return (downTime + TOTAL_STEPS * TIME_BETWEEN_EVENTS); | |
} | |
} | |
interface Flick { | |
public int getNumberOfSteps(); | |
public long getTimeBetweenEvents(); | |
public long getTimeForDestinationPoint(long downTime); | |
} | |
final class DynamicIntervalFlick implements Flick { | |
private final int SPEED_NORMAL = 0; | |
private final int SPEED_FAST = 1; | |
private final int SPEED_SLOW = 2; | |
private int speed; | |
public DynamicIntervalFlick(int speed) { | |
this.speed = speed; | |
} | |
public int getNumberOfSteps() { | |
return speed == SPEED_SLOW ? 8 : 4; | |
} | |
public long getTimeBetweenEvents() { | |
switch (speed) { | |
case SPEED_SLOW: | |
return 50; | |
case SPEED_NORMAL: | |
return 25; // Time in milliseconds to provide a speed similar to normal flick | |
case SPEED_FAST: | |
return 9; | |
default: | |
return 0; | |
} | |
} | |
public long getTimeForDestinationPoint(long downTime) { | |
return (downTime + getNumberOfSteps() * getTimeBetweenEvents()); | |
} | |
} | |
final class FixedIntervalFlick implements Flick { | |
private int EVENT_INTERVAL_MS = 15; | |
private int time; // Total time in ms of flick gesture | |
public FixedIntervalFlick(int time) { | |
this.time = time; | |
} | |
public int getNumberOfSteps() { | |
return time / EVENT_INTERVAL_MS; | |
} | |
public long getTimeBetweenEvents() { | |
return (time == 0) ? 0 : EVENT_INTERVAL_MS; | |
} | |
public long getTimeForDestinationPoint(long downTime) { | |
return downTime + time; | |
} | |
} | |
/** | |
* Generates a two-pointer gesture with arbitrary starting and ending points. | |
* | |
* @param startPoint1 start point of pointer 1 | |
* @param startPoint2 start point of pointer 2 | |
* @param endPoint1 end point of pointer 1 | |
* @param endPoint2 end point of pointer 2 | |
* @param steps the number of steps for the gesture. Steps are injected | |
* about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. | |
* @return none | |
*/ | |
public void performTwoPointerGesture(Point startPoint1, Point startPoint2, Point endPoint1, | |
Point endPoint2, int steps) { | |
// avoid a divide by zero | |
if(steps == 0) | |
steps = 1; | |
final float stepX1 = (endPoint1.x - startPoint1.x) / steps; | |
final float stepY1 = (endPoint1.y - startPoint1.y) / steps; | |
final float stepX2 = (endPoint2.x - startPoint2.x) / steps; | |
final float stepY2 = (endPoint2.y - startPoint2.y) / steps; | |
int eventX1, eventY1, eventX2, eventY2; | |
eventX1 = startPoint1.x; | |
eventY1 = startPoint1.y; | |
eventX2 = startPoint2.x; | |
eventY2 = startPoint2.y; | |
// allocate for steps plus first down and last up | |
PointerCoords[] points1 = new PointerCoords[steps + 2]; | |
PointerCoords[] points2 = new PointerCoords[steps + 2]; | |
// Include the first and last touch downs in the arrays of steps | |
for (int i = 0; i < steps + 1; i++) { | |
PointerCoords p1 = new PointerCoords(); | |
p1.x = eventX1; | |
p1.y = eventY1; | |
p1.pressure = 1; | |
p1.size = 1; | |
points1[i] = p1; | |
PointerCoords p2 = new PointerCoords(); | |
p2.x = eventX2; | |
p2.y = eventY2; | |
p2.pressure = 1; | |
p2.size = 1; | |
points2[i] = p2; | |
eventX1 += stepX1; | |
eventY1 += stepY1; | |
eventX2 += stepX2; | |
eventY2 += stepY2; | |
} | |
// ending pointers coordinates | |
PointerCoords p1 = new PointerCoords(); | |
p1.x = endPoint1.x; | |
p1.y = endPoint1.y; | |
p1.pressure = 1; | |
p1.size = 1; | |
points1[steps + 1] = p1; | |
PointerCoords p2 = new PointerCoords(); | |
p2.x = endPoint2.x; | |
p2.y = endPoint2.y; | |
p2.pressure = 1; | |
p2.size = 1; | |
points2[steps + 1] = p2; | |
performMultiPointerGesture(points1, points2); | |
} | |
/** | |
* Performs a multi-touch gesture | |
* | |
* Takes a series of touch coordinates for at least 2 pointers. Each pointer must have | |
* all of its touch steps defined in an array of {@link PointerCoords}. By having the ability | |
* to specify the touch points along the path of a pointer, the caller is able to specify | |
* complex gestures like circles, irregular shapes etc, where each pointer may take a | |
* different path. | |
* | |
* To create a single point on a pointer's touch path | |
* <code> | |
* PointerCoords p = new PointerCoords(); | |
* p.x = stepX; | |
* p.y = stepY; | |
* p.pressure = 1; | |
* p.size = 1; | |
* </code> | |
* @param touches each array of {@link PointerCoords} constitute a single pointer's touch path. | |
* Multiple {@link PointerCoords} arrays constitute multiple pointers, each with its own | |
* path. Each {@link PointerCoords} in an array constitute a point on a pointer's path. | |
* @return none | |
*/ | |
public void performMultiPointerGesture(PointerCoords[] ... touches) { | |
if (touches.length < 2) { | |
throw new IllegalArgumentException("Must provide coordinates for at least 2 pointers"); | |
} | |
// Get the pointer with the max steps to inject. | |
int maxSteps = 0; | |
for (int x = 0; x < touches.length; x++) | |
maxSteps = (maxSteps < touches[x].length) ? touches[x].length : maxSteps; | |
// specify the properties for each pointer as finger touch | |
PointerProperties[] properties = new PointerProperties[touches.length]; | |
PointerCoords[] pointerCoords = new PointerCoords[touches.length]; | |
for (int x = 0; x < touches.length; x++) { | |
PointerProperties prop = new PointerProperties(); | |
prop.id = x; | |
prop.toolType = MotionEvent.TOOL_TYPE_FINGER; | |
properties[x] = prop; | |
// for each pointer set the first coordinates for touch down | |
pointerCoords[x] = touches[x][0]; | |
} | |
// Touch down all pointers | |
long downTime = SystemClock.uptimeMillis(); | |
MotionEvent event; | |
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 1, | |
properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); | |
injectEventSync(event); | |
for (int x = 1; x < touches.length; x++) { | |
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), | |
getPointerAction(MotionEvent.ACTION_POINTER_DOWN, x), x + 1, properties, | |
pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); | |
injectEventSync(event); | |
} | |
// Move all pointers | |
for (int i = 1; i < maxSteps - 1; i++) { | |
// for each pointer | |
for (int x = 0; x < touches.length; x++) { | |
// check if it has coordinates to move | |
if (touches[x].length > i) | |
pointerCoords[x] = touches[x][i]; | |
else | |
pointerCoords[x] = touches[x][touches[x].length - 1]; | |
} | |
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), | |
MotionEvent.ACTION_MOVE, touches.length, properties, pointerCoords, 0, 0, 1, 1, | |
0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); | |
injectEventSync(event); | |
SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS); | |
} | |
// For each pointer get the last coordinates | |
for (int x = 0; x < touches.length; x++) | |
pointerCoords[x] = touches[x][touches[x].length - 1]; | |
// touch up | |
for (int x = 1; x < touches.length; x++) { | |
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), | |
getPointerAction(MotionEvent.ACTION_POINTER_UP, x), x + 1, properties, | |
pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); | |
injectEventSync(event); | |
} | |
// first to touch down is last up | |
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 1, | |
properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); | |
injectEventSync(event); | |
} | |
private void injectEventSync(MotionEvent event) { | |
instrumentation.sendPointerSync(event); | |
instrumentation.waitForIdleSync(); | |
} | |
private int getPointerAction(int motionEnvent, int index) { | |
return motionEnvent + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment