summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2011-03-07 16:56:21 -0800
committerJeff Brown <jeffbrown@google.com>2011-03-24 15:37:04 -0700
commita032cc008618b83ecbbede537517d1e7998e3264 (patch)
tree735a1f6f5fd7dc5607a0edb18a85abc831e5b7de
parente9f66af90a886cc55fc20c14375d8572bdf6dbd3 (diff)
downloadframeworks_base-a032cc008618b83ecbbede537517d1e7998e3264.zip
frameworks_base-a032cc008618b83ecbbede537517d1e7998e3264.tar.gz
frameworks_base-a032cc008618b83ecbbede537517d1e7998e3264.tar.bz2
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
-rw-r--r--api/current.xml59
-rw-r--r--core/java/android/view/MotionEvent.java43
-rw-r--r--core/java/android/view/View.java152
-rw-r--r--core/java/android/view/ViewGroup.java212
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java6
-rw-r--r--native/include/android/input.h8
-rw-r--r--services/input/InputDispatcher.cpp318
-rw-r--r--services/input/InputDispatcher.h55
-rw-r--r--services/input/InputReader.cpp11
9 files changed, 729 insertions, 135 deletions
diff --git a/api/current.xml b/api/current.xml
index 3305a32..4a619e1 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -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"
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index a17db5d..3c34479 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -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
@@ -195,6 +198,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
* down by {@link #ACTION_POINTER_INDEX_SHIFT} provides the actual pointer
@@ -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) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c729ccd..6c11b03 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1623,6 +1623,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,23 +4649,81 @@ 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>
* Dispatches touch related pointer events to {@link #onTouchEvent} and all
@@ -5223,15 +5287,92 @@ 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.
*
* @param event The motion event.
@@ -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];
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 8dc86ac..377f083 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -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,45 +1195,88 @@ 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);
+
+ handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child);
+ } else { /* must be ACTION_HOVER_ENTER */
+ // Pointer entered the child.
+ handled |= dispatchTransformedGenericPointerEvent(event, child);
}
+ break;
+ }
+ }
+ }
- 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);
- }
+ // Recycle the copy of the event that we made.
+ if (eventNoHistory != event) {
+ eventNoHistory.recycle();
+ }
- if (handled) {
- return true;
- }
+ // 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;
}
- }
- // No child handled the event. Send it to this view group.
- return super.dispatchGenericMotionEvent(event);
+ 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);
}
@@ -1201,6 +1284,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * 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}
*/
@Override
@@ -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;
}
@@ -1476,6 +1579,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.
* Child must not be null.
@@ -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;
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 076a1cb..c34cb9e 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -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;
diff --git a/native/include/android/input.h b/native/include/android/input.h
index 953ee10..f1738c6 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -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,
};
/*
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index 456e0e6..eaa7fa8 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -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;
- }
- 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;
+ 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;
+ }
}
- }
- // 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);
}
}
}
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index 1e118c4..59c5298 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -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);
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 94753bf..82cf62f 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -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) {