diff --git a/engine/src/android/com/jme3/input/android/AndroidGestureHandler.java b/engine/src/android/com/jme3/input/android/AndroidGestureHandler.java
new file mode 100644
index 000000000..8b6896baf
--- /dev/null
+++ b/engine/src/android/com/jme3/input/android/AndroidGestureHandler.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2009-2012 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.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import com.jme3.input.event.InputEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import com.jme3.input.event.TouchEvent;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * AndroidGestureHandler uses Gesture type listeners to create jME TouchEvents
+ * for gestures. This class is designed to handle the gestures supported
+ * on Android rev 9 (Android 2.3). Extend this class to add functionality
+ * added by Android after rev 9.
+ *
+ * @author iwgeric
+ */
+public class AndroidGestureHandler implements
+ GestureDetector.OnGestureListener,
+ GestureDetector.OnDoubleTapListener,
+ ScaleGestureDetector.OnScaleGestureListener {
+ private static final Logger logger = Logger.getLogger(AndroidGestureHandler.class.getName());
+ private AndroidInputHandler androidInput;
+ private GestureDetector gestureDetector;
+ private ScaleGestureDetector scaleDetector;
+ float gestureDownX = -1f;
+ float gestureDownY = -1f;
+ float scaleStartX = -1f;
+ float scaleStartY = -1f;
+
+ public AndroidGestureHandler(AndroidInputHandler androidInput) {
+ this.androidInput = androidInput;
+ }
+
+ public void initialize() {
+ }
+
+ public void destroy() {
+ setView(null);
+ }
+
+ public void setView(View view) {
+ if (view != null) {
+ gestureDetector = new GestureDetector(view.getContext(), this);
+ scaleDetector = new ScaleGestureDetector(view.getContext(), this);
+ } else {
+ gestureDetector = null;
+ scaleDetector = null;
+ }
+ }
+
+ public void detectGesture(MotionEvent event) {
+ if (gestureDetector != null && scaleDetector != null) {
+ gestureDetector.onTouchEvent(event);
+ scaleDetector.onTouchEvent(event);
+ }
+ }
+
+ private int getPointerIndex(MotionEvent event) {
+ return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ private int getPointerId(MotionEvent event) {
+ return event.getPointerId(getPointerIndex(event));
+ }
+
+ private void processEvent(TouchEvent event) {
+ // Add the touch event
+ androidInput.addEvent(event);
+ if (androidInput.isSimulateMouse()) {
+ InputEvent mouseEvent = generateMouseEvent(event);
+ if (mouseEvent != null) {
+ // Add the mouse event
+ androidInput.addEvent(mouseEvent);
+ }
+ }
+ }
+
+ // TODO: Ring Buffer for mouse events?
+ private InputEvent generateMouseEvent(TouchEvent event) {
+ InputEvent inputEvent = null;
+ int newX;
+ int newY;
+ int newDX;
+ int newDY;
+
+ if (androidInput.isMouseEventsInvertX()) {
+ newX = (int) (androidInput.invertX(event.getX()));
+ newDX = (int)event.getDeltaX() * -1;
+ } else {
+ newX = (int) event.getX();
+ newDX = (int)event.getDeltaX();
+ }
+ int wheel = (int) (event.getScaleSpan()); // might need to scale to match mouse wheel
+ int dWheel = (int) (event.getDeltaScaleSpan()); // might need to scale to match mouse wheel
+
+ if (androidInput.isMouseEventsInvertY()) {
+ newY = (int) (androidInput.invertY(event.getY()));
+ newDY = (int)event.getDeltaY() * -1;
+ } else {
+ newY = (int) event.getY();
+ newDY = (int)event.getDeltaY();
+ }
+
+ switch (event.getType()) {
+ case SCALE_MOVE:
+ inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, wheel, dWheel);
+ inputEvent.setTime(event.getTime());
+ break;
+ }
+
+ return inputEvent;
+ }
+
+ /* Events from onGestureListener */
+
+ public boolean onDown(MotionEvent event) {
+ // start of all GestureListeners. Not really a gesture by itself
+ // so we don't create an event.
+ // However, reset the scaleInProgress here since this is the beginning
+ // of a series of gesture events.
+// logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
+ gestureDownX = event.getX();
+ gestureDownY = androidInput.invertY(event.getY());
+ return true;
+ }
+
+ public boolean onSingleTapUp(MotionEvent event) {
+ // Up of single tap. May be followed by a double tap later.
+ // use onSingleTapConfirmed instead.
+// logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
+ return true;
+ }
+
+ public void onShowPress(MotionEvent event) {
+// logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
+ TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.SHOWPRESS, event.getX(), androidInput.invertY(event.getY()), 0, 0);
+ touchEvent.setPointerId(getPointerId(event));
+ touchEvent.setTime(event.getEventTime());
+ touchEvent.setPressure(event.getPressure());
+ processEvent(touchEvent);
+ }
+
+ public void onLongPress(MotionEvent event) {
+// logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
+ TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.LONGPRESSED, event.getX(), androidInput.invertY(event.getY()), 0, 0);
+ touchEvent.setPointerId(getPointerId(event));
+ touchEvent.setTime(event.getEventTime());
+ touchEvent.setPressure(event.getPressure());
+ processEvent(touchEvent);
+ }
+
+ public boolean onScroll(MotionEvent startEvent, MotionEvent endEvent, float distX, float distY) {
+ // if not scaleInProgess, send scroll events. This is to avoid sending
+ // scroll events when one of the fingers is lifted just before the other one.
+ // Avoids sending the scroll for that brief period of time.
+ // Return true so that the next event doesn't accumulate the distX and distY values.
+ // Apparantly, both distX and distY are negative.
+ // Negate distX to get the real value, but leave distY negative to compensate
+ // for the fact that jME has y=0 at bottom where Android has y=0 at top.
+// if (!scaleInProgress) {
+ if (!scaleDetector.isInProgress()) {
+// logger.log(Level.INFO, "onScroll pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, dx: {7}, dy: {8}",
+// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), distX, distY});
+
+ TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.SCROLL, endEvent.getX(), androidInput.invertY(endEvent.getY()), -distX, distY);
+ touchEvent.setPointerId(getPointerId(endEvent));
+ touchEvent.setTime(endEvent.getEventTime());
+ touchEvent.setPressure(endEvent.getPressure());
+ processEvent(touchEvent);
+ }
+ return true;
+ }
+
+ public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float velocityX, float velocityY) {
+ // Fling happens only once at the end of the gesture (all fingers up).
+ // Fling returns the velocity of the finger movement in pixels/sec.
+ // Therefore, the dX and dY values are actually velocity instead of distance values
+ // Since this does not track the movement, use the start position and velocity values.
+
+// logger.log(Level.INFO, "onFling pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, velocityX: {7}, velocityY: {8}",
+// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), velocityX, velocityY});
+
+ TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.FLING, startEvent.getX(), androidInput.invertY(startEvent.getY()), velocityX, velocityY);
+ touchEvent.setPointerId(getPointerId(endEvent));
+ touchEvent.setTime(endEvent.getEventTime());
+ touchEvent.setPressure(endEvent.getPressure());
+ processEvent(touchEvent);
+ return true;
+ }
+
+ /* Events from onDoubleTapListener */
+
+ public boolean onSingleTapConfirmed(MotionEvent event) {
+ // Up of single tap when no double tap followed.
+// logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
+ TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.TAP, event.getX(), androidInput.invertY(event.getY()), 0, 0);
+ touchEvent.setPointerId(getPointerId(event));
+ touchEvent.setTime(event.getEventTime());
+ touchEvent.setPressure(event.getPressure());
+ processEvent(touchEvent);
+ return true;
+ }
+
+ public boolean onDoubleTap(MotionEvent event) {
+ //The down motion event of the first tap of the double-tap
+ // We could use this event to fire off a double tap event, or use
+ // DoubleTapEvent with a check for the UP action
+// logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
+ TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), androidInput.invertY(event.getY()), 0, 0);
+ touchEvent.setPointerId(getPointerId(event));
+ touchEvent.setTime(event.getEventTime());
+ touchEvent.setPressure(event.getPressure());
+ processEvent(touchEvent);
+ return true;
+ }
+
+ public boolean onDoubleTapEvent(MotionEvent event) {
+ //Notified when an event within a double-tap gesture occurs, including the down, move(s), and up events.
+ // this means it will get called multiple times for a single double tap
+// logger.log(Level.INFO, "onDoubleTapEvent pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
+// if (getAction(event) == MotionEvent.ACTION_UP) {
+// TouchEvent touchEvent = touchEventPool.getNextFreeEvent();
+// touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), androidInput.invertY(event.getY()), 0, 0);
+// touchEvent.setPointerId(getPointerId(event));
+// touchEvent.setTime(event.getEventTime());
+// touchEvent.setPressure(event.getPressure());
+// processEvent(touchEvent);
+// }
+ return true;
+ }
+
+ /* Events from ScaleGestureDetector */
+
+ public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
+ // Scale uses a focusX and focusY instead of x and y. Focus is the middle
+ // of the fingers. Therefore, use the x and y values from the Down event
+ // so that the x and y values don't jump to the middle position.
+ // return true or all gestures for this beginning event will be discarded
+ logger.log(Level.INFO, "onScaleBegin");
+ scaleStartX = gestureDownX;
+ scaleStartY = gestureDownY;
+ TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.SCALE_START, scaleStartX, scaleStartY, 0f, 0f);
+ touchEvent.setPointerId(0);
+ touchEvent.setTime(scaleGestureDetector.getEventTime());
+ touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
+ touchEvent.setDeltaScaleSpan(0f);
+ touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
+ touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
+ processEvent(touchEvent);
+
+ return true;
+ }
+
+ public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
+ // return true or all gestures for this event will be accumulated
+ logger.log(Level.INFO, "onScale");
+ scaleStartX = gestureDownX;
+ scaleStartY = gestureDownY;
+ TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.SCALE_MOVE, scaleStartX, scaleStartY, 0f, 0f);
+ touchEvent.setPointerId(0);
+ touchEvent.setTime(scaleGestureDetector.getEventTime());
+ touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
+ touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
+ touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
+ touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
+ processEvent(touchEvent);
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
+ logger.log(Level.INFO, "onScaleEnd");
+ scaleStartX = gestureDownX;
+ scaleStartY = gestureDownY;
+ TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.SCALE_END, scaleStartX, scaleStartY, 0f, 0f);
+ touchEvent.setPointerId(0);
+ touchEvent.setTime(scaleGestureDetector.getEventTime());
+ touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
+ touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
+ touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
+ touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
+ processEvent(touchEvent);
+ }
+}
diff --git a/engine/src/android/com/jme3/input/android/AndroidInputHandler.java b/engine/src/android/com/jme3/input/android/AndroidInputHandler.java
new file mode 100644
index 000000000..8fd594030
--- /dev/null
+++ b/engine/src/android/com/jme3/input/android/AndroidInputHandler.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2009-2012 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.os.Build;
+import android.view.View;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.TouchInput;
+import com.jme3.input.event.InputEvent;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.input.event.MouseButtonEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import com.jme3.input.event.TouchEvent;
+import com.jme3.system.AppSettings;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * AndroidInput
is the main class that connects the Android system
+ * inputs to jME. It serves as the manager that gathers inputs from the various
+ * Android input methods and provides them to jME's InputManager
.
+ *
+ * @author iwgeric
+ */
+public class AndroidInputHandler implements TouchInput {
+ private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName());
+
+ // Custom settings
+ private boolean mouseEventsEnabled = true;
+ private boolean mouseEventsInvertX = false;
+ private boolean mouseEventsInvertY = false;
+ private boolean keyboardEventsEnabled = false;
+ private boolean joystickEventsEnabled = false;
+ private boolean dontSendHistory = false;
+
+
+ // Internal
+ private View view;
+ private AndroidTouchHandler touchHandler;
+ private AndroidKeyHandler keyHandler;
+ private AndroidGestureHandler gestureHandler;
+ private boolean initialized = false;
+ private RawInputListener listener = null;
+ private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue();
+ final private static int MAX_TOUCH_EVENTS = 1024;
+ private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS);
+
+ public AndroidInputHandler() {
+ int buildVersion = Build.VERSION.SDK_INT;
+ logger.log(Level.INFO, "Android Build Version: {0}", buildVersion);
+ if (buildVersion >= 14) {
+ // add support for onHover and GenericMotionEvent (ie. gamepads)
+ gestureHandler = new AndroidGestureHandler(this);
+ touchHandler = new AndroidTouchHandler14(this, gestureHandler);
+ keyHandler = new AndroidKeyHandler(this);
+ } else if (buildVersion >= 8){
+ gestureHandler = new AndroidGestureHandler(this);
+ touchHandler = new AndroidTouchHandler(this, gestureHandler);
+ keyHandler = new AndroidKeyHandler(this);
+ }
+ }
+
+ public AndroidInputHandler(AndroidTouchHandler touchInput,
+ AndroidKeyHandler keyInput, AndroidGestureHandler gestureHandler) {
+ this.touchHandler = touchInput;
+ this.keyHandler = keyInput;
+ this.gestureHandler = gestureHandler;
+ }
+
+ public void setView(View view) {
+ if (touchHandler != null) {
+ touchHandler.setView(view);
+ }
+ if (keyHandler != null) {
+ keyHandler.setView(view);
+ }
+ if (gestureHandler != null) {
+ gestureHandler.setView(view);
+ }
+ this.view = view;
+ }
+
+ public View getView() {
+ return view;
+ }
+
+ public float invertX(float origX) {
+ return view.getWidth()-origX;
+ }
+
+ public float invertY(float origY) {
+ return view.getHeight()-origY;
+ }
+
+ public void loadSettings(AppSettings settings) {
+ // TODO: add simulate keyboard to settings
+// keyboardEventsEnabled = true;
+ mouseEventsEnabled = settings.isEmulateMouse();
+ mouseEventsInvertX = settings.isEmulateMouseFlipX();
+ mouseEventsInvertY = settings.isEmulateMouseFlipY();
+ joystickEventsEnabled = settings.useJoysticks();
+ }
+
+ // -----------------------------------------
+ // JME3 Input interface
+ @Override
+ public void initialize() {
+ touchEventPool.initialize();
+ if (touchHandler != null) {
+ touchHandler.initialize();
+ }
+ if (keyHandler != null) {
+ keyHandler.initialize();
+ }
+ if (gestureHandler != null) {
+ gestureHandler.initialize();
+ }
+
+ initialized = true;
+ }
+
+ @Override
+ public void destroy() {
+ initialized = false;
+
+ touchEventPool.destroy();
+ if (touchHandler != null) {
+ touchHandler.destroy();
+ }
+ if (keyHandler != null) {
+ keyHandler.destroy();
+ }
+ if (gestureHandler != null) {
+ gestureHandler.destroy();
+ }
+
+ setView(null);
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return initialized;
+ }
+
+ @Override
+ public void setInputListener(RawInputListener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public long getInputTimeNanos() {
+ return System.nanoTime();
+ }
+
+ public void update() {
+ if (listener != null) {
+ InputEvent inputEvent;
+
+ while ((inputEvent = inputEventQueue.poll()) != null) {
+ if (inputEvent instanceof TouchEvent) {
+ listener.onTouchEvent((TouchEvent)inputEvent);
+ } else if (inputEvent instanceof MouseButtonEvent) {
+ listener.onMouseButtonEvent((MouseButtonEvent)inputEvent);
+ } else if (inputEvent instanceof MouseMotionEvent) {
+ listener.onMouseMotionEvent((MouseMotionEvent)inputEvent);
+ } else if (inputEvent instanceof KeyInputEvent) {
+ listener.onKeyEvent((KeyInputEvent)inputEvent);
+ }
+ }
+ }
+ }
+
+ // -----------------------------------------
+
+ public TouchEvent getFreeTouchEvent() {
+ return touchEventPool.getNextFreeEvent();
+ }
+
+ public void addEvent(InputEvent event) {
+ inputEventQueue.add(event);
+ if (event instanceof TouchEvent) {
+ touchEventPool.storeEvent((TouchEvent)event);
+ }
+ }
+
+ public void setSimulateMouse(boolean simulate) {
+ this.mouseEventsEnabled = simulate;
+ }
+
+ public boolean isSimulateMouse() {
+ return mouseEventsEnabled;
+ }
+
+ public boolean getSimulateMouse() {
+ return mouseEventsEnabled;
+ }
+
+ public boolean isMouseEventsInvertX() {
+ return mouseEventsInvertX;
+ }
+
+ public boolean isMouseEventsInvertY() {
+ return mouseEventsInvertY;
+ }
+
+ public void setSimulateKeyboard(boolean simulate) {
+ this.keyboardEventsEnabled = simulate;
+ }
+
+ public void setOmitHistoricEvents(boolean dontSendHistory) {
+ this.dontSendHistory = dontSendHistory;
+ }
+
+ public void showVirtualKeyboard(boolean visible) {
+ if (keyHandler != null) {
+ keyHandler.showVirtualKeyboard(visible);
+ }
+ }
+
+
+}
diff --git a/engine/src/android/com/jme3/input/android/AndroidKeyHandler.java b/engine/src/android/com/jme3/input/android/AndroidKeyHandler.java
new file mode 100644
index 000000000..ffd299d60
--- /dev/null
+++ b/engine/src/android/com/jme3/input/android/AndroidKeyHandler.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2009-2012 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.content.Context;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.input.event.TouchEvent;
+import java.util.logging.Logger;
+
+/**
+ * AndroidKeyHandler recieves onKey events from the Android system and creates
+ * the jME KeyEvents. onKey is used by Android to receive keys from the keyboard
+ * or device buttons. All key events are consumed by jME except for the Volume
+ * buttons and menu button.
+ *
+ * This class also provides the functionality to display or hide the soft keyboard
+ * for inputing single key events. Use OGLESContext to display an dialog to type
+ * in complete strings.
+ *
+ * @author iwgeric
+ */
+public class AndroidKeyHandler implements View.OnKeyListener {
+ private static final Logger logger = Logger.getLogger(AndroidKeyHandler.class.getName());
+
+ private AndroidInputHandler androidInput;
+ private boolean sendKeyEvents = true;
+
+ public AndroidKeyHandler(AndroidInputHandler androidInput) {
+ this.androidInput = androidInput;
+ }
+
+ public void initialize() {
+ }
+
+ public void destroy() {
+ }
+
+ public void setView(View view) {
+ if (view != null) {
+ view.setOnKeyListener(this);
+ } else {
+ androidInput.getView().setOnKeyListener(null);
+ }
+ }
+
+ /**
+ * onKey gets called from android thread on key events
+ */
+ public boolean onKey(View view, int keyCode, KeyEvent event) {
+ if (androidInput.isInitialized() && view != androidInput.getView()) {
+ return false;
+ }
+
+ TouchEvent evt;
+ // TODO: get touch event from pool
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ evt = new TouchEvent();
+ evt.set(TouchEvent.Type.KEY_DOWN);
+ evt.setKeyCode(keyCode);
+ evt.setCharacters(event.getCharacters());
+ evt.setTime(event.getEventTime());
+
+ // Send the event
+ androidInput.addEvent(evt);
+
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ evt = new TouchEvent();
+ evt.set(TouchEvent.Type.KEY_UP);
+ evt.setKeyCode(keyCode);
+ evt.setCharacters(event.getCharacters());
+ evt.setTime(event.getEventTime());
+
+ // Send the event
+ androidInput.addEvent(evt);
+
+ }
+
+
+ KeyInputEvent kie;
+ char unicodeChar = (char)event.getUnicodeChar();
+ int jmeKeyCode = AndroidKeyMapping.getJmeKey(keyCode);
+
+ boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
+ boolean repeating = pressed && event.getRepeatCount() > 0;
+
+ kie = new KeyInputEvent(jmeKeyCode, unicodeChar, pressed, repeating);
+ kie.setTime(event.getEventTime());
+ androidInput.addEvent(kie);
+// logger.log(Level.FINE, "onKey keyCode: {0}, jmeKeyCode: {1}, pressed: {2}, repeating: {3}",
+// new Object[]{keyCode, jmeKeyCode, pressed, repeating});
+// logger.log(Level.FINE, "creating KeyInputEvent: {0}", kie);
+
+ // consume all keys ourself except Volume Up/Down and Menu
+ // Don't do Menu so that typical Android Menus can be created and used
+ // by the user in MainActivity
+ if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) ||
+ (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) ||
+ (keyCode == KeyEvent.KEYCODE_MENU)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public void showVirtualKeyboard (final boolean visible) {
+ androidInput.getView().getHandler().post(new Runnable() {
+
+ public void run() {
+ InputMethodManager manager =
+ (InputMethodManager)androidInput.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ if (visible) {
+ manager.showSoftInput(androidInput.getView(), 0);
+ sendKeyEvents = true;
+ } else {
+ manager.hideSoftInputFromWindow(androidInput.getView().getWindowToken(), 0);
+ sendKeyEvents = false;
+ }
+ }
+ });
+ }
+
+}
diff --git a/engine/src/android/com/jme3/input/android/AndroidKeyMapping.java b/engine/src/android/com/jme3/input/android/AndroidKeyMapping.java
new file mode 100644
index 000000000..e99d8e9fc
--- /dev/null
+++ b/engine/src/android/com/jme3/input/android/AndroidKeyMapping.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2009-2012 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 com.jme3.input.KeyInput;
+import java.util.logging.Logger;
+
+/**
+ * AndroidKeyMapping is just a utility to convert the Android keyCodes into
+ * jME KeyCodes received in jME's KeyEvent will match between Desktop and Android.
+ *
+ * @author iwgeric
+ */
+public class AndroidKeyMapping {
+ private static final Logger logger = Logger.getLogger(AndroidKeyMapping.class.getName());
+
+ private static final int[] ANDROID_TO_JME = {
+ 0x0, // unknown
+ 0x0, // key code soft left
+ 0x0, // key code soft right
+ KeyInput.KEY_HOME,
+ KeyInput.KEY_ESCAPE, // key back
+ 0x0, // key call
+ 0x0, // key endcall
+ KeyInput.KEY_0,
+ KeyInput.KEY_1,
+ KeyInput.KEY_2,
+ KeyInput.KEY_3,
+ KeyInput.KEY_4,
+ KeyInput.KEY_5,
+ KeyInput.KEY_6,
+ KeyInput.KEY_7,
+ KeyInput.KEY_8,
+ KeyInput.KEY_9,
+ KeyInput.KEY_MULTIPLY,
+ 0x0, // key pound
+ KeyInput.KEY_UP,
+ KeyInput.KEY_DOWN,
+ KeyInput.KEY_LEFT,
+ KeyInput.KEY_RIGHT,
+ KeyInput.KEY_RETURN, // dpad center
+ 0x0, // volume up
+ 0x0, // volume down
+ KeyInput.KEY_POWER, // power (?)
+ 0x0, // camera
+ 0x0, // clear
+ KeyInput.KEY_A,
+ KeyInput.KEY_B,
+ KeyInput.KEY_C,
+ KeyInput.KEY_D,
+ KeyInput.KEY_E,
+ KeyInput.KEY_F,
+ KeyInput.KEY_G,
+ KeyInput.KEY_H,
+ KeyInput.KEY_I,
+ KeyInput.KEY_J,
+ KeyInput.KEY_K,
+ KeyInput.KEY_L,
+ KeyInput.KEY_M,
+ KeyInput.KEY_N,
+ KeyInput.KEY_O,
+ KeyInput.KEY_P,
+ KeyInput.KEY_Q,
+ KeyInput.KEY_R,
+ KeyInput.KEY_S,
+ KeyInput.KEY_T,
+ KeyInput.KEY_U,
+ KeyInput.KEY_V,
+ KeyInput.KEY_W,
+ KeyInput.KEY_X,
+ KeyInput.KEY_Y,
+ KeyInput.KEY_Z,
+ KeyInput.KEY_COMMA,
+ KeyInput.KEY_PERIOD,
+ KeyInput.KEY_LMENU,
+ KeyInput.KEY_RMENU,
+ KeyInput.KEY_LSHIFT,
+ KeyInput.KEY_RSHIFT,
+ // 0x0, // fn
+ // 0x0, // cap (?)
+
+ KeyInput.KEY_TAB,
+ KeyInput.KEY_SPACE,
+ 0x0, // sym (?) symbol
+ 0x0, // explorer
+ 0x0, // envelope
+ KeyInput.KEY_RETURN, // newline/enter
+ KeyInput.KEY_BACK, //used to be KeyInput.KEY_DELETE,
+ KeyInput.KEY_GRAVE,
+ KeyInput.KEY_MINUS,
+ KeyInput.KEY_EQUALS,
+ KeyInput.KEY_LBRACKET,
+ KeyInput.KEY_RBRACKET,
+ KeyInput.KEY_BACKSLASH,
+ KeyInput.KEY_SEMICOLON,
+ KeyInput.KEY_APOSTROPHE,
+ KeyInput.KEY_SLASH,
+ KeyInput.KEY_AT, // at (@)
+ KeyInput.KEY_NUMLOCK, //0x0, // num
+ 0x0, //headset hook
+ 0x0, //focus
+ KeyInput.KEY_ADD,
+ KeyInput.KEY_LMETA, //menu
+ 0x0,//notification
+ 0x0,//search
+ 0x0,//media play/pause
+ 0x0,//media stop
+ 0x0,//media next
+ 0x0,//media previous
+ 0x0,//media rewind
+ 0x0,//media fastforward
+ 0x0,//mute
+ };
+
+ public static int getJmeKey(int androidKey) {
+ return ANDROID_TO_JME[androidKey];
+ }
+
+}
diff --git a/engine/src/android/com/jme3/input/android/AndroidTouchHandler.java b/engine/src/android/com/jme3/input/android/AndroidTouchHandler.java
new file mode 100644
index 000000000..a5d2d85ed
--- /dev/null
+++ b/engine/src/android/com/jme3/input/android/AndroidTouchHandler.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2009-2012 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 android.view.View;
+import com.jme3.input.event.InputEvent;
+import com.jme3.input.event.MouseButtonEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import com.jme3.input.event.TouchEvent;
+import static com.jme3.input.event.TouchEvent.Type.DOWN;
+import static com.jme3.input.event.TouchEvent.Type.MOVE;
+import static com.jme3.input.event.TouchEvent.Type.UP;
+import com.jme3.math.Vector2f;
+import java.util.HashMap;
+import java.util.logging.Logger;
+
+/**
+ * AndroidTouchHandler is the base class that receives touch inputs from the
+ * Android system and creates the TouchEvents for jME. This class is designed
+ * to handle the base touch events for Android rev 9 (Android 2.3). This is
+ * extended by other classes to add features that were introducted after
+ * Android rev 9.
+ *
+ * @author iwgeric
+ */
+public class AndroidTouchHandler implements View.OnTouchListener {
+ private static final Logger logger = Logger.getLogger(AndroidTouchHandler.class.getName());
+
+ final private HashMap lastPositions = new HashMap();
+
+ protected int numPointers = 0;
+
+ protected AndroidInputHandler androidInput;
+ protected AndroidGestureHandler gestureHandler;
+
+ public AndroidTouchHandler(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) {
+ this.androidInput = androidInput;
+ this.gestureHandler = gestureHandler;
+ }
+
+ public void initialize() {
+ }
+
+ public void destroy() {
+ setView(null);
+ }
+
+ public void setView(View view) {
+ if (view != null) {
+ view.setOnTouchListener(this);
+ } else {
+ androidInput.getView().setOnTouchListener(null);
+ }
+ }
+
+ protected int getPointerIndex(MotionEvent event) {
+ return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ protected int getPointerId(MotionEvent event) {
+ return event.getPointerId(getPointerIndex(event));
+ }
+
+ protected int getAction(MotionEvent event) {
+ return event.getAction() & MotionEvent.ACTION_MASK;
+ }
+
+ /**
+ * onTouch gets called from android thread on touch events
+ */
+ public boolean onTouch(View view, MotionEvent event) {
+ if (!androidInput.isInitialized() || view != androidInput.getView()) {
+ return false;
+ }
+
+ boolean bWasHandled = false;
+ TouchEvent touch = null;
+ // System.out.println("native : " + event.getAction());
+ int action = getAction(event);
+ int pointerIndex = getPointerIndex(event);
+ int pointerId = getPointerId(event);
+ Vector2f lastPos = lastPositions.get(pointerId);
+
+ numPointers = event.getPointerCount();
+
+ // final int historySize = event.getHistorySize();
+ //final int pointerCount = event.getPointerCount();
+ switch (getAction(event)) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_DOWN:
+ touch = androidInput.getFreeTouchEvent();
+ touch.set(TouchEvent.Type.DOWN, event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex)), 0, 0);
+ touch.setPointerId(pointerId);
+ touch.setTime(event.getEventTime());
+ touch.setPressure(event.getPressure(pointerIndex));
+
+ lastPos = new Vector2f(event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex)));
+ lastPositions.put(pointerId, lastPos);
+
+ processEvent(touch);
+
+ bWasHandled = true;
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ touch = androidInput.getFreeTouchEvent();
+ touch.set(TouchEvent.Type.UP, event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex)), 0, 0);
+ touch.setPointerId(pointerId);
+ touch.setTime(event.getEventTime());
+ touch.setPressure(event.getPressure(pointerIndex));
+ lastPositions.remove(pointerId);
+
+ processEvent(touch);
+
+ bWasHandled = true;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Convert all pointers into events
+ for (int p = 0; p < event.getPointerCount(); p++) {
+ lastPos = lastPositions.get(event.getPointerId(p));
+ if (lastPos == null) {
+ lastPos = new Vector2f(event.getX(p), androidInput.invertY(event.getY(p)));
+ lastPositions.put(event.getPointerId(p), lastPos);
+ }
+
+ float dX = event.getX(p) - lastPos.x;
+ float dY = androidInput.invertY(event.getY(p)) - lastPos.y;
+ if (dX != 0 || dY != 0) {
+ touch = androidInput.getFreeTouchEvent();
+ touch.set(TouchEvent.Type.MOVE, event.getX(p), androidInput.invertY(event.getY(p)), dX, dY);
+ touch.setPointerId(event.getPointerId(p));
+ touch.setTime(event.getEventTime());
+ touch.setPressure(event.getPressure(p));
+ lastPos.set(event.getX(p), androidInput.invertY(event.getY(p)));
+
+ processEvent(touch);
+
+ bWasHandled = true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_OUTSIDE:
+ break;
+
+ }
+
+ // Try to detect gestures
+ if (gestureHandler != null) {
+ gestureHandler.detectGesture(event);
+ }
+
+ return bWasHandled;
+ }
+
+ protected void processEvent(TouchEvent event) {
+ // Add the touch event
+ androidInput.addEvent(event);
+ // MouseEvents do not support multi-touch, so only evaluate 1 finger pointer events
+ if (androidInput.isSimulateMouse() && numPointers == 1) {
+ InputEvent mouseEvent = generateMouseEvent(event);
+ if (mouseEvent != null) {
+ // Add the mouse event
+ androidInput.addEvent(mouseEvent);
+ }
+ }
+
+ }
+
+ // TODO: Ring Buffer for mouse events?
+ protected InputEvent generateMouseEvent(TouchEvent event) {
+ InputEvent inputEvent = null;
+ int newX;
+ int newY;
+ int newDX;
+ int newDY;
+
+ if (androidInput.isMouseEventsInvertX()) {
+ newX = (int) (androidInput.invertX(event.getX()));
+ newDX = (int)event.getDeltaX() * -1;
+ } else {
+ newX = (int) event.getX();
+ newDX = (int)event.getDeltaX();
+ }
+
+ if (androidInput.isMouseEventsInvertY()) {
+ newY = (int) (androidInput.invertY(event.getY()));
+ newDY = (int)event.getDeltaY() * -1;
+ } else {
+ newY = (int) event.getY();
+ newDY = (int)event.getDeltaY();
+ }
+
+ switch (event.getType()) {
+ case DOWN:
+ // Handle mouse down event
+ inputEvent = new MouseButtonEvent(0, true, newX, newY);
+ inputEvent.setTime(event.getTime());
+ break;
+
+ case UP:
+ // Handle mouse up event
+ inputEvent = new MouseButtonEvent(0, false, newX, newY);
+ inputEvent.setTime(event.getTime());
+ break;
+
+ case HOVER_MOVE:
+ case MOVE:
+ inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan());
+ inputEvent.setTime(event.getTime());
+ break;
+ }
+
+ return inputEvent;
+ }
+
+}
diff --git a/engine/src/android/com/jme3/input/android/AndroidTouchHandler14.java b/engine/src/android/com/jme3/input/android/AndroidTouchHandler14.java
new file mode 100644
index 000000000..1736ea66b
--- /dev/null
+++ b/engine/src/android/com/jme3/input/android/AndroidTouchHandler14.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2009-2012 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 android.view.View;
+import com.jme3.input.event.TouchEvent;
+import com.jme3.math.Vector2f;
+import java.util.HashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * AndroidTouchHandler14 is an extension of AndroidTouchHander that adds the
+ * Android touch event functionality between Android rev 9 (Android 2.3) and
+ * Android rev 14 (Android 4.0).
+ *
+ * @author iwgeric
+ */
+public class AndroidTouchHandler14 extends AndroidTouchHandler implements
+ View.OnHoverListener {
+ private static final Logger logger = Logger.getLogger(AndroidTouchHandler14.class.getName());
+ final private HashMap lastHoverPositions = new HashMap();
+
+ public AndroidTouchHandler14(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) {
+ super(androidInput, gestureHandler);
+ }
+
+ @Override
+ public void setView(View view) {
+ if (view != null) {
+ view.setOnHoverListener(this);
+ } else {
+ androidInput.getView().setOnHoverListener(null);
+ }
+ super.setView(view);
+ }
+
+ public boolean onHover(View view, MotionEvent event) {
+ if (view == null || view != androidInput.getView()) {
+ return false;
+ }
+
+ boolean consumed = false;
+ int action = getAction(event);
+ int pointerId = getPointerId(event);
+ int pointerIndex = getPointerIndex(event);
+ Vector2f lastPos = lastHoverPositions.get(pointerId);
+
+ numPointers = event.getPointerCount();
+
+ logger.log(Level.INFO, "onHover pointerId: {0}, action: {1}, x: {2}, y: {3}, numPointers: {4}",
+ new Object[]{pointerId, action, event.getX(), event.getY(), event.getPointerCount()});
+
+ TouchEvent touchEvent;
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.HOVER_START, event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex)), 0, 0);
+ touchEvent.setPointerId(pointerId);
+ touchEvent.setTime(event.getEventTime());
+ touchEvent.setPressure(event.getPressure(pointerIndex));
+
+ lastPos = new Vector2f(event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex)));
+ lastHoverPositions.put(pointerId, lastPos);
+
+ processEvent(touchEvent);
+ consumed = true;
+ break;
+ case MotionEvent.ACTION_HOVER_MOVE:
+ // Convert all pointers into events
+ for (int p = 0; p < event.getPointerCount(); p++) {
+ lastPos = lastHoverPositions.get(event.getPointerId(p));
+ if (lastPos == null) {
+ lastPos = new Vector2f(event.getX(p), androidInput.invertY(event.getY(p)));
+ lastHoverPositions.put(event.getPointerId(p), lastPos);
+ }
+
+ float dX = event.getX(p) - lastPos.x;
+ float dY = androidInput.invertY(event.getY(p)) - lastPos.y;
+ if (dX != 0 || dY != 0) {
+ touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.HOVER_MOVE, event.getX(p), androidInput.invertY(event.getY(p)), dX, dY);
+ touchEvent.setPointerId(event.getPointerId(p));
+ touchEvent.setTime(event.getEventTime());
+ touchEvent.setPressure(event.getPressure(p));
+ lastPos.set(event.getX(p), androidInput.invertY(event.getY(p)));
+
+ processEvent(touchEvent);
+
+ }
+ }
+ consumed = true;
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.HOVER_END, event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex)), 0, 0);
+ touchEvent.setPointerId(pointerId);
+ touchEvent.setTime(event.getEventTime());
+ touchEvent.setPressure(event.getPressure(pointerIndex));
+ lastHoverPositions.remove(pointerId);
+
+ processEvent(touchEvent);
+ consumed = true;
+ break;
+ default:
+ consumed = false;
+ break;
+ }
+
+ return consumed;
+ }
+
+}
diff --git a/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java b/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java
deleted file mode 100644
index c8b222832..000000000
--- a/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.jme3.input.android;
-
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import com.jme3.input.RawInputListener;
-import com.jme3.input.event.TouchEvent;
-
-/**
- * AndroidTouchInputListener is an inputlistener interface which defines
- * callbacks/events for android touch screens For use with class AndroidInput
- *
- * @author larynx
- *
- */
-public interface AndroidTouchInputListener extends RawInputListener {
-
- public void onTouchEvent(TouchEvent evt);
-
- public void onMotionEvent(MotionEvent evt);
-
- public void onAndroidKeyEvent(KeyEvent evt);
-}
diff --git a/engine/src/android/com/jme3/input/android/TouchEventPool.java b/engine/src/android/com/jme3/input/android/TouchEventPool.java
new file mode 100644
index 000000000..400a3bd38
--- /dev/null
+++ b/engine/src/android/com/jme3/input/android/TouchEventPool.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2009-2012 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 com.jme3.input.event.TouchEvent;
+import com.jme3.util.RingBuffer;
+import java.util.logging.Logger;
+
+/**
+ * TouchEventPool provides a RingBuffer of jME TouchEvents to help with garbage
+ * collection on Android. Each TouchEvent is stored in the RingBuffer and is
+ * reused if the TouchEvent has been consumed.
+ *
+ * If a TouchEvent has not been consumed, it is placed back into the pool at the
+ * end for later use. If a TouchEvent has been consumed, it is reused to avoid
+ * creating lots of little objects.
+ *
+ * If the pool is full of unconsumed events, then a new event is created and provided.
+ *
+ *
+ * @author iwgeric
+ */
+public class TouchEventPool {
+ private static final Logger logger = Logger.getLogger(TouchEventPool.class.getName());
+ private final RingBuffer eventPool;
+ private final int maxEvents;
+
+ public TouchEventPool (int maxEvents) {
+ eventPool = new RingBuffer(maxEvents);
+ this.maxEvents = maxEvents;
+ }
+
+ public void initialize() {
+ TouchEvent newEvent;
+ while (!eventPool.isEmpty()) {
+ eventPool.pop();
+ }
+ for (int i = 0; i < maxEvents; i++) {
+ newEvent = new TouchEvent();
+ newEvent.setConsumed();
+ eventPool.push(newEvent);
+ }
+ }
+
+ public void destroy() {
+ // Clean up queues
+ while (!eventPool.isEmpty()) {
+ eventPool.pop();
+ }
+ }
+
+ /**
+ * Fetches a touch event from the reuse pool
+ *
+ * @return a usable TouchEvent
+ */
+ public TouchEvent getNextFreeEvent() {
+ TouchEvent evt = null;
+ int curSize = eventPool.size();
+ while (curSize > 0) {
+ evt = (TouchEvent)eventPool.pop();
+ if (evt.isConsumed()) {
+ break;
+ } else {
+ eventPool.push(evt);
+ evt = null;
+ }
+ curSize--;
+ }
+
+ if (evt == null) {
+ logger.warning("eventPool full of unconsumed events");
+ evt = new TouchEvent();
+ }
+ return evt;
+ }
+
+ /**
+ * Stores the TouchEvent back in the pool for later reuse. It is only reused
+ * if the TouchEvent has been consumed.
+ *
+ * @param event TouchEvent to store for later use if consumed.
+ */
+ public void storeEvent(TouchEvent event) {
+ if (eventPool.size() < maxEvents) {
+ eventPool.push(event);
+ } else {
+ logger.warning("eventPool full");
+ }
+ }
+
+}