GestureDescription.java revision a6b64f5099b7be6e8384958d8bcddb97bb06ec93
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.accessibilityservice; 18 19import android.annotation.IntRange; 20import android.annotation.NonNull; 21import android.graphics.Matrix; 22import android.graphics.Path; 23import android.graphics.PathMeasure; 24import android.graphics.RectF; 25import android.view.InputDevice; 26import android.view.MotionEvent; 27import android.view.MotionEvent.PointerCoords; 28import android.view.MotionEvent.PointerProperties; 29import android.view.ViewConfiguration; 30 31import java.util.ArrayList; 32import java.util.Arrays; 33import java.util.List; 34 35/** 36 * Accessibility services with the 37 * {@link android.R.styleable#AccessibilityService_canPerformGestures} property can dispatch 38 * gestures. This class describes those gestures. Gestures are made up of one or more strokes. 39 * Gestures are immutable; use the {@code create} methods to get common gesture, or a 40 * {@code Builder} to create a new one. 41 * <p> 42 * Spatial dimensions throughout are in screen pixels. Time is measured in milliseconds. 43 */ 44public final class GestureDescription { 45 /** Gestures may contain no more than this many strokes */ 46 public static final int MAX_STROKE_COUNT = 10; 47 48 /** 49 * Upper bound on total gesture duration. Nearly all gestures will be much shorter. 50 */ 51 public static final long MAX_GESTURE_DURATION_MS = 60 * 1000; 52 53 private final List<StrokeDescription> mStrokes = new ArrayList<>(); 54 private final float[] mTempPos = new float[2]; 55 56 /** 57 * Create a description of a click gesture 58 * 59 * @param x The x coordinate to click. Must not be negative. 60 * @param y The y coordinate to click. Must not be negative. 61 * 62 * @return A description of a click at (x, y) 63 */ 64 public static GestureDescription createClick(@IntRange(from = 0) int x, 65 @IntRange(from = 0) int y) { 66 Path clickPath = new Path(); 67 clickPath.moveTo(x, y); 68 clickPath.lineTo(x + 1, y); 69 return new GestureDescription( 70 new StrokeDescription(clickPath, 0, ViewConfiguration.getTapTimeout())); 71 } 72 73 /** 74 * Create a description of a long click gesture 75 * 76 * @param x The x coordinate to click. Must not be negative. 77 * @param y The y coordinate to click. Must not be negative. 78 * 79 * @return A description of a click at (x, y) 80 */ 81 public static GestureDescription createLongClick(@IntRange(from = 0) int x, 82 @IntRange(from = 0) int y) { 83 Path clickPath = new Path(); 84 clickPath.moveTo(x, y); 85 clickPath.lineTo(x + 1, y); 86 int longPressTime = ViewConfiguration.getLongPressTimeout(); 87 return new GestureDescription( 88 new StrokeDescription(clickPath, 0, longPressTime + (longPressTime / 2))); 89 } 90 91 /** 92 * Create a description of a swipe gesture 93 * 94 * @param startX The x coordinate of the starting point. Must not be negative. 95 * @param startY The y coordinate of the starting point. Must not be negative. 96 * @param endX The x coordinate of the ending point. Must not be negative. 97 * @param endY The y coordinate of the ending point. Must not be negative. 98 * @param duration The time, in milliseconds, to complete the gesture. Must not be negative. 99 * 100 * @return A description of a swipe from ({@code startX}, {@code startY}) to 101 * ({@code endX}, {@code endY}) that takes {@code duration} milliseconds. Returns {@code null} 102 * if the path specified for the swipe is invalid. 103 */ 104 public static GestureDescription createSwipe(@IntRange(from = 0) int startX, 105 @IntRange(from = 0) int startY, 106 @IntRange(from = 0) int endX, 107 @IntRange(from = 0) int endY, 108 @IntRange(from = 0, to = MAX_GESTURE_DURATION_MS) long duration) { 109 Path swipePath = new Path(); 110 swipePath.moveTo(startX, startY); 111 swipePath.lineTo(endX, endY); 112 return new GestureDescription(new StrokeDescription(swipePath, 0, duration)); 113 } 114 115 /** 116 * Create a description for a pinch (or zoom) gesture. 117 * 118 * @param centerX The x coordinate of the center of the pinch. Must not be negative. 119 * @param centerY The y coordinate of the center of the pinch. Must not be negative. 120 * @param startSpacing The spacing of the touch points at the beginning of the gesture. Must not 121 * be negative. 122 * @param endSpacing The spacing of the touch points at the end of the gesture. Must not be 123 * negative. 124 * @param orientation The angle, in degrees, of the gesture. 0 represents a horizontal pinch 125 * @param duration The time, in milliseconds, to complete the gesture. Must not be negative. 126 * 127 * @return A description of a pinch centered at ({code centerX}, {@code centerY}) that starts 128 * with the touch points spaced by {@code startSpacing} and ends with them spaced by 129 * {@code endSpacing} that lasts {@code duration} ms. Returns {@code null} if either path 130 * specified for the pinch is invalid. 131 */ 132 public static GestureDescription createPinch(@IntRange(from = 0) int centerX, 133 @IntRange(from = 0) int centerY, 134 @IntRange(from = 0) int startSpacing, 135 @IntRange(from = 0) int endSpacing, 136 float orientation, 137 @IntRange(from = 0, to = MAX_GESTURE_DURATION_MS) long duration) { 138 if ((startSpacing < 0) || (endSpacing < 0)) { 139 throw new IllegalArgumentException("Pinch spacing cannot be negative"); 140 } 141 float[] startPoint1 = new float[2]; 142 float[] endPoint1 = new float[2]; 143 float[] startPoint2 = new float[2]; 144 float[] endPoint2 = new float[2]; 145 146 /* Build points for a horizontal gesture centered at the origin */ 147 startPoint1[0] = startSpacing / 2; 148 startPoint1[1] = 0; 149 endPoint1[0] = endSpacing / 2; 150 endPoint1[1] = 0; 151 startPoint2[0] = -startSpacing / 2; 152 startPoint2[1] = 0; 153 endPoint2[0] = -endSpacing / 2; 154 endPoint2[1] = 0; 155 156 /* Rotate and translate the points */ 157 Matrix matrix = new Matrix(); 158 matrix.setRotate(orientation); 159 matrix.postTranslate(centerX, centerY); 160 matrix.mapPoints(startPoint1); 161 matrix.mapPoints(endPoint1); 162 matrix.mapPoints(startPoint2); 163 matrix.mapPoints(endPoint2); 164 165 Path path1 = new Path(); 166 path1.moveTo(startPoint1[0], startPoint1[1]); 167 path1.lineTo(endPoint1[0], endPoint1[1]); 168 Path path2 = new Path(); 169 path2.moveTo(startPoint2[0], startPoint2[1]); 170 path2.lineTo(endPoint2[0], endPoint2[1]); 171 172 return new GestureDescription(Arrays.asList( 173 new StrokeDescription(path1, 0, duration), 174 new StrokeDescription(path2, 0, duration))); 175 } 176 177 private GestureDescription() {} 178 179 private GestureDescription(List<StrokeDescription> strokes) { 180 mStrokes.addAll(strokes); 181 } 182 183 private GestureDescription(StrokeDescription stroke) { 184 mStrokes.add(stroke); 185 } 186 187 /** 188 * Get the number of stroke in the gesture. 189 * 190 * @return the number of strokes in this gesture 191 */ 192 public int getStrokeCount() { 193 return mStrokes.size(); 194 } 195 196 /** 197 * Read a stroke from the gesture 198 * 199 * @param index the index of the stroke 200 * 201 * @return A description of the stroke. 202 */ 203 public StrokeDescription getStroke(@IntRange(from = 0) int index) { 204 return mStrokes.get(index); 205 } 206 207 /** 208 * Return the smallest key point (where a path starts or ends) that is at least a specified 209 * offset 210 * @param offset the minimum start time 211 * @return The next key time that is at least the offset or -1 if one can't be found 212 */ 213 private long getNextKeyPointAtLeast(long offset) { 214 long nextKeyPoint = Long.MAX_VALUE; 215 for (int i = 0; i < mStrokes.size(); i++) { 216 long thisStartTime = mStrokes.get(i).mStartTime; 217 if ((thisStartTime < nextKeyPoint) && (thisStartTime >= offset)) { 218 nextKeyPoint = thisStartTime; 219 } 220 long thisEndTime = mStrokes.get(i).mEndTime; 221 if ((thisEndTime < nextKeyPoint) && (thisEndTime >= offset)) { 222 nextKeyPoint = thisEndTime; 223 } 224 } 225 return (nextKeyPoint == Long.MAX_VALUE) ? -1L : nextKeyPoint; 226 } 227 228 /** 229 * Get the points that correspond to a particular moment in time. 230 * @param time The time of interest 231 * @param touchPoints An array to hold the current touch points. Must be preallocated to at 232 * least the number of paths in the gesture to prevent going out of bounds 233 * @return The number of points found, and thus the number of elements set in each array 234 */ 235 private int getPointsForTime(long time, TouchPoint[] touchPoints) { 236 int numPointsFound = 0; 237 for (int i = 0; i < mStrokes.size(); i++) { 238 StrokeDescription strokeDescription = mStrokes.get(i); 239 if (strokeDescription.hasPointForTime(time)) { 240 touchPoints[numPointsFound].mPathIndex = i; 241 touchPoints[numPointsFound].mIsStartOfPath = (time == strokeDescription.mStartTime); 242 touchPoints[numPointsFound].mIsEndOfPath = (time == strokeDescription.mEndTime); 243 strokeDescription.getPosForTime(time, mTempPos); 244 touchPoints[numPointsFound].mX = Math.round(mTempPos[0]); 245 touchPoints[numPointsFound].mY = Math.round(mTempPos[1]); 246 numPointsFound++; 247 } 248 } 249 return numPointsFound; 250 } 251 252 // Total duration assumes that the gesture starts at 0; waiting around to start a gesture 253 // counts against total duration 254 private static long getTotalDuration(List<StrokeDescription> paths) { 255 long latestEnd = Long.MIN_VALUE; 256 for (int i = 0; i < paths.size(); i++) { 257 StrokeDescription path = paths.get(i); 258 latestEnd = Math.max(latestEnd, path.mEndTime); 259 } 260 return Math.max(latestEnd, 0); 261 } 262 263 /** 264 * Builder for a {@code GestureDescription} 265 */ 266 public static class Builder { 267 268 private final List<StrokeDescription> mStrokes = new ArrayList<>(); 269 270 /** 271 * Add a stroke to the gesture description. Up to {@code MAX_STROKE_COUNT} paths may be 272 * added to a gesture, and the total gesture duration (earliest path start time to latest path 273 * end time) may not exceed {@code MAX_GESTURE_DURATION_MS}. 274 * 275 * @param strokeDescription the stroke to add. 276 * 277 * @return this 278 */ 279 public Builder addStroke(@NonNull StrokeDescription strokeDescription) { 280 if (mStrokes.size() >= MAX_STROKE_COUNT) { 281 throw new RuntimeException("Attempting to add too many strokes to a gesture"); 282 } 283 284 mStrokes.add(strokeDescription); 285 286 if (getTotalDuration(mStrokes) > MAX_GESTURE_DURATION_MS) { 287 mStrokes.remove(strokeDescription); 288 throw new RuntimeException("Gesture would exceed maximum duration with new stroke"); 289 } 290 return this; 291 } 292 293 public GestureDescription build() { 294 if (mStrokes.size() == 0) { 295 throw new RuntimeException("Gestures must have at least one stroke"); 296 } 297 return new GestureDescription(mStrokes); 298 } 299 } 300 301 /** 302 * Immutable description of stroke that can be part of a gesture. 303 */ 304 public static class StrokeDescription { 305 Path mPath; 306 long mStartTime; 307 long mEndTime; 308 private float mTimeToLengthConversion; 309 private PathMeasure mPathMeasure; 310 311 /** 312 * @param path The path to follow. Must have exactly one contour, and that contour must 313 * have nonzero length. The bounds of the path must not be negative. 314 * @param startTime The time, in milliseconds, from the time the gesture starts to the 315 * time the stroke should start. Must not be negative. 316 * @param duration The duration, in milliseconds, the stroke takes to traverse the path. 317 * Must not be negative. 318 */ 319 public StrokeDescription(@NonNull Path path, 320 @IntRange(from = 0, to = MAX_GESTURE_DURATION_MS) long startTime, 321 @IntRange(from = 0, to = MAX_GESTURE_DURATION_MS) long duration) { 322 if (duration <= 0) { 323 throw new IllegalArgumentException("Duration must be positive"); 324 } 325 if (startTime < 0) { 326 throw new IllegalArgumentException("Start time must not be negative"); 327 } 328 RectF bounds = new RectF(); 329 path.computeBounds(bounds, false /* unused */); 330 if ((bounds.bottom < 0) || (bounds.top < 0) || (bounds.right < 0) 331 || (bounds.left < 0)) { 332 throw new IllegalArgumentException("Path bounds must not be negative"); 333 } 334 mPath = new Path(path); 335 mPathMeasure = new PathMeasure(path, false); 336 if (mPathMeasure.getLength() == 0) { 337 throw new IllegalArgumentException("Path has zero length"); 338 } 339 if (mPathMeasure.nextContour()) { 340 throw new IllegalArgumentException("Path has more than one contour"); 341 } 342 /* 343 * Calling nextContour has moved mPathMeasure off the first contour, which is the only 344 * one we care about. Set the path again to go back to the first contour. 345 */ 346 mPathMeasure.setPath(path, false); 347 mStartTime = startTime; 348 mEndTime = startTime + duration; 349 if (duration > 0) { 350 mTimeToLengthConversion = getLength() / duration; 351 } 352 } 353 354 /** 355 * Retrieve a copy of the path for this stroke 356 * 357 * @return A copy of the path 358 */ 359 public Path getPath() { 360 return new Path(mPath); 361 } 362 363 /** 364 * Get the stroke's start time 365 * 366 * @return the start time for this stroke. 367 */ 368 public long getStartTime() { 369 return mStartTime; 370 } 371 372 /** 373 * Get the stroke's duration 374 * 375 * @return the duration for this stroke 376 */ 377 public long getDuration() { 378 return mEndTime - mStartTime; 379 } 380 381 float getLength() { 382 return mPathMeasure.getLength(); 383 } 384 385 /* Assumes hasPointForTime returns true */ 386 boolean getPosForTime(long time, float[] pos) { 387 if (time == mEndTime) { 388 // Close to the end time, roundoff can be a problem 389 return mPathMeasure.getPosTan(getLength(), pos, null); 390 } 391 float length = mTimeToLengthConversion * ((float) (time - mStartTime)); 392 return mPathMeasure.getPosTan(length, pos, null); 393 } 394 395 boolean hasPointForTime(long time) { 396 return ((time >= mStartTime) && (time <= mEndTime)); 397 } 398 } 399 400 private static class TouchPoint { 401 int mPathIndex; 402 boolean mIsStartOfPath; 403 boolean mIsEndOfPath; 404 float mX; 405 float mY; 406 407 void copyFrom(TouchPoint other) { 408 mPathIndex = other.mPathIndex; 409 mIsStartOfPath = other.mIsStartOfPath; 410 mIsEndOfPath = other.mIsEndOfPath; 411 mX = other.mX; 412 mY = other.mY; 413 } 414 } 415 416 /** 417 * Class to convert a GestureDescription to a series of MotionEvents. 418 */ 419 static class MotionEventGenerator { 420 /** 421 * Constants used to initialize all MotionEvents 422 */ 423 private static final int EVENT_META_STATE = 0; 424 private static final int EVENT_BUTTON_STATE = 0; 425 private static final int EVENT_DEVICE_ID = 0; 426 private static final int EVENT_EDGE_FLAGS = 0; 427 private static final int EVENT_SOURCE = InputDevice.SOURCE_TOUCHSCREEN; 428 private static final int EVENT_FLAGS = 0; 429 private static final float EVENT_X_PRECISION = 1; 430 private static final float EVENT_Y_PRECISION = 1; 431 432 /* Lazily-created scratch memory for processing touches */ 433 private static TouchPoint[] sCurrentTouchPoints; 434 private static TouchPoint[] sLastTouchPoints; 435 private static PointerCoords[] sPointerCoords; 436 private static PointerProperties[] sPointerProps; 437 438 static List<MotionEvent> getMotionEventsFromGestureDescription( 439 GestureDescription description, int sampleTimeMs) { 440 final List<MotionEvent> motionEvents = new ArrayList<>(); 441 442 // Point data at each time we generate an event for 443 final TouchPoint[] currentTouchPoints = 444 getCurrentTouchPoints(description.getStrokeCount()); 445 // Point data sent in last touch event 446 int lastTouchPointSize = 0; 447 final TouchPoint[] lastTouchPoints = 448 getLastTouchPoints(description.getStrokeCount()); 449 450 /* Loop through each time slice where there are touch points */ 451 long timeSinceGestureStart = 0; 452 long nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart); 453 while (nextKeyPointTime >= 0) { 454 timeSinceGestureStart = (lastTouchPointSize == 0) ? nextKeyPointTime 455 : Math.min(nextKeyPointTime, timeSinceGestureStart + sampleTimeMs); 456 int currentTouchPointSize = description.getPointsForTime(timeSinceGestureStart, 457 currentTouchPoints); 458 459 appendMoveEventIfNeeded(motionEvents, lastTouchPoints, lastTouchPointSize, 460 currentTouchPoints, currentTouchPointSize, timeSinceGestureStart); 461 lastTouchPointSize = appendUpEvents(motionEvents, lastTouchPoints, 462 lastTouchPointSize, currentTouchPoints, currentTouchPointSize, 463 timeSinceGestureStart); 464 lastTouchPointSize = appendDownEvents(motionEvents, lastTouchPoints, 465 lastTouchPointSize, currentTouchPoints, currentTouchPointSize, 466 timeSinceGestureStart); 467 468 /* Move to next time slice */ 469 nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart + 1); 470 } 471 return motionEvents; 472 } 473 474 private static TouchPoint[] getCurrentTouchPoints(int requiredCapacity) { 475 if ((sCurrentTouchPoints == null) || (sCurrentTouchPoints.length < requiredCapacity)) { 476 sCurrentTouchPoints = new TouchPoint[requiredCapacity]; 477 for (int i = 0; i < requiredCapacity; i++) { 478 sCurrentTouchPoints[i] = new TouchPoint(); 479 } 480 } 481 return sCurrentTouchPoints; 482 } 483 484 private static TouchPoint[] getLastTouchPoints(int requiredCapacity) { 485 if ((sLastTouchPoints == null) || (sLastTouchPoints.length < requiredCapacity)) { 486 sLastTouchPoints = new TouchPoint[requiredCapacity]; 487 for (int i = 0; i < requiredCapacity; i++) { 488 sLastTouchPoints[i] = new TouchPoint(); 489 } 490 } 491 return sLastTouchPoints; 492 } 493 494 private static PointerCoords[] getPointerCoords(int requiredCapacity) { 495 if ((sPointerCoords == null) || (sPointerCoords.length < requiredCapacity)) { 496 sPointerCoords = new PointerCoords[requiredCapacity]; 497 for (int i = 0; i < requiredCapacity; i++) { 498 sPointerCoords[i] = new PointerCoords(); 499 } 500 } 501 return sPointerCoords; 502 } 503 504 private static PointerProperties[] getPointerProps(int requiredCapacity) { 505 if ((sPointerProps == null) || (sPointerProps.length < requiredCapacity)) { 506 sPointerProps = new PointerProperties[requiredCapacity]; 507 for (int i = 0; i < requiredCapacity; i++) { 508 sPointerProps[i] = new PointerProperties(); 509 } 510 } 511 return sPointerProps; 512 } 513 514 private static void appendMoveEventIfNeeded(List<MotionEvent> motionEvents, 515 TouchPoint[] lastTouchPoints, int lastTouchPointsSize, 516 TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) { 517 /* Look for pointers that have moved */ 518 boolean moveFound = false; 519 for (int i = 0; i < currentTouchPointsSize; i++) { 520 int lastPointsIndex = findPointByPathIndex(lastTouchPoints, lastTouchPointsSize, 521 currentTouchPoints[i].mPathIndex); 522 if (lastPointsIndex >= 0) { 523 moveFound |= (lastTouchPoints[lastPointsIndex].mX != currentTouchPoints[i].mX) 524 || (lastTouchPoints[lastPointsIndex].mY != currentTouchPoints[i].mY); 525 lastTouchPoints[lastPointsIndex].copyFrom(currentTouchPoints[i]); 526 } 527 } 528 529 if (moveFound) { 530 long downTime = motionEvents.get(motionEvents.size() - 1).getDownTime(); 531 motionEvents.add(obtainMotionEvent(downTime, currentTime, MotionEvent.ACTION_MOVE, 532 lastTouchPoints, lastTouchPointsSize)); 533 } 534 } 535 536 private static int appendUpEvents(List<MotionEvent> motionEvents, 537 TouchPoint[] lastTouchPoints, int lastTouchPointsSize, 538 TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) { 539 /* Look for a pointer at the end of its path */ 540 for (int i = 0; i < currentTouchPointsSize; i++) { 541 if (currentTouchPoints[i].mIsEndOfPath) { 542 int indexOfUpEvent = findPointByPathIndex(lastTouchPoints, lastTouchPointsSize, 543 currentTouchPoints[i].mPathIndex); 544 if (indexOfUpEvent < 0) { 545 continue; // Should not happen 546 } 547 long downTime = motionEvents.get(motionEvents.size() - 1).getDownTime(); 548 int action = (lastTouchPointsSize == 1) ? MotionEvent.ACTION_UP 549 : MotionEvent.ACTION_POINTER_UP; 550 action |= indexOfUpEvent << MotionEvent.ACTION_POINTER_INDEX_SHIFT; 551 motionEvents.add(obtainMotionEvent(downTime, currentTime, action, 552 lastTouchPoints, lastTouchPointsSize)); 553 /* Remove this point from lastTouchPoints */ 554 for (int j = indexOfUpEvent; j < lastTouchPointsSize - 1; j++) { 555 lastTouchPoints[j].copyFrom(lastTouchPoints[j+1]); 556 } 557 lastTouchPointsSize--; 558 } 559 } 560 return lastTouchPointsSize; 561 } 562 563 private static int appendDownEvents(List<MotionEvent> motionEvents, 564 TouchPoint[] lastTouchPoints, int lastTouchPointsSize, 565 TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) { 566 /* Look for a pointer that is just starting */ 567 for (int i = 0; i < currentTouchPointsSize; i++) { 568 if (currentTouchPoints[i].mIsStartOfPath) { 569 /* Add the point to last coords and use the new array to generate the event */ 570 lastTouchPoints[lastTouchPointsSize++].copyFrom(currentTouchPoints[i]); 571 int action = (lastTouchPointsSize == 1) ? MotionEvent.ACTION_DOWN 572 : MotionEvent.ACTION_POINTER_DOWN; 573 long downTime = (action == MotionEvent.ACTION_DOWN) ? currentTime : 574 motionEvents.get(motionEvents.size() - 1).getDownTime(); 575 action |= i << MotionEvent.ACTION_POINTER_INDEX_SHIFT; 576 motionEvents.add(obtainMotionEvent(downTime, currentTime, action, 577 lastTouchPoints, lastTouchPointsSize)); 578 } 579 } 580 return lastTouchPointsSize; 581 } 582 583 private static MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, 584 TouchPoint[] touchPoints, int touchPointsSize) { 585 PointerCoords[] pointerCoords = getPointerCoords(touchPointsSize); 586 PointerProperties[] pointerProperties = getPointerProps(touchPointsSize); 587 for (int i = 0; i < touchPointsSize; i++) { 588 pointerProperties[i].id = touchPoints[i].mPathIndex; 589 pointerProperties[i].toolType = MotionEvent.TOOL_TYPE_UNKNOWN; 590 pointerCoords[i].clear(); 591 pointerCoords[i].pressure = 1.0f; 592 pointerCoords[i].size = 1.0f; 593 pointerCoords[i].x = touchPoints[i].mX; 594 pointerCoords[i].y = touchPoints[i].mY; 595 } 596 return MotionEvent.obtain(downTime, eventTime, action, touchPointsSize, 597 pointerProperties, pointerCoords, EVENT_META_STATE, EVENT_BUTTON_STATE, 598 EVENT_X_PRECISION, EVENT_Y_PRECISION, EVENT_DEVICE_ID, EVENT_EDGE_FLAGS, 599 EVENT_SOURCE, EVENT_FLAGS); 600 } 601 602 private static int findPointByPathIndex(TouchPoint[] touchPoints, int touchPointsSize, 603 int pathIndex) { 604 for (int i = 0; i < touchPointsSize; i++) { 605 if (touchPoints[i].mPathIndex == pathIndex) { 606 return i; 607 } 608 } 609 return -1; 610 } 611 } 612} 613