Kaynağa Gözat

Feature - Mouse devices on android (#2597)

* Added support for mouse devices on android
Added setMouseGrab method to both MouseInput and InputManager. Android cannot change to grab/capture mode while dragging, all events after mouse down are ignored till mouse up is received

* Added documentation

* More documentation

* fix pointer grab by not handling action cancel

* Removed log on each mouse event. Too verbose

* set consume flag consistently

---------

Co-authored-by: Jesus Oliver <[email protected]>
Co-authored-by: Riccardo Balbo <[email protected]>
joliver82 1 hafta önce
ebeveyn
işleme
77c6e05cd7

+ 8 - 1
jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java

@@ -40,7 +40,9 @@ import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
 import android.view.ScaleGestureDetector;
 import android.view.View;
 import android.view.View;
 import com.jme3.input.JoyInput;
 import com.jme3.input.JoyInput;
+import com.jme3.input.MouseInput;
 import com.jme3.input.TouchInput;
 import com.jme3.input.TouchInput;
+import com.jme3.input.dummy.DummyMouseInput;
 import com.jme3.system.AppSettings;
 import com.jme3.system.AppSettings;
 import java.util.logging.Logger;
 import java.util.logging.Logger;
 
 
@@ -60,11 +62,12 @@ public class AndroidInputHandler implements View.OnTouchListener,
     protected GLSurfaceView view;
     protected GLSurfaceView view;
     protected AndroidTouchInput touchInput;
     protected AndroidTouchInput touchInput;
     protected AndroidJoyInput joyInput;
     protected AndroidJoyInput joyInput;
-
+    protected MouseInput mouseInput;
 
 
     public AndroidInputHandler() {
     public AndroidInputHandler() {
         touchInput = new AndroidTouchInput(this);
         touchInput = new AndroidTouchInput(this);
         joyInput = new AndroidJoyInput(this);
         joyInput = new AndroidJoyInput(this);
+        mouseInput = new DummyMouseInput();
     }
     }
 
 
     public void setView(View view) {
     public void setView(View view) {
@@ -118,6 +121,10 @@ public class AndroidInputHandler implements View.OnTouchListener,
         return joyInput;
         return joyInput;
     }
     }
 
 
+    public MouseInput getMouseInput() {
+        return mouseInput;
+    }
+
     /*
     /*
      *  Android input events include the source from which the input came from.
      *  Android input events include the source from which the input came from.
      *  We must look at the source of the input event to determine which type
      *  We must look at the source of the input event to determine which type

+ 38 - 1
jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java

@@ -37,6 +37,9 @@ import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View;
+
+import com.jme3.system.AppSettings;
+
 import java.util.logging.Logger;
 import java.util.logging.Logger;
 
 
 /**
 /**
@@ -55,6 +58,7 @@ public class AndroidInputHandler14 extends AndroidInputHandler implements View.O
     public AndroidInputHandler14() {
     public AndroidInputHandler14() {
         touchInput = new AndroidTouchInput14(this);
         touchInput = new AndroidTouchInput14(this);
         joyInput = new AndroidJoyInput14(this);
         joyInput = new AndroidJoyInput14(this);
+        mouseInput = new AndroidMouseInput14(this);
     }
     }
 
 
     @Override
     @Override
@@ -71,6 +75,12 @@ public class AndroidInputHandler14 extends AndroidInputHandler implements View.O
         view.setOnGenericMotionListener(this);
         view.setOnGenericMotionListener(this);
     }
     }
 
 
+    @Override
+    public void loadSettings(AppSettings settings) {
+        super.loadSettings(settings);
+        ((AndroidMouseInput14)mouseInput).loadSettings(settings);
+    }
+
     @Override
     @Override
     public boolean onHover(View view, MotionEvent event) {
     public boolean onHover(View view, MotionEvent event) {
         if (view != getView()) {
         if (view != getView()) {
@@ -91,6 +101,12 @@ public class AndroidInputHandler14 extends AndroidInputHandler implements View.O
             consumed = ((AndroidTouchInput14)touchInput).onHover(event);
             consumed = ((AndroidTouchInput14)touchInput).onHover(event);
         }
         }
 
 
+        boolean isMouse = ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE);
+        if (isMouse && mouseInput != null) {
+            // send the event to the mouse processor
+            consumed = consumed || ((AndroidMouseInput14)mouseInput).onHover(event);
+        }
+
         return consumed;
         return consumed;
     }
     }
 
 
@@ -116,6 +132,28 @@ public class AndroidInputHandler14 extends AndroidInputHandler implements View.O
             consumed = consumed || ((AndroidJoyInput14)joyInput).onGenericMotion(event);
             consumed = consumed || ((AndroidJoyInput14)joyInput).onGenericMotion(event);
         }
         }
 
 
+        if((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
+            consumed = consumed || ((AndroidMouseInput14)mouseInput).onGenericMotion(event);
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean onTouch(View view, MotionEvent event) {
+        if (view != getView()) {
+            return false;
+        }
+
+        boolean consumed = super.onTouch(view, event);
+
+        // Mouse movement while button down is received as onTouch event instead
+        boolean isMouse = ((event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE);
+        if (isMouse && mouseInput != null) {
+            // send the event to the mouse processor
+            consumed = consumed || ((AndroidMouseInput14)mouseInput).onGenericMotion(event);
+        }
+
         return consumed;
         return consumed;
     }
     }
 
 
@@ -154,7 +192,6 @@ public class AndroidInputHandler14 extends AndroidInputHandler implements View.O
         }
         }
 
 
         return consumed;
         return consumed;
-
     }
     }
 
 
 }
 }

+ 49 - 0
jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler24.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009-2026 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.android;
+
+/**
+ * <code>AndroidInputHandler24</code> extends <code>AndroidInputHandler14</code> to
+ * use AndroidMouseInput24 which adds usage of newer events and also enables cursor visibility
+ * and cursor image change.
+ *
+ * @author joliver82
+ */
+public class AndroidInputHandler24 extends AndroidInputHandler14 {
+
+    public AndroidInputHandler24() {
+        super();
+        mouseInput = new AndroidMouseInput24(this);
+    }
+
+}

+ 79 - 0
jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler26.java

@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2009-2026 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.android;
+
+import android.opengl.GLSurfaceView;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * <code>AndroidInputHandler26</code> extends <code>AndroidInputHandler24</code> to
+ * add the onCapturedPointer events that where added in Android rev 26.<br>
+ * The onCapturedPointer events are received when mouse is grabbed/captured.
+ *
+ * @author joliver82
+ */
+public class AndroidInputHandler26 extends AndroidInputHandler24 implements View.OnCapturedPointerListener {
+
+    public AndroidInputHandler26() {
+        super();
+        mouseInput = new AndroidMouseInput26(this);
+    }
+
+    protected void removeListeners(GLSurfaceView view) {
+        super.removeListeners(view);
+        view.setOnCapturedPointerListener(null);
+    }
+
+    @Override
+    protected void addListeners(GLSurfaceView view) {
+        super.addListeners(view);
+        view.setOnCapturedPointerListener(this);
+    }
+
+    @Override
+    public boolean onCapturedPointer(View view, MotionEvent event) {
+        if (view != getView()) {
+            return false;
+        }
+
+        boolean consumed = false;
+        boolean isMouse = ((event.getSource() & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE);
+        if (isMouse && mouseInput != null) {
+            consumed = ((AndroidMouseInput26)mouseInput).onCapturedPointer(event);
+        }
+
+        return consumed;
+    }
+}

+ 313 - 0
jme3-android/src/main/java/com/jme3/input/android/AndroidMouseInput14.java

@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2009-2026 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.android;
+
+import android.view.MotionEvent;
+
+import com.jme3.cursors.plugins.JmeCursor;
+import com.jme3.input.MouseInput;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.InputEvent;
+import com.jme3.input.event.MouseButtonEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import com.jme3.system.AppSettings;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>AndroidMouseInput14</code> implements <code>MouseInput</code> to add mouse support for jME3
+ * uses the onGenericMotion events that where added in Android rev 12 and MotionEvent.getButtonState
+ * from Android rev 14 so added "14" suffix to the class to specify the Android required rev and
+ * match other classes naming
+ *
+ * @author joliver82
+ */
+public class AndroidMouseInput14 implements MouseInput {
+    private static final Logger logger = Logger.getLogger(AndroidMouseInput14.class.getName());
+
+    protected AndroidInputHandler inputHandler;
+
+    private boolean initialized = false;
+    private RawInputListener listener = null;
+    private ConcurrentLinkedQueue<InputEvent> eventQueue = new ConcurrentLinkedQueue<>();
+    private float scaleX = 1f;
+    private float scaleY = 1f;
+
+    protected class MouseState {
+        int x, y, wheel;
+        boolean left, right, center;
+
+        protected void setStartPosition(int startingX,int startingY) {
+            x = startingX;
+            y = startingY;
+        }
+
+        protected int updateX(int newX) {
+            int deltaX=newX-x;
+            x=newX;
+            return deltaX;
+        }
+
+        protected int incrementX(int deltaX) {
+            x+=deltaX;
+            return x;
+        }
+
+        protected int updateY(int newY) {
+            int deltaY=newY-y;
+            y=newY;
+            return deltaY;
+        }
+
+        protected int incrementY(int deltaY) {
+            y+=deltaY;
+            return y;
+        }
+
+        protected int incrementWheel(int deltaWheel) {
+            wheel+=deltaWheel;
+            return wheel;
+        }
+
+        protected boolean updateLeftButton(boolean left) {
+            if(this.left == left) {
+                return false;
+            }
+            this.left = left;
+            return true;
+        }
+
+        protected boolean updateRightButton(boolean right) {
+            if(this.right == right) {
+                return false;
+            }
+            this.right = right;
+            return true;
+        }
+
+        protected boolean updateCenterButton(boolean center) {
+            if(this.center == center) {
+                return false;
+            }
+            this.center = center;
+            return true;
+        }
+    }
+
+    MouseState currentMouseState = new MouseState();
+
+    public AndroidMouseInput14(AndroidInputHandler inputHandler) {
+        this.inputHandler = inputHandler;
+    }
+
+    protected int getJmeX(float origX) {
+        return (int) (origX * scaleX);
+    }
+
+    protected int getJmeY(float origY) {
+        return (int) (origY * scaleY);
+    }
+
+    public void loadSettings(AppSettings settings) {
+        // view width and height are 0 until the view is displayed on the screen
+        if (inputHandler.getView().getWidth() != 0 && inputHandler.getView().getHeight() != 0) {
+            scaleX = settings.getWidth() / (float)inputHandler.getView().getWidth();
+            scaleY = settings.getHeight() / (float)inputHandler.getView().getHeight();
+            currentMouseState.setStartPosition(inputHandler.getView().getWidth()/2, inputHandler.getView().getHeight()/2);
+        }
+
+        if (logger.isLoggable(Level.FINE)) {
+            logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}",
+                    new Object[]{scaleX, scaleY});
+        }
+
+    }
+
+    protected void addMouseMotionEventFixedPositions(int x, int y, int deltaWheel) {
+        int deltaX=currentMouseState.updateX(x);
+        int deltaY=currentMouseState.updateY(y);
+        int wheel=currentMouseState.incrementWheel(deltaWheel);
+        eventQueue.add(new MouseMotionEvent(x, y, deltaX, deltaY, wheel, deltaWheel));
+    }
+
+    protected void addMouseMotionEventRelativePositions(int deltaX, int deltaY, int deltaWheel) {
+        int x=currentMouseState.incrementX(deltaX);
+        int y=currentMouseState.incrementY(deltaY);
+        int wheel=currentMouseState.incrementWheel(deltaWheel);
+        eventQueue.add(new MouseMotionEvent(x, y, deltaX, deltaY, wheel, deltaWheel));
+    }
+
+    protected boolean addMouseButtonEvent(boolean left, boolean right, boolean center, int x, int y) {
+        boolean eventAdded = false;
+        if(currentMouseState.updateLeftButton(left)) {
+            eventQueue.add(new MouseButtonEvent(MouseInput.BUTTON_LEFT, left, x, y));
+            eventAdded = true;
+        }
+        if(currentMouseState.updateRightButton(right)) {
+            eventQueue.add(new MouseButtonEvent(MouseInput.BUTTON_RIGHT, right, x, y));
+            eventAdded = true;
+        }
+        if(currentMouseState.updateCenterButton(center)) {
+            eventQueue.add(new MouseButtonEvent(MouseInput.BUTTON_MIDDLE, center, x, y));
+            eventAdded = true;
+        }
+        return eventAdded;
+    }
+
+    public boolean onHover(MotionEvent event) {
+        boolean consumed = false;
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_SCROLL:
+            case MotionEvent.ACTION_MOVE:
+            case MotionEvent.ACTION_HOVER_MOVE:
+            case MotionEvent.ACTION_HOVER_EXIT:
+            case MotionEvent.ACTION_HOVER_ENTER:
+                addMouseMotionEventFixedPositions(getJmeX(event.getX()), getJmeY(event.getY()), (int) event.getAxisValue(MotionEvent.AXIS_VSCROLL));
+                consumed = true;
+                break;
+        }
+
+        return consumed;
+    }
+
+    public boolean onGenericMotion(MotionEvent event) {
+        boolean consumed = false;
+        boolean btnEventReceived = false;
+        boolean leftPressed = false, rightPressed = false, centerPressed = false;
+
+        int btnState = event.getButtonState();
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                if((btnState & MotionEvent.BUTTON_PRIMARY) == MotionEvent.BUTTON_PRIMARY) {
+                    leftPressed = true;
+                }
+                if((btnState & MotionEvent.BUTTON_SECONDARY) == MotionEvent.BUTTON_SECONDARY) {
+                    rightPressed = true;
+                }
+                if((btnState & MotionEvent.BUTTON_TERTIARY) == MotionEvent.BUTTON_TERTIARY) {
+                    centerPressed = true;
+                }
+                btnEventReceived = true;
+                break;
+
+            case MotionEvent.ACTION_UP:
+                if((btnState & MotionEvent.BUTTON_PRIMARY) == MotionEvent.BUTTON_PRIMARY) {
+                    leftPressed = false;
+                }
+                if((btnState & MotionEvent.BUTTON_SECONDARY) == MotionEvent.BUTTON_SECONDARY) {
+                    rightPressed = false;
+                }
+                if((btnState & MotionEvent.BUTTON_TERTIARY) == MotionEvent.BUTTON_TERTIARY) {
+                    centerPressed = false;
+                }
+                btnEventReceived = true;
+                break;
+
+            case MotionEvent.ACTION_HOVER_EXIT:
+            case MotionEvent.ACTION_HOVER_ENTER:
+            case MotionEvent.ACTION_SCROLL:
+            case MotionEvent.ACTION_MOVE:
+            case MotionEvent.ACTION_HOVER_MOVE:
+                addMouseMotionEventFixedPositions(getJmeX(event.getX()), getJmeY(event.getY()), (int) event.getAxisValue(MotionEvent.AXIS_VSCROLL));
+                consumed = true;
+                break;
+        }
+
+        if (btnEventReceived) {
+            consumed = addMouseButtonEvent(leftPressed, rightPressed, centerPressed, getJmeX(event.getX()), getJmeY(event.getY()));
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public void setCursorVisible(boolean visible) {
+        logger.log(Level.FINE, "Cannot hide mouse till API 24");
+    }
+
+
+    @Override
+    public int getButtonCount() {
+        return 3; // No way to get the number of buttons, defaulting to 3 buttons
+    }
+
+    @Override
+    public void setNativeCursor(JmeCursor cursor) {
+        logger.log(Level.FINE, "Cannot change cursor till API 24");
+    }
+
+    @Override
+    public void initialize() {
+        initialized = true;
+    }
+
+    @Override
+    public void update() {
+        if (listener != null) {
+            InputEvent inputEvent;
+
+            while ((inputEvent = eventQueue.poll()) != null) {
+                if (inputEvent instanceof MouseMotionEvent) {
+                    listener.onMouseMotionEvent((MouseMotionEvent)inputEvent);
+                } else if (inputEvent instanceof MouseButtonEvent) {
+                    listener.onMouseButtonEvent((MouseButtonEvent)inputEvent);
+                }
+            }
+        }
+
+    }
+
+    @Override
+    public void destroy() {
+        initialized = false;
+    }
+
+    @Override
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    @Override
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public long getInputTimeNanos() {
+        return System.nanoTime();
+    }
+}

+ 138 - 0
jme3-android/src/main/java/com/jme3/input/android/AndroidMouseInput24.java

@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2009-2026 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.android;
+
+import android.graphics.Bitmap;
+import android.view.MotionEvent;
+import android.view.PointerIcon;
+
+import com.jme3.cursors.plugins.JmeCursor;
+
+import java.nio.IntBuffer;
+import java.util.logging.Logger;
+
+/**
+ * <code>AndroidMouseInput24</code> extends <code>AndroidMouseInput14</code> to improve mouse support
+ * using new events defined in API rev 24 and adding support for cursor change and cursor visibility
+ *
+ * @author joliver82
+ */
+public class AndroidMouseInput24 extends AndroidMouseInput14{
+    private static final Logger logger = Logger.getLogger(AndroidMouseInput24.class.getName());
+
+    public AndroidMouseInput24(AndroidInputHandler inputHandler) {
+        super(inputHandler);
+    }
+
+    @Override
+    public boolean onGenericMotion(MotionEvent event) {
+
+        boolean consumed = super.onGenericMotion(event);
+
+        if (!consumed) {
+            boolean leftPressed = false, rightPressed = false, centerPressed = false;
+            boolean btnEventReceived = false;
+            int btnAction = event.getActionButton();
+
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_BUTTON_PRESS:
+                    if(btnAction == MotionEvent.BUTTON_PRIMARY) {
+                        leftPressed = true;
+                    }
+                    if(btnAction == MotionEvent.BUTTON_SECONDARY) {
+                        rightPressed = true;
+                    }
+                    if(btnAction == MotionEvent.BUTTON_TERTIARY) {
+                        centerPressed = true;
+                    }
+                    btnEventReceived = true;
+                    break;
+
+                case MotionEvent.ACTION_BUTTON_RELEASE:
+                    if(btnAction == MotionEvent.BUTTON_PRIMARY) {
+                        leftPressed = false;
+                    }
+                    if(btnAction == MotionEvent.BUTTON_SECONDARY) {
+                        rightPressed = false;
+                    }
+                    if(btnAction == MotionEvent.BUTTON_TERTIARY) {
+                        centerPressed = false;
+                    }
+                    btnEventReceived = true;
+                    break;
+            }
+
+            if (btnEventReceived) {
+                consumed = addMouseButtonEvent(leftPressed, rightPressed, centerPressed, getJmeX(event.getX()), getJmeY(event.getY()));
+            }
+        }
+        return consumed;
+    }
+
+    @Override
+    public void setCursorVisible(boolean visible) {
+        if(inputHandler.getView()!=null) {
+            if(visible) {
+                inputHandler.getView().setPointerIcon(null);
+            } else {
+                inputHandler.getView().setPointerIcon(PointerIcon.getSystemIcon(inputHandler.getView().getContext(), PointerIcon.TYPE_NULL));
+            }
+        }
+    }
+
+    @Override
+    public void setNativeCursor(JmeCursor cursor) {
+        if(inputHandler.getView()!=null) {
+            if(cursor!=null) {
+                // Translate into Android Bitmap format ARGB888. Assuming input image as RGBA
+                int bufferSize = cursor.getHeight()*cursor.getWidth();
+                int[] outputBitmap=new int[bufferSize];
+                IntBuffer inputImage = cursor.getImagesData().asReadOnlyBuffer();
+                inputImage.clear();
+                int[] tmpPixel = new int[4];
+                for(int i=0 ; i< bufferSize; ++i) {
+                    inputImage.get(tmpPixel);
+                    outputBitmap[i] = (tmpPixel[3] & 0xff) << 24 | (tmpPixel[0] & 0xff) << 16 | (tmpPixel[1] & 0xff) << 8 | (tmpPixel[2] & 0xff);
+                }
+                PointerIcon pointer = PointerIcon.create(
+                        Bitmap.createBitmap(outputBitmap, cursor.getWidth(), cursor.getHeight(), Bitmap.Config.ARGB_8888),
+                        cursor.getXHotSpot(),
+                        cursor.getYHotSpot());
+                inputHandler.getView().setPointerIcon(pointer);
+            } else {
+                inputHandler.getView().setPointerIcon(null);
+            }
+        }
+    }
+
+}

+ 118 - 0
jme3-android/src/main/java/com/jme3/input/android/AndroidMouseInput26.java

@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2009-2026 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.android;
+
+import android.graphics.Bitmap;
+import android.view.MotionEvent;
+import android.view.PointerIcon;
+
+import com.jme3.cursors.plugins.JmeCursor;
+
+import java.nio.IntBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>AndroidMouseInput26</code> extends <code>AndroidMouseInput24</code> to improve mouse support
+ * adding grab/capture support using onCapturedPointer events.
+ *
+ * @author joliver82
+ */
+public class AndroidMouseInput26 extends AndroidMouseInput24{
+    private static final Logger logger = Logger.getLogger(AndroidMouseInput26.class.getName());
+
+    public AndroidMouseInput26(AndroidInputHandler inputHandler) {
+        super(inputHandler);
+    }
+
+    public boolean onCapturedPointer(MotionEvent event) {
+        boolean consumed = false;
+        boolean btnEventReceived = false;
+        boolean leftPressed = false, rightPressed = false, centerPressed = false;
+
+        int btnAction = event.getActionButton();
+
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_BUTTON_PRESS:
+                if(btnAction == MotionEvent.BUTTON_PRIMARY) {
+                    leftPressed = true;
+                }
+                if(btnAction == MotionEvent.BUTTON_SECONDARY) {
+                    rightPressed = true;
+                }
+                if(btnAction == MotionEvent.BUTTON_TERTIARY) {
+                    centerPressed = true;
+                }
+                btnEventReceived = true;
+                break;
+
+            case MotionEvent.ACTION_BUTTON_RELEASE:
+                if(btnAction == MotionEvent.BUTTON_PRIMARY) {
+                    leftPressed = false;
+                }
+                if(btnAction == MotionEvent.BUTTON_SECONDARY) {
+                    rightPressed = false;
+                }
+                if(btnAction == MotionEvent.BUTTON_TERTIARY) {
+                    centerPressed = false;
+                }
+                btnEventReceived = true;
+                break;
+
+            case MotionEvent.ACTION_HOVER_EXIT:
+            case MotionEvent.ACTION_HOVER_ENTER:
+            case MotionEvent.ACTION_SCROLL:
+            case MotionEvent.ACTION_MOVE:
+            case MotionEvent.ACTION_HOVER_MOVE:
+                addMouseMotionEventRelativePositions(getJmeX(event.getX()), getJmeY(event.getY()), (int) event.getAxisValue(MotionEvent.AXIS_VSCROLL));
+                consumed = true;
+                break;
+        }
+
+        if (btnEventReceived) {
+            consumed = addMouseButtonEvent(leftPressed, rightPressed, centerPressed, getJmeX(event.getX()), getJmeY(event.getY()));
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public void setCursorVisible(boolean visible) {
+        if(!visible) {
+            inputHandler.getView().requestPointerCapture();
+        } else {
+            inputHandler.getView().releasePointerCapture();
+        }
+    }
+
+}

+ 11 - 3
jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java

@@ -51,9 +51,10 @@ import android.widget.FrameLayout;
 import com.jme3.input.*;
 import com.jme3.input.*;
 import com.jme3.input.android.AndroidInputHandler;
 import com.jme3.input.android.AndroidInputHandler;
 import com.jme3.input.android.AndroidInputHandler14;
 import com.jme3.input.android.AndroidInputHandler14;
+import com.jme3.input.android.AndroidInputHandler24;
+import com.jme3.input.android.AndroidInputHandler26;
 import com.jme3.input.controls.SoftTextDialogInputListener;
 import com.jme3.input.controls.SoftTextDialogInputListener;
 import com.jme3.input.dummy.DummyKeyInput;
 import com.jme3.input.dummy.DummyKeyInput;
-import com.jme3.input.dummy.DummyMouseInput;
 import com.jme3.renderer.android.AndroidGL;
 import com.jme3.renderer.android.AndroidGL;
 import com.jme3.renderer.opengl.*;
 import com.jme3.renderer.opengl.*;
 import com.jme3.system.*;
 import com.jme3.system.*;
@@ -125,7 +126,11 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
         GLSurfaceView view = new GLSurfaceView(context);
         GLSurfaceView view = new GLSurfaceView(context);
         logger.log(Level.INFO, "Android Build Version: {0}", Build.VERSION.SDK_INT);
         logger.log(Level.INFO, "Android Build Version: {0}", Build.VERSION.SDK_INT);
         if (androidInput == null) {
         if (androidInput == null) {
-            if (Build.VERSION.SDK_INT >= 14) {
+            if (Build.VERSION.SDK_INT >= 26) {
+                androidInput = new AndroidInputHandler26();
+            } else if (Build.VERSION.SDK_INT >= 24) {
+                androidInput = new AndroidInputHandler24();
+            } else if (Build.VERSION.SDK_INT >= 14) {
                 androidInput = new AndroidInputHandler14();
                 androidInput = new AndroidInputHandler14();
             } else if (Build.VERSION.SDK_INT >= 9) {
             } else if (Build.VERSION.SDK_INT >= 9) {
                 androidInput = new AndroidInputHandler();
                 androidInput = new AndroidInputHandler();
@@ -141,6 +146,9 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
 
 
         view.setFocusableInTouchMode(true);
         view.setFocusableInTouchMode(true);
         view.setFocusable(true);
         view.setFocusable(true);
+        view.setFocusedByDefault(true);
+        view.requestFocus();
+        //view.setClickable(true);
 
 
         // setFormat must be set before AndroidConfigChooser is called by the surfaceview.
         // setFormat must be set before AndroidConfigChooser is called by the surfaceview.
         // if setFormat is called after ConfigChooser is called, then execution
         // if setFormat is called after ConfigChooser is called, then execution
@@ -310,7 +318,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
 
 
     @Override
     @Override
     public MouseInput getMouseInput() {
     public MouseInput getMouseInput() {
-        return new DummyMouseInput();
+        return androidInput.getMouseInput();
     }
     }
 
 
     @Override
     @Override