Track unhandled input events in consistency verifiers.
This fixes spurious verification errors that would be generated when a view declined an initial event such as ACTION_DOWN. Since the view would not receive the rest of the event stream, it would not see the corresponding ACTION_UP and the next ACTION_DOWN would trigger a spurious verification error. Change-Id: I2386acf378cd1765d5446faed5ad9c6525f8b400
This commit is contained in:
@@ -590,8 +590,14 @@ public class GestureDetector {
|
||||
mHandler.removeMessages(SHOW_PRESS);
|
||||
mHandler.removeMessages(LONG_PRESS);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
cancel();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handled && mInputEventConsistencyVerifier != null) {
|
||||
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ public final class InputEventConsistencyVerifier {
|
||||
|
||||
// Copy of the most recent events.
|
||||
private InputEvent[] mRecentEvents;
|
||||
private boolean[] mRecentEventsUnhandled;
|
||||
private int mMostRecentEventIndex;
|
||||
|
||||
// Current event and its type.
|
||||
@@ -65,6 +66,7 @@ public final class InputEventConsistencyVerifier {
|
||||
|
||||
// Current state of the trackball.
|
||||
private boolean mTrackballDown;
|
||||
private boolean mTrackballUnhandled;
|
||||
|
||||
// Bitfield of pointer ids that are currently down.
|
||||
// Assumes that the largest possible pointer id is 31, which is potentially subject to change.
|
||||
@@ -79,6 +81,9 @@ public final class InputEventConsistencyVerifier {
|
||||
// Reset on down or cancel.
|
||||
private boolean mTouchEventStreamIsTainted;
|
||||
|
||||
// Set to true if the touch event stream is partially unhandled.
|
||||
private boolean mTouchEventStreamUnhandled;
|
||||
|
||||
// Set to true if we received hover enter.
|
||||
private boolean mHoverEntered;
|
||||
|
||||
@@ -117,9 +122,17 @@ public final class InputEventConsistencyVerifier {
|
||||
mLastEvent = null;
|
||||
mLastNestingLevel = 0;
|
||||
mTrackballDown = false;
|
||||
mTrackballUnhandled = false;
|
||||
mTouchEventStreamPointers = 0;
|
||||
mTouchEventStreamIsTainted = false;
|
||||
mTouchEventStreamUnhandled = false;
|
||||
mHoverEntered = false;
|
||||
|
||||
while (mKeyStateList != null) {
|
||||
final KeyState state = mKeyStateList;
|
||||
mKeyStateList = state.next;
|
||||
state.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,7 +189,9 @@ public final class InputEventConsistencyVerifier {
|
||||
// We don't perform this check when processing raw device input
|
||||
// because the input dispatcher itself is responsible for setting
|
||||
// the key repeat count before it delivers input events.
|
||||
if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
|
||||
if (state.unhandled) {
|
||||
state.unhandled = false;
|
||||
} else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
|
||||
&& event.getRepeatCount() == 0) {
|
||||
problem("ACTION_DOWN but key is already down and this event "
|
||||
+ "is not a key repeat.");
|
||||
@@ -229,10 +244,11 @@ public final class InputEventConsistencyVerifier {
|
||||
if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
if (mTrackballDown) {
|
||||
if (mTrackballDown && !mTrackballUnhandled) {
|
||||
problem("ACTION_DOWN but trackball is already down.");
|
||||
} else {
|
||||
mTrackballDown = true;
|
||||
mTrackballUnhandled = false;
|
||||
}
|
||||
ensureHistorySizeIsZeroForThisAction(event);
|
||||
ensurePointerCountIsOneForThisAction(event);
|
||||
@@ -242,6 +258,7 @@ public final class InputEventConsistencyVerifier {
|
||||
problem("ACTION_UP but trackball is not down.");
|
||||
} else {
|
||||
mTrackballDown = false;
|
||||
mTrackballUnhandled = false;
|
||||
}
|
||||
ensureHistorySizeIsZeroForThisAction(event);
|
||||
ensurePointerCountIsOneForThisAction(event);
|
||||
@@ -285,11 +302,13 @@ public final class InputEventConsistencyVerifier {
|
||||
final int action = event.getAction();
|
||||
final boolean newStream = action == MotionEvent.ACTION_DOWN
|
||||
|| action == MotionEvent.ACTION_CANCEL;
|
||||
if (mTouchEventStreamIsTainted) {
|
||||
if (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled) {
|
||||
if (newStream) {
|
||||
mTouchEventStreamIsTainted = false;
|
||||
mTouchEventStreamUnhandled = false;
|
||||
mTouchEventStreamPointers = 0;
|
||||
} else {
|
||||
finishEvent(true);
|
||||
finishEvent(mTouchEventStreamIsTainted);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -467,6 +486,48 @@ public final class InputEventConsistencyVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the verifier that a given event was unhandled and the rest of the
|
||||
* trace for the event should be ignored.
|
||||
* This method should only be called if the event was previously checked by
|
||||
* the consistency verifier using {@link #onInputEvent} and other methods.
|
||||
* @param event The event.
|
||||
* @param nestingLevel The nesting level: 0 if called from the base class,
|
||||
* or 1 from a subclass. If the event was already checked by this consistency verifier
|
||||
* at a higher nesting level, it will not be checked again. Used to handle the situation
|
||||
* where a subclass dispatching method delegates to its superclass's dispatching method
|
||||
* and both dispatching methods call into the consistency verifier.
|
||||
*/
|
||||
public void onUnhandledEvent(InputEvent event, int nestingLevel) {
|
||||
if (nestingLevel != mLastNestingLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRecentEventsUnhandled != null) {
|
||||
mRecentEventsUnhandled[mMostRecentEventIndex] = true;
|
||||
}
|
||||
|
||||
if (event instanceof KeyEvent) {
|
||||
final KeyEvent keyEvent = (KeyEvent)event;
|
||||
final int deviceId = keyEvent.getDeviceId();
|
||||
final int source = keyEvent.getSource();
|
||||
final int keyCode = keyEvent.getKeyCode();
|
||||
final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
|
||||
if (state != null) {
|
||||
state.unhandled = true;
|
||||
}
|
||||
} else {
|
||||
final MotionEvent motionEvent = (MotionEvent)event;
|
||||
if (motionEvent.isTouchEvent()) {
|
||||
mTouchEventStreamUnhandled = true;
|
||||
} else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
|
||||
if (mTrackballDown) {
|
||||
mTrackballUnhandled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureMetaStateIsNormalized(int metaState) {
|
||||
final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState);
|
||||
if (normalizedMetaState != metaState) {
|
||||
@@ -518,7 +579,8 @@ public final class InputEventConsistencyVerifier {
|
||||
private void finishEvent(boolean tainted) {
|
||||
if (mViolationMessage != null && mViolationMessage.length() != 0) {
|
||||
mViolationMessage.append("\n in ").append(mCaller);
|
||||
mViolationMessage.append("\n ").append(mCurrentEvent);
|
||||
mViolationMessage.append("\n ");
|
||||
appendEvent(mViolationMessage, 0, mCurrentEvent, false);
|
||||
|
||||
if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
|
||||
mViolationMessage.append("\n -- recent events --");
|
||||
@@ -529,7 +591,8 @@ public final class InputEventConsistencyVerifier {
|
||||
if (event == null) {
|
||||
break;
|
||||
}
|
||||
mViolationMessage.append("\n ").append(i + 1).append(": ").append(event);
|
||||
mViolationMessage.append("\n ");
|
||||
appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,6 +610,7 @@ public final class InputEventConsistencyVerifier {
|
||||
if (RECENT_EVENTS_TO_LOG != 0) {
|
||||
if (mRecentEvents == null) {
|
||||
mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
|
||||
mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG];
|
||||
}
|
||||
final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
|
||||
mMostRecentEventIndex = index;
|
||||
@@ -554,12 +618,23 @@ public final class InputEventConsistencyVerifier {
|
||||
mRecentEvents[index].recycle();
|
||||
}
|
||||
mRecentEvents[index] = mCurrentEvent.copy();
|
||||
mRecentEventsUnhandled[index] = false;
|
||||
}
|
||||
|
||||
mCurrentEvent = null;
|
||||
mCurrentEventType = null;
|
||||
}
|
||||
|
||||
private static void appendEvent(StringBuilder message, int index,
|
||||
InputEvent event, boolean unhandled) {
|
||||
message.append(index).append(": sent at ").append(event.getEventTimeNano());
|
||||
message.append(", ");
|
||||
if (unhandled) {
|
||||
message.append("(unhandled) ");
|
||||
}
|
||||
message.append(event);
|
||||
}
|
||||
|
||||
private void problem(String message) {
|
||||
if (mViolationMessage == null) {
|
||||
mViolationMessage = new StringBuilder();
|
||||
@@ -608,6 +683,7 @@ public final class InputEventConsistencyVerifier {
|
||||
public int deviceId;
|
||||
public int source;
|
||||
public int keyCode;
|
||||
public boolean unhandled;
|
||||
|
||||
private KeyState() {
|
||||
}
|
||||
@@ -625,6 +701,7 @@ public final class InputEventConsistencyVerifier {
|
||||
state.deviceId = deviceId;
|
||||
state.source = source;
|
||||
state.keyCode = keyCode;
|
||||
state.unhandled = false;
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
@@ -183,15 +183,15 @@ public class ScaleGestureDetector {
|
||||
}
|
||||
|
||||
final int action = event.getActionMasked();
|
||||
boolean handled = true;
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
reset(); // Start fresh
|
||||
}
|
||||
|
||||
if (mInvalidGesture) return false;
|
||||
|
||||
if (!mGestureInProgress) {
|
||||
boolean handled = true;
|
||||
if (mInvalidGesture) {
|
||||
handled = false;
|
||||
} else if (!mGestureInProgress) {
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mActiveId0 = event.getPointerId(0);
|
||||
@@ -467,6 +467,10 @@ public class ScaleGestureDetector {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled && mInputEventConsistencyVerifier != null) {
|
||||
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
|
||||
@@ -4614,8 +4614,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
|
||||
return true;
|
||||
}
|
||||
|
||||
return event.dispatch(this, mAttachInfo != null
|
||||
? mAttachInfo.mKeyDispatchState : null, this);
|
||||
if (event.dispatch(this, mAttachInfo != null
|
||||
? mAttachInfo.mKeyDispatchState : null, this)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mInputEventConsistencyVerifier != null) {
|
||||
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4640,16 +4647,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
|
||||
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
|
||||
}
|
||||
|
||||
if (!onFilterTouchEventForSecurity(event)) {
|
||||
return false;
|
||||
if (onFilterTouchEventForSecurity(event)) {
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
|
||||
mOnTouchListener.onTouch(this, event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (onTouchEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
|
||||
mOnTouchListener.onTouch(this, event)) {
|
||||
return true;
|
||||
if (mInputEventConsistencyVerifier != null) {
|
||||
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
|
||||
}
|
||||
return onTouchEvent(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4682,7 +4695,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
|
||||
}
|
||||
|
||||
//Log.i("view", "view=" + this + ", " + event.toString());
|
||||
return onTrackballEvent(event);
|
||||
if (onTrackballEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mInputEventConsistencyVerifier != null) {
|
||||
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4723,7 +4743,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
|
||||
&& mOnGenericMotionListener.onGenericMotion(this, event)) {
|
||||
return true;
|
||||
}
|
||||
return onGenericMotionEvent(event);
|
||||
|
||||
if (onGenericMotionEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mInputEventConsistencyVerifier != null) {
|
||||
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1131,9 +1131,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
}
|
||||
|
||||
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
|
||||
return super.dispatchKeyEvent(event);
|
||||
if (super.dispatchKeyEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
|
||||
return mFocused.dispatchKeyEvent(event);
|
||||
if (mFocused.dispatchKeyEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mInputEventConsistencyVerifier != null) {
|
||||
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1161,9 +1169,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
}
|
||||
|
||||
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
|
||||
return super.dispatchTrackballEvent(event);
|
||||
if (super.dispatchTrackballEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
|
||||
return mFocused.dispatchTrackballEvent(event);
|
||||
if (mFocused.dispatchTrackballEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mInputEventConsistencyVerifier != null) {
|
||||
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1344,155 +1360,158 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
|
||||
}
|
||||
|
||||
if (!onFilterTouchEventForSecurity(ev)) {
|
||||
return false;
|
||||
}
|
||||
boolean handled = false;
|
||||
if (onFilterTouchEventForSecurity(ev)) {
|
||||
final int action = ev.getAction();
|
||||
final int actionMasked = action & MotionEvent.ACTION_MASK;
|
||||
|
||||
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;
|
||||
// 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();
|
||||
}
|
||||
} else {
|
||||
// There are no touch targets and this action is not an initial down
|
||||
// so this view group continues to intercept touches.
|
||||
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) {
|
||||
// Check for interception.
|
||||
final boolean intercepted;
|
||||
if (actionMasked == MotionEvent.ACTION_DOWN
|
||||
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|
||||
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
final int actionIndex = ev.getActionIndex(); // always 0 for down
|
||||
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
|
||||
: TouchTarget.ALL_POINTER_IDS;
|
||||
|| mFirstTouchTarget != null) {
|
||||
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
|
||||
if (!disallowIntercept) {
|
||||
intercepted = onInterceptTouchEvent(ev);
|
||||
ev.setAction(action); // restore action in case it was changed
|
||||
} else {
|
||||
intercepted = false;
|
||||
}
|
||||
} else {
|
||||
// There are no touch targets and this action is not an initial down
|
||||
// so this view group continues to intercept touches.
|
||||
intercepted = true;
|
||||
}
|
||||
|
||||
// Clean up earlier touch targets for this pointer id in case they
|
||||
// have become out of sync.
|
||||
removePointersFromTouchTargets(idBitsToAssign);
|
||||
// Check for cancelation.
|
||||
final boolean canceled = resetCancelNextUpFlag(this)
|
||||
|| actionMasked == MotionEvent.ACTION_CANCEL;
|
||||
|
||||
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);
|
||||
// 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)
|
||||
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
final int actionIndex = ev.getActionIndex(); // always 0 for down
|
||||
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
|
||||
: TouchTarget.ALL_POINTER_IDS;
|
||||
|
||||
for (int i = childrenCount - 1; i >= 0; i--) {
|
||||
final View child = children[i];
|
||||
if (!canViewReceivePointerEvents(child)
|
||||
|| !isTransformedTouchPointInView(x, y, child, null)) {
|
||||
// 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 (!canViewReceivePointerEvents(child)
|
||||
|| !isTransformedTouchPointInView(x, y, child, null)) {
|
||||
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.
|
||||
mLastTouchDownTime = ev.getDownTime();
|
||||
mLastTouchDownIndex = i;
|
||||
mLastTouchDownX = ev.getX();
|
||||
mLastTouchDownY = ev.getY();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch to touch targets.
|
||||
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;
|
||||
}
|
||||
|
||||
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.
|
||||
mLastTouchDownTime = ev.getDownTime();
|
||||
mLastTouchDownIndex = i;
|
||||
mLastTouchDownX = ev.getX();
|
||||
mLastTouchDownY = ev.getY();
|
||||
newTouchTarget = addTouchTarget(child, idBitsToAssign);
|
||||
alreadyDispatchedToNewTouchTarget = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
predecessor = target;
|
||||
target = next;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// Update list of touch targets for pointer up or cancel, if needed.
|
||||
if (canceled
|
||||
|| actionMasked == MotionEvent.ACTION_UP
|
||||
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
resetTouchState();
|
||||
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
|
||||
final int actionIndex = ev.getActionIndex();
|
||||
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
|
||||
removePointersFromTouchTargets(idBitsToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (!handled && mInputEventConsistencyVerifier != null) {
|
||||
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
|
||||
}
|
||||
|
||||
// Update list of touch targets for pointer up or cancel, if needed.
|
||||
if (canceled
|
||||
|| actionMasked == MotionEvent.ACTION_UP
|
||||
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
resetTouchState();
|
||||
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
|
||||
final int actionIndex = ev.getActionIndex();
|
||||
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
|
||||
removePointersFromTouchTargets(idBitsToRemove);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user