Update pointer icon when View.setPointerIcon is called

Currently the updated pointer icon is only displayed after
the next mouse move.

Bug:27107871
Change-Id: Ieed57b07fe44699735179cf57968a9bb08981396
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 1703ed1..6a2cc80 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -260,4 +260,6 @@
      * Returns true if the move started successfully; false otherwise.
      */
     boolean startMovingTask(IWindow window, float startX, float startY);
+
+    void updatePointerIcon(IWindow window);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bba5a17..5ae426c2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -21581,6 +21581,13 @@
      */
     public void setPointerIcon(PointerIcon pointerIcon) {
         mPointerIcon = pointerIcon;
+        if (mAttachInfo == null) {
+            return;
+        }
+        try {
+            mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow);
+        } catch (RemoteException e) {
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 0c04d4d..9a3aaa5 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -378,7 +378,9 @@
 
     private void cleanUpDragLw() {
         broadcastDragEndedLw();
-        restorePointerIconLw();
+        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+            mService.restorePointerIconLocked(mDisplay, mCurrentX, mCurrentY);
+        }
 
         // stop intercepting input
         unregister();
@@ -416,7 +418,7 @@
 
     void notifyLocationLw(float x, float y) {
         // Tell the affected window
-        WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+        WindowState touchedWin = mService.getTouchableWinAtPointLocked(mDisplay, x, y);
         if (touchedWin == null) {
             if (DEBUG_DRAG) Slog.d(TAG_WM, "No touched win at x=" + x + " y=" + y);
             return;
@@ -461,10 +463,6 @@
         mTargetWindow = touchedWin;
     }
 
-    WindowState getDropTargetWinLw(float x, float y) {
-        return getTouchedWinAtPointLw(x, y);
-    }
-
     // Tell the drop target about the data.  Returns 'true' if we can immediately
     // dispatch the global drag-ended message, 'false' if we need to wait for a
     // result from the recipient.
@@ -512,77 +510,17 @@
         return false;
     }
 
-    // Find the visible, touch-deliverable window under the given point
-    private WindowState getTouchedWinAtPointLw(float xf, float yf) {
-        WindowState touchedWin = null;
-        final int x = (int) xf;
-        final int y = (int) yf;
-
-        final WindowList windows = mService.getWindowListLocked(mDisplay);
-        if (windows == null) {
-            return null;
-        }
-        final int N = windows.size();
-        for (int i = N - 1; i >= 0; i--) {
-            WindowState child = windows.get(i);
-            final int flags = child.mAttrs.flags;
-            if (!child.isVisibleLw()) {
-                // not visible == don't tell about drags
-                continue;
-            }
-            if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
-                // not touchable == don't tell about drags
-                continue;
-            }
-
-            child.getVisibleBounds(mTmpRect);
-            if (!mTmpRect.contains(x, y)) {
-                // outside of this window's activity stack == don't tell about drags
-                continue;
-            }
-
-            child.getTouchableRegion(mTmpRegion);
-
-            final int touchFlags = flags &
-                    (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
-            if (mTmpRegion.contains(x, y) || touchFlags == 0) {
-                // Found it
-                touchedWin = child;
-                break;
-            }
-        }
-
-        return touchedWin;
-    }
-
     private static DragEvent obtainDragEvent(WindowState win, int action,
             float x, float y, Object localState,
             ClipDescription description, ClipData data,
             IDropPermissions dropPermissions,
             boolean result) {
-        final float winX = translateToWindowX(win, x);
-        final float winY = translateToWindowY(win, y);
+        final float winX = win.translateToWindowX(x);
+        final float winY = win.translateToWindowY(y);
         return DragEvent.obtain(action, winX, winY, localState, description, data,
                 dropPermissions, result);
     }
 
-    private static float translateToWindowX(WindowState win, float x) {
-        float winX = x - win.mFrame.left;
-        if (win.mEnforceSizeCompat) {
-            winX *= win.mGlobalScale;
-        }
-        return winX;
-    }
-
-    private static float translateToWindowY(WindowState win, float y) {
-        float winY = y - win.mFrame.top;
-        if (win.mEnforceSizeCompat) {
-            winY *= win.mGlobalScale;
-        }
-        return winY;
-    }
-
     boolean stepAnimationLocked(long currentTimeMs) {
         if (mAnimation == null) {
             return false;
@@ -638,21 +576,4 @@
             InputManager.getInstance().setPointerIconShape(PointerIcon.STYLE_GRAB);
         }
     }
-
-    private void restorePointerIconLw() {
-        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
-            WindowState touchWin = getTouchedWinAtPointLw(mCurrentX, mCurrentY);
-            if (touchWin != null) {
-                try {
-                    touchWin.mClient.updatePointerIcon(
-                            translateToWindowX(touchWin, mCurrentX),
-                            translateToWindowY(touchWin, mCurrentY));
-                    return;
-                } catch (RemoteException e) {
-                    Slog.w(TAG_WM, "unable to restore pointer icon");
-                }
-            }
-            InputManager.getInstance().setPointerIconShape(PointerIcon.STYLE_DEFAULT);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index a8d974f..25de75a 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -528,6 +528,16 @@
         }
     }
 
+    @Override
+    public void updatePointerIcon(IWindow window) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mService.updatePointerIcon(window);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     void windowAddedLocked() {
         if (mSurfaceSession == null) {
             if (WindowManagerService.localLOGV) Slog.v(
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d54e9e3..c8f5dda 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -41,6 +41,7 @@
 import android.graphics.Region;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -81,6 +82,7 @@
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Gravity;
+import android.view.PointerIcon;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IApplicationToken;
 import android.view.IDockedStackListener;
@@ -466,6 +468,7 @@
     final float[] mTmpFloats = new float[9];
     final Rect mTmpRect = new Rect();
     final Rect mTmpRect2 = new Rect();
+    final Region mTmpRegion = new Region();
 
     boolean mDisplayReady;
     boolean mSafeMode;
@@ -758,7 +761,7 @@
     }
 
     private boolean completeDropLw(float x, float y) {
-        WindowState dropTargetWin = mDragState.getDropTargetWinLw(x, y);
+        WindowState dropTargetWin = getTouchableWinAtPointLocked(mDragState.mDisplay, x, y);
 
         DropPermissionsHandler dropPermissions = null;
         if (dropTargetWin != null &&
@@ -10170,6 +10173,7 @@
         if (displayId == Display.DEFAULT_DISPLAY) {
             displayContent.mTapDetector = new TaskTapPointerEventListener(this, displayContent);
             registerPointerEventListener(displayContent.mTapDetector);
+            registerPointerEventListener(mMousePositionTracker);
         }
 
         return displayContent;
@@ -10262,6 +10266,7 @@
             displayContent.close();
             if (displayId == Display.DEFAULT_DISPLAY) {
                 unregisterPointerEventListener(displayContent.mTapDetector);
+                unregisterPointerEventListener(mMousePositionTracker);
             }
         }
         mAnimator.removeDisplayLocked(displayId);
@@ -10461,6 +10466,128 @@
         }
     }
 
+    /**
+     * Find the visible, touch-deliverable window under the given point
+     */
+    WindowState getTouchableWinAtPointLocked(Display display, float xf, float yf) {
+        WindowState touchedWin = null;
+        final int x = (int) xf;
+        final int y = (int) yf;
+
+        final WindowList windows = getWindowListLocked(display);
+        if (windows == null) {
+            return null;
+        }
+        final int N = windows.size();
+        for (int i = N - 1; i >= 0; i--) {
+            WindowState child = windows.get(i);
+            final int flags = child.mAttrs.flags;
+            if (!child.isVisibleLw()) {
+                continue;
+            }
+            if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+                continue;
+            }
+
+            child.getTouchableRegion(mTmpRegion);
+
+            final int touchFlags = flags &
+                    (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+            if (mTmpRegion.contains(x, y) || touchFlags == 0) {
+                touchedWin = child;
+                break;
+            }
+        }
+
+        return touchedWin;
+    }
+
+    private MousePositionTracker mMousePositionTracker = new MousePositionTracker();
+
+    private static class MousePositionTracker implements PointerEventListener {
+        private boolean mLatestEventWasMouse;
+        private float mLatestMouseX;
+        private float mLatestMouseY;
+
+        void updatePosition(float x, float y) {
+            synchronized (this) {
+                mLatestEventWasMouse = true;
+                mLatestMouseX = x;
+                mLatestMouseY = y;
+            }
+        }
+
+        @Override
+        public void onPointerEvent(MotionEvent motionEvent) {
+            if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) {
+                updatePosition(motionEvent.getRawX(), motionEvent.getRawY());
+            } else {
+                synchronized (this) {
+                    mLatestEventWasMouse = false;
+                }
+            }
+        }
+    };
+
+    void updatePointerIcon(IWindow client) {
+        float mouseX, mouseY;
+
+        synchronized(mMousePositionTracker) {
+            if (!mMousePositionTracker.mLatestEventWasMouse) {
+                return;
+            }
+            mouseX = mMousePositionTracker.mLatestMouseX;
+            mouseY = mMousePositionTracker.mLatestMouseY;
+        }
+
+        synchronized (mWindowMap) {
+            if (mDragState != null) {
+                // Drag cursor overrides the app cursor.
+                return;
+            }
+            WindowState callingWin = windowForClientLocked(null, client, false);
+            if (callingWin == null) {
+                Slog.w(TAG_WM, "Bad requesting window " + client);
+                return;
+            }
+            final DisplayContent displayContent = callingWin.getDisplayContent();
+            if (displayContent == null) {
+                return;
+            }
+            Display display = displayContent.getDisplay();
+            WindowState windowUnderPointer = getTouchableWinAtPointLocked(display, mouseX, mouseY);
+            if (windowUnderPointer != callingWin) {
+                return;
+            }
+            try {
+                windowUnderPointer.mClient.updatePointerIcon(
+                        windowUnderPointer.translateToWindowX(mouseX),
+                        windowUnderPointer.translateToWindowY(mouseY));
+            } catch (RemoteException e) {
+                Slog.w(TAG_WM, "unable to update pointer icon");
+            }
+        }
+    }
+
+    void restorePointerIconLocked(Display display, float latestX, float latestY) {
+        // Mouse position tracker has not been getting updates while dragging, update it now.
+        mMousePositionTracker.updatePosition(latestX, latestY);
+
+        WindowState windowUnderPointer = getTouchableWinAtPointLocked(display, latestX, latestY);
+        if (windowUnderPointer != null) {
+            try {
+                windowUnderPointer.mClient.updatePointerIcon(
+                        windowUnderPointer.translateToWindowX(latestX),
+                        windowUnderPointer.translateToWindowY(latestY));
+            } catch (RemoteException e) {
+                Slog.w(TAG_WM, "unable to restore pointer icon");
+            }
+        } else {
+            InputManager.getInstance().setPointerIconShape(PointerIcon.STYLE_DEFAULT);
+        }
+    }
+
     private final class LocalService extends WindowManagerInternal {
         @Override
         public void requestTraversalFromDisplayManager() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5b9206d..37c8a7e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2483,4 +2483,20 @@
         mReplacingWindow = null;
         mAnimateReplacingWindow = false;
     }
+
+    float translateToWindowX(float x) {
+        float winX = x - mFrame.left;
+        if (mEnforceSizeCompat) {
+            winX *= mGlobalScale;
+        }
+        return winX;
+    }
+
+    float translateToWindowY(float y) {
+        float winY = y - mFrame.top;
+        if (mEnforceSizeCompat) {
+            winY *= mGlobalScale;
+        }
+        return winY;
+    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 7b8e29a..fe05b0e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -236,4 +236,9 @@
     public void prepareToReplaceChildren(IBinder appToken) {
         // pass for now.
     }
+
+    @Override
+    public void updatePointerIcon(IWindow window) {
+        // pass for now.
+    }
 }