/* * Copyright (c) 2009-2015 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.InputDevice; import android.view.InputDevice.MotionRange; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import com.jme3.input.AbstractJoystick; import com.jme3.input.DefaultJoystickAxis; import com.jme3.input.DefaultJoystickButton; import com.jme3.input.InputManager; import com.jme3.input.JoyInput; import com.jme3.input.Joystick; import com.jme3.input.JoystickAxis; import com.jme3.input.JoystickButton; import com.jme3.input.JoystickCompatibilityMappings; import com.jme3.input.event.JoyAxisEvent; import com.jme3.input.event.JoyButtonEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * Main class that creates and manages Android inputs for physical gamepads/joysticks. * * @author iwgeric */ public class AndroidJoystickJoyInput14 { private static final Logger logger = Logger.getLogger(AndroidJoystickJoyInput14.class.getName()); private boolean loaded = false; private AndroidJoyInput joyInput; private Map<Integer, AndroidJoystick> joystickIndex = new HashMap<Integer, AndroidJoystick>(); private static int[] AndroidGamepadButtons = { // Dpad buttons KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_CENTER, // pressing joystick down KeyEvent.KEYCODE_BUTTON_THUMBL, KeyEvent.KEYCODE_BUTTON_THUMBR, // buttons KeyEvent.KEYCODE_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, KeyEvent.KEYCODE_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, // buttons on back of device KeyEvent.KEYCODE_BUTTON_L1, KeyEvent.KEYCODE_BUTTON_R1, KeyEvent.KEYCODE_BUTTON_L2, KeyEvent.KEYCODE_BUTTON_R2, // start / select buttons KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_BUTTON_MODE, }; public AndroidJoystickJoyInput14(AndroidJoyInput joyInput) { this.joyInput = joyInput; } public void pauseJoysticks() { } public void resumeJoysticks() { } public void destroy() { } public List<Joystick> loadJoysticks(int joyId, InputManager inputManager) { logger.log(Level.INFO, "loading Joystick devices"); ArrayList<Joystick> joysticks = new ArrayList<Joystick>(); joysticks.clear(); joystickIndex.clear(); ArrayList gameControllerDeviceIds = new ArrayList(); int[] deviceIds = InputDevice.getDeviceIds(); for (int deviceId : deviceIds) { InputDevice dev = InputDevice.getDevice(deviceId); int sources = dev.getSources(); logger.log(Level.FINE, "deviceId[{0}] sources: {1}", new Object[]{deviceId, sources}); // Verify that the device has gamepad buttons, control sticks, or both. if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) { // This device is a game controller. Store its device ID. if (!gameControllerDeviceIds.contains(deviceId)) { gameControllerDeviceIds.add(deviceId); logger.log(Level.FINE, "Attempting to create joystick for device: {0}", dev); // Create an AndroidJoystick and store the InputDevice so we // can later correspond the input from the InputDevice to the // appropriate jME Joystick event AndroidJoystick joystick = new AndroidJoystick(inputManager, joyInput, dev, joyId+joysticks.size(), dev.getName()); joystickIndex.put(deviceId, joystick); joysticks.add(joystick); // Each analog input is reported as a MotionRange // The axis number corresponds to the type of axis // The AndroidJoystick.addAxis(MotionRange) converts the axis // type reported by Android into the jME Joystick axis List<MotionRange> motionRanges = dev.getMotionRanges(); for (MotionRange motionRange: motionRanges) { logger.log(Level.INFO, "motion range: {0}", motionRange.toString()); logger.log(Level.INFO, "axis: {0}", motionRange.getAxis()); JoystickAxis axis = joystick.addAxis(motionRange); logger.log(Level.INFO, "added axis: {0}", axis); } // InputDevice has a method for determining if a keyCode is // supported (InputDevice public boolean[] hasKeys (int... keys)). // But this method wasn't added until rev 19 (Android 4.4) // Therefore, we only can query the entire device and see if // any InputDevice supports the keyCode. This may result in // buttons being configured that don't exist on the specific // device, but I haven't found a better way yet. for (int keyCode: AndroidGamepadButtons) { logger.log(Level.INFO, "button[{0}]: {1}", new Object[]{keyCode, KeyCharacterMap.deviceHasKey(keyCode)}); if (KeyCharacterMap.deviceHasKey(keyCode)) { // add button even though we aren't sure if the button // actually exists on this InputDevice logger.log(Level.INFO, "button[{0}] exists somewhere", keyCode); JoystickButton button = joystick.addButton(keyCode); logger.log(Level.INFO, "added button: {0}", button); } } } } } loaded = true; return joysticks; } public boolean onGenericMotion(MotionEvent event) { boolean consumed = false; // logger.log(Level.INFO, "onGenericMotion event: {0}", event); event.getDeviceId(); event.getSource(); // logger.log(Level.INFO, "deviceId: {0}, source: {1}", new Object[]{event.getDeviceId(), event.getSource()}); AndroidJoystick joystick = joystickIndex.get(event.getDeviceId()); if (joystick != null) { for (int androidAxis: joystick.getAndroidAxes()) { String axisName = MotionEvent.axisToString(androidAxis); float value = event.getAxisValue(androidAxis); int action = event.getAction(); if (action == MotionEvent.ACTION_MOVE) { // logger.log(Level.INFO, "MOVE axis num: {0}, axisName: {1}, value: {2}", // new Object[]{androidAxis, axisName, value}); JoystickAxis axis = joystick.getAxis(androidAxis); if (axis != null) { // logger.log(Level.INFO, "MOVE axis num: {0}, axisName: {1}, value: {2}, deadzone: {3}", // new Object[]{androidAxis, axisName, value, axis.getDeadZone()}); JoyAxisEvent axisEvent = new JoyAxisEvent(axis, value); joyInput.addEvent(axisEvent); consumed = true; } else { // logger.log(Level.INFO, "axis was null for axisName: {0}", axisName); } } else { // logger.log(Level.INFO, "action: {0}", action); } } } return consumed; } public boolean onKey(KeyEvent event) { boolean consumed = false; // logger.log(Level.INFO, "onKey event: {0}", event); event.getDeviceId(); event.getSource(); AndroidJoystick joystick = joystickIndex.get(event.getDeviceId()); if (joystick != null) { JoystickButton button = joystick.getButton(event.getKeyCode()); boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN; if (button != null) { JoyButtonEvent buttonEvent = new JoyButtonEvent(button, pressed); joyInput.addEvent(buttonEvent); consumed = true; } else { JoystickButton newButton = joystick.addButton(event.getKeyCode()); JoyButtonEvent buttonEvent = new JoyButtonEvent(newButton, pressed); joyInput.addEvent(buttonEvent); consumed = true; } } return consumed; } protected class AndroidJoystick extends AbstractJoystick { private JoystickAxis nullAxis; private InputDevice device; private JoystickAxis xAxis; private JoystickAxis yAxis; private JoystickAxis povX; private JoystickAxis povY; private Map<Integer, JoystickAxis> axisIndex = new HashMap<Integer, JoystickAxis>(); private Map<Integer, JoystickButton> buttonIndex = new HashMap<Integer, JoystickButton>(); public AndroidJoystick( InputManager inputManager, JoyInput joyInput, InputDevice device, int joyId, String name ) { super( inputManager, joyInput, joyId, name ); this.device = device; this.nullAxis = new DefaultJoystickAxis( getInputManager(), this, -1, "Null", "null", false, false, 0 ); this.xAxis = nullAxis; this.yAxis = nullAxis; this.povX = nullAxis; this.povY = nullAxis; } protected JoystickAxis getAxis(int androidAxis) { return axisIndex.get(androidAxis); } protected Set<Integer> getAndroidAxes() { return axisIndex.keySet(); } protected JoystickButton getButton(int keyCode) { return buttonIndex.get(keyCode); } protected JoystickButton addButton( int keyCode ) { // logger.log(Level.FINE, "Adding button: {0}", keyCode); String name = KeyEvent.keyCodeToString(keyCode); String original = KeyEvent.keyCodeToString(keyCode); // A/B/X/Y buttons if (keyCode == KeyEvent.KEYCODE_BUTTON_Y) { original = JoystickButton.BUTTON_0; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_B) { original = JoystickButton.BUTTON_1; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_A) { original = JoystickButton.BUTTON_2; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_X) { original = JoystickButton.BUTTON_3; // Front buttons Some of these have the top ones and the bottoms ones flipped. } else if (keyCode == KeyEvent.KEYCODE_BUTTON_L1) { original = JoystickButton.BUTTON_4; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_R1) { original = JoystickButton.BUTTON_5; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_L2) { original = JoystickButton.BUTTON_6; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_R2) { original = JoystickButton.BUTTON_7; // // Dpad buttons // } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { // original = JoystickButton.BUTTON_8; // } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { // original = JoystickButton.BUTTON_9; // } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { // original = JoystickButton.BUTTON_8; // } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { // original = JoystickButton.BUTTON_9; // Select and start buttons } else if (keyCode == KeyEvent.KEYCODE_BUTTON_SELECT) { original = JoystickButton.BUTTON_8; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_START) { original = JoystickButton.BUTTON_9; // Joystick push buttons } else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBL) { original = JoystickButton.BUTTON_10; } else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBR) { original = JoystickButton.BUTTON_11; } String logicalId = JoystickCompatibilityMappings.remapComponent( getName(), original ); if( logicalId == null ? original != null : !logicalId.equals(original) ) { logger.log(Level.FINE, "Remapped: {0} to: {1}", new Object[]{original, logicalId}); } JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(), name, logicalId ); addButton(button); buttonIndex.put( keyCode, button ); return button; } protected JoystickAxis addAxis(MotionRange motionRange) { String name = MotionEvent.axisToString(motionRange.getAxis()); String original = MotionEvent.axisToString(motionRange.getAxis()); if (motionRange.getAxis() == MotionEvent.AXIS_X) { original = JoystickAxis.X_AXIS; } else if (motionRange.getAxis() == MotionEvent.AXIS_Y) { original = JoystickAxis.Y_AXIS; } else if (motionRange.getAxis() == MotionEvent.AXIS_Z) { original = JoystickAxis.Z_AXIS; } else if (motionRange.getAxis() == MotionEvent.AXIS_RZ) { original = JoystickAxis.Z_ROTATION; } else if (motionRange.getAxis() == MotionEvent.AXIS_HAT_X) { original = JoystickAxis.POV_X; } else if (motionRange.getAxis() == MotionEvent.AXIS_HAT_Y) { original = JoystickAxis.POV_Y; } String logicalId = JoystickCompatibilityMappings.remapComponent( getName(), original ); if( logicalId == null ? original != null : !logicalId.equals(original) ) { logger.log(Level.FINE, "Remapped: {0} to: {1}", new Object[]{original, logicalId}); } JoystickAxis axis = new DefaultJoystickAxis(getInputManager(), this, getAxisCount(), name, logicalId, true, true, motionRange.getFlat()); if (motionRange.getAxis() == MotionEvent.AXIS_X) { xAxis = axis; } if (motionRange.getAxis() == MotionEvent.AXIS_Y) { yAxis = axis; } if (motionRange.getAxis() == MotionEvent.AXIS_HAT_X) { povX = axis; } if (motionRange.getAxis() == MotionEvent.AXIS_HAT_Y) { povY = axis; } addAxis(axis); axisIndex.put(motionRange.getAxis(), axis); return axis; } @Override public JoystickAxis getXAxis() { return xAxis; } @Override public JoystickAxis getYAxis() { return yAxis; } @Override public JoystickAxis getPovXAxis() { return povX; } @Override public JoystickAxis getPovYAxis() { return povY; } @Override public int getXAxisIndex(){ return xAxis.getAxisId(); } @Override public int getYAxisIndex(){ return yAxis.getAxisId(); } } }