1 package org.robolectric.shadows; 2 3 import static com.google.common.base.Preconditions.checkState; 4 import static org.robolectric.shadows.NativeAndroidInput.AINPUT_EVENT_TYPE_MOTION; 5 import static org.robolectric.shadows.NativeAndroidInput.AINPUT_SOURCE_CLASS_POINTER; 6 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_CANCEL; 7 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_DOWN; 8 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_MASK; 9 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_MOVE; 10 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_OUTSIDE; 11 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_POINTER_DOWN; 12 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_POINTER_INDEX_MASK; 13 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; 14 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_POINTER_UP; 15 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_UP; 16 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_ORIENTATION; 17 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_PRESSURE; 18 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_SIZE; 19 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOOL_MAJOR; 20 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOOL_MINOR; 21 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOUCH_MAJOR; 22 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOUCH_MINOR; 23 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_X; 24 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_Y; 25 26 import android.os.Parcel; 27 import android.view.MotionEvent.PointerProperties; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.List; 31 import org.robolectric.res.android.Ref; 32 33 /** 34 * Java representation of framework native input Transliterated from oreo-mr1 (SDK 27) 35 * frameworks/native/include/input/Input.h and libs/input/Input.cpp 36 * 37 * @see <a href="https://android.googlesource.com/platform/frameworks/native/+/oreo-mr1-release/include/input/Input.h">include/input/Input.h</a> 38 * @see <a href="https://android.googlesource.com/platform/frameworks/native/+/oreo-mr1-release/libs/input/Input.cpp>libs/input/Input.cpp</a> 39 */ 40 public class NativeInput { 41 42 /* 43 * Maximum number of pointers supported per motion event. 44 * Smallest number of pointers is 1. 45 * (We want at least 10 but some touch controllers obstensibly configured for 10 pointers 46 * will occasionally emit 11. There is not much harm making this ant bigger.) 47 */ 48 private static final int MAX_POINTERS = 16; 49 /* 50 * Maximum number of samples supported per motion event. 51 */ 52 private static final int MAX_SAMPLES = 2 ^ 16; /* UINT16_MAX */ 53 /* 54 * Maximum pointer id value supported in a motion event. 55 * Smallest pointer id is 0. 56 * (This is limited by our use of BitSet32 to track pointer assignments.) 57 */ 58 private static final int MAX_POINTER_ID = 31; 59 60 /* 61 * Declare a concrete type for the NDK's input event forward declaration. 62 */ 63 static class AInputEvent {} 64 65 /* 66 * Pointer coordinate data. 67 * 68 * Deviates from original platform implementation to store axises in simple SparseArray as opposed 69 * to complicated bitset + array arrangement. 70 */ 71 static class PointerCoords { 72 73 private static final int MAX_AXES = 30; 74 75 // Bitfield of axes that are present in this structure. 76 private NativeBitSet64 bits = new NativeBitSet64(); 77 78 NativeBitSet64 getBits() { 79 return bits; 80 } 81 82 // Values of axes that are stored in this structure 83 private float[] values = new float[MAX_AXES]; 84 85 public void clear() { 86 bits.clear(); 87 } 88 89 public boolean isEmpty() { 90 return bits.isEmpty(); 91 } 92 93 public float getAxisValue(int axis) { 94 if (axis < 0 || axis > 63 || !bits.hasBit(axis)) { 95 return 0; 96 } 97 return values[bits.getIndexOfBit(axis)]; 98 } 99 100 public boolean setAxisValue(int axis, float value) { 101 checkState(axis >= 0 && axis <= 63, "axis out of range"); 102 int index = bits.getIndexOfBit(axis); 103 if (!bits.hasBit(axis)) { 104 if (value == 0) { 105 return true; // axes with value 0 do not need to be stored 106 } 107 108 int count = bits.count(); 109 if (count >= MAX_AXES) { 110 tooManyAxes(axis); 111 return false; 112 } 113 bits.markBit(axis); 114 for (int i = count; i > index; i--) { 115 values[i] = values[i - 1]; 116 } 117 } 118 values[index] = value; 119 return true; 120 } 121 122 static void scaleAxisValue(PointerCoords c, int axis, float scaleFactor) { 123 float value = c.getAxisValue(axis); 124 if (value != 0) { 125 c.setAxisValue(axis, value * scaleFactor); 126 } 127 } 128 129 public void scale(float scaleFactor) { 130 // No need to scale pressure or size since they are normalized. 131 // No need to scale orientation since it is meaningless to do so. 132 scaleAxisValue(this, AMOTION_EVENT_AXIS_X, scaleFactor); 133 scaleAxisValue(this, AMOTION_EVENT_AXIS_Y, scaleFactor); 134 scaleAxisValue(this, AMOTION_EVENT_AXIS_TOUCH_MAJOR, scaleFactor); 135 scaleAxisValue(this, AMOTION_EVENT_AXIS_TOUCH_MINOR, scaleFactor); 136 scaleAxisValue(this, AMOTION_EVENT_AXIS_TOOL_MAJOR, scaleFactor); 137 scaleAxisValue(this, AMOTION_EVENT_AXIS_TOOL_MINOR, scaleFactor); 138 } 139 140 public void applyOffset(float xOffset, float yOffset) { 141 setAxisValue(AMOTION_EVENT_AXIS_X, getX() + xOffset); 142 setAxisValue(AMOTION_EVENT_AXIS_Y, getY() + yOffset); 143 } 144 145 public float getX() { 146 return getAxisValue(AMOTION_EVENT_AXIS_X); 147 } 148 149 public float getY() { 150 return getAxisValue(AMOTION_EVENT_AXIS_Y); 151 } 152 153 public boolean readFromParcel(Parcel parcel) { 154 bits.setValue(parcel.readLong()); 155 int count = bits.count(); 156 if (count > MAX_AXES) { 157 return false; 158 } 159 for (int i = 0; i < count; i++) { 160 values[i] = parcel.readFloat(); 161 } 162 return true; 163 } 164 165 public boolean writeToParcel(Parcel parcel) { 166 parcel.writeLong(bits.getValue()); 167 int count = bits.count(); 168 for (int i = 0; i < count; i++) { 169 parcel.writeFloat(values[i]); 170 } 171 return true; 172 } 173 174 // bool operator==( PointerCoords& other) ; 175 // bool operator!=( PointerCoords& other) { 176 // return !(*this == other); 177 // } 178 179 public void copyFrom(PointerCoords other) { 180 bits = new NativeBitSet64(other.bits); 181 int count = bits.count(); 182 for (int i = 0; i < count; i++) { 183 values[i] = other.values[i]; 184 } 185 } 186 187 private static void tooManyAxes(int axis) { 188 // native code just logs this as warning. Be a bit more defensive for now and throw 189 throw new IllegalStateException( 190 String.format( 191 "Could not set value for axis %d because the PointerCoords structure is full and " 192 + "cannot contain more than %d axis values.", 193 axis, MAX_AXES)); 194 } 195 } 196 197 /* 198 * Input events. 199 */ 200 static class InputEvent extends AInputEvent { 201 202 protected int mDeviceId; 203 protected int mSource; 204 205 public int getType() { 206 return 0; 207 } 208 209 public int getDeviceId() { 210 return mDeviceId; 211 } 212 213 public int getSource() { 214 return mSource; 215 } 216 217 public void setSource(int source) { 218 mSource = source; 219 } 220 221 protected void initialize(int deviceId, int source) { 222 this.mDeviceId = deviceId; 223 this.mSource = source; 224 } 225 226 protected void initialize(NativeInput.InputEvent from) { 227 initialize(from.getDeviceId(), from.getSource()); 228 } 229 } 230 231 /* 232 * Key events. 233 */ 234 static class KeyEvent extends InputEvent { 235 // public: 236 // virtual ~KeyEvent() { } 237 // virtual int getType() { return AINPUT_EVENT_TYPE_KEY; } 238 // int getAction() { return mAction; } 239 // int getFlags() { return mFlags; } 240 // void setFlags(int flags) { mFlags = flags; } 241 // int getKeyCode() { return mKeyCode; } 242 // int getScanCode() { return mScanCode; } 243 // int getMetaState() { return mMetaState; } 244 // int getRepeatCount() { return mRepeatCount; } 245 // nsecs_t getDownTime() { return mDownTime; } 246 // nsecs_t getEventTime() { return mEventTime; } 247 // static char* getLabel(int keyCode); 248 // static int getKeyCodeFromLabel( char* label); 249 // 250 // void initialize( 251 // int deviceId, 252 // int source, 253 // int action, 254 // int flags, 255 // int keyCode, 256 // int scanCode, 257 // int metaState, 258 // int repeatCount, 259 // nsecs_t downTime, 260 // nsecs_t eventTime); 261 // void initialize( KeyEvent& from); 262 // protected: 263 // int mAction; 264 // int mFlags; 265 // int mKeyCode; 266 // int mScanCode; 267 // int mMetaState; 268 // int mRepeatCount; 269 // nsecs_t mDownTime; 270 // nsecs_t mEventTime; 271 } 272 273 /* 274 * Motion events. 275 */ 276 static class MotionEvent extends InputEvent { 277 278 // constants copied from android bionic/libc/include/math.h 279 @SuppressWarnings("FloatingPointLiteralPrecision") 280 private static final double M_PI = 3.14159265358979323846f; /* pi */ 281 282 @SuppressWarnings("FloatingPointLiteralPrecision") 283 private static final double M_PI_2 = 1.57079632679489661923f; /* pi/2 */ 284 285 private int mAction; 286 private int mActionButton; 287 private int mFlags; 288 private int mEdgeFlags; 289 private int mMetaState; 290 private int mButtonState; 291 private float mXOffset; 292 private float mYOffset; 293 private float mXPrecision; 294 private float mYPrecision; 295 private long mDownTime; 296 private List<PointerProperties> mPointerProperties = new ArrayList<>(); 297 private List<Long> mSampleEventTimes = new ArrayList<>(); 298 private List<NativeInput.PointerCoords> mSamplePointerCoords = new ArrayList<>(); 299 300 @Override 301 public int getType() { 302 return AINPUT_EVENT_TYPE_MOTION; 303 } 304 305 public int getAction() { 306 return mAction; 307 } 308 309 public int getActionMasked() { 310 return mAction & AMOTION_EVENT_ACTION_MASK; 311 } 312 313 public int getActionIndex() { 314 return (mAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) 315 >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; 316 } 317 318 public void setAction(int action) { 319 mAction = action; 320 } 321 322 public int getFlags() { 323 return mFlags; 324 } 325 326 public void setFlags(int flags) { 327 mFlags = flags; 328 } 329 330 public int getEdgeFlags() { 331 return mEdgeFlags; 332 } 333 334 public void setEdgeFlags(int edgeFlags) { 335 mEdgeFlags = edgeFlags; 336 } 337 338 public int getMetaState() { 339 return mMetaState; 340 } 341 342 public void setMetaState(int metaState) { 343 mMetaState = metaState; 344 } 345 346 public int getButtonState() { 347 return mButtonState; 348 } 349 350 public void setButtonState(int buttonState) { 351 mButtonState = buttonState; 352 } 353 354 public int getActionButton() { 355 return mActionButton; 356 } 357 358 public void setActionButton(int button) { 359 mActionButton = button; 360 } 361 362 public float getXOffset() { 363 return mXOffset; 364 } 365 366 public float getYOffset() { 367 return mYOffset; 368 } 369 370 public float getXPrecision() { 371 return mXPrecision; 372 } 373 374 public float getYPrecision() { 375 return mYPrecision; 376 } 377 378 public long getDownTime() { 379 return mDownTime; 380 } 381 382 public void setDownTime(long downTime) { 383 mDownTime = downTime; 384 } 385 386 public int getPointerCount() { 387 return mPointerProperties.size(); 388 } 389 390 public PointerProperties getPointerProperties(int pointerIndex) { 391 return mPointerProperties.get(pointerIndex); 392 } 393 394 public int getPointerId(int pointerIndex) { 395 return mPointerProperties.get(pointerIndex).id; 396 } 397 398 public int getToolType(int pointerIndex) { 399 return mPointerProperties.get(pointerIndex).toolType; 400 } 401 402 public long getEventTime() { 403 return mSampleEventTimes.get(getHistorySize()); 404 } 405 406 public PointerCoords getRawPointerCoords(int pointerIndex) { 407 408 return mSamplePointerCoords.get(getHistorySize() * getPointerCount() + pointerIndex); 409 } 410 411 public float getRawAxisValue(int axis, int pointerIndex) { 412 return getRawPointerCoords(pointerIndex).getAxisValue(axis); 413 } 414 415 public float getRawX(int pointerIndex) { 416 return getRawAxisValue(AMOTION_EVENT_AXIS_X, pointerIndex); 417 } 418 419 public float getRawY(int pointerIndex) { 420 return getRawAxisValue(AMOTION_EVENT_AXIS_Y, pointerIndex); 421 } 422 423 public float getAxisValue(int axis, int pointerIndex) { 424 float value = getRawPointerCoords(pointerIndex).getAxisValue(axis); 425 switch (axis) { 426 case AMOTION_EVENT_AXIS_X: 427 return value + mXOffset; 428 case AMOTION_EVENT_AXIS_Y: 429 return value + mYOffset; 430 } 431 return value; 432 } 433 434 public float getX(int pointerIndex) { 435 return getAxisValue(AMOTION_EVENT_AXIS_X, pointerIndex); 436 } 437 438 public float getY(int pointerIndex) { 439 return getAxisValue(AMOTION_EVENT_AXIS_Y, pointerIndex); 440 } 441 442 public float getPressure(int pointerIndex) { 443 return getAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerIndex); 444 } 445 446 public float getSize(int pointerIndex) { 447 return getAxisValue(AMOTION_EVENT_AXIS_SIZE, pointerIndex); 448 } 449 450 public float getTouchMajor(int pointerIndex) { 451 return getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex); 452 } 453 454 public float getTouchMinor(int pointerIndex) { 455 return getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex); 456 } 457 458 public float getToolMajor(int pointerIndex) { 459 return getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex); 460 } 461 462 public float getToolMinor(int pointerIndex) { 463 return getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex); 464 } 465 466 public float getOrientation(int pointerIndex) { 467 return getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex); 468 } 469 470 public int getHistorySize() { 471 return mSampleEventTimes.size() - 1; 472 } 473 474 public long getHistoricalEventTime(int historicalIndex) { 475 return mSampleEventTimes.get(historicalIndex); 476 } 477 478 public PointerCoords getHistoricalRawPointerCoords(int pointerIndex, int historicalIndex) { 479 return mSamplePointerCoords.get(historicalIndex * getPointerCount() + pointerIndex); 480 } 481 482 public float getHistoricalRawAxisValue(int axis, int pointerIndex, int historicalIndex) { 483 return getHistoricalRawPointerCoords(pointerIndex, historicalIndex).getAxisValue(axis); 484 } 485 486 public float getHistoricalRawX(int pointerIndex, int historicalIndex) { 487 return getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_X, pointerIndex, historicalIndex); 488 } 489 490 public float getHistoricalRawY(int pointerIndex, int historicalIndex) { 491 return getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, pointerIndex, historicalIndex); 492 } 493 494 public float getHistoricalAxisValue(int axis, int pointerIndex, int historicalIndex) { 495 float value = getHistoricalRawPointerCoords(pointerIndex, historicalIndex).getAxisValue(axis); 496 switch (axis) { 497 case AMOTION_EVENT_AXIS_X: 498 return value + mXOffset; 499 case AMOTION_EVENT_AXIS_Y: 500 return value + mYOffset; 501 } 502 return value; 503 } 504 505 public float getHistoricalX(int pointerIndex, int historicalIndex) { 506 return getHistoricalAxisValue(AMOTION_EVENT_AXIS_X, pointerIndex, historicalIndex); 507 } 508 509 public float getHistoricalY(int pointerIndex, int historicalIndex) { 510 return getHistoricalAxisValue(AMOTION_EVENT_AXIS_Y, pointerIndex, historicalIndex); 511 } 512 513 public float getHistoricalPressure(int pointerIndex, int historicalIndex) { 514 return getHistoricalAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historicalIndex); 515 } 516 517 public float getHistoricalSize(int pointerIndex, int historicalIndex) { 518 return getHistoricalAxisValue(AMOTION_EVENT_AXIS_SIZE, pointerIndex, historicalIndex); 519 } 520 521 public float getHistoricalTouchMajor(int pointerIndex, int historicalIndex) { 522 return getHistoricalAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historicalIndex); 523 } 524 525 public float getHistoricalTouchMinor(int pointerIndex, int historicalIndex) { 526 return getHistoricalAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historicalIndex); 527 } 528 529 public float getHistoricalToolMajor(int pointerIndex, int historicalIndex) { 530 return getHistoricalAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historicalIndex); 531 } 532 533 public float getHistoricalToolMinor(int pointerIndex, int historicalIndex) { 534 return getHistoricalAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historicalIndex); 535 } 536 537 public float getHistoricalOrientation(int pointerIndex, int historicalIndex) { 538 return getHistoricalAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex); 539 } 540 541 public int findPointerIndex(int pointerId) { 542 int pointerCount = mPointerProperties.size(); 543 for (int i = 0; i < pointerCount; i++) { 544 if (mPointerProperties.get(i).id == pointerId) { 545 return i; 546 } 547 } 548 return -1; 549 } 550 551 public void initialize( 552 int deviceId, 553 int source, 554 int action, 555 int actionButton, 556 int flags, 557 int edgeFlags, 558 int metaState, 559 int buttonState, 560 float xOffset, 561 float yOffset, 562 float xPrecision, 563 float yPrecision, 564 long downTime, 565 long eventTime, 566 int pointerCount, 567 PointerProperties[] pointerProperties, 568 NativeInput.PointerCoords[] pointerCoords) { 569 super.initialize(deviceId, source); 570 mAction = action; 571 mActionButton = actionButton; 572 mFlags = flags; 573 mEdgeFlags = edgeFlags; 574 mMetaState = metaState; 575 mButtonState = buttonState; 576 mXOffset = xOffset; 577 mYOffset = yOffset; 578 mXPrecision = xPrecision; 579 mYPrecision = yPrecision; 580 mDownTime = downTime; 581 mPointerProperties.clear(); 582 mPointerProperties.addAll(Arrays.asList(pointerProperties).subList(0, pointerCount)); 583 mSampleEventTimes.clear(); 584 mSamplePointerCoords.clear(); 585 addSample(eventTime, Arrays.asList(pointerCoords).subList(0, pointerCount)); 586 } 587 588 public void copyFrom(MotionEvent other, boolean keepHistory) { 589 super.initialize(other.getDeviceId(), other.getSource()); 590 mAction = other.mAction; 591 mActionButton = other.mActionButton; 592 mFlags = other.mFlags; 593 mEdgeFlags = other.mEdgeFlags; 594 mMetaState = other.mMetaState; 595 mButtonState = other.mButtonState; 596 mXOffset = other.mXOffset; 597 mYOffset = other.mYOffset; 598 mXPrecision = other.mXPrecision; 599 mYPrecision = other.mYPrecision; 600 mDownTime = other.mDownTime; 601 mPointerProperties = other.mPointerProperties; 602 mSampleEventTimes.clear(); 603 mSamplePointerCoords.clear(); 604 if (keepHistory) { 605 mSampleEventTimes.addAll(other.mSampleEventTimes); 606 mSamplePointerCoords.addAll(other.mSamplePointerCoords); 607 } else { 608 mSampleEventTimes.add(other.getEventTime()); 609 int pointerCount = other.getPointerCount(); 610 int historySize = other.getHistorySize(); 611 // mSamplePointerCoords.appendArray(other->mSamplePointerCoords.array() 612 // + (historySize * pointerCount), pointerCount); 613 int currentStartIndex = historySize * pointerCount; 614 mSamplePointerCoords.addAll( 615 other.mSamplePointerCoords.subList( 616 currentStartIndex, currentStartIndex + pointerCount)); 617 } 618 } 619 620 public void addSample(long eventTime, PointerCoords[] pointerCoords) { 621 addSample(eventTime, Arrays.asList(pointerCoords)); 622 } 623 624 public void addSample(long eventTime, List<PointerCoords> pointerCoords) { 625 mSampleEventTimes.add(eventTime); 626 mSamplePointerCoords.addAll(pointerCoords); 627 } 628 629 public void offsetLocation(float xOffset, float yOffset) { 630 mXOffset += xOffset; 631 mYOffset += yOffset; 632 } 633 634 public void scale(float scaleFactor) { 635 mXOffset *= scaleFactor; 636 mYOffset *= scaleFactor; 637 mXPrecision *= scaleFactor; 638 mYPrecision *= scaleFactor; 639 int numSamples = mSamplePointerCoords.size(); 640 for (int i = 0; i < numSamples; i++) { 641 mSamplePointerCoords.get(i).scale(scaleFactor); 642 } 643 } 644 645 // Apply 3x3 perspective matrix transformation. 646 // Matrix is in row-major form and compatible with SkMatrix. 647 public void transform(float[] matrix) { 648 checkState(matrix.length == 9); 649 // The tricky part of this implementation is to preserve the value of 650 // rawX and rawY. So we apply the transformation to the first point 651 // then derive an appropriate new X/Y offset that will preserve rawX 652 // and rawY for that point. 653 float oldXOffset = mXOffset; 654 float oldYOffset = mYOffset; 655 final Ref<Float> newX = new Ref<>(0f); 656 final Ref<Float> newY = new Ref<>(0f); 657 float rawX = getRawX(0); 658 float rawY = getRawY(0); 659 transformPoint(matrix, rawX + oldXOffset, rawY + oldYOffset, newX, newY); 660 mXOffset = newX.get() - rawX; 661 mYOffset = newY.get() - rawY; 662 // Determine how the origin is transformed by the matrix so that we 663 // can transform orientation vectors. 664 final Ref<Float> originX = new Ref<>(0f); 665 final Ref<Float> originY = new Ref<>(0f); 666 transformPoint(matrix, 0, 0, originX, originY); 667 // Apply the transformation to all samples. 668 int numSamples = mSamplePointerCoords.size(); 669 for (int i = 0; i < numSamples; i++) { 670 PointerCoords c = mSamplePointerCoords.get(i); 671 final Ref<Float> x = new Ref<>(c.getAxisValue(AMOTION_EVENT_AXIS_X) + oldXOffset); 672 final Ref<Float> y = new Ref<>(c.getAxisValue(AMOTION_EVENT_AXIS_Y) + oldYOffset); 673 transformPoint(matrix, x.get(), y.get(), x, y); 674 c.setAxisValue(AMOTION_EVENT_AXIS_X, x.get() - mXOffset); 675 c.setAxisValue(AMOTION_EVENT_AXIS_Y, y.get() - mYOffset); 676 float orientation = c.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); 677 c.setAxisValue( 678 AMOTION_EVENT_AXIS_ORIENTATION, 679 transformAngle(matrix, orientation, originX.get(), originY.get())); 680 } 681 } 682 683 private static void transformPoint( 684 float[] matrix, float x, float y, Ref<Float> outX, Ref<Float> outY) { 685 checkState(matrix.length == 9); 686 // Apply perspective transform like Skia. 687 float newX = matrix[0] * x + matrix[1] * y + matrix[2]; 688 float newY = matrix[3] * x + matrix[4] * y + matrix[5]; 689 float newZ = matrix[6] * x + matrix[7] * y + matrix[8]; 690 if (newZ != 0) { 691 newZ = 1.0f / newZ; 692 } 693 outX.set(newX * newZ); 694 outY.set(newY * newZ); 695 } 696 697 static float transformAngle(float[] matrix, float angleRadians, float originX, float originY) { 698 checkState(matrix.length == 9); 699 // ruct and transform a vector oriented at the specified clockwise angle from vertical. 700 // Coordinate system: down is increasing Y, right is increasing X. 701 final Ref<Float> x = new Ref<>((float) Math.sin(angleRadians)); 702 final Ref<Float> y = new Ref<>(-(float) Math.cos(angleRadians)); 703 transformPoint(matrix, x.get(), y.get(), x, y); 704 x.set(x.get() - originX); 705 y.set(y.get() - originY); 706 // Derive the transformed vector's clockwise angle from vertical. 707 double result = Math.atan2(x.get(), -y.get()); 708 if (result < -M_PI_2) { 709 result += M_PI; 710 } else if (result > M_PI_2) { 711 result -= M_PI; 712 } 713 return (float) result; 714 } 715 716 public boolean readFromParcel(Parcel parcel) { 717 int pointerCount = parcel.readInt(); 718 int sampleCount = parcel.readInt(); 719 if (pointerCount == 0 720 || pointerCount > MAX_POINTERS 721 || sampleCount == 0 722 || sampleCount > MAX_SAMPLES) { 723 return false; 724 } 725 mDeviceId = parcel.readInt(); 726 mSource = parcel.readInt(); 727 mAction = parcel.readInt(); 728 mActionButton = parcel.readInt(); 729 mFlags = parcel.readInt(); 730 mEdgeFlags = parcel.readInt(); 731 mMetaState = parcel.readInt(); 732 mButtonState = parcel.readInt(); 733 mXOffset = parcel.readFloat(); 734 mYOffset = parcel.readFloat(); 735 mXPrecision = parcel.readFloat(); 736 mYPrecision = parcel.readFloat(); 737 mDownTime = parcel.readLong(); 738 mPointerProperties = new ArrayList<>(pointerCount); 739 mSampleEventTimes = new ArrayList<>(sampleCount); 740 mSamplePointerCoords = new ArrayList<>(sampleCount * pointerCount); 741 for (int i = 0; i < pointerCount; i++) { 742 PointerProperties properties = new PointerProperties(); 743 mPointerProperties.add(properties); 744 properties.id = parcel.readInt(); 745 properties.toolType = parcel.readInt(); 746 } 747 while (sampleCount > 0) { 748 sampleCount--; 749 mSampleEventTimes.add(parcel.readLong()); 750 for (int i = 0; i < pointerCount; i++) { 751 NativeInput.PointerCoords pointerCoords = new NativeInput.PointerCoords(); 752 mSamplePointerCoords.add(pointerCoords); 753 if (!pointerCoords.readFromParcel(parcel)) { 754 return false; 755 } 756 } 757 } 758 return true; 759 } 760 761 public boolean writeToParcel(Parcel parcel) { 762 int pointerCount = mPointerProperties.size(); 763 int sampleCount = mSampleEventTimes.size(); 764 parcel.writeInt(pointerCount); 765 parcel.writeInt(sampleCount); 766 parcel.writeInt(mDeviceId); 767 parcel.writeInt(mSource); 768 parcel.writeInt(mAction); 769 parcel.writeInt(mActionButton); 770 parcel.writeInt(mFlags); 771 parcel.writeInt(mEdgeFlags); 772 parcel.writeInt(mMetaState); 773 parcel.writeInt(mButtonState); 774 parcel.writeFloat(mXOffset); 775 parcel.writeFloat(mYOffset); 776 parcel.writeFloat(mXPrecision); 777 parcel.writeFloat(mYPrecision); 778 parcel.writeLong(mDownTime); 779 for (int i = 0; i < pointerCount; i++) { 780 PointerProperties properties = mPointerProperties.get(i); 781 parcel.writeInt(properties.id); 782 parcel.writeInt(properties.toolType); 783 } 784 for (int h = 0; h < sampleCount; h++) { 785 parcel.writeLong(mSampleEventTimes.get(h)); 786 for (int i = 0; i < pointerCount; i++) { 787 if (!mSamplePointerCoords.get(i).writeToParcel(parcel)) { 788 return false; 789 } 790 } 791 } 792 return true; 793 } 794 795 public static boolean isTouchEvent(int source, int action) { 796 if ((source & AINPUT_SOURCE_CLASS_POINTER) != 0) { 797 // Specifically excludes HOVER_MOVE and SCROLL. 798 switch (action & AMOTION_EVENT_ACTION_MASK) { 799 case AMOTION_EVENT_ACTION_DOWN: 800 case AMOTION_EVENT_ACTION_MOVE: 801 case AMOTION_EVENT_ACTION_UP: 802 case AMOTION_EVENT_ACTION_POINTER_DOWN: 803 case AMOTION_EVENT_ACTION_POINTER_UP: 804 case AMOTION_EVENT_ACTION_CANCEL: 805 case AMOTION_EVENT_ACTION_OUTSIDE: 806 return true; 807 } 808 } 809 return false; 810 } 811 812 public boolean isTouchEvent() { 813 return isTouchEvent(getSource(), mAction); 814 } 815 816 // Low-level accessors. 817 public List<PointerProperties> getPointerProperties() { 818 return mPointerProperties; 819 } 820 821 List<Long> getSampleEventTimes() { 822 return mSampleEventTimes; 823 } 824 825 List<NativeInput.PointerCoords> getSamplePointerCoords() { 826 return mSamplePointerCoords; 827 } 828 } 829 } 830