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