diff options
author | Jeff Brown <jeffbrown@google.com> | 2011-03-07 16:56:21 -0800 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2011-03-24 15:37:04 -0700 |
commit | a032cc008618b83ecbbede537517d1e7998e3264 (patch) | |
tree | 735a1f6f5fd7dc5607a0edb18a85abc831e5b7de | |
parent | e9f66af90a886cc55fc20c14375d8572bdf6dbd3 (diff) | |
download | frameworks_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.xml | 59 | ||||
-rw-r--r-- | core/java/android/view/MotionEvent.java | 43 | ||||
-rw-r--r-- | core/java/android/view/View.java | 152 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 212 | ||||
-rw-r--r-- | core/java/com/android/internal/widget/PointerLocationView.java | 6 | ||||
-rw-r--r-- | native/include/android/input.h | 8 | ||||
-rw-r--r-- | services/input/InputDispatcher.cpp | 318 | ||||
-rw-r--r-- | services/input/InputDispatcher.h | 55 | ||||
-rw-r--r-- | services/input/InputReader.cpp | 11 |
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) { |