blob: 667364c9c90196f9f11d37cca97d3e248d686ce0 [file] [log] [blame]
/*
* 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 rawEvent The original event prior to magnification or other transformations.
* @param pointerIdBits The bits of the pointers to send.
* @param policyFlags The policy flags associated with the event.
*/
void sendMotionEvent(
MotionEvent prototype,
int action,
MotionEvent rawEvent,
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;
if (mReceiver != null) {
mReceiver.onMotionEvent(event, rawEvent, 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,
mState.getLastReceivedEvent(),
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 = prototype.getPointerIdBits();
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;
}
final int action = computeInjectionAction(MotionEvent.ACTION_POINTER_UP, i);
sendMotionEvent(
prototype, action, mState.getLastReceivedEvent(), pointerIdBits, policyFlags);
pointerIdBits &= ~(1 << pointerId);
}
}
}