Android: Handle duplicate axis, and fix known bad axis.
Android reports the same physical axis multiple times for analog triggers, and this handles this case. There are also some controllers with broken mappings (eg the analog triggers on a PS4 DualShock 4). These axis don't center correctly. There are also some controllers (again, the PS4) that send both a button press and an axis movement. This ignores the buttons so we can use the analog axis. Otherwise, since the button comes before the axis moves far we would always take the button.
This commit is contained in:
parent
d9d4bd7eef
commit
6787fcb712
|
@ -39,6 +39,7 @@ import org.dolphinemu.dolphinemu.fragments.SaveLoadStateFragment;
|
||||||
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
|
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
|
||||||
import org.dolphinemu.dolphinemu.ui.platform.Platform;
|
import org.dolphinemu.dolphinemu.ui.platform.Platform;
|
||||||
import org.dolphinemu.dolphinemu.utils.Animations;
|
import org.dolphinemu.dolphinemu.utils.Animations;
|
||||||
|
import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper;
|
||||||
import org.dolphinemu.dolphinemu.utils.Java_GCAdapter;
|
import org.dolphinemu.dolphinemu.utils.Java_GCAdapter;
|
||||||
import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter;
|
import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter;
|
||||||
import org.dolphinemu.dolphinemu.utils.Log;
|
import org.dolphinemu.dolphinemu.utils.Log;
|
||||||
|
@ -57,6 +58,7 @@ public final class EmulationActivity extends AppCompatActivity
|
||||||
private EmulationFragment mEmulationFragment;
|
private EmulationFragment mEmulationFragment;
|
||||||
|
|
||||||
private SharedPreferences mPreferences;
|
private SharedPreferences mPreferences;
|
||||||
|
private ControllerMappingHelper mControllerMappingHelper;
|
||||||
|
|
||||||
// So that MainActivity knows which view to invalidate before the return animation.
|
// So that MainActivity knows which view to invalidate before the return animation.
|
||||||
private int mPosition;
|
private int mPosition;
|
||||||
|
@ -164,6 +166,7 @@ public final class EmulationActivity extends AppCompatActivity
|
||||||
mScreenPath = gameToEmulate.getStringExtra("ScreenPath");
|
mScreenPath = gameToEmulate.getStringExtra("ScreenPath");
|
||||||
mPosition = gameToEmulate.getIntExtra("GridPosition", -1);
|
mPosition = gameToEmulate.getIntExtra("GridPosition", -1);
|
||||||
mDeviceHasTouchScreen = getPackageManager().hasSystemFeature("android.hardware.touchscreen");
|
mDeviceHasTouchScreen = getPackageManager().hasSystemFeature("android.hardware.touchscreen");
|
||||||
|
mControllerMappingHelper = new ControllerMappingHelper();
|
||||||
|
|
||||||
int themeId;
|
int themeId;
|
||||||
if (mDeviceHasTouchScreen)
|
if (mDeviceHasTouchScreen)
|
||||||
|
@ -729,7 +732,10 @@ public final class EmulationActivity extends AppCompatActivity
|
||||||
|
|
||||||
for (InputDevice.MotionRange range : motions)
|
for (InputDevice.MotionRange range : motions)
|
||||||
{
|
{
|
||||||
NativeLibrary.onGamePadMoveEvent(input.getDescriptor(), range.getAxis(), event.getAxisValue(range.getAxis()));
|
int axis = range.getAxis();
|
||||||
|
float origValue = event.getAxisValue(axis);
|
||||||
|
float value = mControllerMappingHelper.scaleAxis(input, axis, origValue);
|
||||||
|
NativeLibrary.onGamePadMoveEvent(input.getDescriptor(), axis, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import org.dolphinemu.dolphinemu.model.settings.view.InputBindingSetting;
|
import org.dolphinemu.dolphinemu.model.settings.view.InputBindingSetting;
|
||||||
|
import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper;
|
||||||
import org.dolphinemu.dolphinemu.utils.Log;
|
import org.dolphinemu.dolphinemu.utils.Log;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -21,6 +22,7 @@ public final class MotionAlertDialog extends AlertDialog
|
||||||
{
|
{
|
||||||
// The selected input preference
|
// The selected input preference
|
||||||
private final InputBindingSetting setting;
|
private final InputBindingSetting setting;
|
||||||
|
private final ControllerMappingHelper mControllerMappingHelper;
|
||||||
private boolean mWaitingForEvent = true;
|
private boolean mWaitingForEvent = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,6 +36,7 @@ public final class MotionAlertDialog extends AlertDialog
|
||||||
super(context);
|
super(context);
|
||||||
|
|
||||||
this.setting = setting;
|
this.setting = setting;
|
||||||
|
this.mControllerMappingHelper = new ControllerMappingHelper();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean onKeyEvent(int keyCode, KeyEvent event)
|
public boolean onKeyEvent(int keyCode, KeyEvent event)
|
||||||
|
@ -42,8 +45,11 @@ public final class MotionAlertDialog extends AlertDialog
|
||||||
switch (event.getAction())
|
switch (event.getAction())
|
||||||
{
|
{
|
||||||
case KeyEvent.ACTION_DOWN:
|
case KeyEvent.ACTION_DOWN:
|
||||||
|
if (!mControllerMappingHelper.shouldKeyBeIgnored(event.getDevice(), keyCode))
|
||||||
|
{
|
||||||
saveKeyInput(event);
|
saveKeyInput(event);
|
||||||
|
}
|
||||||
|
// Even if we ignore the key, we still consume it. Thus return true regardless.
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -69,13 +75,15 @@ public final class MotionAlertDialog extends AlertDialog
|
||||||
{
|
{
|
||||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
|
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
|
||||||
return false;
|
return false;
|
||||||
|
if (event.getAction() != MotionEvent.ACTION_MOVE)
|
||||||
Log.debug("[MotionAlertDialog] Received motion event: " + event.getAction());
|
return false;
|
||||||
|
|
||||||
InputDevice input = event.getDevice();
|
InputDevice input = event.getDevice();
|
||||||
|
|
||||||
List<InputDevice.MotionRange> motionRanges = input.getMotionRanges();
|
List<InputDevice.MotionRange> motionRanges = input.getMotionRanges();
|
||||||
|
|
||||||
int numMovedAxis = 0;
|
int numMovedAxis = 0;
|
||||||
|
float axisMoveValue = 0.0f;
|
||||||
InputDevice.MotionRange lastMovedRange = null;
|
InputDevice.MotionRange lastMovedRange = null;
|
||||||
char lastMovedDir = '?';
|
char lastMovedDir = '?';
|
||||||
if (mWaitingForEvent)
|
if (mWaitingForEvent)
|
||||||
|
@ -84,14 +92,25 @@ public final class MotionAlertDialog extends AlertDialog
|
||||||
for (InputDevice.MotionRange range : motionRanges)
|
for (InputDevice.MotionRange range : motionRanges)
|
||||||
{
|
{
|
||||||
int axis = range.getAxis();
|
int axis = range.getAxis();
|
||||||
float value = event.getAxisValue(axis);
|
float origValue = event.getAxisValue(axis);
|
||||||
|
float value = mControllerMappingHelper.scaleAxis(input, axis, origValue);
|
||||||
if (Math.abs(value) > 0.5f)
|
if (Math.abs(value) > 0.5f)
|
||||||
{
|
{
|
||||||
|
// It is common to have multiple axis with the same physical input. For example,
|
||||||
|
// shoulder butters are provided as both AXIS_LTRIGGER and AXIS_BRAKE.
|
||||||
|
// To handle this, we ignore an axis motion that's the exact same as a motion
|
||||||
|
// we already saw. This way, we ignore axis with two names, but catch the case
|
||||||
|
// where a joystick is moved in two directions.
|
||||||
|
// ref: bottom of https://developer.android.com/training/game-controllers/controller-input.html
|
||||||
|
if (value != axisMoveValue)
|
||||||
|
{
|
||||||
|
axisMoveValue = value;
|
||||||
numMovedAxis++;
|
numMovedAxis++;
|
||||||
lastMovedRange = range;
|
lastMovedRange = range;
|
||||||
lastMovedDir = value < 0.0f ? '-' : '+';
|
lastMovedDir = value < 0.0f ? '-' : '+';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If only one axis moved, that's the winner.
|
// If only one axis moved, that's the winner.
|
||||||
if (numMovedAxis == 1)
|
if (numMovedAxis == 1)
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package org.dolphinemu.dolphinemu.utils;
|
||||||
|
|
||||||
|
import android.view.InputDevice;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
/** Some controllers have incorrect mappings. This class has special-case fixes for them. */
|
||||||
|
public class ControllerMappingHelper
|
||||||
|
{
|
||||||
|
/** Some controllers report extra button presses that can be ignored. */
|
||||||
|
public boolean shouldKeyBeIgnored(InputDevice inputDevice, int keyCode)
|
||||||
|
{
|
||||||
|
if (isDualShock4(inputDevice)) {
|
||||||
|
// The two analog triggers generate analog motion events as well as a keycode.
|
||||||
|
// We always prefer to use the analog values, so throw away the button press
|
||||||
|
// Even though the triggers are L/R2, without mappings they generate L/R1 events.
|
||||||
|
return keyCode == KeyEvent.KEYCODE_BUTTON_L1 || keyCode == KeyEvent.KEYCODE_BUTTON_R1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Scale an axis to be zero-centered with a proper range. */
|
||||||
|
public float scaleAxis(InputDevice inputDevice, int axis, float value)
|
||||||
|
{
|
||||||
|
if (isDualShock4(inputDevice))
|
||||||
|
{
|
||||||
|
// Android doesn't have correct mappings for this controller's triggers. It reports them
|
||||||
|
// as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
|
||||||
|
// Scale them to properly zero-centered with a range of [0.0, 1.0].
|
||||||
|
if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY)
|
||||||
|
{
|
||||||
|
return (value + 1) / 2.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isXboxOneWireless(inputDevice))
|
||||||
|
{
|
||||||
|
// Same as the DualShock 4, the mappings are missing.
|
||||||
|
if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ)
|
||||||
|
{
|
||||||
|
return (value + 1) / 2.0f;
|
||||||
|
}
|
||||||
|
if (axis == MotionEvent.AXIS_GENERIC_1)
|
||||||
|
{
|
||||||
|
// This axis is stuck at ~.5. Ignore it.
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDualShock4(InputDevice inputDevice)
|
||||||
|
{
|
||||||
|
// Sony DualShock 4 controller
|
||||||
|
return inputDevice.getVendorId() == 0x54c && inputDevice.getProductId() == 0x9cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isXboxOneWireless(InputDevice inputDevice)
|
||||||
|
{
|
||||||
|
// Microsoft Xbox One controller
|
||||||
|
return inputDevice.getVendorId() == 0x45e && inputDevice.getProductId() == 0x2e0;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue