Bug 1264017 - Add synthesized mouse support to Android. r=rbarker draft
authorKartikaya Gupta <kgupta@mozilla.com>
Sun, 15 May 2016 20:29:48 -0400
changeset 367371 36778a75fc4a1a1c300fa73223ca20a65a51ee43
parent 367370 c30755a90eb33f54f084f2af81c1a27f11d094b3
child 367372 7e8f98f43ee4cee9a9d3651e6263ab3975ac5d74
push id18221
push userkgupta@mozilla.com
push dateMon, 16 May 2016 13:48:43 +0000
reviewersrbarker
bugs1264017
milestone49.0a1
Bug 1264017 - Add synthesized mouse support to Android. r=rbarker MozReview-Commit-ID: 708j300kN3M
gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
gfx/layers/apz/test/mochitest/mochitest.ini
mobile/android/base/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
@@ -2,16 +2,20 @@
 
 function getPlatform() {
   if (navigator.platform.indexOf("Win") == 0) {
     return "windows";
   }
   if (navigator.platform.indexOf("Mac") == 0) {
     return "mac";
   }
+  // Check for Android before Linux
+  if (navigator.appVersion.indexOf("Android") >= 0) {
+    return "android"
+  }
   if (navigator.platform.indexOf("Linux") == 0) {
     return "linux";
   }
   return "unknown";
 }
 
 function nativeVerticalWheelEventMsg() {
   switch (getPlatform()) {
@@ -44,34 +48,39 @@ function nativeScrollUnits(aElement, aDi
   return aDimen;
 }
 
 function nativeMouseDownEventMsg() {
   switch (getPlatform()) {
     case "windows": return 2; // MOUSEEVENTF_LEFTDOWN
     case "mac": return 1; // NSLeftMouseDown
     case "linux": return 4; // GDK_BUTTON_PRESS
+    case "android": return 5; // ACTION_POINTER_DOWN
   }
+  throw "Native mouse-down events not supported on platform " + getPlatform();
 }
 
 function nativeMouseMoveEventMsg() {
   switch (getPlatform()) {
     case "windows": return 1; // MOUSEEVENTF_MOVE
     case "mac": return 5; // NSMouseMoved
     case "linux": return 3; // GDK_MOTION_NOTIFY
+    case "android": return 7; // ACTION_HOVER_MOVE
   }
-  throw "Native wheel events not supported on platform " + getPlatform();
+  throw "Native mouse-move events not supported on platform " + getPlatform();
 }
 
 function nativeMouseUpEventMsg() {
   switch (getPlatform()) {
     case "windows": return 4; // MOUSEEVENTF_LEFTUP
     case "mac": return 2; // NSLeftMouseUp
     case "linux": return 7; // GDK_BUTTON_RELEASE
+    case "android": return 6; // ACTION_POINTER_UP
   }
+  throw "Native mouse-up events not supported on platform " + getPlatform();
 }
 
 // Convert (aX, aY), in CSS pixels relative to aElement's bounding rect,
 // to device pixels relative to aElement's containing window.
 function coordinatesRelativeToWindow(aX, aY, aElement) {
   var targetWindow = aElement.ownerDocument.defaultView;
   var scale = targetWindow.devicePixelRatio;
   var rect = aElement.getBoundingClientRect();
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -40,10 +40,8 @@ skip-if = (os == 'android') || (os == 'b
 [test_frame_reconstruction.html]
 [test_tap.html]
 # Windows touch injection doesn't work in automation, but this test can be run locally on a windows touch device.
 # On OS X we don't support touch events at all.
 skip-if = (toolkit == 'windows') || (toolkit == 'cocoa')
 [test_scroll_window.html]
 skip-if = (toolkit == 'android') # wheel events not supported on mobile
 [test_click.html]
-# Very similar to test_tap, but we don't yet have mouse event synthesization on android
-skip-if = (os == 'android')
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -708,17 +708,24 @@ class GeckoLayerClient implements LayerV
                 .setZoomFactor(zoom)
                 .setPageRect(RectUtils.scale(cssPageRect, zoom), cssPageRect);
         }
         return syncViewportInfo(dpX, dpY, dpWidth, dpHeight, paintedResolution,
                     layersUpdated, paintSyncId);
     }
 
     class PointerInfo {
+        // We reserve one pointer ID for the mouse, so that tests don't have
+        // to worry about tracking pointer IDs if they just want to test mouse
+        // event synthesization. If somebody tries to use this ID for a
+        // synthesized touch event we'll throw an exception.
+        public static final int RESERVED_MOUSE_POINTER_ID = 100000;
+
         public int pointerId;
+        public int source;
         public int screenX;
         public int screenY;
         public double pressure;
         public int orientation;
 
         public MotionEvent.PointerCoords getCoords() {
             MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
             coords.orientation = orientation;
@@ -741,44 +748,73 @@ class GeckoLayerClient implements LayerV
             for (int i = 0; i < pointers.size(); i++) {
                 if (pointers.get(i).pointerId == pointerId) {
                     return i;
                 }
             }
             return -1;
         }
 
-        int addPointer(int pointerId) {
+        int addPointer(int pointerId, int source) {
             PointerInfo info = new PointerInfo();
             info.pointerId = pointerId;
+            info.source = source;
             pointers.add(info);
             return pointers.size() - 1;
         }
 
-        int[] getPointerIds() {
-            int[] ids = new int[pointers.size()];
-            for (int i = 0; i < ids.length; i++) {
-                ids[i] = pointers.get(i).pointerId;
+        int getPointerCount(int source) {
+            int count = 0;
+            for (int i = 0; i < pointers.size(); i++) {
+                if (pointers.get(i).source == source) {
+                    count++;
+                }
             }
-            return ids;
+            return count;
         }
 
-        MotionEvent.PointerCoords[] getPointerCoords() {
-            MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointers.size()];
-            for (int i = 0; i < coords.length; i++) {
-                coords[i] = pointers.get(i).getCoords();
+        MotionEvent.PointerProperties[] getPointerProperties(int source) {
+            MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[getPointerCount(source)];
+            int index = 0;
+            for (int i = 0; i < pointers.size(); i++) {
+                if (pointers.get(i).source == source) {
+                    MotionEvent.PointerProperties p = new MotionEvent.PointerProperties();
+                    p.id = pointers.get(i).pointerId;
+                    switch (source) {
+                        case InputDevice.SOURCE_TOUCHSCREEN:
+                            p.toolType = MotionEvent.TOOL_TYPE_FINGER;
+                            break;
+                        case InputDevice.SOURCE_MOUSE:
+                            p.toolType = MotionEvent.TOOL_TYPE_MOUSE;
+                            break;
+                    }
+                    props[index++] = p;
+                }
+            }
+            return props;
+        }
+
+        MotionEvent.PointerCoords[] getPointerCoords(int source) {
+            MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[getPointerCount(source)];
+            int index = 0;
+            for (int i = 0; i < pointers.size(); i++) {
+                if (pointers.get(i).source == source) {
+                    coords[index++] = pointers.get(i).getCoords();
+                }
             }
             return coords;
         }
     }
 
-    @WrapForJNI
-    public void synthesizeNativeTouchPoint(int pointerId, int eventType, int screenX,
-            int screenY, double pressure, int orientation)
+    private void synthesizeNativePointer(int source, int pointerId,
+            int eventType, int screenX, int screenY, double pressure,
+            int orientation)
     {
+        Log.d(LOGTAG, "Synthesizing pointer from " + source + " id " + pointerId + " at " + screenX + ", " + screenY);
+
         if (mPointerState == null) {
             mPointerState = new SynthesizedEventState();
         }
 
         // Find the pointer if it already exists
         int pointerIndex = mPointerState.getPointerIndex(pointerId);
 
         // Event-specific handling
@@ -797,61 +833,104 @@ class GeckoLayerClient implements LayerV
                 if (pointerIndex < 0) {
                     Log.d(LOGTAG, "Requested synthesis of a pointer-cancel for a pointer that doesn't exist!");
                     return;
                 }
                 break;
             case MotionEvent.ACTION_POINTER_DOWN:
                 if (pointerIndex < 0) {
                     // Adding a new pointer
-                    pointerIndex = mPointerState.addPointer(pointerId);
+                    pointerIndex = mPointerState.addPointer(pointerId, source);
                     if (pointerIndex == 0) {
                         // first pointer
                         eventType = MotionEvent.ACTION_DOWN;
                         mPointerState.downTime = SystemClock.uptimeMillis();
                     }
                 } else {
                     // We're moving an existing pointer
                     eventType = MotionEvent.ACTION_MOVE;
                 }
                 break;
+            case MotionEvent.ACTION_HOVER_MOVE:
+                if (pointerIndex < 0) {
+                    // Mouse-move a pointer without it going "down". However
+                    // in order to send the right MotionEvent without a lot of
+                    // duplicated code, we add the pointer to mPointerState,
+                    // and then remove it at the bottom of this function.
+                    pointerIndex = mPointerState.addPointer(pointerId, source);
+                } else {
+                    // We're moving an existing mouse pointer that went down.
+                    eventType = MotionEvent.ACTION_MOVE;
+                }
+                break;
         }
 
         // Update the pointer with the new info
         PointerInfo info = mPointerState.pointers.get(pointerIndex);
         info.screenX = screenX;
         info.screenY = screenY;
         info.pressure = pressure;
         info.orientation = orientation;
 
         // Dispatch the event
         int action = (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
         action &= MotionEvent.ACTION_POINTER_INDEX_MASK;
         action |= (eventType & MotionEvent.ACTION_MASK);
-        final MotionEvent event = MotionEvent.obtain(mPointerState.downTime,
-            SystemClock.uptimeMillis(), action, mPointerState.pointers.size(),
-            mPointerState.getPointerIds(), mPointerState.getPointerCoords(),
-            0, 0, 0, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+        boolean isButtonDown = (source == InputDevice.SOURCE_MOUSE) &&
+                               (eventType == MotionEvent.ACTION_DOWN || eventType == MotionEvent.ACTION_MOVE);
+        final MotionEvent event = MotionEvent.obtain(
+            /*downTime*/ mPointerState.downTime,
+            /*eventTime*/ SystemClock.uptimeMillis(),
+            /*action*/ action,
+            /*pointerCount*/ mPointerState.getPointerCount(source),
+            /*pointerProperties*/ mPointerState.getPointerProperties(source),
+            /*pointerCoords*/ mPointerState.getPointerCoords(source),
+            /*metaState*/ 0,
+            /*buttonState*/ (isButtonDown ? MotionEvent.BUTTON_PRIMARY : 0),
+            /*xPrecision*/ 0,
+            /*yPrecision*/ 0,
+            /*deviceId*/ 0,
+            /*edgeFlags*/ 0,
+            /*source*/ source,
+            /*flags*/ 0);
         mView.post(new Runnable() {
             @Override
             public void run() {
                 event.offsetLocation(0, mView.getSurfaceTranslation());
                 mView.dispatchTouchEvent(event);
             }
         });
 
         // Forget about removed pointers
         if (eventType == MotionEvent.ACTION_POINTER_UP ||
             eventType == MotionEvent.ACTION_UP ||
-            eventType == MotionEvent.ACTION_CANCEL)
+            eventType == MotionEvent.ACTION_CANCEL ||
+            eventType == MotionEvent.ACTION_HOVER_MOVE)
         {
             mPointerState.pointers.remove(pointerIndex);
         }
     }
 
+    @WrapForJNI
+    public void synthesizeNativeTouchPoint(int pointerId, int eventType, int screenX,
+            int screenY, double pressure, int orientation)
+    {
+        if (pointerId == PointerInfo.RESERVED_MOUSE_POINTER_ID) {
+            throw new IllegalArgumentException("Use a different pointer ID in your test, this one is reserved for mouse");
+        }
+        synthesizeNativePointer(InputDevice.SOURCE_TOUCHSCREEN, pointerId,
+            eventType, screenX, screenY, pressure, orientation);
+    }
+
+    @WrapForJNI
+    public void synthesizeNativeMouseEvent(int eventType, int screenX, int screenY) {
+        synthesizeNativePointer(InputDevice.SOURCE_MOUSE, PointerInfo.RESERVED_MOUSE_POINTER_ID,
+            eventType, screenX, screenY, 0, 0);
+    }
+
     @WrapForJNI(allowMultithread = true)
     public LayerRenderer.Frame createFrame() {
         // Create the shaders and textures if necessary.
         if (!mLayerRendererInitialized) {
             if (mLayerRenderer == null) {
                 return null;
             }
             mLayerRenderer.checkMonitoringEnabled();
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -1439,16 +1439,24 @@ auto GeckoLayerClient::SyncFrameMetrics(
 constexpr char GeckoLayerClient::SyncViewportInfo_t::name[];
 constexpr char GeckoLayerClient::SyncViewportInfo_t::signature[];
 
 auto GeckoLayerClient::SyncViewportInfo(int32_t a0, int32_t a1, int32_t a2, int32_t a3, float a4, bool a5, int32_t a6) const -> mozilla::jni::Object::LocalRef
 {
     return mozilla::jni::Method<SyncViewportInfo_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2, a3, a4, a5, a6);
 }
 
+constexpr char GeckoLayerClient::SynthesizeNativeMouseEvent_t::name[];
+constexpr char GeckoLayerClient::SynthesizeNativeMouseEvent_t::signature[];
+
+auto GeckoLayerClient::SynthesizeNativeMouseEvent(int32_t a0, int32_t a1, int32_t a2) const -> void
+{
+    return mozilla::jni::Method<SynthesizeNativeMouseEvent_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2);
+}
+
 constexpr char GeckoLayerClient::SynthesizeNativeTouchPoint_t::name[];
 constexpr char GeckoLayerClient::SynthesizeNativeTouchPoint_t::signature[];
 
 auto GeckoLayerClient::SynthesizeNativeTouchPoint(int32_t a0, int32_t a1, int32_t a2, int32_t a3, double a4, int32_t a5) const -> void
 {
     return mozilla::jni::Method<SynthesizeNativeTouchPoint_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2, a3, a4, a5);
 }
 
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -3433,16 +3433,34 @@ public:
                 "(IIIIFZI)Lorg/mozilla/gecko/gfx/ViewTransform;";
         static const bool isStatic = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     auto SyncViewportInfo(int32_t, int32_t, int32_t, int32_t, float, bool, int32_t) const -> mozilla::jni::Object::LocalRef;
 
+    struct SynthesizeNativeMouseEvent_t {
+        typedef GeckoLayerClient Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t,
+                int32_t> Args;
+        static constexpr char name[] = "synthesizeNativeMouseEvent";
+        static constexpr char signature[] =
+                "(III)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+    auto SynthesizeNativeMouseEvent(int32_t, int32_t, int32_t) const -> void;
+
     struct SynthesizeNativeTouchPoint_t {
         typedef GeckoLayerClient Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 int32_t,
                 int32_t,
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -3400,16 +3400,43 @@ nsWindow::SynthesizeNativeTouchPoint(uin
     MOZ_ASSERT(mGLControllerSupport);
     GeckoLayerClient::LocalRef client = mGLControllerSupport->GetLayerClient();
     client->SynthesizeNativeTouchPoint(aPointerId, eventType,
         aPoint.x, aPoint.y, aPointerPressure, aPointerOrientation);
 
     return NS_OK;
 }
 
+nsresult
+nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+                                     uint32_t aNativeMessage,
+                                     uint32_t aModifierFlags,
+                                     nsIObserver* aObserver)
+{
+    mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+    MOZ_ASSERT(mGLControllerSupport);
+    GeckoLayerClient::LocalRef client = mGLControllerSupport->GetLayerClient();
+    client->SynthesizeNativeMouseEvent(aNativeMessage, aPoint.x, aPoint.y);
+
+    return NS_OK;
+}
+
+nsresult
+nsWindow::SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+                                    nsIObserver* aObserver)
+{
+    mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+    MOZ_ASSERT(mGLControllerSupport);
+    GeckoLayerClient::LocalRef client = mGLControllerSupport->GetLayerClient();
+    client->SynthesizeNativeMouseEvent(sdk::MotionEvent::ACTION_HOVER_MOVE, aPoint.x, aPoint.y);
+
+    return NS_OK;
+}
 
 void
 nsWindow::DrawWindowUnderlay(LayerManagerComposite* aManager,
                              LayoutDeviceIntRect aRect)
 {
     if (Destroyed()) {
         return;
     }
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -195,16 +195,22 @@ public:
                                const mozilla::Maybe<ZoomConstraints>& aConstraints) override;
 
     nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
                                         TouchPointerState aPointerState,
                                         LayoutDeviceIntPoint aPoint,
                                         double aPointerPressure,
                                         uint32_t aPointerOrientation,
                                         nsIObserver* aObserver) override;
+    nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+                                        uint32_t aNativeMessage,
+                                        uint32_t aModifierFlags,
+                                        nsIObserver* aObserver) override;
+    nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+                                       nsIObserver* aObserver) override;
 
 protected:
     void BringToFront();
     nsWindow *FindTopLevel();
     bool IsTopLevel();
 
     RefPtr<mozilla::TextComposition> GetIMEComposition();
     void RemoveIMEComposition();