summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.xml13
-rw-r--r--core/java/android/view/MotionEvent.java16
-rw-r--r--core/java/android/view/View.java10
-rw-r--r--core/java/android/view/ViewGroup.java1040
-rw-r--r--core/jni/android/graphics/Matrix.cpp12
-rw-r--r--core/jni/android/graphics/Matrix.h30
-rw-r--r--core/jni/android_view_MotionEvent.cpp99
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,