From 60c58fd0816dba41877c8165f6af4a881914fb3b Mon Sep 17 00:00:00 2001 From: "iwg..ic" Date: Fri, 22 Nov 2013 13:33:30 +0000 Subject: [PATCH] Android: Add new extendable input system to organize various Android inputs so that they can be extended to support new Android input functionality when the OS running the app supports it. Not activated yet. Just adding the supporting classes for now. git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10907 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../input/android/AndroidGestureHandler.java | 336 ++++++++++++++++++ .../input/android/AndroidInputHandler.java | 252 +++++++++++++ .../jme3/input/android/AndroidKeyHandler.java | 156 ++++++++ .../jme3/input/android/AndroidKeyMapping.java | 149 ++++++++ .../input/android/AndroidTouchHandler.java | 249 +++++++++++++ .../input/android/AndroidTouchHandler14.java | 144 ++++++++ .../android/AndroidTouchInputListener.java | 22 -- .../jme3/input/android/TouchEventPool.java | 121 +++++++ 8 files changed, 1407 insertions(+), 22 deletions(-) create mode 100644 engine/src/android/com/jme3/input/android/AndroidGestureHandler.java create mode 100644 engine/src/android/com/jme3/input/android/AndroidInputHandler.java create mode 100644 engine/src/android/com/jme3/input/android/AndroidKeyHandler.java create mode 100644 engine/src/android/com/jme3/input/android/AndroidKeyMapping.java create mode 100644 engine/src/android/com/jme3/input/android/AndroidTouchHandler.java create mode 100644 engine/src/android/com/jme3/input/android/AndroidTouchHandler14.java delete mode 100644 engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java create mode 100644 engine/src/android/com/jme3/input/android/TouchEventPool.java 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"); + } + } + +}