Add MotionEvent.HOVER_ENTER and HOVER_EXIT.

The input dispatcher sends a HOVER_ENTER to a window before dispatching
it any HOVER_MOVE events.  For compatibility reasons, the window will
*also* receive the HOVER_MOVE.  When the pointer moves into a different
window or the pointer goes down or when events are canceled for some reason,
the input dispatcher sends a HOVER_EXIT to the previously hovered window.

The view hierarchy behavior is similar.  All views under the pointer
receive onHoverEvent with HOVER_ENTER followed by any number of HOVER_MOVE
events.  When the pointer leaves a view, the view receives HOVER_EXIT.
Similarly, if a parent view decides to capture hover by returning true
from onHoverEvent, the hovered descendants will receive HOVER_EXIT.

The default behavior of onHoverEvent is to update the view's hovered
state by calling setHovered(true/false).  Views can query their current
hovered state using isHovered().

For testing purposes, the hovered state is mapped to the pressed
drawable state.  This will change in a subsequent commit with the
introduction of a new hovered drawable state.

Change-Id: Ib76a7a90236c8f2c7336e55773acade6346cacbe
This commit is contained in:
Jeff Brown
2011-03-07 16:56:21 -08:00
parent e9f66af90a
commit a032cc0086
9 changed files with 730 additions and 136 deletions

View File

@@ -218638,6 +218638,28 @@
visibility="public"
>
</field>
<field name="ACTION_HOVER_ENTER"
type="int"
transient="false"
volatile="false"
value="9"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="ACTION_HOVER_EXIT"
type="int"
transient="false"
volatile="false"
value="10"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
<field name="ACTION_HOVER_MOVE"
type="int"
transient="false"
@@ -223461,6 +223483,17 @@
visibility="public"
>
</method>
<method name="isHovered"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="isInEditMode"
return="boolean"
abstract="false"
@@ -223936,6 +223969,19 @@
<parameter name="event" type="android.view.MotionEvent">
</parameter>
</method>
<method name="onHoverEvent"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="event" type="android.view.MotionEvent">
</parameter>
</method>
<method name="onKeyDown"
return="boolean"
abstract="false"
@@ -224976,6 +225022,19 @@
<parameter name="horizontalScrollBarEnabled" type="boolean">
</parameter>
</method>
<method name="setHovered"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="hovered" type="boolean">
</parameter>
</method>
<method name="setId"
return="void"
abstract="false"

View File

@@ -172,6 +172,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* recent point, as well as any intermediate points since the last
* hover move event.
* <p>
* This action is always delivered to the window or view under the pointer.
* </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
@@ -184,8 +186,9 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* vertical and/or horizontal scroll offsets. Use {@link #getAxisValue(int)}
* to retrieve the information from {@link #AXIS_VSCROLL} and {@link #AXIS_HSCROLL}.
* The pointer may or may not be down when this event is dispatched.
* This action is always delivered to the winder under the pointer, which
* may not be the window currently touched.
* <p></p>
* This action is always delivered to the window or view under the pointer, which
* may not be the window or view currently touched.
* <p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
@@ -194,6 +197,32 @@ public final class MotionEvent extends InputEvent implements Parcelable {
*/
public static final int ACTION_SCROLL = 8;
/**
* Constant for {@link #getAction}: The pointer is not down but has entered the
* boundaries of a window or view.
* <p>
* This action is always delivered to the window or view under the pointer.
* </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
* </p>
*/
public static final int ACTION_HOVER_ENTER = 9;
/**
* Constant for {@link #getAction}: The pointer is not down but has exited the
* boundaries of a window or view.
* <p>
* This action is always delivered to the window or view that was previously under the pointer.
* </p><p>
* This action is not a touch event so it is delivered to
* {@link View#onGenericMotionEvent(MotionEvent)} rather than
* {@link View#onTouchEvent(MotionEvent)}.
* </p>
*/
public static final int ACTION_HOVER_EXIT = 10;
/**
* Bits in the action code that represent a pointer index, used with
* {@link #ACTION_POINTER_DOWN} and {@link #ACTION_POINTER_UP}. Shifting
@@ -1354,9 +1383,9 @@ public final class MotionEvent extends InputEvent implements Parcelable {
/**
* Returns true if this motion event is a touch event.
* <p>
* Specifically excludes pointer events with action {@link #ACTION_HOVER_MOVE}
* or {@link #ACTION_SCROLL} because they are not actually touch events
* (the pointer is not down).
* Specifically excludes pointer events with action {@link #ACTION_HOVER_MOVE},
* {@link #ACTION_HOVER_ENTER}, {@link #ACTION_HOVER_EXIT}, or {@link #ACTION_SCROLL}
* because they are not actually touch events (the pointer is not down).
* </p>
* @return True if this motion event is a touch event.
* @hide
@@ -2313,6 +2342,10 @@ public final class MotionEvent extends InputEvent implements Parcelable {
return "ACTION_HOVER_MOVE";
case ACTION_SCROLL:
return "ACTION_SCROLL";
case ACTION_HOVER_ENTER:
return "ACTION_HOVER_ENTER";
case ACTION_HOVER_EXIT:
return "ACTION_HOVER_EXIT";
}
int index = (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
switch (action & ACTION_MASK) {

View File

@@ -1622,6 +1622,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
/**
* Indicates that the view has received HOVER_ENTER. Cleared on HOVER_EXIT.
* @hide
*/
private static final int HOVERED = 0x10000000;
/**
* Indicates that pivotX or pivotY were explicitly set and we should not assume the center
* for transform operations
@@ -4643,22 +4649,80 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* <p>
* Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER}
* are delivered to the view under the pointer. All other generic motion events are
* delivered to the focused view.
* delivered to the focused view. Hover events are handled specially and are delivered
* to {@link #onHoverEvent}.
* </p>
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchGenericMotionEvent(MotionEvent event) {
final int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
final int action = event.getAction();
if (action == MotionEvent.ACTION_HOVER_ENTER
|| action == MotionEvent.ACTION_HOVER_MOVE
|| action == MotionEvent.ACTION_HOVER_EXIT) {
if (dispatchHoverEvent(event)) {
return true;
}
} else if (dispatchGenericPointerEvent(event)) {
return true;
}
} else if (dispatchGenericFocusedEvent(event)) {
return true;
}
//noinspection SimplifiableIfStatement
if (mOnGenericMotionListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& mOnGenericMotionListener.onGenericMotion(this, event)) {
return true;
}
return onGenericMotionEvent(event);
}
/**
* Dispatch a hover event.
* <p>
* Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead.
* </p>
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
* @hide
*/
protected boolean dispatchHoverEvent(MotionEvent event) {
return onHoverEvent(event);
}
/**
* Dispatch a generic motion event to the view under the first pointer.
* <p>
* Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead.
* </p>
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
* @hide
*/
protected boolean dispatchGenericPointerEvent(MotionEvent event) {
return false;
}
/**
* Dispatch a generic motion event to the currently focused view.
* <p>
* Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead.
* </p>
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
* @hide
*/
protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
return false;
}
/**
* Dispatch a pointer event.
* <p>
@@ -5223,14 +5287,91 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* </code>
*
* @param event The generic motion event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
* @return True if the event was handled, false otherwise.
*/
public boolean onGenericMotionEvent(MotionEvent event) {
return false;
}
/**
* Implement this method to handle hover events.
* <p>
* Hover events are pointer events with action {@link MotionEvent#ACTION_HOVER_ENTER},
* {@link MotionEvent#ACTION_HOVER_MOVE}, or {@link MotionEvent#ACTION_HOVER_EXIT}.
* </p><p>
* The view receives hover enter as the pointer enters the bounds of the view and hover
* exit as the pointer exits the bound of the view or just before the pointer goes down
* (which implies that {@link #onTouchEvent} will be called soon).
* </p><p>
* If the view would like to handle the hover event itself and prevent its children
* from receiving hover, it should return true from this method. If this method returns
* true and a child has already received a hover enter event, the child will
* automatically receive a hover exit event.
* </p><p>
* The default implementation sets the hovered state of the view if the view is
* clickable.
* </p>
*
* @param event The motion event that describes the hover.
* @return True if this view handled the hover event and does not want its children
* to receive the hover event.
*/
public boolean onHoverEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if (((viewFlags & CLICKABLE) != CLICKABLE &&
(viewFlags & LONG_CLICKABLE) != LONG_CLICKABLE)) {
// Nothing to do if the view is not clickable.
return false;
}
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the hover events, it just doesn't
// respond to them.
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_ENTER:
setHovered(true);
break;
case MotionEvent.ACTION_HOVER_EXIT:
setHovered(false);
break;
}
return true;
}
/**
* Returns true if the view is currently hovered.
*
* @return True if the view is currently hovered.
*/
public boolean isHovered() {
return (mPrivateFlags & HOVERED) != 0;
}
/**
* Sets whether the view is currently hovered.
*
* @param hovered True if the view is hovered.
*/
public void setHovered(boolean hovered) {
if (hovered) {
if ((mPrivateFlags & HOVERED) == 0) {
mPrivateFlags |= HOVERED;
refreshDrawableState();
}
} else {
if ((mPrivateFlags & HOVERED) != 0) {
mPrivateFlags &= ~HOVERED;
refreshDrawableState();
}
}
}
/**
* Implement this method to handle touch screen motion events.
*
@@ -9877,6 +10018,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
// windows to better match their app.
viewStateIndex |= VIEW_STATE_ACCELERATED;
}
if ((privateFlags & HOVERED) != 0) viewStateIndex |= VIEW_STATE_PRESSED; // temporary
drawableState = VIEW_STATE_SETS[viewStateIndex];

View File

@@ -147,6 +147,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@ViewDebug.ExportedProperty(category = "events")
private float mLastTouchDownY;
// Child which last received ACTION_HOVER_ENTER and ACTION_HOVER_MOVE.
private View mHoveredChild;
/**
* Internal flags.
*
@@ -1140,13 +1143,50 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return false;
}
/**
* {@inheritDoc}
*/
/** @hide */
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
// Send the event to the child under the pointer.
protected boolean dispatchHoverEvent(MotionEvent event) {
// Send the hover enter or hover move event to the view group first.
// If it handles the event then a hovered child should receive hover exit.
boolean handled = false;
final boolean interceptHover;
final int action = event.getAction();
if (action == MotionEvent.ACTION_HOVER_EXIT) {
interceptHover = true;
} else {
handled = super.dispatchHoverEvent(event);
interceptHover = handled;
}
// Send successive hover events to the hovered child as long as the pointer
// remains within the child's bounds.
MotionEvent eventNoHistory = event;
if (mHoveredChild != null) {
final float x = event.getX();
final float y = event.getY();
if (interceptHover
|| !isTransformedTouchPointInView(x, y, mHoveredChild, null)) {
// Pointer exited the child.
// Send it a hover exit with only the most recent coordinates. We could
// try to find the exact point in history when the pointer left the view
// but it is not worth the effort.
eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, mHoveredChild);
eventNoHistory.setAction(action);
mHoveredChild = null;
} else if (action == MotionEvent.ACTION_HOVER_MOVE) {
// Pointer is still within the child.
handled |= dispatchTransformedGenericPointerEvent(event, mHoveredChild);
}
}
// Find a new hovered child if needed.
if (!interceptHover && mHoveredChild == null
&& (action == MotionEvent.ACTION_HOVER_ENTER
|| action == MotionEvent.ACTION_HOVER_MOVE)) {
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final View[] children = mChildren;
@@ -1155,51 +1195,121 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE
&& child.getAnimation() == null) {
// Skip invisible child unless it is animating.
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
if (!isTransformedTouchPointInView(x, y, child, null)) {
// Scroll point is out of child's bounds.
continue;
}
// Found the hovered child.
mHoveredChild = child;
if (action == MotionEvent.ACTION_HOVER_MOVE) {
// Pointer was moving within the view group and entered the child.
// Send it a hover enter and hover move with only the most recent
// coordinates. We could try to find the exact point in history when
// the pointer entered the view but it is not worth the effort.
eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child);
eventNoHistory.setAction(action);
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
final boolean handled;
if (!child.hasIdentityMatrix()) {
MotionEvent transformedEvent = MotionEvent.obtain(event);
transformedEvent.offsetLocation(offsetX, offsetY);
transformedEvent.transform(child.getInverseMatrix());
handled = child.dispatchGenericMotionEvent(transformedEvent);
transformedEvent.recycle();
} else {
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchGenericMotionEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
if (handled) {
return true;
handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child);
} else { /* must be ACTION_HOVER_ENTER */
// Pointer entered the child.
handled |= dispatchTransformedGenericPointerEvent(event, child);
}
break;
}
}
// No child handled the event. Send it to this view group.
return super.dispatchGenericMotionEvent(event);
}
// Recycle the copy of the event that we made.
if (eventNoHistory != event) {
eventNoHistory.recycle();
}
// Send hover exit to the view group. If there was a child, we will already have
// sent the hover exit to it.
if (action == MotionEvent.ACTION_HOVER_EXIT) {
handled |= super.dispatchHoverEvent(event);
}
// Done.
return handled;
}
private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) {
if (event.getHistorySize() == 0) {
return event;
}
return MotionEvent.obtainNoHistory(event);
}
/** @hide */
@Override
protected boolean dispatchGenericPointerEvent(MotionEvent event) {
// Send the event to the child under the pointer.
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final View[] children = mChildren;
final float x = event.getX();
final float y = event.getY();
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
if (dispatchTransformedGenericPointerEvent(event, child)) {
return true;
}
}
}
// No child handled the event. Send it to this view group.
return super.dispatchGenericPointerEvent(event);
}
/** @hide */
@Override
protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
// Send the event to the focused child or to this view group if it has focus.
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
return super.dispatchGenericMotionEvent(event);
return super.dispatchGenericFocusedEvent(event);
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
return mFocused.dispatchGenericMotionEvent(event);
}
return false;
}
/**
* Dispatches a generic pointer event to a child, taking into account
* transformations that apply to the child.
*
* @param event The event to send.
* @param child The view to send the event to.
* @return {@code true} if the child handled the event.
*/
private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
boolean handled;
if (!child.hasIdentityMatrix()) {
MotionEvent transformedEvent = MotionEvent.obtain(event);
transformedEvent.offsetLocation(offsetX, offsetY);
transformedEvent.transform(child.getInverseMatrix());
handled = child.dispatchGenericMotionEvent(transformedEvent);
transformedEvent.recycle();
} else {
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchGenericMotionEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
/**
* {@inheritDoc}
*/
@@ -1213,8 +1323,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
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.
@@ -1268,14 +1377,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE
&& child.getAnimation() == null) {
// Skip invisible child unless it is animating.
continue;
}
if (!isTransformedTouchPointInView(x, y, child, null)) {
// New pointer is out of child's bounds.
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
@@ -1475,6 +1578,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
/**
* Returns true if a child view can receive pointer events.
* @hide
*/
private static boolean canViewReceivePointerEvents(View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
/**
* Returns true if a child view contains the specified point when transformed
* into its coordinate space.
@@ -3244,6 +3356,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mTransition.removeChild(this, view);
}
if (view == mHoveredChild) {
mHoveredChild = null;
}
boolean clearChildFocus = false;
if (view == mFocused) {
view.clearFocusForRemoval();
@@ -3307,6 +3423,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final OnHierarchyChangeListener onHierarchyChangeListener = mOnHierarchyChangeListener;
final boolean notifyListener = onHierarchyChangeListener != null;
final View focused = mFocused;
final View hoveredChild = mHoveredChild;
final boolean detach = mAttachInfo != null;
View clearChildFocus = null;
@@ -3320,6 +3437,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mTransition.removeChild(this, view);
}
if (view == hoveredChild) {
mHoveredChild = null;
}
if (view == focused) {
view.clearFocusForRemoval();
clearChildFocus = view;
@@ -3377,6 +3498,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final OnHierarchyChangeListener listener = mOnHierarchyChangeListener;
final boolean notify = listener != null;
final View focused = mFocused;
final View hoveredChild = mHoveredChild;
final boolean detach = mAttachInfo != null;
View clearChildFocus = null;
@@ -3389,6 +3511,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mTransition.removeChild(this, view);
}
if (view == hoveredChild) {
mHoveredChild = null;
}
if (view == focused) {
view.clearFocusForRemoval();
clearChildFocus = view;

View File

@@ -357,6 +357,12 @@ public class PointerLocationView extends View {
case MotionEvent.ACTION_HOVER_MOVE:
prefix = "HOVER MOVE";
break;
case MotionEvent.ACTION_HOVER_ENTER:
prefix = "HOVER ENTER";
break;
case MotionEvent.ACTION_HOVER_EXIT:
prefix = "HOVER EXIT";
break;
case MotionEvent.ACTION_SCROLL:
prefix = "SCROLL";
break;

View File

@@ -297,6 +297,14 @@ enum {
* may not be the window currently touched.
*/
AMOTION_EVENT_ACTION_SCROLL = 8,
/* The pointer is not down but has entered the boundaries of a window or view.
*/
AMOTION_EVENT_ACTION_HOVER_ENTER = 9,
/* The pointer is not down but has exited the boundaries of a window or view.
*/
AMOTION_EVENT_ACTION_HOVER_EXIT = 10,
};
/*

View File

@@ -48,6 +48,9 @@
// Log debug messages about the app switch latency optimization.
#define DEBUG_APP_SWITCH 0
// Log debug messages about hover events.
#define DEBUG_HOVER 0
#include "InputDispatcher.h"
#include <cutils/log.h>
@@ -115,7 +118,9 @@ static bool isValidMotionAction(int32_t action, size_t pointerCount) {
case AMOTION_EVENT_ACTION_CANCEL:
case AMOTION_EVENT_ACTION_MOVE:
case AMOTION_EVENT_ACTION_OUTSIDE:
case AMOTION_EVENT_ACTION_HOVER_ENTER:
case AMOTION_EVENT_ACTION_HOVER_MOVE:
case AMOTION_EVENT_ACTION_HOVER_EXIT:
case AMOTION_EVENT_ACTION_SCROLL:
return true;
case AMOTION_EVENT_ACTION_POINTER_DOWN:
@@ -185,7 +190,8 @@ InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& polic
mFocusedWindow(NULL),
mFocusedApplication(NULL),
mCurrentInputTargetsValid(false),
mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE),
mLastHoverWindow(NULL) {
mLooper = new Looper(false);
mInboundQueue.headSentinel.refCount = -1;
@@ -837,10 +843,11 @@ bool InputDispatcher::dispatchMotionLocked(
bool conflictingPointerActions = false;
if (! mCurrentInputTargetsValid) {
int32_t injectionResult;
const MotionSample* splitBatchAfterSample = NULL;
if (isPointerEvent) {
// Pointer event. (eg. touchscreen)
injectionResult = findTouchedWindowTargetsLocked(currentTime,
entry, nextWakeupTime, &conflictingPointerActions);
entry, nextWakeupTime, &conflictingPointerActions, &splitBatchAfterSample);
} else {
// Non touch event. (eg. trackball)
injectionResult = findFocusedWindowTargetsLocked(currentTime,
@@ -857,6 +864,41 @@ bool InputDispatcher::dispatchMotionLocked(
addMonitoringTargetsLocked();
commitTargetsLocked();
// Unbatch the event if necessary by splitting it into two parts after the
// motion sample indicated by splitBatchAfterSample.
if (splitBatchAfterSample && splitBatchAfterSample->next) {
#if DEBUG_BATCHING
uint32_t originalSampleCount = entry->countSamples();
#endif
MotionSample* nextSample = splitBatchAfterSample->next;
MotionEntry* nextEntry = mAllocator.obtainMotionEntry(nextSample->eventTime,
entry->deviceId, entry->source, entry->policyFlags,
entry->action, entry->flags, entry->metaState, entry->edgeFlags,
entry->xPrecision, entry->yPrecision, entry->downTime,
entry->pointerCount, entry->pointerIds, nextSample->pointerCoords);
if (nextSample != entry->lastSample) {
nextEntry->firstSample.next = nextSample->next;
nextEntry->lastSample = entry->lastSample;
}
mAllocator.freeMotionSample(nextSample);
entry->lastSample = const_cast<MotionSample*>(splitBatchAfterSample);
entry->lastSample->next = NULL;
if (entry->injectionState) {
nextEntry->injectionState = entry->injectionState;
entry->injectionState->refCount += 1;
}
#if DEBUG_BATCHING
LOGD("Split batch of %d samples into two parts, first part has %d samples, "
"second part has %d samples.", originalSampleCount,
entry->countSamples(), nextEntry->countSamples());
#endif
mInboundQueue.enqueueAtHead(nextEntry);
}
}
// Dispatch the motion.
@@ -1107,7 +1149,8 @@ int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
// Success! Output targets.
injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_FOREGROUND, BitSet32(0));
addWindowTargetLocked(mFocusedWindow,
InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0));
// Done.
Failed:
@@ -1124,7 +1167,8 @@ Unresponsive:
}
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
const MotionEntry* entry, nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) {
const MotionEntry* entry, nsecs_t* nextWakeupTime, bool* outConflictingPointerActions,
const MotionSample** outSplitBatchAfterSample) {
enum InjectionPermission {
INJECTION_PERMISSION_UNKNOWN,
INJECTION_PERMISSION_GRANTED,
@@ -1167,14 +1211,19 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
// Update the touch state as needed based on the properties of the touch event.
int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING;
InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN;
const InputWindow* newHoverWindow = NULL;
bool isSplit = mTouchState.split;
bool wrongDevice = mTouchState.down
&& (mTouchState.deviceId != entry->deviceId
|| mTouchState.source != entry->source);
if (maskedAction == AMOTION_EVENT_ACTION_DOWN
|| maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
|| maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
|| maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
|| maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN
|| maskedAction == AMOTION_EVENT_ACTION_SCROLL
|| isHoverAction);
if (newGesture) {
bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
if (wrongDevice && !down) {
mTempTouchState.copyFrom(mTouchState);
@@ -1197,19 +1246,18 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
goto Failed;
}
if (maskedAction == AMOTION_EVENT_ACTION_DOWN
|| (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)
|| maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
|| maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
/* Case 1: New splittable pointer going down, or need target for hover or scroll. */
const MotionSample* sample = &entry->firstSample;
int32_t pointerIndex = getMotionEventActionPointerIndex(action);
int32_t x = int32_t(entry->firstSample.pointerCoords[pointerIndex].
int32_t x = int32_t(sample->pointerCoords[pointerIndex].
getAxisValue(AMOTION_EVENT_AXIS_X));
int32_t y = int32_t(entry->firstSample.pointerCoords[pointerIndex].
int32_t y = int32_t(sample->pointerCoords[pointerIndex].
getAxisValue(AMOTION_EVENT_AXIS_Y));
const InputWindow* newTouchedWindow = NULL;
const InputWindow* topErrorWindow = NULL;
bool isTouchModal = false;
// Traverse windows from front to back to find touched window and outside targets.
size_t numWindows = mWindows.size();
@@ -1225,7 +1273,7 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
if (window->visible) {
if (! (flags & InputWindow::FLAG_NOT_TOUCHABLE)) {
bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE
isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE
| InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0;
if (isTouchModal || window->touchableRegionContainsPoint(x, y)) {
if (! screenWasOff || flags & InputWindow::FLAG_TOUCHABLE_WHEN_WAKING) {
@@ -1237,7 +1285,7 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
if (maskedAction == AMOTION_EVENT_ACTION_DOWN
&& (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH)) {
int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE;
int32_t outsideTargetFlags = InputTarget::FLAG_DISPATCH_AS_OUTSIDE;
if (isWindowObscuredAtPointLocked(window, x, y)) {
outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
}
@@ -1290,7 +1338,7 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
}
// Set target flags.
int32_t targetFlags = InputTarget::FLAG_FOREGROUND;
int32_t targetFlags = InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS;
if (isSplit) {
targetFlags |= InputTarget::FLAG_SPLIT;
}
@@ -1298,6 +1346,28 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
}
// Update hover state.
if (isHoverAction) {
newHoverWindow = newTouchedWindow;
// Ensure all subsequent motion samples are also within the touched window.
// Set *outSplitBatchAfterSample to the sample before the first one that is not
// within the touched window.
if (!isTouchModal) {
while (sample->next) {
if (!newHoverWindow->touchableRegionContainsPoint(
sample->next->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X),
sample->next->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y))) {
*outSplitBatchAfterSample = sample;
break;
}
sample = sample->next;
}
}
} else if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
newHoverWindow = mLastHoverWindow;
}
// Update the temporary touch state.
BitSet32 pointerIds;
if (isSplit) {
@@ -1319,6 +1389,29 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
}
}
if (newHoverWindow != mLastHoverWindow) {
// Split the batch here so we send exactly one sample as part of ENTER or EXIT.
*outSplitBatchAfterSample = &entry->firstSample;
// Let the previous window know that the hover sequence is over.
if (mLastHoverWindow) {
#if DEBUG_HOVER
LOGD("Sending hover exit event to window %s.", mLastHoverWindow->name.string());
#endif
mTempTouchState.addOrUpdateWindow(mLastHoverWindow,
InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT, BitSet32(0));
}
// Let the new window know that the hover sequence is starting.
if (newHoverWindow) {
#if DEBUG_HOVER
LOGD("Sending hover enter event to window %s.", newHoverWindow->name.string());
#endif
mTempTouchState.addOrUpdateWindow(newHoverWindow,
InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER, BitSet32(0));
}
}
// Check permission to inject into all touched foreground windows and ensure there
// is at least one touched foreground window.
{
@@ -1385,7 +1478,9 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
const InputWindow* window = & mWindows[i];
if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) {
mTempTouchState.addOrUpdateWindow(window,
InputTarget::FLAG_WINDOW_IS_OBSCURED, BitSet32(0));
InputTarget::FLAG_WINDOW_IS_OBSCURED
| InputTarget::FLAG_DISPATCH_AS_IS,
BitSet32(0));
}
}
}
@@ -1400,8 +1495,9 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
touchedWindow.pointerIds);
}
// Drop the outside touch window since we will not care about them in the next iteration.
mTempTouchState.removeOutsideTouchWindows();
// Drop the outside or hover touch windows since we will not care about them
// in the next iteration.
mTempTouchState.filterNonAsIsTouchWindows();
Failed:
// Check injection permission once and for all.
@@ -1418,7 +1514,7 @@ Failed:
if (!wrongDevice) {
if (maskedAction == AMOTION_EVENT_ACTION_UP
|| maskedAction == AMOTION_EVENT_ACTION_CANCEL
|| maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
|| isHoverAction) {
// All pointers up or canceled.
mTouchState.reset();
} else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
@@ -1455,6 +1551,9 @@ Failed:
// Save changes to touch state as-is for all other actions.
mTouchState.copyFrom(mTempTouchState);
}
// Update hover state.
mLastHoverWindow = newHoverWindow;
}
} else {
#if DEBUG_FOCUS
@@ -1720,10 +1819,36 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
}
}
// Enqueue dispatch entries for the requested modes.
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_IS);
// If the outbound queue was previously empty, start the dispatch cycle going.
if (wasEmpty) {
activateConnectionLocked(connection.get());
startDispatchCycleLocked(currentTime, connection);
}
}
void InputDispatcher::enqueueDispatchEntryLocked(
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
bool resumeWithAppendedMotionSample, int32_t dispatchMode) {
int32_t inputTargetFlags = inputTarget->flags;
if (!(inputTargetFlags & dispatchMode)) {
return;
}
inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;
// This is a new event.
// Enqueue a new dispatch entry onto the outbound queue for this connection.
DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref
inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset);
inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset);
if (dispatchEntry->hasForegroundTarget()) {
incrementPendingForegroundDispatchesLocked(eventEntry);
}
@@ -1744,12 +1869,6 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
// Enqueue the dispatch entry.
connection->outboundQueue.enqueueAtTail(dispatchEntry);
// If the outbound queue was previously empty, start the dispatch cycle going.
if (wasEmpty) {
activateConnectionLocked(connection.get());
startDispatchCycleLocked(currentTime, connection);
}
}
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
@@ -1768,12 +1887,9 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
// Mark the dispatch entry as in progress.
dispatchEntry->inProgress = true;
// Update the connection's input state.
EventEntry* eventEntry = dispatchEntry->eventEntry;
connection->inputState.trackEvent(eventEntry);
// Publish the event.
status_t status;
EventEntry* eventEntry = dispatchEntry->eventEntry;
switch (eventEntry->type) {
case EventEntry::TYPE_KEY: {
KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
@@ -1782,6 +1898,9 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
int32_t action = keyEntry->action;
int32_t flags = keyEntry->flags;
// Update the connection's input state.
connection->inputState.trackKey(keyEntry, action);
// Publish the key event.
status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source,
action, flags, keyEntry->keyCode, keyEntry->scanCode,
@@ -1803,8 +1922,12 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
// Apply target flags.
int32_t action = motionEntry->action;
int32_t flags = motionEntry->flags;
if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) {
if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
action = AMOTION_EVENT_ACTION_OUTSIDE;
} else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
action = AMOTION_EVENT_ACTION_HOVER_EXIT;
} else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
action = AMOTION_EVENT_ACTION_HOVER_ENTER;
}
if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
@@ -1829,6 +1952,9 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
yOffset = 0.0f;
}
// Update the connection's input state.
connection->inputState.trackMotion(motionEntry, action);
// Publish the motion event and the first motion sample.
status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState,
@@ -1845,31 +1971,34 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
return;
}
// Append additional motion samples.
MotionSample* nextMotionSample = firstMotionSample->next;
for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) {
status = connection->inputPublisher.appendMotionSample(
nextMotionSample->eventTime, nextMotionSample->pointerCoords);
if (status == NO_MEMORY) {
if (action == AMOTION_EVENT_ACTION_MOVE
|| action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
// Append additional motion samples.
MotionSample* nextMotionSample = firstMotionSample->next;
for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) {
status = connection->inputPublisher.appendMotionSample(
nextMotionSample->eventTime, nextMotionSample->pointerCoords);
if (status == NO_MEMORY) {
#if DEBUG_DISPATCH_CYCLE
LOGD("channel '%s' ~ Shared memory buffer full. Some motion samples will "
"be sent in the next dispatch cycle.",
connection->getInputChannelName());
#endif
break;
break;
}
if (status != OK) {
LOGE("channel '%s' ~ Could not append motion sample "
"for a reason other than out of memory, status=%d",
connection->getInputChannelName(), status);
abortBrokenDispatchCycleLocked(currentTime, connection);
return;
}
}
if (status != OK) {
LOGE("channel '%s' ~ Could not append motion sample "
"for a reason other than out of memory, status=%d",
connection->getInputChannelName(), status);
abortBrokenDispatchCycleLocked(currentTime, connection);
return;
}
}
// Remember the next motion sample that we could not dispatch, in case we ran out
// of space in the shared memory buffer.
dispatchEntry->tailMotionSample = nextMotionSample;
// Remember the next motion sample that we could not dispatch, in case we ran out
// of space in the shared memory buffer.
dispatchEntry->tailMotionSample = nextMotionSample;
}
break;
}
@@ -2199,6 +2328,11 @@ InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet
splitPointerCoords);
}
if (originalMotionEntry->injectionState) {
splitMotionEntry->injectionState = originalMotionEntry->injectionState;
splitMotionEntry->injectionState->refCount += 1;
}
return splitMotionEntry;
}
@@ -2408,6 +2542,29 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t
continue;
}
if (action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
if (!mLastHoverWindow) {
#if DEBUG_BATCHING
LOGD("Not streaming hover move because there is no "
"last hovered window.");
#endif
goto NoBatchingOrStreaming;
}
const InputWindow* hoverWindow = findTouchedWindowAtLocked(
pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X),
pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
if (mLastHoverWindow != hoverWindow) {
#if DEBUG_BATCHING
LOGD("Not streaming hover move because the last hovered window "
"is '%s' but the currently hovered window is '%s'.",
mLastHoverWindow->name.string(),
hoverWindow ? hoverWindow->name.string() : "<null>");
#endif
goto NoBatchingOrStreaming;
}
}
// Hurray! This foreground target is currently dispatching a move event
// that we can stream onto. Append the motion sample and resume dispatch.
mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords);
@@ -2686,6 +2843,11 @@ void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) {
oldFocusedWindowChannel = mFocusedWindow->inputChannel;
mFocusedWindow = NULL;
}
sp<InputChannel> oldLastHoverWindowChannel;
if (mLastHoverWindow) {
oldLastHoverWindowChannel = mLastHoverWindow->inputChannel;
mLastHoverWindow = NULL;
}
mWindows.clear();
@@ -2736,6 +2898,12 @@ void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) {
}
}
// Recover the last hovered window.
if (oldLastHoverWindowChannel != NULL) {
mLastHoverWindow = getWindowLocked(oldLastHoverWindowChannel);
oldLastHoverWindowChannel.clear();
}
#if DEBUG_FOCUS
//logDispatchStateLocked();
#endif
@@ -2845,7 +3013,8 @@ bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel,
mTouchState.windows.removeAt(i);
int32_t newTargetFlags = oldTargetFlags
& (InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_SPLIT);
& (InputTarget::FLAG_FOREGROUND
| InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS);
mTouchState.addOrUpdateWindow(toWindow, newTargetFlags, pointerIds);
found = true;
@@ -3533,6 +3702,10 @@ void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) {
}
}
void InputDispatcher::Allocator::freeMotionSample(MotionSample* sample) {
mMotionSamplePool.free(sample);
}
void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) {
releaseEventEntry(entry->eventEntry);
mDispatchEntryPool.free(entry);
@@ -3588,22 +3761,19 @@ bool InputDispatcher::InputState::isNeutral() const {
return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
}
void InputDispatcher::InputState::trackEvent(
const EventEntry* entry) {
void InputDispatcher::InputState::trackEvent(const EventEntry* entry, int32_t action) {
switch (entry->type) {
case EventEntry::TYPE_KEY:
trackKey(static_cast<const KeyEntry*>(entry));
trackKey(static_cast<const KeyEntry*>(entry), action);
break;
case EventEntry::TYPE_MOTION:
trackMotion(static_cast<const MotionEntry*>(entry));
trackMotion(static_cast<const MotionEntry*>(entry), action);
break;
}
}
void InputDispatcher::InputState::trackKey(
const KeyEntry* entry) {
int32_t action = entry->action;
void InputDispatcher::InputState::trackKey(const KeyEntry* entry, int32_t action) {
for (size_t i = 0; i < mKeyMementos.size(); i++) {
KeyMemento& memento = mKeyMementos.editItemAt(i);
if (memento.deviceId == entry->deviceId
@@ -3638,17 +3808,18 @@ Found:
}
}
void InputDispatcher::InputState::trackMotion(
const MotionEntry* entry) {
int32_t action = entry->action & AMOTION_EVENT_ACTION_MASK;
void InputDispatcher::InputState::trackMotion(const MotionEntry* entry, int32_t action) {
int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
for (size_t i = 0; i < mMotionMementos.size(); i++) {
MotionMemento& memento = mMotionMementos.editItemAt(i);
if (memento.deviceId == entry->deviceId
&& memento.source == entry->source) {
switch (action) {
switch (actionMasked) {
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_CANCEL:
case AMOTION_EVENT_ACTION_HOVER_ENTER:
case AMOTION_EVENT_ACTION_HOVER_MOVE:
case AMOTION_EVENT_ACTION_HOVER_EXIT:
mMotionMementos.removeAt(i);
return;
@@ -3669,7 +3840,11 @@ void InputDispatcher::InputState::trackMotion(
}
Found:
if (action == AMOTION_EVENT_ACTION_DOWN) {
switch (actionMasked) {
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_HOVER_ENTER:
case AMOTION_EVENT_ACTION_HOVER_MOVE:
case AMOTION_EVENT_ACTION_HOVER_EXIT:
mMotionMementos.push();
MotionMemento& memento = mMotionMementos.editTop();
memento.deviceId = entry->deviceId;
@@ -3678,6 +3853,7 @@ Found:
memento.yPrecision = entry->yPrecision;
memento.downTime = entry->downTime;
memento.setPointers(entry);
memento.hovering = actionMasked != AMOTION_EVENT_ACTION_DOWN;
}
}
@@ -3710,7 +3886,10 @@ void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTim
if (shouldCancelMotion(memento, options)) {
outEvents.push(allocator->obtainMotionEntry(currentTime,
memento.deviceId, memento.source, 0,
AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0,
memento.hovering
? AMOTION_EVENT_ACTION_HOVER_EXIT
: AMOTION_EVENT_ACTION_CANCEL,
0, 0, 0,
memento.xPrecision, memento.yPrecision, memento.downTime,
memento.pointerCount, memento.pointerIds, memento.pointerCoords));
mMotionMementos.removeAt(i);
@@ -3876,12 +4055,15 @@ void InputDispatcher::TouchState::addOrUpdateWindow(const InputWindow* window,
touchedWindow.channel = window->inputChannel;
}
void InputDispatcher::TouchState::removeOutsideTouchWindows() {
void InputDispatcher::TouchState::filterNonAsIsTouchWindows() {
for (size_t i = 0 ; i < windows.size(); ) {
if (windows[i].targetFlags & InputTarget::FLAG_OUTSIDE) {
windows.removeAt(i);
} else {
TouchedWindow& window = windows.editItemAt(i);
if (window.targetFlags & InputTarget::FLAG_DISPATCH_AS_IS) {
window.targetFlags &= ~InputTarget::FLAG_DISPATCH_MASK;
window.targetFlags |= InputTarget::FLAG_DISPATCH_AS_IS;
i += 1;
} else {
windows.removeAt(i);
}
}
}

View File

@@ -86,20 +86,40 @@ enum {
struct InputTarget {
enum {
/* This flag indicates that the event is being delivered to a foreground application. */
FLAG_FOREGROUND = 0x01,
/* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
* of the area of this target and so should instead be delivered as an
* AMOTION_EVENT_ACTION_OUTSIDE to this target. */
FLAG_OUTSIDE = 0x02,
FLAG_FOREGROUND = 1 << 0,
/* This flag indicates that the target of a MotionEvent is partly or wholly
* obscured by another visible window above it. The motion event should be
* delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
FLAG_WINDOW_IS_OBSCURED = 0x04,
FLAG_WINDOW_IS_OBSCURED = 1 << 1,
/* This flag indicates that a motion event is being split across multiple windows. */
FLAG_SPLIT = 0x08,
FLAG_SPLIT = 1 << 2,
/* This flag indicates that the event should be sent as is.
* Should always be set unless the event is to be transmuted. */
FLAG_DISPATCH_AS_IS = 1 << 8,
/* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
* of the area of this target and so should instead be delivered as an
* AMOTION_EVENT_ACTION_OUTSIDE to this target. */
FLAG_DISPATCH_AS_OUTSIDE = 1 << 9,
/* This flag indicates that a hover sequence is starting in the given window.
* The event is transmuted into ACTION_HOVER_ENTER. */
FLAG_DISPATCH_AS_HOVER_ENTER = 1 << 10,
/* This flag indicates that a hover event happened outside of a window which handled
* previous hover events, signifying the end of the current hover sequence for that
* window.
* The event is transmuted into ACTION_HOVER_ENTER. */
FLAG_DISPATCH_AS_HOVER_EXIT = 1 << 11,
/* Mask for all dispatch modes. */
FLAG_DISPATCH_MASK = FLAG_DISPATCH_AS_IS
| FLAG_DISPATCH_AS_OUTSIDE
| FLAG_DISPATCH_AS_HOVER_ENTER
| FLAG_DISPATCH_AS_HOVER_EXIT,
};
// The input channel to be targeted.
@@ -567,6 +587,7 @@ private:
void releaseConfigurationChangedEntry(ConfigurationChangedEntry* entry);
void releaseKeyEntry(KeyEntry* entry);
void releaseMotionEntry(MotionEntry* entry);
void freeMotionSample(MotionSample* sample);
void releaseDispatchEntry(DispatchEntry* entry);
void releaseCommandEntry(CommandEntry* entry);
@@ -608,13 +629,13 @@ private:
bool isNeutral() const;
// Records tracking information for an event that has just been published.
void trackEvent(const EventEntry* entry);
void trackEvent(const EventEntry* entry, int32_t action);
// Records tracking information for a key event that has just been published.
void trackKey(const KeyEntry* entry);
void trackKey(const KeyEntry* entry, int32_t action);
// Records tracking information for a motion event that has just been published.
void trackMotion(const MotionEntry* entry);
void trackMotion(const MotionEntry* entry, int32_t action);
// Synthesizes cancelation events for the current state and resets the tracked state.
void synthesizeCancelationEvents(nsecs_t currentTime, Allocator* allocator,
@@ -645,6 +666,7 @@ private:
uint32_t pointerCount;
int32_t pointerIds[MAX_POINTERS];
PointerCoords pointerCoords[MAX_POINTERS];
bool hovering;
void setPointers(const MotionEntry* entry);
};
@@ -839,7 +861,7 @@ private:
void reset();
void copyFrom(const TouchState& other);
void addOrUpdateWindow(const InputWindow* window, int32_t targetFlags, BitSet32 pointerIds);
void removeOutsideTouchWindows();
void filterNonAsIsTouchWindows();
const InputWindow* getFirstForegroundWindow();
};
@@ -882,6 +904,9 @@ private:
bool mInputTargetWaitTimeoutExpired;
sp<InputApplicationHandle> mInputTargetWaitApplication;
// Contains the last window which received a hover event.
const InputWindow* mLastHoverWindow;
// Finding targets for input events.
void resetTargetsLocked();
void commitTargetsLocked();
@@ -896,7 +921,8 @@ private:
int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry,
nsecs_t* nextWakeupTime);
int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry,
nsecs_t* nextWakeupTime, bool* outConflictingPointerActions);
nsecs_t* nextWakeupTime, bool* outConflictingPointerActions,
const MotionSample** outSplitBatchAfterSample);
void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,
BitSet32 pointerIds);
@@ -915,6 +941,9 @@ private:
void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
EventEntry* eventEntry, const InputTarget* inputTarget,
bool resumeWithAppendedMotionSample);
void enqueueDispatchEntryLocked(const sp<Connection>& connection,
EventEntry* eventEntry, const InputTarget* inputTarget,
bool resumeWithAppendedMotionSample, int32_t dispatchMode);
void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
bool handled);

View File

@@ -1605,8 +1605,15 @@ void CursorInputMapper::sync(nsecs_t when) {
motionEventAction, 0, metaState, motionEventEdgeFlags,
1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime);
mAccumulator.clear();
// Send hover move after UP to tell the application that the mouse is hovering now.
if (motionEventAction == AMOTION_EVENT_ACTION_UP
&& mPointerController != NULL) {
getDispatcher()->notifyMotion(when, getDeviceId(), mSource, policyFlags,
AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime);
}
// Send scroll events.
if (vscroll != 0 || hscroll != 0) {
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
@@ -1615,6 +1622,8 @@ void CursorInputMapper::sync(nsecs_t when) {
AMOTION_EVENT_ACTION_SCROLL, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime);
}
mAccumulator.clear();
}
int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {