Refresh pointer icons locally from ViewRootImpl
The previous pipeline for refreshing pointer icon from a View would
call into WindowManagerService, which would look up the cursor position
and call back into the app to updatePointerIcon(x, y). This
implmenetation requires WM to know the cursor position, and adds
unnecessary binder roundtrips.
When PointerChoreographer is enabled, we will store the last input event
used to resolve the pointer icon locally in the app, and will use that
whenever we need to force the pointer icon to resolve. We can do this
because the hit test in InputDispatcher will verify that the pointer is
indeed being sent to the app before allowing the icon to be changed.
Bug: 293587049
Test: manual with test app that calls View#setPointerIcon every second,
observe that the icon changes without needing to generate new events.
Change-Id: I5344fe7023d0caaff001215c4195947f94d24ae6
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a268bca..75f8eba 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -29901,12 +29901,20 @@
*/
public void setPointerIcon(PointerIcon pointerIcon) {
mMousePointerIcon = pointerIcon;
- if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
- return;
- }
- try {
- mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow);
- } catch (RemoteException e) {
+ if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
+ return;
+ }
+ viewRootImpl.refreshPointerIcon();
+ } else {
+ if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
+ return;
+ }
+ try {
+ mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow);
+ } catch (RemoteException e) {
+ }
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8d469ee..1a4dbef 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1061,6 +1061,9 @@
sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
}
+ // The latest input event from the gesture that was used to resolve the pointer icon.
+ private MotionEvent mPointerIconEvent = null;
+
public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
}
@@ -6090,6 +6093,7 @@
private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38;
private static final int MSG_TOUCH_BOOST_TIMEOUT = 39;
private static final int MSG_CHECK_INVALIDATION_IDLE = 40;
+ private static final int MSG_REFRESH_POINTER_ICON = 41;
final class ViewRootHandler extends Handler {
@Override
@@ -6155,6 +6159,8 @@
return "MSG_WINDOW_TOUCH_MODE_CHANGED";
case MSG_KEEP_CLEAR_RECTS_CHANGED:
return "MSG_KEEP_CLEAR_RECTS_CHANGED";
+ case MSG_REFRESH_POINTER_ICON:
+ return "MSG_REFRESH_POINTER_ICON";
}
return super.getMessageName(message);
}
@@ -6411,6 +6417,12 @@
FRAME_RATE_IDLENESS_REEVALUATE_TIME);
}
break;
+ case MSG_REFRESH_POINTER_ICON:
+ if (mPointerIconEvent == null) {
+ break;
+ }
+ updatePointerIcon(mPointerIconEvent);
+ break;
}
}
}
@@ -7399,23 +7411,42 @@
if (event.getPointerCount() != 1) {
return;
}
+ final int action = event.getActionMasked();
final boolean needsStylusPointerIcon = event.isStylusPointer()
&& event.isHoverEvent()
&& mIsStylusPointerIconEnabled;
- if (needsStylusPointerIcon || event.isFromSource(InputDevice.SOURCE_MOUSE)) {
- if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
- || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
- // Other apps or the window manager may change the icon type outside of
- // this app, therefore the icon type has to be reset on enter/exit event.
+ if (!needsStylusPointerIcon && !event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ return;
+ }
+
+ if (action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_EXIT) {
+ // Other apps or the window manager may change the icon type outside of
+ // this app, therefore the icon type has to be reset on enter/exit event.
+ mPointerIconType = null;
+ }
+
+ if (action != MotionEvent.ACTION_HOVER_EXIT) {
+ // Resolve the pointer icon
+ if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) {
mPointerIconType = null;
}
+ }
- if (event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
- if (!updatePointerIcon(event) &&
- event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
- mPointerIconType = null;
+ // Keep track of the newest event used to resolve the pointer icon.
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_EXIT:
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mPointerIconEvent != null) {
+ mPointerIconEvent.recycle();
}
- }
+ mPointerIconEvent = null;
+ break;
+ default:
+ mPointerIconEvent = MotionEvent.obtain(event);
+ break;
}
}
@@ -7456,6 +7487,16 @@
updatePointerIcon(event);
}
+
+ /**
+ * If there is pointer that is showing a PointerIcon in this window, refresh the icon for that
+ * pointer. This will resolve the PointerIcon through the view hierarchy.
+ */
+ public void refreshPointerIcon() {
+ mHandler.removeMessages(MSG_REFRESH_POINTER_ICON);
+ mHandler.sendEmptyMessage(MSG_REFRESH_POINTER_ICON);
+ }
+
private boolean updatePointerIcon(MotionEvent event) {
final int pointerIndex = 0;
final float x = event.getX(pointerIndex);