diff --git a/api/current.txt b/api/current.txt index 5421f05b198b5..e39444dd4ad60 100644 --- a/api/current.txt +++ b/api/current.txt @@ -40244,6 +40244,8 @@ package android.view { field public static final int AXIS_LTRIGGER = 17; // 0x11 field public static final int AXIS_ORIENTATION = 8; // 0x8 field public static final int AXIS_PRESSURE = 2; // 0x2 + field public static final int AXIS_RELATIVE_X = 27; // 0x1b + field public static final int AXIS_RELATIVE_Y = 28; // 0x1c field public static final int AXIS_RTRIGGER = 18; // 0x12 field public static final int AXIS_RUDDER = 20; // 0x14 field public static final int AXIS_RX = 12; // 0xc @@ -40779,6 +40781,7 @@ package android.view { method public boolean hasNestedScrollingParent(); method public boolean hasOnClickListeners(); method public boolean hasOverlappingRendering(); + method public boolean hasPointerCapture(); method public boolean hasTransientState(); method public boolean hasWindowFocus(); method public static android.view.View inflate(android.content.Context, int, android.view.ViewGroup); @@ -40905,6 +40908,7 @@ package android.view { method public void postOnAnimation(java.lang.Runnable); method public void postOnAnimationDelayed(java.lang.Runnable, long); method public void refreshDrawableState(); + method public void releasePointerCapture(); method public boolean removeCallbacks(java.lang.Runnable); method public void removeOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void removeOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); @@ -41005,6 +41009,7 @@ package android.view { method public void setPaddingRelative(int, int, int, int); method public void setPivotX(float); method public void setPivotY(float); + method public void setPointerCapture(); method public void setPointerIcon(android.view.PointerIcon); method public void setPressed(boolean); method public final void setRight(int); diff --git a/api/system-current.txt b/api/system-current.txt index 3071fd9782390..e430b9f515fe1 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -42599,6 +42599,8 @@ package android.view { field public static final int AXIS_LTRIGGER = 17; // 0x11 field public static final int AXIS_ORIENTATION = 8; // 0x8 field public static final int AXIS_PRESSURE = 2; // 0x2 + field public static final int AXIS_RELATIVE_X = 27; // 0x1b + field public static final int AXIS_RELATIVE_Y = 28; // 0x1c field public static final int AXIS_RTRIGGER = 18; // 0x12 field public static final int AXIS_RUDDER = 20; // 0x14 field public static final int AXIS_RX = 12; // 0xc @@ -43134,6 +43136,7 @@ package android.view { method public boolean hasNestedScrollingParent(); method public boolean hasOnClickListeners(); method public boolean hasOverlappingRendering(); + method public boolean hasPointerCapture(); method public boolean hasTransientState(); method public boolean hasWindowFocus(); method public static android.view.View inflate(android.content.Context, int, android.view.ViewGroup); @@ -43260,6 +43263,7 @@ package android.view { method public void postOnAnimation(java.lang.Runnable); method public void postOnAnimationDelayed(java.lang.Runnable, long); method public void refreshDrawableState(); + method public void releasePointerCapture(); method public boolean removeCallbacks(java.lang.Runnable); method public void removeOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void removeOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); @@ -43360,6 +43364,7 @@ package android.view { method public void setPaddingRelative(int, int, int, int); method public void setPivotX(float); method public void setPivotY(float); + method public void setPointerCapture(); method public void setPointerIcon(android.view.PointerIcon); method public void setPressed(boolean); method public final void setRight(int); diff --git a/api/test-current.txt b/api/test-current.txt index f418d365832b1..88ddc21a92b9f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -40246,6 +40246,8 @@ package android.view { field public static final int AXIS_LTRIGGER = 17; // 0x11 field public static final int AXIS_ORIENTATION = 8; // 0x8 field public static final int AXIS_PRESSURE = 2; // 0x2 + field public static final int AXIS_RELATIVE_X = 27; // 0x1b + field public static final int AXIS_RELATIVE_Y = 28; // 0x1c field public static final int AXIS_RTRIGGER = 18; // 0x12 field public static final int AXIS_RUDDER = 20; // 0x14 field public static final int AXIS_RX = 12; // 0xc @@ -40781,6 +40783,7 @@ package android.view { method public boolean hasNestedScrollingParent(); method public boolean hasOnClickListeners(); method public boolean hasOverlappingRendering(); + method public boolean hasPointerCapture(); method public boolean hasTransientState(); method public boolean hasWindowFocus(); method public static android.view.View inflate(android.content.Context, int, android.view.ViewGroup); @@ -40907,6 +40910,7 @@ package android.view { method public void postOnAnimation(java.lang.Runnable); method public void postOnAnimationDelayed(java.lang.Runnable, long); method public void refreshDrawableState(); + method public void releasePointerCapture(); method public boolean removeCallbacks(java.lang.Runnable); method public void removeOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void removeOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); @@ -41007,6 +41011,7 @@ package android.view { method public void setPaddingRelative(int, int, int, int); method public void setPivotX(float); method public void setPivotY(float); + method public void setPointerCapture(); method public void setPointerIcon(android.view.PointerIcon); method public void setPressed(boolean); method public final void setRight(int); diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index ff33bd959175e..b8f464d76b0df 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -73,4 +73,6 @@ interface IInputManager { void setPointerIconShape(int shapeId); void setCustomPointerIcon(in PointerIcon icon); + + void setPointerIconDetached(boolean detached); } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index fab47181cfef3..16b872233f066 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -829,6 +829,24 @@ public final class InputManager { } } + /** + * Update the pointer icon status. When detached, the pointer icon disappears, and further + * mouse location will be stuck at the current point. Mouse movement events will still arrive, + * and movement should be handled through {@link MotionEvent.AXIS_RELATIVE_X} and + * {@link MotionEvent.AXIS_RELATIVE_Y}. + * + * @param detached true if the icon will be detached from the actual mouse movement. + * + * @hide + */ + public void setPointerIconDetached(boolean detached) { + try { + mIm.setPointerIconDetached(detached); + } catch (RemoteException ex) { + // Do nothing. + } + } + private void populateInputDevicesLocked() { if (mInputDevicesChangedListener == null) { final InputDevicesChangedListener listener = new InputDevicesChangedListener(); diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 0195decfcf16e..7a544b8f0bb39 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -976,6 +976,37 @@ public final class MotionEvent extends InputEvent implements Parcelable { */ public static final int AXIS_SCROLL = 26; + /** + * Axis constant: The movement of x position of a motion event. + *
+ *
+ * This is similar to {@link #AXIS_RELATIVE_X} but for y-axis. + *
+ * + * @see #getAxisValue(int, int) + * @see #getHistoricalAxisValue(int, int, int) + * @see MotionEvent.PointerCoords#getAxisValue(int, int) + * @see InputDevice#getMotionRange + */ + public static final int AXIS_RELATIVE_Y = 28; + /** * Axis constant: Generic 1 axis of a motion event. * The interpretation of a generic axis is device-specific. @@ -1187,6 +1218,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { names.append(AXIS_DISTANCE, "AXIS_DISTANCE"); names.append(AXIS_TILT, "AXIS_TILT"); names.append(AXIS_SCROLL, "AXIS_SCROLL"); + names.append(AXIS_RELATIVE_X, "AXIS_REALTIVE_X"); + names.append(AXIS_RELATIVE_Y, "AXIS_REALTIVE_Y"); names.append(AXIS_GENERIC_1, "AXIS_GENERIC_1"); names.append(AXIS_GENERIC_2, "AXIS_GENERIC_2"); names.append(AXIS_GENERIC_3, "AXIS_GENERIC_3"); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index afa6c783f1501..2d7ea2e6e3a86 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -14716,6 +14716,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, destroyDrawingCache(); cleanupDraw(); + releasePointerCapture(); mCurrentAnimation = null; } @@ -21212,6 +21213,56 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPointerIcon = pointerIcon; } + /** + * Request capturing further mouse events. + * + * When the view captures, the mouse pointer icon will disappear and will not change its + * position. Further mouse events will come to the capturing view, and the mouse movements + * will can be detected through {@link MotionEvent#AXIS_RELATIVE_X} and + * {@link MotionEvent#AXIS_RELATIVE_Y}. Non-mouse events (touchscreens, or stylus) will not + * be affected. + * + * The capture will be released through {@link #releasePointerCapture()}, or will be lost + * automatically when the view or containing window disappear. + * + * @return true when succeeds. + * @see #releasePointerCapture() + * @see #hasPointerCapture() + */ + public void setPointerCapture() { + final ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null) { + viewRootImpl.setPointerCapture(this); + } + } + + + /** + * Release the current capture of mouse events. + * + * If the view does not have the capture, it will do nothing. + * @see #setPointerCapture() + * @see #hasPointerCapture() + */ + public void releasePointerCapture() { + final ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl != null) { + viewRootImpl.releasePointerCapture(this); + } + } + + /** + * Checks the capture status of mouse events. + * + * @return true if the view has the capture. + * @see #setPointerCapture() + * @see #hasPointerCapture() + */ + public boolean hasPointerCapture() { + final ViewRootImpl viewRootImpl = getViewRootImpl(); + return (viewRootImpl != null) && viewRootImpl.hasPointerCapture(this); + } + // // Properties // diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index faf26405c199a..f758fef9f518c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -39,6 +39,7 @@ import android.graphics.Region; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; +import android.hardware.input.InputManager; import android.media.AudioManager; import android.os.Binder; import android.os.Build; @@ -174,6 +175,9 @@ public final class ViewRootImpl implements ViewParent, View mAccessibilityFocusedHost; AccessibilityNodeInfo mAccessibilityFocusedVirtualView; + // The view which captures mouse input, or null when no one is capturing. + View mCapturingView; + int mViewVisibility; boolean mAppVisible = true; // For recents to freeform transition we need to keep drawing after the app receives information @@ -3004,6 +3008,31 @@ public final class ViewRootImpl implements ViewParent, } } + void setPointerCapture(View view) { + if (!mAttachInfo.mHasWindowFocus) { + Log.w(TAG, "Can't set capture if it's not focused."); + return; + } + if (mCapturingView == view) { + return; + } + mCapturingView = view; + InputManager.getInstance().setPointerIconDetached(true); + } + + void releasePointerCapture(View view) { + if (mCapturingView != view || mCapturingView == null) { + return; + } + + mCapturingView = null; + InputManager.getInstance().setPointerIconDetached(false); + } + + boolean hasPointerCapture(View view) { + return view != null && mCapturingView == view; + } + @Override public void requestChildFocus(View child, View focused) { if (DEBUG_INPUT_RESIZE) { @@ -3081,6 +3110,10 @@ public final class ViewRootImpl implements ViewParent, mView = null; mAttachInfo.mRootView = null; + if (mCapturingView != null) { + releasePointerCapture(mCapturingView); + } + mSurface.release(); if (mInputQueueCallback != null && mInputQueue != null) { @@ -3390,6 +3423,8 @@ public final class ViewRootImpl implements ViewParent, .softInputMode &= ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; mHasHadWindowFocus = true; + } else if (mCapturingView != null) { + releasePointerCapture(mCapturingView); } } } break; @@ -4236,7 +4271,10 @@ public final class ViewRootImpl implements ViewParent, } mAttachInfo.mUnbufferedDispatchRequested = false; - boolean handled = mView.dispatchPointerEvent(event); + final View eventTarget = + (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ? + mCapturingView : mView; + boolean handled = eventTarget.dispatchPointerEvent(event); if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) { mUnbufferedInputDispatch = true; if (mConsumeBatchedInputScheduled) { diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 529849e99fb66..212c6a0d2c3c8 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -90,6 +90,7 @@ PointerController::PointerController(const sp