Loading api/current.xml +59 −0 Original line number Diff line number Diff line Loading @@ -218679,6 +218679,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" Loading Loading @@ -223502,6 +223524,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" Loading Loading @@ -223977,6 +224010,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" Loading Loading @@ -225017,6 +225063,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" core/java/android/view/MotionEvent.java +38 −5 Original line number Diff line number Diff line Loading @@ -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)}. Loading @@ -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 Loading @@ -194,6 +197,32 @@ public final class MotionEvent extends InputEvent implements Parcelable { */ public static final int ACTION_SCROLL = 8; /** * Constant for {@link #getAction}: The pointer is not down but has entered the * boundaries of a window or view. * <p> * This action is always delivered to the window or view under the pointer. * </p><p> * This action is not a touch event so it is delivered to * {@link View#onGenericMotionEvent(MotionEvent)} rather than * {@link View#onTouchEvent(MotionEvent)}. * </p> */ public static final int ACTION_HOVER_ENTER = 9; /** * Constant for {@link #getAction}: The pointer is not down but has exited the * boundaries of a window or view. * <p> * This action is always delivered to the window or view that was previously under the pointer. * </p><p> * This action is not a touch event so it is delivered to * {@link View#onGenericMotionEvent(MotionEvent)} rather than * {@link View#onTouchEvent(MotionEvent)}. * </p> */ public static final int ACTION_HOVER_EXIT = 10; /** * Bits in the action code that represent a pointer index, used with * {@link #ACTION_POINTER_DOWN} and {@link #ACTION_POINTER_UP}. Shifting Loading Loading @@ -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 Loading Loading @@ -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) { Loading core/java/android/view/View.java +147 −5 Original line number Diff line number Diff line Loading @@ -1622,6 +1622,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility */ private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000; /** * Indicates that the view has received HOVER_ENTER. Cleared on HOVER_EXIT. * @hide */ private static final int HOVERED = 0x10000000; /** * Indicates that pivotX or pivotY were explicitly set and we should not assume the center * for transform operations Loading Loading @@ -4643,22 +4649,80 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * <p> * Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER} * are delivered to the view under the pointer. All other generic motion events are * delivered to the focused view. * delivered to the focused view. Hover events are handled specially and are delivered * to {@link #onHoverEvent}. * </p> * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchGenericMotionEvent(MotionEvent event) { final int source = event.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { final int action = event.getAction(); if (action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_MOVE || action == MotionEvent.ACTION_HOVER_EXIT) { if (dispatchHoverEvent(event)) { return true; } } else if (dispatchGenericPointerEvent(event)) { return true; } } else if (dispatchGenericFocusedEvent(event)) { return true; } //noinspection SimplifiableIfStatement if (mOnGenericMotionListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnGenericMotionListener.onGenericMotion(this, event)) { return true; } return onGenericMotionEvent(event); } /** * Dispatch a hover event. * <p> * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead. * </p> * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. * @hide */ protected boolean dispatchHoverEvent(MotionEvent event) { return onHoverEvent(event); } /** * Dispatch a generic motion event to the view under the first pointer. * <p> * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead. * </p> * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. * @hide */ protected boolean dispatchGenericPointerEvent(MotionEvent event) { return false; } /** * Dispatch a generic motion event to the currently focused view. * <p> * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead. * </p> * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. * @hide */ protected boolean dispatchGenericFocusedEvent(MotionEvent event) { return false; } /** * Dispatch a pointer event. * <p> Loading Loading @@ -5223,14 +5287,91 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * </code> * * @param event The generic motion event being processed. * * @return Return true if you have consumed the event, false if you haven't. * The default implementation always returns false. * @return True if the event was handled, false otherwise. */ public boolean onGenericMotionEvent(MotionEvent event) { return false; } /** * Implement this method to handle hover events. * <p> * Hover events are pointer events with action {@link MotionEvent#ACTION_HOVER_ENTER}, * {@link MotionEvent#ACTION_HOVER_MOVE}, or {@link MotionEvent#ACTION_HOVER_EXIT}. * </p><p> * The view receives hover enter as the pointer enters the bounds of the view and hover * exit as the pointer exits the bound of the view or just before the pointer goes down * (which implies that {@link #onTouchEvent} will be called soon). * </p><p> * If the view would like to handle the hover event itself and prevent its children * from receiving hover, it should return true from this method. If this method returns * true and a child has already received a hover enter event, the child will * automatically receive a hover exit event. * </p><p> * The default implementation sets the hovered state of the view if the view is * clickable. * </p> * * @param event The motion event that describes the hover. * @return True if this view handled the hover event and does not want its children * to receive the hover event. */ public boolean onHoverEvent(MotionEvent event) { final int viewFlags = mViewFlags; if (((viewFlags & CLICKABLE) != CLICKABLE && (viewFlags & LONG_CLICKABLE) != LONG_CLICKABLE)) { // Nothing to do if the view is not clickable. return false; } if ((viewFlags & ENABLED_MASK) == DISABLED) { // A disabled view that is clickable still consumes the hover events, it just doesn't // respond to them. return true; } switch (event.getAction()) { case MotionEvent.ACTION_HOVER_ENTER: setHovered(true); break; case MotionEvent.ACTION_HOVER_EXIT: setHovered(false); break; } return true; } /** * Returns true if the view is currently hovered. * * @return True if the view is currently hovered. */ public boolean isHovered() { return (mPrivateFlags & HOVERED) != 0; } /** * Sets whether the view is currently hovered. * * @param hovered True if the view is hovered. */ public void setHovered(boolean hovered) { if (hovered) { if ((mPrivateFlags & HOVERED) == 0) { mPrivateFlags |= HOVERED; refreshDrawableState(); } } else { if ((mPrivateFlags & HOVERED) != 0) { mPrivateFlags &= ~HOVERED; refreshDrawableState(); } } } /** * Implement this method to handle touch screen motion events. * Loading Loading @@ -9882,6 +10023,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]; Loading core/java/android/view/ViewGroup.java +169 −43 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -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; Loading @@ -1155,51 +1195,121 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = childrenCount - 1; i >= 0; i--) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE && child.getAnimation() == null) { // Skip invisible child unless it is animating. if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } if (!isTransformedTouchPointInView(x, y, child, null)) { // Scroll point is out of child's bounds. continue; // Found the hovered child. mHoveredChild = child; if (action == MotionEvent.ACTION_HOVER_MOVE) { // Pointer was moving within the view group and entered the child. // Send it a hover enter and hover move with only the most recent // coordinates. We could try to find the exact point in history when // the pointer entered the view but it is not worth the effort. eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER); handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child); eventNoHistory.setAction(action); 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) { // Send hover exit to the view group. If there was a child, we will already have // sent the hover exit to it. if (action == MotionEvent.ACTION_HOVER_EXIT) { handled |= super.dispatchHoverEvent(event); } // Done. return handled; } private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) { if (event.getHistorySize() == 0) { return event; } return MotionEvent.obtainNoHistory(event); } /** @hide */ @Override protected boolean dispatchGenericPointerEvent(MotionEvent event) { // Send the event to the child under the pointer. final int childrenCount = mChildrenCount; if (childrenCount != 0) { final View[] children = mChildren; final float x = event.getX(); final float y = event.getY(); for (int i = childrenCount - 1; i >= 0; i--) { final View child = children[i]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } if (dispatchTransformedGenericPointerEvent(event, child)) { return true; } } } // No child handled the event. Send it to this view group. return super.dispatchGenericMotionEvent(event); return super.dispatchGenericPointerEvent(event); } /** @hide */ @Override protected boolean dispatchGenericFocusedEvent(MotionEvent event) { // Send the event to the focused child or to this view group if it has focus. if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { return super.dispatchGenericMotionEvent(event); return super.dispatchGenericFocusedEvent(event); } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { return mFocused.dispatchGenericMotionEvent(event); } return false; } /** * Dispatches a generic pointer event to a child, taking into account * transformations that apply to the child. * * @param event The event to send. * @param child The view to send the event to. * @return {@code true} if the child handled the event. */ private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; boolean handled; if (!child.hasIdentityMatrix()) { MotionEvent transformedEvent = MotionEvent.obtain(event); transformedEvent.offsetLocation(offsetX, offsetY); transformedEvent.transform(child.getInverseMatrix()); handled = child.dispatchGenericMotionEvent(transformedEvent); transformedEvent.recycle(); } else { event.offsetLocation(offsetX, offsetY); handled = child.dispatchGenericMotionEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } /** * {@inheritDoc} */ Loading @@ -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. Loading Loading @@ -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; } Loading Loading @@ -1475,6 +1578,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } /** * Returns true if a child view can receive pointer events. * @hide */ private static boolean canViewReceivePointerEvents(View child) { return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null; } /** * Returns true if a child view contains the specified point when transformed * into its coordinate space. Loading Loading @@ -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(); Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading core/java/com/android/internal/widget/PointerLocationView.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading
api/current.xml +59 −0 Original line number Diff line number Diff line Loading @@ -218679,6 +218679,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" Loading Loading @@ -223502,6 +223524,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" Loading Loading @@ -223977,6 +224010,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" Loading Loading @@ -225017,6 +225063,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"
core/java/android/view/MotionEvent.java +38 −5 Original line number Diff line number Diff line Loading @@ -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)}. Loading @@ -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 Loading @@ -194,6 +197,32 @@ public final class MotionEvent extends InputEvent implements Parcelable { */ public static final int ACTION_SCROLL = 8; /** * Constant for {@link #getAction}: The pointer is not down but has entered the * boundaries of a window or view. * <p> * This action is always delivered to the window or view under the pointer. * </p><p> * This action is not a touch event so it is delivered to * {@link View#onGenericMotionEvent(MotionEvent)} rather than * {@link View#onTouchEvent(MotionEvent)}. * </p> */ public static final int ACTION_HOVER_ENTER = 9; /** * Constant for {@link #getAction}: The pointer is not down but has exited the * boundaries of a window or view. * <p> * This action is always delivered to the window or view that was previously under the pointer. * </p><p> * This action is not a touch event so it is delivered to * {@link View#onGenericMotionEvent(MotionEvent)} rather than * {@link View#onTouchEvent(MotionEvent)}. * </p> */ public static final int ACTION_HOVER_EXIT = 10; /** * Bits in the action code that represent a pointer index, used with * {@link #ACTION_POINTER_DOWN} and {@link #ACTION_POINTER_UP}. Shifting Loading Loading @@ -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 Loading Loading @@ -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) { Loading
core/java/android/view/View.java +147 −5 Original line number Diff line number Diff line Loading @@ -1622,6 +1622,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility */ private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000; /** * Indicates that the view has received HOVER_ENTER. Cleared on HOVER_EXIT. * @hide */ private static final int HOVERED = 0x10000000; /** * Indicates that pivotX or pivotY were explicitly set and we should not assume the center * for transform operations Loading Loading @@ -4643,22 +4649,80 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * <p> * Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER} * are delivered to the view under the pointer. All other generic motion events are * delivered to the focused view. * delivered to the focused view. Hover events are handled specially and are delivered * to {@link #onHoverEvent}. * </p> * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchGenericMotionEvent(MotionEvent event) { final int source = event.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { final int action = event.getAction(); if (action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_MOVE || action == MotionEvent.ACTION_HOVER_EXIT) { if (dispatchHoverEvent(event)) { return true; } } else if (dispatchGenericPointerEvent(event)) { return true; } } else if (dispatchGenericFocusedEvent(event)) { return true; } //noinspection SimplifiableIfStatement if (mOnGenericMotionListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnGenericMotionListener.onGenericMotion(this, event)) { return true; } return onGenericMotionEvent(event); } /** * Dispatch a hover event. * <p> * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead. * </p> * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. * @hide */ protected boolean dispatchHoverEvent(MotionEvent event) { return onHoverEvent(event); } /** * Dispatch a generic motion event to the view under the first pointer. * <p> * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead. * </p> * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. * @hide */ protected boolean dispatchGenericPointerEvent(MotionEvent event) { return false; } /** * Dispatch a generic motion event to the currently focused view. * <p> * Do not call this method directly. Call {@link #dispatchGenericMotionEvent} instead. * </p> * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. * @hide */ protected boolean dispatchGenericFocusedEvent(MotionEvent event) { return false; } /** * Dispatch a pointer event. * <p> Loading Loading @@ -5223,14 +5287,91 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * </code> * * @param event The generic motion event being processed. * * @return Return true if you have consumed the event, false if you haven't. * The default implementation always returns false. * @return True if the event was handled, false otherwise. */ public boolean onGenericMotionEvent(MotionEvent event) { return false; } /** * Implement this method to handle hover events. * <p> * Hover events are pointer events with action {@link MotionEvent#ACTION_HOVER_ENTER}, * {@link MotionEvent#ACTION_HOVER_MOVE}, or {@link MotionEvent#ACTION_HOVER_EXIT}. * </p><p> * The view receives hover enter as the pointer enters the bounds of the view and hover * exit as the pointer exits the bound of the view or just before the pointer goes down * (which implies that {@link #onTouchEvent} will be called soon). * </p><p> * If the view would like to handle the hover event itself and prevent its children * from receiving hover, it should return true from this method. If this method returns * true and a child has already received a hover enter event, the child will * automatically receive a hover exit event. * </p><p> * The default implementation sets the hovered state of the view if the view is * clickable. * </p> * * @param event The motion event that describes the hover. * @return True if this view handled the hover event and does not want its children * to receive the hover event. */ public boolean onHoverEvent(MotionEvent event) { final int viewFlags = mViewFlags; if (((viewFlags & CLICKABLE) != CLICKABLE && (viewFlags & LONG_CLICKABLE) != LONG_CLICKABLE)) { // Nothing to do if the view is not clickable. return false; } if ((viewFlags & ENABLED_MASK) == DISABLED) { // A disabled view that is clickable still consumes the hover events, it just doesn't // respond to them. return true; } switch (event.getAction()) { case MotionEvent.ACTION_HOVER_ENTER: setHovered(true); break; case MotionEvent.ACTION_HOVER_EXIT: setHovered(false); break; } return true; } /** * Returns true if the view is currently hovered. * * @return True if the view is currently hovered. */ public boolean isHovered() { return (mPrivateFlags & HOVERED) != 0; } /** * Sets whether the view is currently hovered. * * @param hovered True if the view is hovered. */ public void setHovered(boolean hovered) { if (hovered) { if ((mPrivateFlags & HOVERED) == 0) { mPrivateFlags |= HOVERED; refreshDrawableState(); } } else { if ((mPrivateFlags & HOVERED) != 0) { mPrivateFlags &= ~HOVERED; refreshDrawableState(); } } } /** * Implement this method to handle touch screen motion events. * Loading Loading @@ -9882,6 +10023,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]; Loading
core/java/android/view/ViewGroup.java +169 −43 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -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; Loading @@ -1155,51 +1195,121 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = childrenCount - 1; i >= 0; i--) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != VISIBLE && child.getAnimation() == null) { // Skip invisible child unless it is animating. if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } if (!isTransformedTouchPointInView(x, y, child, null)) { // Scroll point is out of child's bounds. continue; // Found the hovered child. mHoveredChild = child; if (action == MotionEvent.ACTION_HOVER_MOVE) { // Pointer was moving within the view group and entered the child. // Send it a hover enter and hover move with only the most recent // coordinates. We could try to find the exact point in history when // the pointer entered the view but it is not worth the effort. eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER); handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child); eventNoHistory.setAction(action); 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) { // Send hover exit to the view group. If there was a child, we will already have // sent the hover exit to it. if (action == MotionEvent.ACTION_HOVER_EXIT) { handled |= super.dispatchHoverEvent(event); } // Done. return handled; } private static MotionEvent obtainMotionEventNoHistoryOrSelf(MotionEvent event) { if (event.getHistorySize() == 0) { return event; } return MotionEvent.obtainNoHistory(event); } /** @hide */ @Override protected boolean dispatchGenericPointerEvent(MotionEvent event) { // Send the event to the child under the pointer. final int childrenCount = mChildrenCount; if (childrenCount != 0) { final View[] children = mChildren; final float x = event.getX(); final float y = event.getY(); for (int i = childrenCount - 1; i >= 0; i--) { final View child = children[i]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } if (dispatchTransformedGenericPointerEvent(event, child)) { return true; } } } // No child handled the event. Send it to this view group. return super.dispatchGenericMotionEvent(event); return super.dispatchGenericPointerEvent(event); } /** @hide */ @Override protected boolean dispatchGenericFocusedEvent(MotionEvent event) { // Send the event to the focused child or to this view group if it has focus. if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { return super.dispatchGenericMotionEvent(event); return super.dispatchGenericFocusedEvent(event); } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { return mFocused.dispatchGenericMotionEvent(event); } return false; } /** * Dispatches a generic pointer event to a child, taking into account * transformations that apply to the child. * * @param event The event to send. * @param child The view to send the event to. * @return {@code true} if the child handled the event. */ private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; boolean handled; if (!child.hasIdentityMatrix()) { MotionEvent transformedEvent = MotionEvent.obtain(event); transformedEvent.offsetLocation(offsetX, offsetY); transformedEvent.transform(child.getInverseMatrix()); handled = child.dispatchGenericMotionEvent(transformedEvent); transformedEvent.recycle(); } else { event.offsetLocation(offsetX, offsetY); handled = child.dispatchGenericMotionEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } /** * {@inheritDoc} */ Loading @@ -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. Loading Loading @@ -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; } Loading Loading @@ -1475,6 +1578,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } /** * Returns true if a child view can receive pointer events. * @hide */ private static boolean canViewReceivePointerEvents(View child) { return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null; } /** * Returns true if a child view contains the specified point when transformed * into its coordinate space. Loading Loading @@ -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(); Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading
core/java/com/android/internal/widget/PointerLocationView.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading