Merge "Extract event sending code into EventDispatcher."

This commit is contained in:
Ameer Armaly
2019-09-20 17:23:13 +00:00
committed by Android (Google) Code Review
3 changed files with 395 additions and 325 deletions

View File

@@ -0,0 +1,309 @@
/*
* Copyright (C) 2019 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.
*/
package com.android.server.accessibility.gestures;
import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS;
import static com.android.server.accessibility.gestures.TouchState.MAX_POINTER_COUNT;
import android.content.Context;
import android.util.Slog;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.policy.WindowManagerPolicy;
/**
* This class dispatches motion events and accessibility events relating to touch exploration and
* gesture dispatch. TouchExplorer is responsible for insuring that the receiver of motion events is
* set correctly so that events go to the right place.
*/
class EventDispatcher {
private static final String LOG_TAG = "EventDispatcher";
private final AccessibilityManagerService mAms;
private Context mContext;
// The receiver of motion events.
private EventStreamTransformation mReceiver;
// Keep track of which pointers sent to the system are down.
private int mInjectedPointersDown;
// The time of the last injected down.
private long mLastInjectedDownEventTime;
// The last injected hover event.
private MotionEvent mLastInjectedHoverEvent;
private TouchState mState;
EventDispatcher(
Context context,
AccessibilityManagerService ams,
EventStreamTransformation receiver,
TouchState state) {
mContext = context;
mAms = ams;
mReceiver = receiver;
mState = state;
}
public void setReceiver(EventStreamTransformation receiver) {
mReceiver = receiver;
}
/**
* Sends an event.
*
* @param prototype The prototype from which to create the injected events.
* @param action The action of the event.
* @param pointerIdBits The bits of the pointers to send.
* @param policyFlags The policy flags associated with the event.
*/
void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits, int policyFlags) {
prototype.setAction(action);
MotionEvent event = null;
if (pointerIdBits == ALL_POINTER_ID_BITS) {
event = prototype;
} else {
try {
event = prototype.split(pointerIdBits);
} catch (IllegalArgumentException e) {
Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e);
return;
}
}
if (action == MotionEvent.ACTION_DOWN) {
event.setDownTime(event.getEventTime());
} else {
event.setDownTime(getLastInjectedDownEventTime());
}
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;
// 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.
if (mReceiver != null) {
mReceiver.onMotionEvent(event, null, policyFlags);
} else {
Slog.e(LOG_TAG, "Error sending event: no receiver specified.");
}
updateState(event);
if (event != prototype) {
event.recycle();
}
}
/**
* Sends an accessibility event of the given type.
*
* @param type The event type.
*/
void sendAccessibilityEvent(int type) {
AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
if (accessibilityManager.isEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain(type);
event.setWindowId(mAms.getActiveWindowId());
accessibilityManager.sendAccessibilityEvent(event);
if (DEBUG) {
Slog.d(
LOG_TAG,
"Sending accessibility event" + AccessibilityEvent.eventTypeToString(type));
}
}
// Todo: get rid of this and have TouchState control the sending of events rather than react
// to it.
mState.onInjectedAccessibilityEvent(type);
}
/**
* Processes an injected {@link MotionEvent} event.
*
* @param event The event to process.
*/
void updateState(MotionEvent event) {
final int action = event.getActionMasked();
final int pointerId = event.getPointerId(event.getActionIndex());
final int pointerFlag = (1 << pointerId);
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
mInjectedPointersDown |= pointerFlag;
mLastInjectedDownEventTime = event.getDownTime();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mInjectedPointersDown &= ~pointerFlag;
if (mInjectedPointersDown == 0) {
mLastInjectedDownEventTime = 0;
}
break;
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_HOVER_EXIT:
if (mLastInjectedHoverEvent != null) {
mLastInjectedHoverEvent.recycle();
}
mLastInjectedHoverEvent = MotionEvent.obtain(event);
break;
}
if (DEBUG) {
Slog.i(LOG_TAG, "Injected pointer:\n" + toString());
}
}
/** Clears the internals state. */
public void clear() {
mInjectedPointersDown = 0;
}
/** @return The time of the last injected down event. */
public long getLastInjectedDownEventTime() {
return mLastInjectedDownEventTime;
}
/** @return The number of down pointers injected to the view hierarchy. */
public int getInjectedPointerDownCount() {
return Integer.bitCount(mInjectedPointersDown);
}
/** @return The bits of the injected pointers that are down. */
public int getInjectedPointersDown() {
return mInjectedPointersDown;
}
/**
* Whether an injected pointer is down.
*
* @param pointerId The unique pointer id.
* @return True if the pointer is down.
*/
public boolean isInjectedPointerDown(int pointerId) {
final int pointerFlag = (1 << pointerId);
return (mInjectedPointersDown & pointerFlag) != 0;
}
/** @return The the last injected hover event. */
public MotionEvent getLastInjectedHoverEvent() {
return mLastInjectedHoverEvent;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("=========================");
builder.append("\nDown pointers #");
builder.append(Integer.bitCount(mInjectedPointersDown));
builder.append(" [ ");
for (int i = 0; i < MAX_POINTER_COUNT; i++) {
if ((mInjectedPointersDown & i) != 0) {
builder.append(i);
builder.append(" ");
}
}
builder.append("]");
builder.append("\n=========================");
return builder.toString();
}
/**
* Computes the action for an injected event based on a masked action and a pointer index.
*
* @param actionMasked The masked action.
* @param pointerIndex The index of the pointer which has changed.
* @return The action to be used for injection.
*/
private int computeInjectionAction(int actionMasked, int pointerIndex) {
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
// Compute the action based on how many down pointers are injected.
if (getInjectedPointerDownCount() == 0) {
return MotionEvent.ACTION_DOWN;
} else {
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
| MotionEvent.ACTION_POINTER_DOWN;
}
case MotionEvent.ACTION_POINTER_UP:
// Compute the action based on how many down pointers are injected.
if (getInjectedPointerDownCount() == 1) {
return MotionEvent.ACTION_UP;
} else {
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
| MotionEvent.ACTION_POINTER_UP;
}
default:
return actionMasked;
}
}
/**
* Sends down events to the view hierarchy for all pointers which are not already being
* delivered i.e. pointers that are not yet injected.
*
* @param prototype The prototype from which to create the injected events.
* @param policyFlags The policy flags associated with the event.
*/
void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) {
// 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 (!isInjectedPointerDown(pointerId)) {
pointerIdBits |= (1 << pointerId);
final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
}
}
}
/**
* Sends up events to the view hierarchy for all pointers which are already being delivered i.e.
* pointers that are injected.
*
* @param prototype The prototype from which to create the injected events.
* @param policyFlags The policy flags associated with the event.
*/
void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
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 (!isInjectedPointerDown(pointerId)) {
continue;
}
pointerIdBits |= (1 << pointerId);
final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
}
}
}

View File

@@ -29,11 +29,11 @@ import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.BaseEventStreamTransformation;
import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.policy.WindowManagerPolicy;
import java.util.ArrayList;
@@ -61,7 +61,7 @@ import java.util.List;
public class TouchExplorer extends BaseEventStreamTransformation
implements AccessibilityGestureDetector.Listener {
private static final boolean DEBUG = false;
static final boolean DEBUG = false;
// Tag for logging received events.
private static final String LOG_TAG = "TouchExplorer";
@@ -109,8 +109,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
// Helper class to track received pointers.
private final TouchState.ReceivedPointerTracker mReceivedPointerTracker;
// Helper class to track injected pointers.
private final TouchState.InjectedPointerTracker mInjectedPointerTracker;
private final EventDispatcher mDispatcher;
// Handle to the accessibility manager service.
private final AccessibilityManagerService mAms;
@@ -148,7 +147,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
mAms = service;
mState = new TouchState();
mReceivedPointerTracker = mState.getReceivedPointerTracker();
mInjectedPointerTracker = mState.getInjectedPointerTracker();
mDispatcher = new EventDispatcher(context, mAms, super.getNext(), mState);
mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout();
mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
mHandler = new Handler(context.getMainLooper());
@@ -197,10 +196,10 @@ public class TouchExplorer extends BaseEventStreamTransformation
} else if (mState.isDragging()) {
mDraggingPointerId = INVALID_POINTER_ID;
// Send exit to all pointers that we have delivered.
sendUpForInjectedDownPointers(event, policyFlags);
mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
} else if (mState.isDelegating()) {
// Send exit to all pointers that we have delivered.
sendUpForInjectedDownPointers(event, policyFlags);
mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
} else if (mState.isGestureDetecting()) {
// No state specific cleanup required.
}
@@ -271,7 +270,8 @@ public class TouchExplorer extends BaseEventStreamTransformation
if (mSendTouchExplorationEndDelayed.isPending()
&& eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
mSendTouchExplorationEndDelayed.cancel();
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
mDispatcher.sendAccessibilityEvent(
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
}
// The event for touch interaction end should be strictly after the
@@ -279,7 +279,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
if (mSendTouchInteractionEndDelayed.isPending()
&& eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
mSendTouchInteractionEndDelayed.cancel();
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
}
super.onAccessibilityEvent(event);
}
@@ -318,7 +318,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
// Announce the end of a new touch interaction.
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
// Try to use the standard accessibility API to click
if (!mAms.performActionOnAccessibilityFocusedItem(
@@ -338,7 +338,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
mExitGestureDetectionModeDelayed.post();
// Send accessibility event to announce the start
// of gesture recognition.
sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
return false;
}
@@ -371,7 +371,8 @@ public class TouchExplorer extends BaseEventStreamTransformation
mSendHoverEnterAndMoveDelayed.addEvent(event);
mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
mSendHoverExitDelayed.cancel();
sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
mDispatcher.sendMotionEvent(
event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
return true;
}
}
@@ -417,7 +418,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
if (!mGestureDetector.firstTapDetected() && mState.isClear()) {
mSendTouchExplorationEndDelayed.forceSendAndRemove();
mSendTouchInteractionEndDelayed.forceSendAndRemove();
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
} else {
// Let gesture to handle to avoid duplicated TYPE_TOUCH_INTERACTION_END event.
mSendTouchInteractionEndDelayed.cancel();
@@ -536,18 +537,19 @@ public class TouchExplorer extends BaseEventStreamTransformation
mState.startDragging();
mDraggingPointerId = pointerId;
event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags());
sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
mDispatcher.sendMotionEvent(
event, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
} else {
// Two pointers moving arbitrary are delegated to the view hierarchy.
mState.startDelegating();
sendDownForAllNotInjectedPointers(event, policyFlags);
mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
}
break;
default:
// More than two pointers are delegated to the view hierarchy.
mState.startDelegating();
event = MotionEvent.obtainNoHistory(event);
sendDownForAllNotInjectedPointers(event, policyFlags);
mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
break;
}
}
@@ -585,7 +587,8 @@ public class TouchExplorer extends BaseEventStreamTransformation
case 1:
// Touch exploration.
sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
mDispatcher.sendMotionEvent(
event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
break;
case 2:
if (mSendHoverEnterAndMoveDelayed.isPending()) {
@@ -658,9 +661,10 @@ public class TouchExplorer extends BaseEventStreamTransformation
// goes down => delegate the three pointers to the view hierarchy
mState.startDelegating();
if (mDraggingPointerId != INVALID_POINTER_ID) {
sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
mDispatcher.sendMotionEvent(
event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
}
sendDownForAllNotInjectedPointers(event, policyFlags);
mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
} break;
case MotionEvent.ACTION_MOVE: {
if (mDraggingPointerId == INVALID_POINTER_ID) {
@@ -672,21 +676,12 @@ public class TouchExplorer extends BaseEventStreamTransformation
} break;
case 2: {
if (isDraggingGesture(event)) {
// Adjust event location to the middle location of the two pointers.
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 int pointerIndex = event.findPointerIndex(mDraggingPointerId);
final float deltaX =
(pointerIndex == 0) ? (secondPtrX - firstPtrX)
: (firstPtrX - secondPtrX);
final float deltaY =
(pointerIndex == 0) ? (secondPtrY - firstPtrY)
: (firstPtrY - secondPtrY);
event.offsetLocation(deltaX / 2, deltaY / 2);
// If still dragging send a drag event.
sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
adjustEventLocationForDrag(event);
mDispatcher.sendMotionEvent(
event,
MotionEvent.ACTION_MOVE,
pointerIdBits,
policyFlags);
} else {
// The two pointers are moving either in different directions or
@@ -695,20 +690,20 @@ public class TouchExplorer extends BaseEventStreamTransformation
// Remove move history before send injected non-move events
event = MotionEvent.obtainNoHistory(event);
// Send an event to the end of the drag gesture.
sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
policyFlags);
// Deliver all pointers to the view hierarchy.
sendDownForAllNotInjectedPointers(event, policyFlags);
mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
}
} break;
default: {
mState.startDelegating();
event = MotionEvent.obtainNoHistory(event);
// Send an event to the end of the drag gesture.
sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
policyFlags);
// Deliver all pointers to the view hierarchy.
sendDownForAllNotInjectedPointers(event, policyFlags);
mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
}
}
} break;
@@ -716,20 +711,22 @@ public class TouchExplorer extends BaseEventStreamTransformation
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);
// Send an event to the end of the drag gesture.
mDispatcher.sendMotionEvent(
event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
}
} break;
case MotionEvent.ACTION_UP: {
mAms.onTouchInteractionEnd();
// Announce the end of a new touch interaction.
sendAccessibilityEvent(
mDispatcher.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);
mDispatcher.sendMotionEvent(
event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
}
} break;
}
@@ -751,16 +748,18 @@ public class TouchExplorer extends BaseEventStreamTransformation
}
case MotionEvent.ACTION_UP: {
// Deliver the event.
sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
mDispatcher.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);
mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
} break;
default: {
// Deliver the event.
sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
// Deliver the event.
mDispatcher.sendMotionEvent(
event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
}
}
}
@@ -769,57 +768,15 @@ public class TouchExplorer extends BaseEventStreamTransformation
mAms.onTouchInteractionEnd();
// Announce the end of the gesture recognition.
sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
// Don't announce the end of a the touch interaction if users didn't lift their fingers.
if (interactionEnd) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
}
mExitGestureDetectionModeDelayed.cancel();
}
/**
* Sends an accessibility event of the given type.
*
* @param type The event type.
*/
private void sendAccessibilityEvent(int type) {
AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
if (accessibilityManager.isEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain(type);
event.setWindowId(mAms.getActiveWindowId());
accessibilityManager.sendAccessibilityEvent(event);
if (DEBUG) {
Slog.d(
LOG_TAG,
"Sending accessibility event" + AccessibilityEvent.eventTypeToString(type));
}
}
mState.onInjectedAccessibilityEvent(type);
}
/**
* Sends down events to the view hierarchy for all pointers which are
* not already being delivered i.e. pointers that are not yet injected.
*
* @param prototype The prototype from which to create the injected events.
* @param policyFlags The policy flags associated with the event.
*/
private void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) {
// 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 (!mInjectedPointerTracker.isInjectedPointerDown(pointerId)) {
pointerIdBits |= (1 << pointerId);
final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
}
}
}
/**
* Sends the exit events if needed. Such events are hover exit and touch explore
@@ -828,13 +785,14 @@ public class TouchExplorer extends BaseEventStreamTransformation
* @param policyFlags The policy flags associated with the event.
*/
private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) {
MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
MotionEvent event = mDispatcher.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);
mDispatcher.sendMotionEvent(
event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
}
}
@@ -845,115 +803,14 @@ public class TouchExplorer extends BaseEventStreamTransformation
* @param policyFlags The policy flags associated with the event.
*/
private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) {
MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
MotionEvent event = mDispatcher.getLastInjectedHoverEvent();
if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
final int pointerIdBits = event.getPointerIdBits();
sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
mDispatcher.sendMotionEvent(
event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
}
}
/**
* Sends up events to the view hierarchy for all pointers which are
* already being delivered i.e. pointers that are injected.
*
* @param prototype The prototype from which to create the injected events.
* @param policyFlags The policy flags associated with the event.
*/
private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
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 (!mInjectedPointerTracker.isInjectedPointerDown(pointerId)) {
continue;
}
pointerIdBits |= (1 << pointerId);
final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
}
}
/**
* Sends an event.
*
* @param prototype The prototype from which to create the injected events.
* @param action The action of the event.
* @param pointerIdBits The bits of the pointers to send.
* @param policyFlags The policy flags associated with the event.
*/
private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits,
int policyFlags) {
prototype.setAction(action);
MotionEvent event = null;
if (pointerIdBits == ALL_POINTER_ID_BITS) {
event = prototype;
} else {
try {
event = prototype.split(pointerIdBits);
} catch (IllegalArgumentException e) {
Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e);
return;
}
}
if (action == MotionEvent.ACTION_DOWN) {
event.setDownTime(event.getEventTime());
} else {
event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
}
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;
// 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.
super.onMotionEvent(event, null, policyFlags);
mInjectedPointerTracker.onMotionEvent(event);
if (event != prototype) {
event.recycle();
}
}
/**
* Computes the action for an injected event based on a masked action
* and a pointer index.
*
* @param actionMasked The masked action.
* @param pointerIndex The index of the pointer which has changed.
* @return The action to be used for injection.
*/
private int computeInjectionAction(int actionMasked, int pointerIndex) {
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
// Compute the action based on how many down pointers are injected.
if (mInjectedPointerTracker.getInjectedPointerDownCount() == 0) {
return MotionEvent.ACTION_DOWN;
} else {
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
| MotionEvent.ACTION_POINTER_DOWN;
}
}
case MotionEvent.ACTION_POINTER_UP: {
// Compute the action based on how many down pointers are injected.
if (mInjectedPointerTracker.getInjectedPointerDownCount() == 1) {
return MotionEvent.ACTION_UP;
} else {
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
| MotionEvent.ACTION_POINTER_UP;
}
}
default:
return actionMasked;
}
}
/**
* Determines whether a two pointer gesture is a dragging one.
@@ -978,10 +835,34 @@ public class TouchExplorer extends BaseEventStreamTransformation
MAX_DRAGGING_ANGLE_COS);
}
/**
* Adjust the location of an injected event when performing a drag The new location will be in
* between the two fingers touching the screen.
*/
private void adjustEventLocationForDrag(MotionEvent 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 int pointerIndex = event.findPointerIndex(mDraggingPointerId);
final float deltaX =
(pointerIndex == 0) ? (secondPtrX - firstPtrX) : (firstPtrX - secondPtrX);
final float deltaY =
(pointerIndex == 0) ? (secondPtrY - firstPtrY) : (firstPtrY - secondPtrY);
event.offsetLocation(deltaX / 2, deltaY / 2);
}
public TouchState getState() {
return mState;
}
@Override
public void setNext(EventStreamTransformation next) {
mDispatcher.setReceiver(next);
super.setNext(next);
}
/**
* Class for delayed exiting from gesture detecting mode.
*/
@@ -998,7 +879,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public void run() {
// Announce the end of gesture recognition.
sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
clear();
}
}
@@ -1055,11 +936,12 @@ public class TouchExplorer extends BaseEventStreamTransformation
public void run() {
// Send an accessibility event to announce the touch exploration start.
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
mDispatcher.sendAccessibilityEvent(
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
if (!mEvents.isEmpty()) {
// Deliver a down event.
sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER,
mDispatcher.sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER,
mPointerIdBits, mPolicyFlags);
if (DEBUG) {
Slog.d(LOG_TAG_SEND_HOVER_DELAYED,
@@ -1069,7 +951,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
// Deliver move events.
final int eventCount = mEvents.size();
for (int i = 1; i < eventCount; i++) {
sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE,
mDispatcher.sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE,
mPointerIdBits, mPolicyFlags);
if (DEBUG) {
Slog.d(LOG_TAG_SEND_HOVER_DELAYED,
@@ -1129,7 +1011,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event:"
+ " ACTION_HOVER_EXIT");
}
sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT,
mDispatcher.sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT,
mPointerIdBits, mPolicyFlags);
if (!mSendTouchExplorationEndDelayed.isPending()) {
mSendTouchExplorationEndDelayed.cancel();
@@ -1173,7 +1055,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public void run() {
sendAccessibilityEvent(mEventType);
mDispatcher.sendAccessibilityEvent(mEventType);
}
}

View File

@@ -18,6 +18,8 @@ package com.android.server.accessibility.gestures;
import static android.view.MotionEvent.INVALID_POINTER_ID;
import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
import android.annotation.IntDef;
import android.util.Slog;
import android.view.MotionEvent;
@@ -29,14 +31,12 @@ import android.view.accessibility.AccessibilityEvent;
* dispatch.
*/
public class TouchState {
private static final boolean DEBUG = false;
private static final String LOG_TAG = "TouchState";
// Pointer-related constants
// This constant captures the current implementation detail that
// pointer IDs are between 0 and 31 inclusive (subject to change).
// (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
private static final int MAX_POINTER_COUNT = 32;
static final int MAX_POINTER_COUNT = 32;
// Constant referring to the ids bits of all pointers.
public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
@@ -71,13 +71,9 @@ public class TouchState {
// Helper class to track received pointers.
// Todo: collapse or hide this class so multiple classes don't modify it.
private final ReceivedPointerTracker mReceivedPointerTracker;
// Helper class to track injected pointers.
// Todo: collapse or hide this class so multiple classes don't modify it.
private final InjectedPointerTracker mInjectedPointerTracker;
public TouchState() {
mReceivedPointerTracker = new ReceivedPointerTracker();
mInjectedPointerTracker = new InjectedPointerTracker();
}
/** Clears the internal shared state. */
@@ -85,16 +81,6 @@ public class TouchState {
setState(STATE_CLEAR);
// Reset the pointer trackers.
mReceivedPointerTracker.clear();
mInjectedPointerTracker.clear();
}
/**
* Updates the state in response to a hover event dispatched by TouchExplorer.
*
* @param event The event sent from TouchExplorer.
*/
public void onInjectedMotionEvent(MotionEvent event) {
mInjectedPointerTracker.onMotionEvent(event);
}
/**
@@ -226,117 +212,10 @@ public class TouchState {
}
}
public InjectedPointerTracker getInjectedPointerTracker() {
return mInjectedPointerTracker;
}
public ReceivedPointerTracker getReceivedPointerTracker() {
return mReceivedPointerTracker;
}
/** This class tracks the up/down state of each pointer. It does not track movement. */
class InjectedPointerTracker {
private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker";
// Keep track of which pointers sent to the system are down.
private int mInjectedPointersDown;
// The time of the last injected down.
private long mLastInjectedDownEventTime;
// The last injected hover event.
private MotionEvent mLastInjectedHoverEvent;
/**
* Processes an injected {@link MotionEvent} event.
*
* @param event The event to process.
*/
public void onMotionEvent(MotionEvent event) {
final int action = event.getActionMasked();
final int pointerId = event.getPointerId(event.getActionIndex());
final int pointerFlag = (1 << pointerId);
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
mInjectedPointersDown |= pointerFlag;
mLastInjectedDownEventTime = event.getDownTime();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mInjectedPointersDown &= ~pointerFlag;
if (mInjectedPointersDown == 0) {
mLastInjectedDownEventTime = 0;
}
break;
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_HOVER_EXIT:
if (mLastInjectedHoverEvent != null) {
mLastInjectedHoverEvent.recycle();
}
mLastInjectedHoverEvent = MotionEvent.obtain(event);
break;
}
if (DEBUG) {
Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString());
}
}
/** Clears the internals state. */
public void clear() {
mInjectedPointersDown = 0;
}
/** @return The time of the last injected down event. */
public long getLastInjectedDownEventTime() {
return mLastInjectedDownEventTime;
}
/** @return The number of down pointers injected to the view hierarchy. */
public int getInjectedPointerDownCount() {
return Integer.bitCount(mInjectedPointersDown);
}
/** @return The bits of the injected pointers that are down. */
public int getInjectedPointersDown() {
return mInjectedPointersDown;
}
/**
* Whether an injected pointer is down.
*
* @param pointerId The unique pointer id.
* @return True if the pointer is down.
*/
public boolean isInjectedPointerDown(int pointerId) {
final int pointerFlag = (1 << pointerId);
return (mInjectedPointersDown & pointerFlag) != 0;
}
/** @return The the last injected hover event. */
public MotionEvent getLastInjectedHoverEvent() {
return mLastInjectedHoverEvent;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("=========================");
builder.append("\nDown pointers #");
builder.append(Integer.bitCount(mInjectedPointersDown));
builder.append(" [ ");
for (int i = 0; i < MAX_POINTER_COUNT; i++) {
if ((mInjectedPointersDown & i) != 0) {
builder.append(i);
builder.append(" ");
}
}
builder.append("]");
builder.append("\n=========================");
return builder.toString();
}
}
/** This class tracks where and when a pointer went down. It does not track its movement. */
class ReceivedPointerTracker {
private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";