diff options
-rw-r--r-- | api/current.xml | 13 | ||||
-rw-r--r-- | core/java/android/view/MotionEvent.java | 16 | ||||
-rw-r--r-- | core/java/android/view/View.java | 10 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 1040 | ||||
-rw-r--r-- | core/jni/android/graphics/Matrix.cpp | 12 | ||||
-rw-r--r-- | core/jni/android/graphics/Matrix.h | 30 | ||||
-rw-r--r-- | core/jni/android_view_MotionEvent.cpp | 99 |
7 files changed, 627 insertions, 593 deletions
diff --git a/api/current.xml b/api/current.xml index e3b6a01..1b8381e 100644 --- a/api/current.xml +++ b/api/current.xml @@ -195957,6 +195957,19 @@ <parameter name="y" type="float"> </parameter> </method> +<method name="transform" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="matrix" type="android.graphics.Matrix"> +</parameter> +</method> <method name="writeToParcel" return="void" abstract="false" diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 6705596..dfbe65c 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -16,6 +16,7 @@ package android.view; +import android.graphics.Matrix; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; @@ -347,6 +348,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { private RuntimeException mRecycledLocation; private boolean mRecycled; + private native void nativeTransform(Matrix matrix); + private MotionEvent(int pointerCount, int sampleCount) { mPointerIdentifiers = new int[pointerCount]; mDataSamples = new float[pointerCount * sampleCount * NUM_SAMPLE_DATA]; @@ -1413,6 +1416,19 @@ public final class MotionEvent extends InputEvent implements Parcelable { mYOffset = y - dataSamples[lastDataSampleIndex + SAMPLE_Y]; } + /** + * Applies a transformation matrix to all of the points in the event. + * + * @param matrix The transformation matrix to apply. + */ + public final void transform(Matrix matrix) { + if (matrix == null) { + throw new IllegalArgumentException("matrix must not be null"); + } + + nativeTransform(matrix); + } + private final void getPointerCoordsAtSampleIndex(int sampleIndex, PointerCoords outPointerCoords) { final float[] dataSamples = mDataSamples; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3b10437..f2d134b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5761,13 +5761,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Determines whether the given point, in local coordinates is inside the view. + */ + /*package*/ final boolean pointInView(float localX, float localY) { + return localX >= 0 && localX < (mRight - mLeft) + && localY >= 0 && localY < (mBottom - mTop); + } + + /** * Utility method to determine whether the given point, in local coordinates, * is inside the view, where the area of the view is expanded by the slop factor. * This method is called while processing touch-move events to determine if the event * is still within the view. */ private boolean pointInView(float localX, float localY, float slop) { - return localX > -slop && localY > -slop && localX < ((mRight - mLeft) + slop) && + return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) && localY < ((mBottom - mTop) + slop); } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 570e288..1e86f74 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -105,16 +105,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ private Transformation mInvalidationTransformation; - // Target of Motion events - private View mMotionTarget; - - // Targets of MotionEvents in split mode - private SplitMotionTargets mSplitMotionTargets; - // Layout animation private LayoutAnimationController mLayoutAnimationController; private Animation.AnimationListener mAnimationListener; + // First touch target in the linked list of touch targets. + private TouchTarget mFirstTouchTarget; + + // Temporary arrays for splitting pointers. + private int[] mTmpPointerIndexMap; + private int[] mTmpPointerIds; + private MotionEvent.PointerCoords[] mTmpPointerCoords; + /** * Internal flags. * @@ -872,150 +874,254 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return false; } - if ((mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) == FLAG_SPLIT_MOTION_EVENTS) { - if (mSplitMotionTargets == null) { - mSplitMotionTargets = new SplitMotionTargets(); + final int action = ev.getAction(); + final int actionMasked = action & MotionEvent.ACTION_MASK; + + // Handle an initial down. + if (actionMasked == MotionEvent.ACTION_DOWN) { + // Throw away all previous state when starting a new touch gesture. + // The framework may have dropped the up or cancel event for the previous gesture + // due to an app switch, ANR, or some other state change. + cancelAndClearTouchTargets(ev); + resetTouchState(); + } + + // Check for interception. + final boolean intercepted; + if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { + final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; + if (!disallowIntercept) { + intercepted = onInterceptTouchEvent(ev); + ev.setAction(action); // restore action in case onInterceptTouchEvent() changed it + } else { + intercepted = false; } - return dispatchSplitTouchEvent(ev); - } + } else { + intercepted = true; + } + + // Check for cancelation. + final boolean canceled = resetCancelNextUpFlag(this) + || actionMasked == MotionEvent.ACTION_CANCEL; + + // Update list of touch targets for pointer down, if needed. + final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; + TouchTarget newTouchTarget = null; + boolean alreadyDispatchedToNewTouchTarget = false; + if (!canceled && !intercepted) { + if (actionMasked == MotionEvent.ACTION_DOWN + || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)) { + final int actionIndex = ev.getActionIndex(); // always 0 for down + final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) + : TouchTarget.ALL_POINTER_IDS; + + // Clean up earlier touch targets for this pointer id in case they + // have become out of sync. + removePointersFromTouchTargets(idBitsToAssign); + + final int childrenCount = mChildrenCount; + if (childrenCount != 0) { + // Find a child that can receive the event. Scan children from front to back. + final View[] children = mChildren; + final float x = ev.getX(actionIndex); + final float y = ev.getY(actionIndex); + + for (int i = childrenCount - 1; i >= 0; i--) { + final View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE + && child.getAnimation() == null) { + // Skip invisible child unless it is animating. + continue; + } - final int action = ev.getAction(); - final float xf = ev.getX(); - final float yf = ev.getY(); - final float scrolledXFloat = xf + mScrollX; - final float scrolledYFloat = yf + mScrollY; - - boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; - - if (action == MotionEvent.ACTION_DOWN) { - if (mMotionTarget != null) { - // this is weird, we got a pen down, but we thought it was - // already down! - // XXX: We should probably send an ACTION_UP to the current - // target. - mMotionTarget = null; + if (!isTransformedTouchPointInView(x, y, child)) { + // New pointer is out of child's bounds. + continue; + } + + newTouchTarget = getTouchTarget(child); + if (newTouchTarget != null) { + // Child is already receiving touch within its bounds. + // Give it the new pointer in addition to the ones it is handling. + newTouchTarget.pointerIdBits |= idBitsToAssign; + break; + } + + resetCancelNextUpFlag(child); + if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { + // Child wants to receive touch within its bounds. + newTouchTarget = addTouchTarget(child, idBitsToAssign); + alreadyDispatchedToNewTouchTarget = true; + break; + } + } + } + + if (newTouchTarget == null && mFirstTouchTarget != null) { + // Did not find a child to receive the event. + // Assign the pointer to the least recently added target. + newTouchTarget = mFirstTouchTarget; + while (newTouchTarget.next != null) { + newTouchTarget = newTouchTarget.next; + } + newTouchTarget.pointerIdBits |= idBitsToAssign; + } } - // If we're disallowing intercept or if we're allowing and we didn't - // intercept - if (disallowIntercept || !onInterceptTouchEvent(ev)) { - // reset this event's action (just to protect ourselves) - ev.setAction(MotionEvent.ACTION_DOWN); - // We know we want to dispatch the event down, find a child - // who can handle it, start with the front-most child. - final View[] children = mChildren; - final int count = mChildrenCount; - - for (int i = count - 1; i >= 0; i--) { - final View child = children[i]; - if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE - || child.getAnimation() != null) { - // Single dispatch always picks its target based on the initial down - // event's position - index 0 - if (dispatchTouchEventIfInView(child, ev, 0)) { - mMotionTarget = child; - return true; + } + + // Dispatch to touch targets. + boolean handled = false; + if (mFirstTouchTarget == null) { + // No touch targets so treat this as an ordinary view. + handled = dispatchTransformedTouchEvent(ev, canceled, null, + TouchTarget.ALL_POINTER_IDS); + } else { + // Dispatch to touch targets, excluding the new touch target if we already + // dispatched to it. Cancel touch targets if necessary. + TouchTarget predecessor = null; + TouchTarget target = mFirstTouchTarget; + while (target != null) { + final TouchTarget next = target.next; + if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { + handled = true; + } else { + final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; + if (dispatchTransformedTouchEvent(ev, cancelChild, + target.child, target.pointerIdBits)) { + handled = true; + } + if (cancelChild) { + if (predecessor == null) { + mFirstTouchTarget = next; + } else { + predecessor.next = next; } + target.recycle(); + target = next; + continue; } } + predecessor = target; + target = next; } } - boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || - (action == MotionEvent.ACTION_CANCEL); + // Update list of touch targets for pointer up or cancel, if needed. + if (canceled || actionMasked == MotionEvent.ACTION_UP) { + resetTouchState(); + } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { + final int actionIndex = ev.getActionIndex(); + final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); + removePointersFromTouchTargets(idBitsToRemove); + } - if (isUpOrCancel) { - // Note, we've already copied the previous state to our local - // variable, so this takes effect on the next event - mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; + return handled; + } + + /* Resets all touch state in preparation for a new cycle. */ + private final void resetTouchState() { + clearTouchTargets(); + resetCancelNextUpFlag(this); + mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; + } + + /* Resets the cancel next up flag. + * Returns true if the flag was previously set. */ + private final boolean resetCancelNextUpFlag(View view) { + if ((view.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { + view.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; + return true; } + return false; + } - // The event wasn't an ACTION_DOWN, dispatch it to our target if - // we have one. - final View target = mMotionTarget; - if (target == null) { - // We don't have a target, this means we're handling the - // event as a regular view. - ev.setLocation(xf, yf); - if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { - ev.setAction(MotionEvent.ACTION_CANCEL); - mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; + /* Clears all touch targets. */ + private final void clearTouchTargets() { + TouchTarget target = mFirstTouchTarget; + if (target != null) { + do { + TouchTarget next = target.next; + target.recycle(); + target = next; + } while (target != null); + mFirstTouchTarget = null; + } + } + + /* Cancels and clears all touch targets. */ + private final void cancelAndClearTouchTargets(MotionEvent event) { + if (mFirstTouchTarget != null) { + boolean syntheticEvent = false; + if (event == null) { + final long now = SystemClock.uptimeMillis(); + event = MotionEvent.obtain(now, now, + MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); + syntheticEvent = true; } - return super.dispatchTouchEvent(ev); - } - // Calculate the offset point into the target's local coordinates - float xc = scrolledXFloat - (float) target.mLeft; - float yc = scrolledYFloat - (float) target.mTop; - if (!target.hasIdentityMatrix() && mAttachInfo != null) { - // non-identity matrix: transform the point into the view's coordinates - final float[] localXY = mAttachInfo.mTmpTransformLocation; - localXY[0] = xc; - localXY[1] = yc; - target.getInverseMatrix().mapPoints(localXY); - xc = localXY[0]; - yc = localXY[1]; - } - - // if have a target, see if we're allowed to and want to intercept its - // events - if (!disallowIntercept && onInterceptTouchEvent(ev)) { - mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; - ev.setAction(MotionEvent.ACTION_CANCEL); - ev.setLocation(xc, yc); - if (!target.dispatchTouchEvent(ev)) { - // target didn't handle ACTION_CANCEL. not much we can do - // but they should have. + for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { + resetCancelNextUpFlag(target.child); + dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits); } - // clear the target - mMotionTarget = null; - // Don't dispatch this event to our own view, because we already - // saw it when intercepting; we just want to give the following - // event to the normal onTouchEvent(). - return true; - } + clearTouchTargets(); - if (isUpOrCancel) { - mMotionTarget = null; + if (syntheticEvent) { + event.recycle(); + } } + } - // finally offset the event to the target's coordinate system and - // dispatch the event. - ev.setLocation(xc, yc); - - if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { - ev.setAction(MotionEvent.ACTION_CANCEL); - target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; - mMotionTarget = null; + /* Gets the touch target for specified child view. + * Returns null if not found. */ + private final TouchTarget getTouchTarget(View child) { + for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { + if (target.child == child) { + return target; + } } + return null; + } - if (target.dispatchTouchEvent(ev)) { - return true; - } else { - ev.setLocation(xf, yf); + /* Adds a touch target for specified child to the beginning of the list. + * Assumes the target child is not already present. */ + private final TouchTarget addTouchTarget(View child, int pointerIdBits) { + TouchTarget target = TouchTarget.obtain(child, pointerIdBits); + target.next = mFirstTouchTarget; + mFirstTouchTarget = target; + return target; + } + + /* Removes the pointer ids from consideration. */ + private final void removePointersFromTouchTargets(int pointerIdBits) { + TouchTarget predecessor = null; + TouchTarget target = mFirstTouchTarget; + while (target != null) { + final TouchTarget next = target.next; + if ((target.pointerIdBits & pointerIdBits) != 0) { + target.pointerIdBits &= ~pointerIdBits; + if (target.pointerIdBits == 0) { + if (predecessor == null) { + mFirstTouchTarget = next; + } else { + predecessor.next = next; + } + target.recycle(); + target = next; + continue; + } + } + predecessor = target; + target = next; } - return false; } - /** - * This method detects whether the pointer location at <code>pointerIndex</code> within - * <code>ev</code> is inside the specified view. If so, the transformed event is dispatched to - * <code>child</code>. - * - * @param child View to hit test against - * @param ev MotionEvent to test - * @param pointerIndex Index of the pointer within <code>ev</code> to test - * @return <code>false</code> if the hit test failed, or the result of - * <code>child.dispatchTouchEvent</code> - */ - private boolean dispatchTouchEventIfInView(View child, MotionEvent ev, int pointerIndex) { - final float x = ev.getX(pointerIndex); - final float y = ev.getY(pointerIndex); - final float scrolledX = x + mScrollX; - final float scrolledY = y + mScrollY; - float localX = scrolledX - child.mLeft; - float localY = scrolledY - child.mTop; - if (!child.hasIdentityMatrix() && mAttachInfo != null) { - // non-identity matrix: transform the point into the view's coordinates + /* Returns true if a child view contains the specified point when transformed + * into its coordinate space. + * Child must not be null. */ + private final boolean isTransformedTouchPointInView(float x, float y, View child) { + float localX = x + mScrollX - child.mLeft; + float localY = y + mScrollY - child.mTop; + if (! child.hasIdentityMatrix() && mAttachInfo != null) { final float[] localXY = mAttachInfo.mTmpTransformLocation; localXY[0] = localX; localXY[1] = localY; @@ -1023,222 +1129,213 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager localX = localXY[0]; localY = localXY[1]; } - if (localX >= 0 && localY >= 0 && localX < (child.mRight - child.mLeft) && - localY < (child.mBottom - child.mTop)) { - // It would be safer to clone the event here but we don't for performance. - // There are many subtle interactions in touch event dispatch; change at your own risk. - child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; - ev.offsetLocation(localX - x, localY - y); - if (child.dispatchTouchEvent(ev)) { - return true; + return child.pointInView(localX, localY); + } + + /* Transforms a motion event into the coordinate space of a particular child view, + * filters out irrelevant pointer ids, and overrides its action if necessary. + * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */ + private final boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, + View child, int desiredPointerIdBits) { + final boolean handled; + + // Canceling motions is a special case. We don't need to perform any transformations + // or filtering. The important part is the action, not the contents. + final int oldAction = event.getAction(); + if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { + event.setAction(MotionEvent.ACTION_CANCEL); + if (child == null) { + handled = super.dispatchTouchEvent(event); } else { - ev.offsetLocation(x - localX, y - localY); - return false; + handled = child.dispatchTouchEvent(event); } + event.setAction(oldAction); + return handled; } - return false; - } - private boolean dispatchSplitTouchEvent(MotionEvent ev) { - final SplitMotionTargets targets = mSplitMotionTargets; - final int action = ev.getAction(); - final int maskedAction = ev.getActionMasked(); - float xf = ev.getX(); - float yf = ev.getY(); - float scrolledXFloat = xf + mScrollX; - float scrolledYFloat = yf + mScrollY; + // Calculate the number of pointers to deliver. + final int oldPointerCount = event.getPointerCount(); + int newPointerCount = 0; + if (desiredPointerIdBits == TouchTarget.ALL_POINTER_IDS) { + newPointerCount = oldPointerCount; + } else { + for (int i = 0; i < oldPointerCount; i++) { + final int pointerId = event.getPointerId(i); + final int pointerIdBit = 1 << pointerId; + if ((pointerIdBit & desiredPointerIdBits) != 0) { + newPointerCount += 1; + } + } + } + + // If for some reason we ended up in an inconsistent state where it looks like we + // might produce a motion event with no pointers in it, then drop the event. + if (newPointerCount == 0) { + return false; + } - boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; + // If the number of pointers is the same and we don't need to perform any fancy + // irreversible transformations, then we can reuse the motion event for this + // dispatch as long as we are careful to revert any changes we make. + final boolean reuse = newPointerCount == oldPointerCount + && (child == null || child.hasIdentityMatrix()); + if (reuse) { + if (child == null) { + handled = super.dispatchTouchEvent(event); + } else { + final float offsetX = mScrollX - child.mLeft; + final float offsetY = mScrollY - child.mTop; + event.offsetLocation(offsetX, offsetY); - if (maskedAction == MotionEvent.ACTION_DOWN || - maskedAction == MotionEvent.ACTION_POINTER_DOWN) { - final int actionIndex = ev.getActionIndex(); - final int actionId = ev.getPointerId(actionIndex); - - // Clear out any current target for this ID. - // XXX: We should probably send an ACTION_UP to the current - // target if present. - targets.removeById(actionId); - - // If we're disallowing intercept or if we're allowing and we didn't - // intercept - if (disallowIntercept || !onInterceptTouchEvent(ev)) { - // reset this event's action (just to protect ourselves) - ev.setAction(action); - // We know we want to dispatch the event down, try to find a child - // who can handle it, start with the front-most child. - final long downTime = ev.getEventTime(); - final View[] children = mChildren; - final int count = mChildrenCount; - for (int i = count - 1; i >= 0; i--) { - final View child = children[i]; - if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE - || child.getAnimation() != null) { - final MotionEvent childEvent = - targets.filterMotionEventForChild(ev, child, downTime); - if (childEvent != null) { - try { - final int childActionIndex = childEvent.findPointerIndex(actionId); - if (dispatchTouchEventIfInView(child, childEvent, - childActionIndex)) { - targets.add(actionId, child, downTime); - - return true; - } - } finally { - childEvent.recycle(); - } - } + handled = child.dispatchTouchEvent(event); + + event.offsetLocation(-offsetX, -offsetY); + } + return handled; + } + + // Make a copy of the event. + // If the number of pointers is different, then we need to filter out irrelevant pointers + // as we make a copy of the motion event. + MotionEvent transformedEvent; + if (newPointerCount == oldPointerCount) { + transformedEvent = MotionEvent.obtain(event); + } else { + growTmpPointerArrays(newPointerCount); + final int[] newPointerIndexMap = mTmpPointerIndexMap; + final int[] newPointerIds = mTmpPointerIds; + final MotionEvent.PointerCoords[] newPointerCoords = mTmpPointerCoords; + + int newPointerIndex = 0; + int oldPointerIndex = 0; + while (newPointerIndex < newPointerCount) { + final int pointerId = event.getPointerId(oldPointerIndex); + final int pointerIdBits = 1 << pointerId; + if ((pointerIdBits & desiredPointerIdBits) != 0) { + newPointerIndexMap[newPointerIndex] = oldPointerIndex; + newPointerIds[newPointerIndex] = pointerId; + if (newPointerCoords[newPointerIndex] == null) { + newPointerCoords[newPointerIndex] = new MotionEvent.PointerCoords(); } + + newPointerIndex += 1; } + oldPointerIndex += 1; + } - // Didn't find a new target. Do we have a "primary" target to send to? - final SplitMotionTargets.TargetInfo primaryTargetInfo = targets.getPrimaryTarget(); - if (primaryTargetInfo != null) { - final View primaryTarget = primaryTargetInfo.view; - final MotionEvent childEvent = targets.filterMotionEventForChild(ev, - primaryTarget, primaryTargetInfo.downTime); - if (childEvent != null) { - try { - // Calculate the offset point into the target's local coordinates - float xc = scrolledXFloat - (float) primaryTarget.mLeft; - float yc = scrolledYFloat - (float) primaryTarget.mTop; - if (!primaryTarget.hasIdentityMatrix() && mAttachInfo != null) { - // non-identity matrix: transform the point into the view's - // coordinates - final float[] localXY = mAttachInfo.mTmpTransformLocation; - localXY[0] = xc; - localXY[1] = yc; - primaryTarget.getInverseMatrix().mapPoints(localXY); - xc = localXY[0]; - yc = localXY[1]; - } - childEvent.setLocation(xc, yc); - if (primaryTarget.dispatchTouchEvent(childEvent)) { - targets.add(actionId, primaryTarget, primaryTargetInfo.downTime); - return true; + final int newAction; + if (cancel) { + newAction = MotionEvent.ACTION_CANCEL; + } else { + final int oldMaskedAction = oldAction & MotionEvent.ACTION_MASK; + if (oldMaskedAction == MotionEvent.ACTION_POINTER_DOWN + || oldMaskedAction == MotionEvent.ACTION_POINTER_UP) { + final int changedPointerId = event.getPointerId( + (oldAction & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT); + final int changedPointerIdBits = 1 << changedPointerId; + if ((changedPointerIdBits & desiredPointerIdBits) != 0) { + if (newPointerCount == 1) { + // The first/last pointer went down/up. + newAction = oldMaskedAction == MotionEvent.ACTION_POINTER_DOWN + ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP; + } else { + // A secondary pointer went down/up. + int newChangedPointerIndex = 0; + while (newPointerIds[newChangedPointerIndex] != changedPointerId) { + newChangedPointerIndex += 1; } - } finally { - childEvent.recycle(); + newAction = oldMaskedAction | (newChangedPointerIndex + << MotionEvent.ACTION_POINTER_INDEX_SHIFT); } + } else { + // An unrelated pointer changed. + newAction = MotionEvent.ACTION_MOVE; } + } else { + // Simple up/down/cancel/move motion action. + newAction = oldMaskedAction; } } - } - - boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || - (action == MotionEvent.ACTION_CANCEL); - if (isUpOrCancel) { - // Note, we've already copied the previous state to our local - // variable, so this takes effect on the next event - mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; - } + transformedEvent = null; + final int historySize = event.getHistorySize(); + for (int historyIndex = 0; historyIndex <= historySize; historyIndex++) { + for (newPointerIndex = 0; newPointerIndex < newPointerCount; newPointerIndex++) { + final MotionEvent.PointerCoords c = newPointerCoords[newPointerIndex]; + oldPointerIndex = newPointerIndexMap[newPointerIndex]; + if (historyIndex != historySize) { + event.getHistoricalPointerCoords(oldPointerIndex, historyIndex, c); + } else { + event.getPointerCoords(oldPointerIndex, c); + } + } - if (targets.isEmpty()) { - // We don't have any targets, this means we're handling the - // event as a regular view. - ev.setLocation(xf, yf); - if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { - ev.setAction(MotionEvent.ACTION_CANCEL); - mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; - } - return super.dispatchTouchEvent(ev); - } - - // if we have targets, see if we're allowed to and want to intercept their - // events - int uniqueTargetCount = targets.getUniqueTargetCount(); - if (!disallowIntercept && onInterceptTouchEvent(ev)) { - mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; - - for (int uniqueIndex = 0; uniqueIndex < uniqueTargetCount; uniqueIndex++) { - final View target = targets.getUniqueTargetAt(uniqueIndex).view; - - // Calculate the offset point into the target's local coordinates - float xc = scrolledXFloat - (float) target.mLeft; - float yc = scrolledYFloat - (float) target.mTop; - if (!target.hasIdentityMatrix() && mAttachInfo != null) { - // non-identity matrix: transform the point into the view's coordinates - final float[] localXY = mAttachInfo.mTmpTransformLocation; - localXY[0] = xc; - localXY[1] = yc; - target.getInverseMatrix().mapPoints(localXY); - xc = localXY[0]; - yc = localXY[1]; + final long eventTime; + if (historyIndex != historySize) { + eventTime = event.getHistoricalEventTime(historyIndex); + } else { + eventTime = event.getEventTime(); } - ev.setAction(MotionEvent.ACTION_CANCEL); - ev.setLocation(xc, yc); - if (!target.dispatchTouchEvent(ev)) { - // target didn't handle ACTION_CANCEL. not much we can do - // but they should have. + if (transformedEvent == null) { + transformedEvent = MotionEvent.obtain( + event.getDownTime(), eventTime, newAction, + newPointerCount, newPointerIds, newPointerCoords, + event.getMetaState(), event.getXPrecision(), event.getYPrecision(), + event.getDeviceId(), event.getEdgeFlags(), event.getSource(), + event.getFlags()); + } else { + transformedEvent.addBatch(eventTime, newPointerCoords, 0); } } - targets.clear(); - // Don't dispatch this event to our own view, because we already - // saw it when intercepting; we just want to give the following - // event to the normal onTouchEvent(). - return true; } - boolean handled = false; - for (int uniqueIndex = 0; uniqueIndex < uniqueTargetCount; uniqueIndex++) { - final SplitMotionTargets.TargetInfo targetInfo = targets.getUniqueTargetAt(uniqueIndex); - final View target = targetInfo.view; - - final MotionEvent targetEvent = - targets.filterMotionEventForChild(ev, target, targetInfo.downTime); - if (targetEvent == null) { - continue; + // Perform any necessary transformations and dispatch. + if (child == null) { + handled = super.dispatchTouchEvent(transformedEvent); + } else { + final float offsetX = mScrollX - child.mLeft; + final float offsetY = mScrollY - child.mTop; + transformedEvent.offsetLocation(offsetX, offsetY); + if (! child.hasIdentityMatrix()) { + transformedEvent.transform(child.getInverseMatrix()); } - try { - // Calculate the offset point into the target's local coordinates - xf = targetEvent.getX(); - yf = targetEvent.getY(); - scrolledXFloat = xf + mScrollX; - scrolledYFloat = yf + mScrollY; - float xc = scrolledXFloat - (float) target.mLeft; - float yc = scrolledYFloat - (float) target.mTop; - if (!target.hasIdentityMatrix() && mAttachInfo != null) { - // non-identity matrix: transform the point into the view's coordinates - final float[] localXY = mAttachInfo.mTmpTransformLocation; - localXY[0] = xc; - localXY[1] = yc; - target.getInverseMatrix().mapPoints(localXY); - xc = localXY[0]; - yc = localXY[1]; - } - - // finally offset the event to the target's coordinate system and - // dispatch the event. - targetEvent.setLocation(xc, yc); + handled = child.dispatchTouchEvent(transformedEvent); + } - if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { - targetEvent.setAction(MotionEvent.ACTION_CANCEL); - target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; - targets.removeView(target); - uniqueIndex--; - uniqueTargetCount--; - } + // Done. + transformedEvent.recycle(); + return handled; + } - handled |= target.dispatchTouchEvent(targetEvent); - } finally { - targetEvent.recycle(); + /* Enlarge the temporary pointer arrays for splitting pointers. + * May discard contents (but keeps PointerCoords objects to avoid reallocating them). */ + private final void growTmpPointerArrays(int desiredCapacity) { + final MotionEvent.PointerCoords[] oldTmpPointerCoords = mTmpPointerCoords; + int capacity; + if (oldTmpPointerCoords != null) { + capacity = oldTmpPointerCoords.length; + if (desiredCapacity <= capacity) { + return; } + } else { + capacity = 4; } - if (maskedAction == MotionEvent.ACTION_POINTER_UP) { - final int removeId = ev.getPointerId(ev.getActionIndex()); - targets.removeById(removeId); + while (capacity < desiredCapacity) { + capacity *= 2; } - if (isUpOrCancel) { - targets.clear(); - } + mTmpPointerIndexMap = new int[capacity]; + mTmpPointerIds = new int[capacity]; + mTmpPointerCoords = new MotionEvent.PointerCoords[capacity]; - return handled; + if (oldTmpPointerCoords != null) { + System.arraycopy(oldTmpPointerCoords, 0, mTmpPointerCoords, 0, + oldTmpPointerCoords.length); + } } /** @@ -1262,7 +1359,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS; } else { mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS; - mSplitMotionTargets = null; } } @@ -1473,19 +1569,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override void dispatchDetachedFromWindow() { - // If we still have a motion target, we are still in the process of + // If we still have a touch target, we are still in the process of // dispatching motion events to a child; we need to get rid of that // child to avoid dispatching events to it after the window is torn // down. To make sure we keep the child in a consistent state, we // first send it an ACTION_CANCEL motion event. - if (mMotionTarget != null) { - final long now = SystemClock.uptimeMillis(); - final MotionEvent event = MotionEvent.obtain(now, now, - MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); - mMotionTarget.dispatchTouchEvent(event); - event.recycle(); - mMotionTarget = null; - } + cancelAndClearTouchTargets(null); final int count = mChildrenCount; final View[] children = mChildren; @@ -4287,290 +4376,57 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } - private static class SplitMotionTargets { - private SparseArray<View> mTargets; - private TargetInfo[] mUniqueTargets; - private int mUniqueTargetCount; - private MotionEvent.PointerCoords[] mPointerCoords; - private int[] mPointerIds; - - private static final int INITIAL_UNIQUE_MOTION_TARGETS_SIZE = 5; - private static final int INITIAL_BUCKET_SIZE = 5; - - public SplitMotionTargets() { - mTargets = new SparseArray<View>(); - mUniqueTargets = new TargetInfo[INITIAL_UNIQUE_MOTION_TARGETS_SIZE]; - mPointerIds = new int[INITIAL_BUCKET_SIZE]; - mPointerCoords = new MotionEvent.PointerCoords[INITIAL_BUCKET_SIZE]; - for (int i = 0; i < INITIAL_BUCKET_SIZE; i++) { - mPointerCoords[i] = new MotionEvent.PointerCoords(); - } - } - - public void clear() { - mTargets.clear(); - final int count = mUniqueTargetCount; - for (int i = 0; i < count; i++) { - mUniqueTargets[i].recycle(); - mUniqueTargets[i] = null; - } - mUniqueTargetCount = 0; - } - - public void add(int pointerId, View target, long downTime) { - mTargets.put(pointerId, target); - - final int uniqueCount = mUniqueTargetCount; - boolean addUnique = true; - for (int i = 0; i < uniqueCount; i++) { - if (mUniqueTargets[i].view == target) { - addUnique = false; - } - } - if (addUnique) { - if (mUniqueTargets.length == uniqueCount) { - TargetInfo[] newTargets = - new TargetInfo[uniqueCount + INITIAL_UNIQUE_MOTION_TARGETS_SIZE]; - System.arraycopy(mUniqueTargets, 0, newTargets, 0, uniqueCount); - mUniqueTargets = newTargets; - } - mUniqueTargets[uniqueCount] = TargetInfo.obtain(target, downTime); - mUniqueTargetCount++; - } - } - - public int getIdCount() { - return mTargets.size(); - } - - public int getUniqueTargetCount() { - return mUniqueTargetCount; - } - - public TargetInfo getUniqueTargetAt(int index) { - return mUniqueTargets[index]; - } - - public View get(int id) { - return mTargets.get(id); - } - - public int indexOfTarget(View target) { - return mTargets.indexOfValue(target); - } - - public View targetAt(int index) { - return mTargets.valueAt(index); - } - - public TargetInfo getPrimaryTarget() { - if (!isEmpty()) { - // Find the longest-lived target - long firstTime = Long.MAX_VALUE; - int firstIndex = 0; - final int uniqueCount = mUniqueTargetCount; - for (int i = 0; i < uniqueCount; i++) { - TargetInfo info = mUniqueTargets[i]; - if (info.downTime < firstTime) { - firstTime = info.downTime; - firstIndex = i; - } - } - return mUniqueTargets[firstIndex]; - } - return null; - } - - public boolean isEmpty() { - return mUniqueTargetCount == 0; - } - - public void removeById(int id) { - final int index = mTargets.indexOfKey(id); - removeAt(index); - } - - public void removeView(View view) { - int i = 0; - while (i < mTargets.size()) { - if (mTargets.valueAt(i) == view) { - mTargets.removeAt(i); - } else { - i++; - } - } - removeUnique(view); - } - - public void removeAt(int index) { - if (index < 0 || index >= mTargets.size()) { - return; - } - - final View removeView = mTargets.valueAt(index); - mTargets.removeAt(index); - if (mTargets.indexOfValue(removeView) < 0) { - removeUnique(removeView); - } - } - - private void removeUnique(View removeView) { - TargetInfo[] unique = mUniqueTargets; - int uniqueCount = mUniqueTargetCount; - for (int i = 0; i < uniqueCount; i++) { - if (unique[i].view == removeView) { - unique[i].recycle(); - unique[i] = unique[--uniqueCount]; - unique[uniqueCount] = null; - break; - } - } - - mUniqueTargetCount = uniqueCount; - } - - /** - * Return a new (obtain()ed) MotionEvent containing only data for pointers that should - * be dispatched to child. Don't forget to recycle it! - */ - public MotionEvent filterMotionEventForChild(MotionEvent ev, View child, long downTime) { - int action = ev.getAction(); - final int maskedAction = action & MotionEvent.ACTION_MASK; - - // Only send pointer up events if this child was the target. Drop it otherwise. - if (maskedAction == MotionEvent.ACTION_POINTER_UP && - get(ev.getPointerId(ev.getActionIndex())) != child) { - return null; - } - - int pointerCount = 0; - final int idCount = getIdCount(); - for (int i = 0; i < idCount; i++) { - if (targetAt(i) == child) { - pointerCount++; - } - } - - int actionId = -1; - boolean needsNewIndex = false; // True if we should fill in the action's masked index - - // If we have a down event, it wasn't counted above. - if (maskedAction == MotionEvent.ACTION_DOWN) { - pointerCount++; - actionId = ev.getPointerId(0); - } else if (maskedAction == MotionEvent.ACTION_POINTER_DOWN) { - pointerCount++; - - actionId = ev.getPointerId(ev.getActionIndex()); - - if (indexOfTarget(child) < 0) { - // The new action should be ACTION_DOWN if this child isn't currently getting - // any events. - action = MotionEvent.ACTION_DOWN; - } else { - // Fill in the index portion of the action later. - needsNewIndex = true; - } - } else if (maskedAction == MotionEvent.ACTION_POINTER_UP) { - actionId = ev.getPointerId(ev.getActionIndex()); - if (pointerCount == 1) { - // The new action should be ACTION_UP if there's only one pointer left for - // this target. - action = MotionEvent.ACTION_UP; - } else { - // Fill in the index portion of the action later. - needsNewIndex = true; - } - } - - if (pointerCount == 0) { - return null; - } - - // Fill the buckets with pointer data! - final int eventPointerCount = ev.getPointerCount(); - int bucketIndex = 0; - int newActionIndex = -1; - for (int evp = 0; evp < eventPointerCount; evp++) { - final int id = ev.getPointerId(evp); - - // Add this pointer to the bucket if it is new or targeted at child - if (id == actionId || get(id) == child) { - // Expand scratch arrays if needed - if (mPointerCoords.length <= bucketIndex) { - int[] pointerIds = new int[pointerCount]; - MotionEvent.PointerCoords[] pointerCoords = - new MotionEvent.PointerCoords[pointerCount]; - for (int i = mPointerCoords.length; i < pointerCoords.length; i++) { - pointerCoords[i] = new MotionEvent.PointerCoords(); - } - - System.arraycopy(mPointerCoords, 0, - pointerCoords, 0, mPointerCoords.length); - System.arraycopy(mPointerIds, 0, pointerIds, 0, mPointerIds.length); - - mPointerCoords = pointerCoords; - mPointerIds = pointerIds; - } + /* Describes a touched view and the ids of the pointers that it has captured. + * + * This code assumes that pointer ids are always in the range 0..31 such that + * it can use a bitfield to track which pointer ids are present. + * As it happens, the lower layers of the input dispatch pipeline also use the + * same trick so the assumption should be safe here... + */ + private static final class TouchTarget { + private static final int MAX_RECYCLED = 32; + private static final Object sRecycleLock = new Object(); + private static TouchTarget sRecycleBin; + private static int sRecycledCount; - mPointerIds[bucketIndex] = id; - ev.getPointerCoords(evp, mPointerCoords[bucketIndex]); + public static final int ALL_POINTER_IDS = -1; // all ones - if (needsNewIndex && id == actionId) { - newActionIndex = bucketIndex; - } + // The touched child view. + public View child; - bucketIndex++; - } - } + // The combined bit mask of pointer ids for all pointers captured by the target. + public int pointerIdBits; - // Encode the new action index if we have one - if (newActionIndex >= 0) { - action = (action & MotionEvent.ACTION_MASK) | - (newActionIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT); - } + // The next target in the target list. + public TouchTarget next; - return MotionEvent.obtain(downTime, ev.getEventTime(), - action, pointerCount, mPointerIds, mPointerCoords, ev.getMetaState(), - ev.getXPrecision(), ev.getYPrecision(), ev.getDeviceId(), ev.getEdgeFlags(), - ev.getSource(), ev.getFlags()); + private TouchTarget() { } - static class TargetInfo { - public View view; - public long downTime; - - private TargetInfo mNextRecycled; - - private static TargetInfo sRecycleBin; - private static int sRecycledCount; - - private static int MAX_RECYCLED = 15; - - private TargetInfo() { - } - - public static TargetInfo obtain(View v, long time) { - TargetInfo info; + public static TouchTarget obtain(View child, int pointerIdBits) { + final TouchTarget target; + synchronized (sRecycleLock) { if (sRecycleBin == null) { - info = new TargetInfo(); + target = new TouchTarget(); } else { - info = sRecycleBin; - sRecycleBin = info.mNextRecycled; - sRecycledCount--; + target = sRecycleBin; + sRecycleBin = target.next; + sRecycledCount--; + target.next = null; } - info.view = v; - info.downTime = time; - return info; } + target.child = child; + target.pointerIdBits = pointerIdBits; + return target; + } - public void recycle() { - if (sRecycledCount >= MAX_RECYCLED) { - return; + public void recycle() { + synchronized (sRecycleLock) { + if (sRecycledCount < MAX_RECYCLED) { + next = sRecycleBin; + sRecycleBin = this; + sRecycledCount += 1; } - mNextRecycled = sRecycleBin; - sRecycleBin = this; - sRecycledCount++; } } } diff --git a/core/jni/android/graphics/Matrix.cpp b/core/jni/android/graphics/Matrix.cpp index b782766..cafceab 100644 --- a/core/jni/android/graphics/Matrix.cpp +++ b/core/jni/android/graphics/Matrix.cpp @@ -27,6 +27,8 @@ #include "SkMatrix.h" #include "SkTemplates.h" +#include "Matrix.h" + namespace android { class SkMatrixGlue { @@ -403,10 +405,20 @@ static JNINativeMethod methods[] = { {"native_equals", "(II)Z", (void*) SkMatrixGlue::equals} }; +static jfieldID sNativeInstanceField; + int register_android_graphics_Matrix(JNIEnv* env) { int result = AndroidRuntime::registerNativeMethods(env, "android/graphics/Matrix", methods, sizeof(methods) / sizeof(methods[0])); + + jclass clazz = env->FindClass("android/graphics/Matrix"); + sNativeInstanceField = env->GetFieldID(clazz, "native_instance", "I"); + return result; } +SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj) { + return reinterpret_cast<SkMatrix*>(env->GetIntField(matrixObj, sNativeInstanceField)); +} + } diff --git a/core/jni/android/graphics/Matrix.h b/core/jni/android/graphics/Matrix.h new file mode 100644 index 0000000..31edf88 --- /dev/null +++ b/core/jni/android/graphics/Matrix.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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. + */ + +#ifndef _ANDROID_GRAPHICS_MATRIX_H +#define _ANDROID_GRAPHICS_MATRIX_H + +#include "jni.h" +#include "SkMatrix.h" + +namespace android { + +/* Gets the underlying SkMatrix from a Matrix object. */ +extern SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj); + +} // namespace android + +#endif // _ANDROID_GRAPHICS_MATRIX_H diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index 93fd54f..537ac72 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -22,10 +22,26 @@ #include <utils/Log.h> #include <ui/Input.h> #include "android_view_MotionEvent.h" +#include "android/graphics/Matrix.h" + +#include <math.h> +#include "SkMatrix.h" +#include "SkScalar.h" // Number of float items per entry in a DVM sample data array #define NUM_SAMPLE_DATA 9 +#define SAMPLE_X 0 +#define SAMPLE_Y 1 +#define SAMPLE_PRESSURE 2 +#define SAMPLE_SIZE 3 +#define SAMPLE_TOUCH_MAJOR 4 +#define SAMPLE_TOUCH_MINOR 5 +#define SAMPLE_TOOL_MAJOR 6 +#define SAMPLE_TOOL_MINOR 7 +#define SAMPLE_ORIENTATION 8 + + namespace android { // ---------------------------------------------------------------------------- @@ -238,8 +254,87 @@ void android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj) { } } +static inline float transformAngle(const SkMatrix* matrix, float angleRadians) { + // Construct and transform a vector oriented at the specified clockwise angle from vertical. + // Coordinate system: down is increasing Y, right is increasing X. + SkPoint vector; + vector.fX = SkFloatToScalar(sinf(angleRadians)); + vector.fY = SkFloatToScalar(- cosf(angleRadians)); + matrix->mapVectors(& vector, 1); + + // Derive the transformed vector's clockwise angle from vertical. + float result = atan2f(SkScalarToFloat(vector.fX), SkScalarToFloat(- vector.fY)); + if (result < - M_PI_2) { + result += M_PI; + } else if (result > M_PI_2) { + result -= M_PI; + } + return result; +} + +static void android_view_MotionEvent_nativeTransform(JNIEnv* env, + jobject eventObj, jobject matrixObj) { + SkMatrix* matrix = android_graphics_Matrix_getSkMatrix(env, matrixObj); + + jfloat oldXOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mXOffset); + jfloat oldYOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mYOffset); + jint numPointers = env->GetIntField(eventObj, gMotionEventClassInfo.mNumPointers); + jint numSamples = env->GetIntField(eventObj, gMotionEventClassInfo.mNumSamples); + jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mDataSamples)); + jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL); + + // The tricky part of this implementation is to preserve the value of + // rawX and rawY. So we apply the transformation to the first point + // then derive an appropriate new X/Y offset that will preserve rawX and rawY. + SkPoint point; + jfloat rawX = dataSamples[SAMPLE_X]; + jfloat rawY = dataSamples[SAMPLE_Y]; + matrix->mapXY(SkFloatToScalar(rawX + oldXOffset), SkFloatToScalar(rawY + oldYOffset), + & point); + jfloat newX = SkScalarToFloat(point.fX); + jfloat newY = SkScalarToFloat(point.fY); + jfloat newXOffset = newX - rawX; + jfloat newYOffset = newY - rawY; + + dataSamples[SAMPLE_ORIENTATION] = transformAngle(matrix, dataSamples[SAMPLE_ORIENTATION]); + + // Apply the transformation to all samples. + jfloat* currentDataSample = dataSamples; + jfloat* endDataSample = dataSamples + numPointers * numSamples * NUM_SAMPLE_DATA; + for (;;) { + currentDataSample += NUM_SAMPLE_DATA; + if (currentDataSample == endDataSample) { + break; + } + + jfloat x = currentDataSample[SAMPLE_X] + oldXOffset; + jfloat y = currentDataSample[SAMPLE_Y] + oldYOffset; + matrix->mapXY(SkFloatToScalar(x), SkFloatToScalar(y), & point); + currentDataSample[SAMPLE_X] = SkScalarToFloat(point.fX) - newXOffset; + currentDataSample[SAMPLE_Y] = SkScalarToFloat(point.fY) - newYOffset; + + currentDataSample[SAMPLE_ORIENTATION] = transformAngle(matrix, + currentDataSample[SAMPLE_ORIENTATION]); + } + + env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, 0); + + env->SetFloatField(eventObj, gMotionEventClassInfo.mXOffset, newXOffset); + env->SetFloatField(eventObj, gMotionEventClassInfo.mYOffset, newYOffset); + + env->DeleteLocalRef(dataSampleArray); +} + // ---------------------------------------------------------------------------- +static JNINativeMethod gMotionEventMethods[] = { + /* name, signature, funcPtr */ + { "nativeTransform", + "(Landroid/graphics/Matrix;)V", + (void*)android_view_MotionEvent_nativeTransform }, +}; + #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ LOG_FATAL_IF(! var, "Unable to find class " className); \ @@ -258,6 +353,10 @@ void android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj) { LOG_FATAL_IF(! var, "Unable to find field " fieldName); int register_android_view_MotionEvent(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/view/MotionEvent", + gMotionEventMethods, NELEM(gMotionEventMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent"); GET_STATIC_METHOD_ID(gMotionEventClassInfo.obtain, gMotionEventClassInfo.clazz, |