TouchExplorerpublic class TouchExplorer extends Object implements EventStreamTransformationThis class is a strategy for performing touch exploration. It
transforms the motion event stream by modifying, adding, replacing,
and consuming certain events. The interaction model is:
- 1. One finger moving slow around performs touch exploration.
- 2. One finger moving fast around performs gestures.
- 3. Two close fingers moving in the same direction perform a drag.
- 4. Multi-finger gestures are delivered to view hierarchy.
- 5. Two fingers moving in different directions are considered a multi-finger gesture.
- 7. Double tapping clicks on the on the last touch explored location if it was in
a window that does not take focus, otherwise the click is within the accessibility
focused rectangle.
- 7. Tapping and holding for a while performs a long press in a similar fashion
as the click above.
|
Fields Summary |
---|
private static final boolean | DEBUG | private static final String | LOG_TAG | private static final int | STATE_TOUCH_EXPLORING | private static final int | STATE_DRAGGING | private static final int | STATE_DELEGATING | private static final int | STATE_GESTURE_DETECTING | private static final int | CLICK_LOCATION_NONE | private static final int | CLICK_LOCATION_ACCESSIBILITY_FOCUS | private static final int | CLICK_LOCATION_LAST_TOUCH_EXPLORED | private static final float | MAX_DRAGGING_ANGLE_COS | private static final int | ALL_POINTER_ID_BITS | private static final int | MAX_POINTER_COUNT | private static final int | INVALID_POINTER_ID | private static final int | GESTURE_DETECTION_VELOCITY_DIP | private static final int | MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP | private static final int | EXIT_GESTURE_DETECTION_TIMEOUT | private final int | mDetermineUserIntentTimeout | private final int | mTapTimeout | private final int | mDoubleTapTimeout | private final int | mTouchSlop | private final int | mDoubleTapSlop | private int | mCurrentState | private int | mDraggingPointerId | private final android.os.Handler | mHandler | private final SendHoverEnterAndMoveDelayed | mSendHoverEnterAndMoveDelayed | private final SendHoverExitDelayed | mSendHoverExitDelayed | private final SendAccessibilityEventDelayed | mSendTouchExplorationEndDelayed | private final SendAccessibilityEventDelayed | mSendTouchInteractionEndDelayed | private final PerformLongPressDelayed | mPerformLongPressDelayed | private final ExitGestureDetectionModeDelayed | mExitGestureDetectionModeDelayed | private final DoubleTapDetector | mDoubleTapDetector | private final int | mScaledMinPointerDistanceToUseMiddleLocation | private final int | mScaledGestureDetectionVelocity | private EventStreamTransformation | mNext | private final android.view.VelocityTracker | mVelocityTracker | private final ReceivedPointerTracker | mReceivedPointerTracker | private final InjectedPointerTracker | mInjectedPointerTracker | private final AccessibilityManagerService | mAms | private final android.graphics.Rect | mTempRect | private final android.graphics.Point | mTempPoint | private final android.content.Context | mContext | private float | mPreviousX | private float | mPreviousY | private final ArrayList | mStrokeBuffer | private static final int | TOUCH_TOLERANCE | private static final float | MIN_PREDICTION_SCORE | private android.gesture.GestureLibrary | mGestureLibrary | private int | mLongPressingPointerId | private int | mLongPressingPointerDeltaX | private int | mLongPressingPointerDeltaY | private int | mLastTouchedWindowId | private boolean | mTouchExplorationInProgress |
Constructors Summary |
---|
public TouchExplorer(android.content.Context context, AccessibilityManagerService service)Creates a new instance.
mContext = context;
mAms = service;
mReceivedPointerTracker = new ReceivedPointerTracker();
mInjectedPointerTracker = new InjectedPointerTracker();
mTapTimeout = ViewConfiguration.getTapTimeout();
mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout();
mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
mHandler = new Handler(context.getMainLooper());
mPerformLongPressDelayed = new PerformLongPressDelayed();
mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed();
mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
mGestureLibrary.setOrientationStyle(8);
mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
mGestureLibrary.load();
mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed();
mSendHoverExitDelayed = new SendHoverExitDelayed();
mSendTouchExplorationEndDelayed = new SendAccessibilityEventDelayed(
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END,
mDetermineUserIntentTimeout);
mSendTouchInteractionEndDelayed = new SendAccessibilityEventDelayed(
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
mDetermineUserIntentTimeout);
mDoubleTapDetector = new DoubleTapDetector();
final float density = context.getResources().getDisplayMetrics().density;
mScaledMinPointerDistanceToUseMiddleLocation =
(int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
|
Methods Summary |
---|
public void | clear()
// If we have not received an event then we are in initial
// state. Therefore, there is not need to clean anything.
MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent();
if (event != null) {
clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED);
}
| private void | clear(android.view.MotionEvent event, int policyFlags)
switch (mCurrentState) {
case STATE_TOUCH_EXPLORING: {
// If a touch exploration gesture is in progress send events for its end.
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
} break;
case STATE_DRAGGING: {
mDraggingPointerId = INVALID_POINTER_ID;
// Send exit to all pointers that we have delivered.
sendUpForInjectedDownPointers(event, policyFlags);
} break;
case STATE_DELEGATING: {
// Send exit to all pointers that we have delivered.
sendUpForInjectedDownPointers(event, policyFlags);
} break;
case STATE_GESTURE_DETECTING: {
// Clear the current stroke.
mStrokeBuffer.clear();
} break;
}
// Remove all pending callbacks.
mSendHoverEnterAndMoveDelayed.cancel();
mSendHoverExitDelayed.cancel();
mPerformLongPressDelayed.cancel();
mExitGestureDetectionModeDelayed.cancel();
mSendTouchExplorationEndDelayed.cancel();
mSendTouchInteractionEndDelayed.cancel();
// Reset the pointer trackers.
mReceivedPointerTracker.clear();
mInjectedPointerTracker.clear();
// Clear the double tap detector
mDoubleTapDetector.clear();
// Go to initial state.
// Clear the long pressing pointer remap data.
mLongPressingPointerId = -1;
mLongPressingPointerDeltaX = 0;
mLongPressingPointerDeltaY = 0;
mCurrentState = STATE_TOUCH_EXPLORING;
if (mNext != null) {
mNext.clear();
}
mTouchExplorationInProgress = false;
mAms.onTouchInteractionEnd();
| private int | computeClickLocation(android.graphics.Point outLocation)
MotionEvent lastExploreEvent = mInjectedPointerTracker.getLastInjectedHoverEventForClick();
if (lastExploreEvent != null) {
final int lastExplorePointerIndex = lastExploreEvent.getActionIndex();
outLocation.x = (int) lastExploreEvent.getX(lastExplorePointerIndex);
outLocation.y = (int) lastExploreEvent.getY(lastExplorePointerIndex);
if (!mAms.accessibilityFocusOnlyInActiveWindow()
|| mLastTouchedWindowId == mAms.getActiveWindowId()) {
if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) {
return CLICK_LOCATION_ACCESSIBILITY_FOCUS;
} else {
return CLICK_LOCATION_LAST_TOUCH_EXPLORED;
}
}
}
if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) {
return CLICK_LOCATION_ACCESSIBILITY_FOCUS;
}
return CLICK_LOCATION_NONE;
| private int | computeInjectionAction(int actionMasked, int pointerIndex)Computes the action for an injected event based on a masked action
and a pointer index.
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
// Compute the action based on how many down pointers are injected.
if (injectedTracker.getInjectedPointerDownCount() == 0) {
return MotionEvent.ACTION_DOWN;
} else {
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
| MotionEvent.ACTION_POINTER_DOWN;
}
}
case MotionEvent.ACTION_POINTER_UP: {
InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
// Compute the action based on how many down pointers are injected.
if (injectedTracker.getInjectedPointerDownCount() == 1) {
return MotionEvent.ACTION_UP;
} else {
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
| MotionEvent.ACTION_POINTER_UP;
}
}
default:
return actionMasked;
}
| private static java.lang.String | getStateSymbolicName(int state)Gets the symbolic name of a state.
switch (state) {
case STATE_TOUCH_EXPLORING:
return "STATE_TOUCH_EXPLORING";
case STATE_DRAGGING:
return "STATE_DRAGGING";
case STATE_DELEGATING:
return "STATE_DELEGATING";
case STATE_GESTURE_DETECTING:
return "STATE_GESTURE_DETECTING";
default:
throw new IllegalArgumentException("Unknown state: " + state);
}
| private void | handleMotionEventGestureDetecting(android.view.MotionEvent event, int policyFlags)
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
final float x = event.getX();
final float y = event.getY();
mPreviousX = x;
mPreviousY = y;
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
} break;
case MotionEvent.ACTION_MOVE: {
final float x = event.getX();
final float y = event.getY();
final float dX = Math.abs(x - mPreviousX);
final float dY = Math.abs(y - mPreviousY);
if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
mPreviousX = x;
mPreviousY = y;
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
}
} break;
case MotionEvent.ACTION_UP: {
mAms.onTouchInteractionEnd();
// Announce the end of the gesture recognition.
sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
// Announce the end of a the touch interaction.
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
float x = event.getX();
float y = event.getY();
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
Gesture gesture = new Gesture();
gesture.addStroke(new GestureStroke(mStrokeBuffer));
ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture);
if (!predictions.isEmpty()) {
Prediction bestPrediction = predictions.get(0);
if (bestPrediction.score >= MIN_PREDICTION_SCORE) {
if (DEBUG) {
Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: "
+ bestPrediction.score);
}
try {
final int gestureId = Integer.parseInt(bestPrediction.name);
mAms.onGesture(gestureId);
} catch (NumberFormatException nfe) {
Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
}
}
}
mStrokeBuffer.clear();
mExitGestureDetectionModeDelayed.cancel();
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
case MotionEvent.ACTION_CANCEL: {
clear(event, policyFlags);
} break;
}
| private void | handleMotionEventStateDelegating(android.view.MotionEvent event, int policyFlags)Handles a motion event in delegating state.
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
throw new IllegalStateException("Delegating state can only be reached if "
+ "there is at least one pointer down!");
}
case MotionEvent.ACTION_UP: {
// Offset the event if we are doing a long press as the
// target is not necessarily under the user's finger.
if (mLongPressingPointerId >= 0) {
event = offsetEvent(event, - mLongPressingPointerDeltaX,
- mLongPressingPointerDeltaY);
// Clear the long press state.
mLongPressingPointerId = -1;
mLongPressingPointerDeltaX = 0;
mLongPressingPointerDeltaY = 0;
}
// Deliver the event.
sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
// Announce the end of a the touch interaction.
mAms.onTouchInteractionEnd();
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
case MotionEvent.ACTION_CANCEL: {
clear(event, policyFlags);
} break;
default: {
// Deliver the event.
sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
}
}
| private void | handleMotionEventStateDragging(android.view.MotionEvent event, int policyFlags)Handles a motion event in dragging state.
final int pointerIdBits = (1 << mDraggingPointerId);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
throw new IllegalStateException("Dragging state can be reached only if two "
+ "pointers are already down");
}
case MotionEvent.ACTION_POINTER_DOWN: {
// We are in dragging state so we have two pointers and another one
// goes down => delegate the three pointers to the view hierarchy
mCurrentState = STATE_DELEGATING;
if (mDraggingPointerId != INVALID_POINTER_ID) {
sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
}
sendDownForAllNotInjectedPointers(event, policyFlags);
} break;
case MotionEvent.ACTION_MOVE: {
switch (event.getPointerCount()) {
case 1: {
// do nothing
} break;
case 2: {
if (isDraggingGesture(event)) {
final float firstPtrX = event.getX(0);
final float firstPtrY = event.getY(0);
final float secondPtrX = event.getX(1);
final float secondPtrY = event.getY(1);
final float deltaX = firstPtrX - secondPtrX;
final float deltaY = firstPtrY - secondPtrY;
final double distance = Math.hypot(deltaX, deltaY);
if (distance > mScaledMinPointerDistanceToUseMiddleLocation) {
event.setLocation(deltaX / 2, deltaY / 2);
}
// If still dragging send a drag event.
sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
policyFlags);
} else {
// The two pointers are moving either in different directions or
// no close enough => delegate the gesture to the view hierarchy.
mCurrentState = STATE_DELEGATING;
// Send an event to the end of the drag gesture.
sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
policyFlags);
// Deliver all pointers to the view hierarchy.
sendDownForAllNotInjectedPointers(event, policyFlags);
}
} break;
default: {
mCurrentState = STATE_DELEGATING;
// Send an event to the end of the drag gesture.
sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
policyFlags);
// Deliver all pointers to the view hierarchy.
sendDownForAllNotInjectedPointers(event, policyFlags);
}
}
} break;
case MotionEvent.ACTION_POINTER_UP: {
final int pointerId = event.getPointerId(event.getActionIndex());
if (pointerId == mDraggingPointerId) {
mDraggingPointerId = INVALID_POINTER_ID;
// Send an event to the end of the drag gesture.
sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
}
} break;
case MotionEvent.ACTION_UP: {
mAms.onTouchInteractionEnd();
// Announce the end of a new touch interaction.
sendAccessibilityEvent(
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
final int pointerId = event.getPointerId(event.getActionIndex());
if (pointerId == mDraggingPointerId) {
mDraggingPointerId = INVALID_POINTER_ID;
// Send an event to the end of the drag gesture.
sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
}
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
case MotionEvent.ACTION_CANCEL: {
clear(event, policyFlags);
} break;
}
| private void | handleMotionEventStateTouchExploring(android.view.MotionEvent event, android.view.MotionEvent rawEvent, int policyFlags)Handles a motion event in touch exploring state.
ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
mVelocityTracker.addMovement(rawEvent);
mDoubleTapDetector.onMotionEvent(event, policyFlags);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mAms.onTouchInteractionStart();
// Pre-feed the motion events to the gesture detector since we
// have a distance slop before getting into gesture detection
// mode and not using the points within this slop significantly
// decreases the quality of gesture recognition.
handleMotionEventGestureDetecting(rawEvent, policyFlags);
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
// If we still have not notified the user for the last
// touch, we figure out what to do. If were waiting
// we resent the delayed callback and wait again.
mSendHoverEnterAndMoveDelayed.cancel();
mSendHoverExitDelayed.cancel();
mPerformLongPressDelayed.cancel();
if (mSendTouchExplorationEndDelayed.isPending()) {
mSendTouchExplorationEndDelayed.forceSendAndRemove();
}
if (mSendTouchInteractionEndDelayed.isPending()) {
mSendTouchInteractionEndDelayed.forceSendAndRemove();
}
// If we have the first tap, schedule a long press and break
// since we do not want to schedule hover enter because
// the delayed callback will kick in before the long click.
// This would lead to a state transition resulting in long
// pressing the item below the double taped area which is
// not necessary where accessibility focus is.
if (mDoubleTapDetector.firstTapDetected()) {
// We got a tap now post a long press action.
mPerformLongPressDelayed.post(event, policyFlags);
break;
}
if (!mTouchExplorationInProgress) {
if (!mSendHoverEnterAndMoveDelayed.isPending()) {
// Deliver hover enter with a delay to have a chance
// to detect what the user is trying to do.
final int pointerId = receivedTracker.getPrimaryPointerId();
final int pointerIdBits = (1 << pointerId);
mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits,
policyFlags);
} else {
// Cache the event until we discern exploration from gesturing.
mSendHoverEnterAndMoveDelayed.addEvent(event);
}
}
} break;
case MotionEvent.ACTION_POINTER_DOWN: {
// Another finger down means that if we have not started to deliver
// hover events, we will not have to. The code for ACTION_MOVE will
// decide what we will actually do next.
mSendHoverEnterAndMoveDelayed.cancel();
mSendHoverExitDelayed.cancel();
mPerformLongPressDelayed.cancel();
} break;
case MotionEvent.ACTION_MOVE: {
final int pointerId = receivedTracker.getPrimaryPointerId();
final int pointerIndex = event.findPointerIndex(pointerId);
final int pointerIdBits = (1 << pointerId);
switch (event.getPointerCount()) {
case 1: {
// We have not started sending events since we try to
// figure out what the user is doing.
if (mSendHoverEnterAndMoveDelayed.isPending()) {
// Pre-feed the motion events to the gesture detector since we
// have a distance slop before getting into gesture detection
// mode and not using the points within this slop significantly
// decreases the quality of gesture recognition.
handleMotionEventGestureDetecting(rawEvent, policyFlags);
// Cache the event until we discern exploration from gesturing.
mSendHoverEnterAndMoveDelayed.addEvent(event);
// It is *important* to use the distance traveled by the pointers
// on the screen which may or may not be magnified.
final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
- rawEvent.getX(pointerIndex);
final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
- rawEvent.getY(pointerIndex);
final double moveDelta = Math.hypot(deltaX, deltaY);
// The user has moved enough for us to decide.
if (moveDelta > mDoubleTapSlop) {
// Check whether the user is performing a gesture. We
// detect gestures if the pointer is moving above a
// given velocity.
mVelocityTracker.computeCurrentVelocity(1000);
final float maxAbsVelocity = Math.max(
Math.abs(mVelocityTracker.getXVelocity(pointerId)),
Math.abs(mVelocityTracker.getYVelocity(pointerId)));
if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
// We have to perform gesture detection, so
// clear the current state and try to detect.
mCurrentState = STATE_GESTURE_DETECTING;
mVelocityTracker.clear();
mSendHoverEnterAndMoveDelayed.cancel();
mSendHoverExitDelayed.cancel();
mPerformLongPressDelayed.cancel();
mExitGestureDetectionModeDelayed.post();
// Send accessibility event to announce the start
// of gesture recognition.
sendAccessibilityEvent(
AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
} else {
// We have just decided that the user is touch,
// exploring so start sending events.
mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
mSendHoverExitDelayed.cancel();
mPerformLongPressDelayed.cancel();
sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
pointerIdBits, policyFlags);
}
break;
}
} else {
// Cancel the long press if pending and the user
// moved more than the slop.
if (mPerformLongPressDelayed.isPending()) {
final float deltaX =
receivedTracker.getReceivedPointerDownX(pointerId)
- rawEvent.getX(pointerIndex);
final float deltaY =
receivedTracker.getReceivedPointerDownY(pointerId)
- rawEvent.getY(pointerIndex);
final double moveDelta = Math.hypot(deltaX, deltaY);
// The user has moved enough for us to decide.
if (moveDelta > mTouchSlop) {
mPerformLongPressDelayed.cancel();
}
}
if (mTouchExplorationInProgress) {
sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
policyFlags);
}
}
} break;
case 2: {
// More than one pointer so the user is not touch exploring
// and now we have to decide whether to delegate or drag.
if (mSendHoverEnterAndMoveDelayed.isPending()) {
// We have not started sending events so cancel
// scheduled sending events.
mSendHoverEnterAndMoveDelayed.cancel();
mSendHoverExitDelayed.cancel();
mPerformLongPressDelayed.cancel();
} else {
mPerformLongPressDelayed.cancel();
if (mTouchExplorationInProgress) {
// If the user is touch exploring the second pointer may be
// performing a double tap to activate an item without need
// for the user to lift his exploring finger.
// It is *important* to use the distance traveled by the pointers
// on the screen which may or may not be magnified.
final float deltaX = receivedTracker.getReceivedPointerDownX(
pointerId) - rawEvent.getX(pointerIndex);
final float deltaY = receivedTracker.getReceivedPointerDownY(
pointerId) - rawEvent.getY(pointerIndex);
final double moveDelta = Math.hypot(deltaX, deltaY);
if (moveDelta < mDoubleTapSlop) {
break;
}
// We are sending events so send exit and gesture
// end since we transition to another state.
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
}
}
// We know that a new state transition is to happen and the
// new state will not be gesture recognition, so clear the
// stashed gesture strokes.
mStrokeBuffer.clear();
if (isDraggingGesture(event)) {
// Two pointers moving in the same direction within
// a given distance perform a drag.
mCurrentState = STATE_DRAGGING;
mDraggingPointerId = pointerId;
event.setEdgeFlags(receivedTracker.getLastReceivedDownEdgeFlags());
sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits,
policyFlags);
} else {
// Two pointers moving arbitrary are delegated to the view hierarchy.
mCurrentState = STATE_DELEGATING;
sendDownForAllNotInjectedPointers(event, policyFlags);
}
mVelocityTracker.clear();
} break;
default: {
// More than one pointer so the user is not touch exploring
// and now we have to decide whether to delegate or drag.
if (mSendHoverEnterAndMoveDelayed.isPending()) {
// We have not started sending events so cancel
// scheduled sending events.
mSendHoverEnterAndMoveDelayed.cancel();
mSendHoverExitDelayed.cancel();
mPerformLongPressDelayed.cancel();
} else {
mPerformLongPressDelayed.cancel();
// We are sending events so send exit and gesture
// end since we transition to another state.
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
}
// More than two pointers are delegated to the view hierarchy.
mCurrentState = STATE_DELEGATING;
sendDownForAllNotInjectedPointers(event, policyFlags);
mVelocityTracker.clear();
}
}
} break;
case MotionEvent.ACTION_UP: {
mAms.onTouchInteractionEnd();
// We know that we do not need the pre-fed gesture points are not
// needed anymore since the last pointer just went up.
mStrokeBuffer.clear();
final int pointerId = event.getPointerId(event.getActionIndex());
final int pointerIdBits = (1 << pointerId);
mPerformLongPressDelayed.cancel();
mVelocityTracker.clear();
if (mSendHoverEnterAndMoveDelayed.isPending()) {
// If we have not delivered the enter schedule an exit.
mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
} else {
// The user is touch exploring so we send events for end.
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
}
if (!mSendTouchInteractionEndDelayed.isPending()) {
mSendTouchInteractionEndDelayed.post();
}
} break;
case MotionEvent.ACTION_CANCEL: {
clear(event, policyFlags);
} break;
}
| private boolean | isDraggingGesture(android.view.MotionEvent event)Determines whether a two pointer gesture is a dragging one.
ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
final float firstPtrX = event.getX(0);
final float firstPtrY = event.getY(0);
final float secondPtrX = event.getX(1);
final float secondPtrY = event.getY(1);
final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(0);
final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(0);
final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(1);
final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(1);
return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX,
secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY,
MAX_DRAGGING_ANGLE_COS);
| private android.view.MotionEvent | offsetEvent(android.view.MotionEvent event, int offsetX, int offsetY)Offsets all pointers in the given event by adding the specified X and Y
offsets.
if (offsetX == 0 && offsetY == 0) {
return event;
}
final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
final int pointerCount = event.getPointerCount();
PointerProperties[] props = PointerProperties.createArray(pointerCount);
PointerCoords[] coords = PointerCoords.createArray(pointerCount);
for (int i = 0; i < pointerCount; i++) {
event.getPointerProperties(i, props[i]);
event.getPointerCoords(i, coords[i]);
if (i == remappedIndex) {
coords[i].x += offsetX;
coords[i].y += offsetY;
}
}
return MotionEvent.obtain(event.getDownTime(),
event.getEventTime(), event.getAction(), event.getPointerCount(),
props, coords, event.getMetaState(), event.getButtonState(),
1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
event.getSource(), event.getFlags());
| public void | onAccessibilityEvent(android.view.accessibility.AccessibilityEvent event)
final int eventType = event.getEventType();
// The event for gesture end should be strictly after the
// last hover exit event.
if (mSendTouchExplorationEndDelayed.isPending()
&& eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
mSendTouchExplorationEndDelayed.cancel();
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
}
// The event for touch interaction end should be strictly after the
// last hover exit and the touch exploration gesture end events.
if (mSendTouchInteractionEndDelayed.isPending()
&& eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
mSendTouchInteractionEndDelayed.cancel();
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
}
// If a new window opens or the accessibility focus moves we no longer
// want to click/long press on the last touch explored location.
switch (eventType) {
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
if (mInjectedPointerTracker.mLastInjectedHoverEventForClick != null) {
mInjectedPointerTracker.mLastInjectedHoverEventForClick.recycle();
mInjectedPointerTracker.mLastInjectedHoverEventForClick = null;
}
mLastTouchedWindowId = -1;
} break;
case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
mLastTouchedWindowId = event.getWindowId();
} break;
}
if (mNext != null) {
mNext.onAccessibilityEvent(event);
}
| public void | onDestroy()
// TODO: Implement
| public void | onMotionEvent(android.view.MotionEvent event, android.view.MotionEvent rawEvent, int policyFlags)
if (DEBUG) {
Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x"
+ Integer.toHexString(policyFlags));
Slog.d(LOG_TAG, getStateSymbolicName(mCurrentState));
}
mReceivedPointerTracker.onMotionEvent(rawEvent);
switch(mCurrentState) {
case STATE_TOUCH_EXPLORING: {
handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
} break;
case STATE_DRAGGING: {
handleMotionEventStateDragging(event, policyFlags);
} break;
case STATE_DELEGATING: {
handleMotionEventStateDelegating(event, policyFlags);
} break;
case STATE_GESTURE_DETECTING: {
handleMotionEventGestureDetecting(rawEvent, policyFlags);
} break;
default:
throw new IllegalStateException("Illegal state: " + mCurrentState);
}
| private void | sendAccessibilityEvent(int type)Sends an accessibility event of the given type.
AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
if (accessibilityManager.isEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain(type);
event.setWindowId(mAms.getActiveWindowId());
accessibilityManager.sendAccessibilityEvent(event);
switch (type) {
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: {
mTouchExplorationInProgress = true;
} break;
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: {
mTouchExplorationInProgress = false;
} break;
}
}
| private void | sendActionDownAndUp(android.view.MotionEvent prototype, int policyFlags, boolean targetAccessibilityFocus)Sends an up and down events.
// Tap with the pointer that last explored.
final int pointerId = prototype.getPointerId(prototype.getActionIndex());
final int pointerIdBits = (1 << pointerId);
prototype.setTargetAccessibilityFocus(targetAccessibilityFocus);
sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
prototype.setTargetAccessibilityFocus(targetAccessibilityFocus);
sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
| private void | sendDownForAllNotInjectedPointers(android.view.MotionEvent prototype, int policyFlags)Sends down events to the view hierarchy for all pointers which are
not already being delivered i.e. pointers that are not yet injected.
InjectedPointerTracker injectedPointers = mInjectedPointerTracker;
// Inject the injected pointers.
int pointerIdBits = 0;
final int pointerCount = prototype.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
final int pointerId = prototype.getPointerId(i);
// Do not send event for already delivered pointers.
if (!injectedPointers.isInjectedPointerDown(pointerId)) {
pointerIdBits |= (1 << pointerId);
final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
}
}
| private void | sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags)Sends the exit events if needed. Such events are hover exit and touch explore
gesture end.
MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
final int pointerIdBits = event.getPointerIdBits();
if (!mSendTouchExplorationEndDelayed.isPending()) {
mSendTouchExplorationEndDelayed.post();
}
sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
}
| private void | sendMotionEvent(android.view.MotionEvent prototype, int action, int pointerIdBits, int policyFlags)Sends an event.
prototype.setAction(action);
MotionEvent event = null;
if (pointerIdBits == ALL_POINTER_ID_BITS) {
event = prototype;
} else {
event = prototype.split(pointerIdBits);
}
if (action == MotionEvent.ACTION_DOWN) {
event.setDownTime(event.getEventTime());
} else {
event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
}
// If the user is long pressing but the long pressing pointer
// was not exactly over the accessibility focused item we need
// to remap the location of that pointer so the user does not
// have to explicitly touch explore something to be able to
// long press it, or even worse to avoid the user long pressing
// on the wrong item since click and long press behave differently.
if (mLongPressingPointerId >= 0) {
event = offsetEvent(event, - mLongPressingPointerDeltaX,
- mLongPressingPointerDeltaY);
}
if (DEBUG) {
Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
+ Integer.toHexString(policyFlags));
}
// Make sure that the user will see the event.
policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
if (mNext != null) {
// TODO: For now pass null for the raw event since the touch
// explorer is the last event transformation and it does
// not care about the raw event.
mNext.onMotionEvent(event, null, policyFlags);
}
mInjectedPointerTracker.onMotionEvent(event);
if (event != prototype) {
event.recycle();
}
| private void | sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags)Sends the enter events if needed. Such events are hover enter and touch explore
gesture start.
MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
final int pointerIdBits = event.getPointerIdBits();
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
}
| private void | sendUpForInjectedDownPointers(android.view.MotionEvent prototype, int policyFlags)Sends up events to the view hierarchy for all pointers which are
already being delivered i.e. pointers that are injected.
final InjectedPointerTracker injectedTracked = mInjectedPointerTracker;
int pointerIdBits = 0;
final int pointerCount = prototype.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
final int pointerId = prototype.getPointerId(i);
// Skip non injected down pointers.
if (!injectedTracked.isInjectedPointerDown(pointerId)) {
continue;
}
pointerIdBits |= (1 << pointerId);
final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
}
| public void | setNext(EventStreamTransformation next)
mNext = next;
| public java.lang.String | toString()
return LOG_TAG;
|
|