Merge "Implement pointer capture API"
diff --git a/api/current.txt b/api/current.txt
index dc0da56..194f6ab 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -42758,6 +42758,7 @@
field public static final int SOURCE_JOYSTICK = 16777232; // 0x1000010
field public static final int SOURCE_KEYBOARD = 257; // 0x101
field public static final int SOURCE_MOUSE = 8194; // 0x2002
+ field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
field public static final int SOURCE_STYLUS = 16386; // 0x4002
field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -43912,6 +43913,7 @@
method public void createContextMenu(android.view.ContextMenu);
method public void destroyDrawingCache();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
+ method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
method public void dispatchConfigurationChanged(android.content.res.Configuration);
method public void dispatchDisplayHint(int);
method public boolean dispatchDragEvent(android.view.DragEvent);
@@ -43930,6 +43932,7 @@
method public boolean dispatchNestedPrePerformAccessibilityAction(int, android.os.Bundle);
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
+ method public void dispatchPointerCaptureChanged(boolean);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void dispatchProvideAutoFillStructure(android.view.ViewStructure, int);
method public void dispatchProvideStructure(android.view.ViewStructure);
@@ -44112,6 +44115,7 @@
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);
@@ -44176,6 +44180,7 @@
method public android.view.WindowInsets onApplyWindowInsets(android.view.WindowInsets);
method protected void onAttachedToWindow();
method public void onCancelPendingInputEvents();
+ method public boolean onCapturedPointerEvent(android.view.MotionEvent);
method public boolean onCheckIsTextEditor();
method protected void onConfigurationChanged(android.content.res.Configuration);
method protected void onCreateContextMenu(android.view.ContextMenu);
@@ -44205,6 +44210,7 @@
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
+ method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void onProvideAutoFillStructure(android.view.ViewStructure, int);
method public void onProvideAutoFillVirtualStructure(android.view.ViewStructure, int);
@@ -44247,6 +44253,7 @@
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);
@@ -44257,6 +44264,7 @@
method public boolean requestFocus(int, android.graphics.Rect);
method public final boolean requestFocusFromTouch();
method public void requestLayout();
+ method public void requestPointerCapture();
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
method public final void requestUnbufferedDispatch(android.view.MotionEvent);
@@ -44334,6 +44342,7 @@
method public void setNextFocusRightId(int);
method public void setNextFocusUpId(int);
method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener);
+ method public void setOnCapturedPointerListener(android.view.View.OnCapturedPointerListener);
method public void setOnClickListener(android.view.View.OnClickListener);
method public void setOnContextClickListener(android.view.View.OnContextClickListener);
method public void setOnCreateContextMenuListener(android.view.View.OnCreateContextMenuListener);
@@ -44607,6 +44616,10 @@
method public abstract void onViewDetachedFromWindow(android.view.View);
}
+ public static abstract interface View.OnCapturedPointerListener {
+ method public abstract boolean onCapturedPointer(android.view.View, android.view.MotionEvent);
+ }
+
public static abstract interface View.OnClickListener {
method public abstract void onClick(android.view.View);
}
@@ -45339,6 +45352,7 @@
method public abstract boolean onMenuItemSelected(int, android.view.MenuItem);
method public abstract boolean onMenuOpened(int, android.view.Menu);
method public abstract void onPanelClosed(int, android.view.Menu);
+ method public default void onPointerCaptureChanged(boolean);
method public abstract boolean onPreparePanel(int, android.view.View, android.view.Menu);
method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
method public abstract boolean onSearchRequested();
diff --git a/api/system-current.txt b/api/system-current.txt
index b62da2b..5046ee9 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -46157,6 +46157,7 @@
field public static final int SOURCE_JOYSTICK = 16777232; // 0x1000010
field public static final int SOURCE_KEYBOARD = 257; // 0x101
field public static final int SOURCE_MOUSE = 8194; // 0x2002
+ field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
field public static final int SOURCE_STYLUS = 16386; // 0x4002
field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -47311,6 +47312,7 @@
method public void createContextMenu(android.view.ContextMenu);
method public void destroyDrawingCache();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
+ method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
method public void dispatchConfigurationChanged(android.content.res.Configuration);
method public void dispatchDisplayHint(int);
method public boolean dispatchDragEvent(android.view.DragEvent);
@@ -47329,6 +47331,7 @@
method public boolean dispatchNestedPrePerformAccessibilityAction(int, android.os.Bundle);
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
+ method public void dispatchPointerCaptureChanged(boolean);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void dispatchProvideAutoFillStructure(android.view.ViewStructure, int);
method public void dispatchProvideStructure(android.view.ViewStructure);
@@ -47511,6 +47514,7 @@
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);
@@ -47575,6 +47579,7 @@
method public android.view.WindowInsets onApplyWindowInsets(android.view.WindowInsets);
method protected void onAttachedToWindow();
method public void onCancelPendingInputEvents();
+ method public boolean onCapturedPointerEvent(android.view.MotionEvent);
method public boolean onCheckIsTextEditor();
method protected void onConfigurationChanged(android.content.res.Configuration);
method protected void onCreateContextMenu(android.view.ContextMenu);
@@ -47604,6 +47609,7 @@
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
+ method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void onProvideAutoFillStructure(android.view.ViewStructure, int);
method public void onProvideAutoFillVirtualStructure(android.view.ViewStructure, int);
@@ -47646,6 +47652,7 @@
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);
@@ -47656,6 +47663,7 @@
method public boolean requestFocus(int, android.graphics.Rect);
method public final boolean requestFocusFromTouch();
method public void requestLayout();
+ method public void requestPointerCapture();
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
method public final void requestUnbufferedDispatch(android.view.MotionEvent);
@@ -47733,6 +47741,7 @@
method public void setNextFocusRightId(int);
method public void setNextFocusUpId(int);
method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener);
+ method public void setOnCapturedPointerListener(android.view.View.OnCapturedPointerListener);
method public void setOnClickListener(android.view.View.OnClickListener);
method public void setOnContextClickListener(android.view.View.OnContextClickListener);
method public void setOnCreateContextMenuListener(android.view.View.OnCreateContextMenuListener);
@@ -48006,6 +48015,10 @@
method public abstract void onViewDetachedFromWindow(android.view.View);
}
+ public static abstract interface View.OnCapturedPointerListener {
+ method public abstract boolean onCapturedPointer(android.view.View, android.view.MotionEvent);
+ }
+
public static abstract interface View.OnClickListener {
method public abstract void onClick(android.view.View);
}
@@ -48739,6 +48752,7 @@
method public abstract boolean onMenuItemSelected(int, android.view.MenuItem);
method public abstract boolean onMenuOpened(int, android.view.Menu);
method public abstract void onPanelClosed(int, android.view.Menu);
+ method public default void onPointerCaptureChanged(boolean);
method public abstract boolean onPreparePanel(int, android.view.View, android.view.Menu);
method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
method public abstract boolean onSearchRequested();
diff --git a/api/test-current.txt b/api/test-current.txt
index f088582..50e56a3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -43060,6 +43060,7 @@
field public static final int SOURCE_JOYSTICK = 16777232; // 0x1000010
field public static final int SOURCE_KEYBOARD = 257; // 0x101
field public static final int SOURCE_MOUSE = 8194; // 0x2002
+ field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
field public static final int SOURCE_STYLUS = 16386; // 0x4002
field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -44216,6 +44217,7 @@
method public void createContextMenu(android.view.ContextMenu);
method public void destroyDrawingCache();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
+ method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
method public void dispatchConfigurationChanged(android.content.res.Configuration);
method public void dispatchDisplayHint(int);
method public boolean dispatchDragEvent(android.view.DragEvent);
@@ -44234,6 +44236,7 @@
method public boolean dispatchNestedPrePerformAccessibilityAction(int, android.os.Bundle);
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
+ method public void dispatchPointerCaptureChanged(boolean);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void dispatchProvideAutoFillStructure(android.view.ViewStructure, int);
method public void dispatchProvideStructure(android.view.ViewStructure);
@@ -44417,6 +44420,7 @@
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);
@@ -44481,6 +44485,7 @@
method public android.view.WindowInsets onApplyWindowInsets(android.view.WindowInsets);
method protected void onAttachedToWindow();
method public void onCancelPendingInputEvents();
+ method public boolean onCapturedPointerEvent(android.view.MotionEvent);
method public boolean onCheckIsTextEditor();
method protected void onConfigurationChanged(android.content.res.Configuration);
method protected void onCreateContextMenu(android.view.ContextMenu);
@@ -44510,6 +44515,7 @@
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
method protected void onOverScrolled(int, int, boolean, boolean);
+ method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void onProvideAutoFillStructure(android.view.ViewStructure, int);
method public void onProvideAutoFillVirtualStructure(android.view.ViewStructure, int);
@@ -44552,6 +44558,7 @@
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);
@@ -44562,6 +44569,7 @@
method public boolean requestFocus(int, android.graphics.Rect);
method public final boolean requestFocusFromTouch();
method public void requestLayout();
+ method public void requestPointerCapture();
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
method public final void requestUnbufferedDispatch(android.view.MotionEvent);
@@ -44639,6 +44647,7 @@
method public void setNextFocusRightId(int);
method public void setNextFocusUpId(int);
method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener);
+ method public void setOnCapturedPointerListener(android.view.View.OnCapturedPointerListener);
method public void setOnClickListener(android.view.View.OnClickListener);
method public void setOnContextClickListener(android.view.View.OnContextClickListener);
method public void setOnCreateContextMenuListener(android.view.View.OnCreateContextMenuListener);
@@ -44912,6 +44921,10 @@
method public abstract void onViewDetachedFromWindow(android.view.View);
}
+ public static abstract interface View.OnCapturedPointerListener {
+ method public abstract boolean onCapturedPointer(android.view.View, android.view.MotionEvent);
+ }
+
public static abstract interface View.OnClickListener {
method public abstract void onClick(android.view.View);
}
@@ -45648,6 +45661,7 @@
method public abstract boolean onMenuItemSelected(int, android.view.MenuItem);
method public abstract boolean onMenuOpened(int, android.view.Menu);
method public abstract void onPanelClosed(int, android.view.Menu);
+ method public default void onPointerCaptureChanged(boolean);
method public abstract boolean onPreparePanel(int, android.view.View, android.view.Menu);
method public default void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
method public abstract boolean onSearchRequested();
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index a9c09c4..bdb278b 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -81,4 +81,6 @@
void setPointerIconType(int typeId);
void setCustomPointerIcon(in PointerIcon icon);
+
+ void requestPointerCapture(IBinder windowToken, boolean enabled);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 2b0593f..6e202b0 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -40,6 +40,7 @@
import android.util.SparseArray;
import android.view.InputDevice;
import android.view.InputEvent;
+import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -898,6 +899,25 @@
}
}
+ /**
+ * Request or release pointer capture.
+ * <p>
+ * When in capturing mode, the pointer icon disappears and all mouse events are dispatched to
+ * the window which has requested the capture. Relative position changes are available through
+ * {@link MotionEvent#getX} and {@link MotionEvent#getY}.
+ *
+ * @param enable true when requesting pointer capture, false when releasing.
+ *
+ * @hide
+ */
+ public void requestPointerCapture(IBinder windowToken, boolean enable) {
+ try {
+ mIm.requestPointerCapture(windowToken, enable);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private void populateInputDevicesLocked() {
if (mInputDevicesChangedListener == null) {
final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 707300f..10b1e19 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -96,4 +96,9 @@
* Called when Keyboard Shortcuts are requested for the window.
*/
void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId);
+
+ /**
+ * Tell the window that it is either gaining or losing pointer capture.
+ */
+ void dispatchPointerCaptureChanged(boolean hasCapture);
}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 55f64d9..035d48f 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -237,6 +237,14 @@
public static final int SOURCE_TRACKBALL = 0x00010000 | SOURCE_CLASS_TRACKBALL;
/**
+ * The input source is a mouse device whose relative motions should be interpreted as
+ * navigation events.
+ *
+ * @see #SOURCE_CLASS_TRACKBALL
+ */
+ public static final int SOURCE_MOUSE_RELATIVE = 0x00020000 | SOURCE_CLASS_TRACKBALL;
+
+ /**
* The input source is a touch pad or digitizer tablet that is not
* associated with a display (unlike {@link #SOURCE_TOUCHSCREEN}).
*
@@ -975,6 +983,7 @@
appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE, "mouse");
appendSourceDescriptionIfApplicable(description, SOURCE_STYLUS, "stylus");
appendSourceDescriptionIfApplicable(description, SOURCE_TRACKBALL, "trackball");
+ appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE_RELATIVE, "mouse_relative");
appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHPAD, "touchpad");
appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK, "joystick");
appendSourceDescriptionIfApplicable(description, SOURCE_GAMEPAD, "gamepad");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8358f08..9e5c37d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3704,6 +3704,8 @@
private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
+
+ OnCapturedPointerListener mOnCapturedPointerListener;
}
ListenerInfo mListenerInfo;
@@ -10661,6 +10663,25 @@
}
/**
+ * Pass a captured pointer event down to the focused view.
+ *
+ * @param event The motion event to be dispatched.
+ * @return True if the event was handled by the view, false otherwise.
+ */
+ public boolean dispatchCapturedPointerEvent(MotionEvent event) {
+ if (!hasPointerCapture()) {
+ return false;
+ }
+ //noinspection SimplifiableIfStatement
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnCapturedPointerListener != null
+ && li.mOnCapturedPointerListener.onCapturedPointer(this, event)) {
+ return true;
+ }
+ return onCapturedPointerEvent(event);
+ }
+
+ /**
* Dispatch a generic motion event.
* <p>
* Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER}
@@ -22686,7 +22707,110 @@
return mPointerIcon;
}
- //
+ /**
+ * Checks pointer capture status.
+ *
+ * @return true if the view has pointer capture.
+ * @see #requestPointerCapture()
+ * @see #hasPointerCapture()
+ */
+ public boolean hasPointerCapture() {
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
+ return false;
+ }
+ return viewRootImpl.hasPointerCapture();
+ }
+
+ /**
+ * Requests pointer capture mode.
+ * <p>
+ * When the window has pointer capture, the mouse pointer icon will disappear and will not
+ * change its position. Further mouse will be dispatched with the source
+ * {@link InputDevice#SOURCE_MOUSE_RELATIVE}, and relative position changes will be available
+ * through {@link MotionEvent#getX} and {@link MotionEvent#getY}. Non-mouse events
+ * (touchscreens, or stylus) will not be affected.
+ * <p>
+ * If the window already has pointer capture, this call does nothing.
+ * <p>
+ * The capture may be released through {@link #releasePointerCapture()}, or will be lost
+ * automatically when the window loses focus.
+ *
+ * @see #releasePointerCapture()
+ * @see #hasPointerCapture()
+ */
+ public void requestPointerCapture() {
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.requestPointerCapture(true);
+ }
+ }
+
+
+ /**
+ * Releases the pointer capture.
+ * <p>
+ * If the window does not have pointer capture, this call will do nothing.
+ * @see #requestPointerCapture()
+ * @see #hasPointerCapture()
+ */
+ public void releasePointerCapture() {
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.requestPointerCapture(false);
+ }
+ }
+
+ /**
+ * Called when the window has just acquired or lost pointer capture.
+ *
+ * @param hasCapture True if the view now has pointerCapture, false otherwise.
+ */
+ @CallSuper
+ public void onPointerCaptureChange(boolean hasCapture) {
+ }
+
+ /**
+ * @see #onPointerCaptureChange
+ */
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ onPointerCaptureChange(hasCapture);
+ }
+
+ /**
+ * Implement this method to handle captured pointer events
+ *
+ * @param event The captured pointer event.
+ * @return True if the event was handled, false otherwise.
+ * @see #requestPointerCapture()
+ */
+ public boolean onCapturedPointerEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a captured pointer event
+ * is being dispatched this view. The callback will be invoked before the event is
+ * given to the view.
+ */
+ public interface OnCapturedPointerListener {
+ /**
+ * Called when a captured pointer event is dispatched to a view.
+ * @param view The view this event has been dispatched to.
+ * @param event The captured event.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ boolean onCapturedPointer(View view, MotionEvent event);
+ }
+
+ /**
+ * Set a listener to receive callbacks when the pointer capture state of a view changes.
+ * @param l The {@link OnCapturedPointerListener} to receive callbacks.
+ */
+ public void setOnCapturedPointerListener(OnCapturedPointerListener l) {
+ getListenerInfo().mOnCapturedPointerListener = l;
+ }
+
// Properties
//
/**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index f8a1c6b..af39eb3 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1756,6 +1756,34 @@
}
@Override
+ public boolean dispatchCapturedPointerEvent(MotionEvent event) {
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
+ if (super.dispatchCapturedPointerEvent(event)) {
+ return true;
+ }
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
+ if (mFocused.dispatchCapturedPointerEvent(event)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ exitHoverTargets();
+
+ super.dispatchPointerCaptureChanged(hasCapture);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchPointerCaptureChanged(hasCapture);
+ }
+ }
+
+ @Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6987e09..c9b9d5f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -192,8 +192,8 @@
View mAccessibilityFocusedHost;
AccessibilityNodeInfo mAccessibilityFocusedVirtualView;
- // The view which captures mouse input, or null when no one is capturing.
- View mCapturingView;
+ // True if the window currently has pointer capture enabled.
+ boolean mPointerCapture;
int mViewVisibility;
boolean mAppVisible = true;
@@ -3200,6 +3200,27 @@
}
}
+ boolean hasPointerCapture() {
+ return mPointerCapture;
+ }
+
+ void requestPointerCapture(boolean enabled) {
+ if (mPointerCapture == enabled) {
+ return;
+ }
+ InputManager.getInstance().requestPointerCapture(mAttachInfo.mWindowToken, enabled);
+ }
+
+ private void handlePointerCaptureChanged(boolean hasCapture) {
+ if (mPointerCapture == hasCapture) {
+ return;
+ }
+ mPointerCapture = hasCapture;
+ if (mView != null) {
+ mView.dispatchPointerCaptureChanged(hasCapture);
+ }
+ }
+
@Override
public void requestChildFocus(View child, View focused) {
if (DEBUG_INPUT_RESIZE) {
@@ -3393,6 +3414,7 @@
private final static int MSG_DISPATCH_WINDOW_SHOWN = 25;
private final static int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
private final static int MSG_UPDATE_POINTER_ICON = 27;
+ private final static int MSG_POINTER_CAPTURE_CHANGED = 28;
final class ViewRootHandler extends Handler {
@Override
@@ -3442,6 +3464,8 @@
return "MSG_DISPATCH_WINDOW_SHOWN";
case MSG_UPDATE_POINTER_ICON:
return "MSG_UPDATE_POINTER_ICON";
+ case MSG_POINTER_CAPTURE_CHANGED:
+ return "MSG_POINTER_CAPTURE_CHANGED";
}
return super.getMessageName(message);
}
@@ -3613,6 +3637,10 @@
.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
mHasHadWindowFocus = true;
+ } else {
+ if (mPointerCapture) {
+ handlePointerCaptureChanged(false);
+ }
}
}
} break;
@@ -3691,6 +3719,10 @@
MotionEvent event = (MotionEvent) msg.obj;
resetPointerIcon(event);
} break;
+ case MSG_POINTER_CAPTURE_CHANGED: {
+ final boolean hasCapture = msg.arg1 != 0;
+ handlePointerCaptureChanged(hasCapture);
+ } break;
}
}
}
@@ -4472,11 +4504,8 @@
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
- final View eventTarget =
- (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
- mCapturingView : mView;
mAttachInfo.mHandlingPointerEvent = true;
- boolean handled = eventTarget.dispatchPointerEvent(event);
+ boolean handled = mView.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent = false;
@@ -4510,6 +4539,12 @@
private int processTrackballEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
+ if (!hasPointerCapture() || mView.dispatchCapturedPointerEvent(event)) {
+ return FINISH_HANDLED;
+ }
+ }
+
if (mView.dispatchTrackballEvent(event)) {
return FINISH_HANDLED;
}
@@ -6700,6 +6735,14 @@
MSG_REQUEST_KEYBOARD_SHORTCUTS, deviceId, 0, receiver).sendToTarget();
}
+ public void dispatchPointerCaptureChanged(boolean on) {
+ final int what = MSG_POINTER_CAPTURE_CHANGED;
+ mHandler.removeMessages(what);
+ Message msg = mHandler.obtainMessage(what);
+ msg.arg1 = on ? 1 : 0;
+ mHandler.sendMessage(msg);
+ }
+
/**
* Post a callback to send a
* {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
@@ -7280,6 +7323,15 @@
viewAncestor.dispatchRequestKeyboardShortcuts(receiver, deviceId);
}
}
+
+ @Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchPointerCaptureChanged(hasCapture);
+ }
+ }
+
}
public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index e2bdd97..c424086 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -574,6 +574,13 @@
*/
default public void onProvideKeyboardShortcuts(
List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) { };
+
+ /**
+ * Called when pointer capture is enabled or disabled for the current window.
+ *
+ * @param hasCapture True if the window has pointer capture.
+ */
+ default public void onPointerCaptureChanged(boolean hasCapture) { };
}
/** @hide */
diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java
index 8f2d2e1..02c8945 100644
--- a/core/java/android/view/WindowCallbackWrapper.java
+++ b/core/java/android/view/WindowCallbackWrapper.java
@@ -158,5 +158,10 @@
List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
mWrapped.onProvideKeyboardShortcuts(data, menu, deviceId);
}
+
+ @Override
+ public void onPointerCaptureChanged(boolean hasCapture) {
+ mWrapped.onPointerCaptureChanged(hasCapture);
+ }
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 789e9d4..7ff115b 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -2243,6 +2243,14 @@
}
@Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ super.dispatchPointerCaptureChanged(hasCapture);
+ if (!mWindow.isDestroyed() && mWindow.getCallback() != null) {
+ mWindow.getCallback().onPointerCaptureChanged(hasCapture);
+ }
+ }
+
+ @Override
public String toString() {
return "DecorView@" + Integer.toHexString(this.hashCode()) + "["
+ getTitleSuffix(mWindow.getAttributes()) + "]";
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index c9c8292..dd91d2f 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -122,4 +122,8 @@
@Override
public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
}
+
+ @Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ }
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index fbb39384..3793b91 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -84,6 +84,7 @@
import android.util.Xml;
import android.view.IInputFilter;
import android.view.IInputFilterHost;
+import android.view.IWindow;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -180,6 +181,9 @@
IInputFilter mInputFilter; // guarded by mInputFilterLock
InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
+ private IWindow mFocusedWindow;
+ private boolean mFocusedWindowHasCapture;
+
private static native long nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
private static native void nativeStart(long ptr);
@@ -226,6 +230,7 @@
private static native void nativeSetPointerIconType(long ptr, int iconId);
private static native void nativeReloadPointerIcons(long ptr);
private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon);
+ private static native void nativeSetPointerCapture(long ptr, boolean detached);
// Input event injection constants defined in InputDispatcher.h.
private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0;
@@ -1503,7 +1508,16 @@
}
}
- public void setInputWindows(InputWindowHandle[] windowHandles) {
+ public void setInputWindows(InputWindowHandle[] windowHandles,
+ InputWindowHandle focusedWindowHandle) {
+ final IWindow newFocusedWindow =
+ focusedWindowHandle != null ? focusedWindowHandle.clientWindow : null;
+ if (mFocusedWindow != newFocusedWindow) {
+ mFocusedWindow = newFocusedWindow;
+ if (mFocusedWindowHasCapture) {
+ setPointerCapture(false);
+ }
+ }
nativeSetInputWindows(mPtr, windowHandles);
}
@@ -1511,6 +1525,30 @@
nativeSetFocusedApplication(mPtr, application);
}
+ @Override
+ public void requestPointerCapture(IBinder windowToken, boolean enabled) {
+ if (mFocusedWindow == null || mFocusedWindow.asBinder() != windowToken) {
+ Slog.e(TAG, "requestPointerCapture called for a window that has no focus: "
+ + windowToken);
+ return;
+ }
+ if (mFocusedWindowHasCapture == enabled) {
+ Slog.i(TAG, "requestPointerCapture: already " + (enabled ? "enabled" : "disabled"));
+ return;
+ }
+ setPointerCapture(enabled);
+ try {
+ mFocusedWindow.dispatchPointerCaptureChanged(enabled);
+ } catch (RemoteException ex) {
+ /* ignore */
+ }
+ }
+
+ private void setPointerCapture(boolean enabled) {
+ mFocusedWindowHasCapture = enabled;
+ nativeSetPointerCapture(mPtr, enabled);
+ }
+
public void setInputDispatchMode(boolean enabled, boolean frozen) {
nativeSetInputDispatchMode(mPtr, enabled, frozen);
}
diff --git a/services/core/java/com/android/server/input/InputWindowHandle.java b/services/core/java/com/android/server/input/InputWindowHandle.java
index eb3581a..3d6f7ad 100644
--- a/services/core/java/com/android/server/input/InputWindowHandle.java
+++ b/services/core/java/com/android/server/input/InputWindowHandle.java
@@ -18,6 +18,7 @@
import android.graphics.Region;
import android.view.InputChannel;
+import android.view.IWindow;
/**
* Functions as a handle for a window that can receive input.
@@ -36,6 +37,9 @@
// The window manager's window state.
public final Object windowState;
+ // The client window.
+ public final IWindow clientWindow;
+
// The input channel associated with the window.
public InputChannel inputChannel;
@@ -93,9 +97,10 @@
private native void nativeDispose();
public InputWindowHandle(InputApplicationHandle inputApplicationHandle,
- Object windowState, int displayId) {
+ Object windowState, IWindow clientWindow, int displayId) {
this.inputApplicationHandle = inputApplicationHandle;
this.windowState = windowState;
+ this.clientWindow = clientWindow;
this.displayId = displayId;
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 36520a9..1ae987f 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -148,7 +148,7 @@
mDragApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
+ mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
display.getDisplayId());
mDragWindowHandle.name = "drag";
mDragWindowHandle.inputChannel = mServerChannel;
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 24783bc..b92bfb9 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -48,7 +48,8 @@
mApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mWindowHandle = new InputWindowHandle(mApplicationHandle, null, Display.DEFAULT_DISPLAY);
+ mWindowHandle = new InputWindowHandle(mApplicationHandle, null, null,
+ Display.DEFAULT_DISPLAY);
mWindowHandle.name = name;
mWindowHandle.inputChannel = mServerChannel;
mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index f754775..e039583 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -77,6 +77,8 @@
// Array of window handles to provide to the input dispatcher.
private InputWindowHandle[] mInputWindowHandles;
private int mInputWindowHandleCount;
+ private InputWindowHandle mFocusedInputWindowHandle;
+
private boolean mAddInputConsumerHandle;
private boolean mAddPipInputConsumerHandle;
private boolean mAddWallpaperInputConsumerHandle;
@@ -327,12 +329,16 @@
+ child + ", " + inputWindowHandle);
}
addInputWindowHandle(inputWindowHandle);
+ if (hasFocus) {
+ mFocusedInputWindowHandle = inputWindowHandle;
+ }
}
private void clearInputWindowHandlesLw() {
while (mInputWindowHandleCount != 0) {
mInputWindowHandles[--mInputWindowHandleCount] = null;
}
+ mFocusedInputWindowHandle = null;
}
void setUpdateInputWindowsNeededLw() {
@@ -609,7 +615,7 @@
}
// Send windows to native code.
- mService.mInputManager.setInputWindows(mInputWindowHandles);
+ mService.mInputManager.setInputWindows(mInputWindowHandles, mFocusedInputWindowHandle);
clearInputWindowHandlesLw();
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 7bc577e..90106a9 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -269,7 +269,7 @@
mDragApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
+ mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
mDisplay.getDisplayId());
mDragWindowHandle.name = TAG;
mDragWindowHandle.inputChannel = mServerChannel;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5e458d4..724aab1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -657,7 +657,8 @@
mYOffset = 0;
mLayer = 0;
mInputWindowHandle = new InputWindowHandle(
- mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, getDisplayId());
+ mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, c,
+ getDisplayId());
}
void attach() {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 6791da9..8baa96b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -207,6 +207,7 @@
void setPointerIconType(int32_t iconId);
void reloadPointerIcons();
void setCustomPointerIcon(const SpriteIcon& icon);
+ void setPointerCapture(bool enabled);
/* --- InputReaderPolicyInterface implementation --- */
@@ -276,6 +277,9 @@
// Show touches feature enable/disable.
bool showTouches;
+ // Pointer capture feature enable/disable.
+ bool pointerCapture;
+
// Sprite controller singleton, created on first use.
sp<SpriteController> spriteController;
@@ -312,6 +316,7 @@
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
+ mLocked.pointerCapture = false;
}
mInteractive = true;
@@ -339,6 +344,7 @@
dump.appendFormat(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
dump.appendFormat(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
+ dump.appendFormat(INDENT "Pointer Capture Enabled: %s\n", toString(mLocked.pointerCapture));
}
dump.append("\n");
@@ -460,6 +466,8 @@
outConfig->showTouches = mLocked.showTouches;
+ outConfig->pointerCapture = mLocked.pointerCapture;
+
outConfig->setDisplayInfo(false /*external*/, mLocked.internalViewport);
outConfig->setDisplayInfo(true /*external*/, mLocked.externalViewport);
} // release lock
@@ -767,6 +775,22 @@
InputReaderConfiguration::CHANGE_SHOW_TOUCHES);
}
+void NativeInputManager::setPointerCapture(bool enabled) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mLocked.pointerCapture == enabled) {
+ return;
+ }
+
+ ALOGI("Setting pointer capture to %s.", enabled ? "enabled" : "disabled");
+ mLocked.pointerCapture = enabled;
+ } // release lock
+
+ mInputManager->getReader()->requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+}
+
void NativeInputManager::setInteractive(bool interactive) {
mInteractive = interactive;
}
@@ -1323,6 +1347,12 @@
im->setFocusedApplication(env, applicationHandleObj);
}
+static void nativeSetPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
+ jboolean enabled) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ im->setPointerCapture(enabled);
+}
+
static void nativeSetInputDispatchMode(JNIEnv* /* env */,
jclass /* clazz */, jlong ptr, jboolean enabled, jboolean frozen) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1509,6 +1539,8 @@
(void*) nativeSetInputWindows },
{ "nativeSetFocusedApplication", "(JLcom/android/server/input/InputApplicationHandle;)V",
(void*) nativeSetFocusedApplication },
+ { "nativeSetPointerCapture", "(JZ)V",
+ (void*) nativeSetPointerCapture },
{ "nativeSetInputDispatchMode", "(JZZ)V",
(void*) nativeSetInputDispatchMode },
{ "nativeSetSystemUiVisibility", "(JI)V",
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
index 1514e69..c029a9f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java
@@ -107,4 +107,8 @@
throws RemoteException {
}
+
+ @Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
index a83f100..a51ad2e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -108,6 +108,10 @@
}
@Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {
+ }
+
+ @Override
public IBinder asBinder() {
// pass for now.
return null;