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:
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user