Commit 7ae51a81 authored by Andrew Walbran's avatar Andrew Walbran Committed by Commit Bot

[gamepad] Add standard mapping for Snakebyte iDroid:con on Android.

This adds support for the analog mode, handles thumbstick buttons which
give incorrect keycodes on old versions of Android, and marks the
mapping as standard.

The Android mapping of the thumbstick buttons was fixed in
https://android-review.googlesource.com/c/platform/frameworks/base/+/1331927,
so the fixed keycodes are also supported.

BUG=1073130

Change-Id: Id14e3c7f5d5b35aa565d374860257d1f0960abbd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2245482
Commit-Queue: Andrew Walbran <qwandor@google.com>
Reviewed-by: default avatarMatt Reynolds <mattreynolds@chromium.org>
Cr-Commit-Position: refs/heads/master@{#784513}
parent 73801ec2
...@@ -46,12 +46,21 @@ abstract class GamepadMappings { ...@@ -46,12 +46,21 @@ abstract class GamepadMappings {
@VisibleForTesting @VisibleForTesting
static final int XBOX_ONE_S_2016_FIRMWARE_PRODUCT_ID = 0x02e0; static final int XBOX_ONE_S_2016_FIRMWARE_PRODUCT_ID = 0x02e0;
@VisibleForTesting
static final int BROADCOM_VENDOR_ID = 0x0a5c;
@VisibleForTesting
static final int SNAKEBYTE_IDROIDCON_PRODUCT_ID = 0x8502;
private static final float BUTTON_AXIS_DEADZONE = 0.01f;
public static GamepadMappings getMappings(InputDevice device, int[] axes, BitSet buttons) { public static GamepadMappings getMappings(InputDevice device, int[] axes, BitSet buttons) {
GamepadMappings mappings = null; GamepadMappings mappings = null;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mappings = getMappings(device.getProductId(), device.getVendorId()); mappings = getMappings(device.getProductId(), device.getVendorId(), axes);
}
if (mappings == null) {
mappings = getMappings(device.getName());
} }
if (mappings == null) mappings = getMappings(device.getName());
if (mappings == null) { if (mappings == null) {
mappings = new UnknownGamepadMappings(axes, buttons); mappings = new UnknownGamepadMappings(axes, buttons);
} }
...@@ -60,7 +69,7 @@ abstract class GamepadMappings { ...@@ -60,7 +69,7 @@ abstract class GamepadMappings {
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
@VisibleForTesting @VisibleForTesting
static GamepadMappings getMappings(int productId, int vendorId) { static GamepadMappings getMappings(int productId, int vendorId, int[] axes) {
// Device name of a DualShock 4 gamepad is "Wireless Controller". This is not reliably // Device name of a DualShock 4 gamepad is "Wireless Controller". This is not reliably
// unique so we better go by the product and vendor ids. // unique so we better go by the product and vendor ids.
if (vendorId == PS_DUALSHOCK_4_VENDOR_ID if (vendorId == PS_DUALSHOCK_4_VENDOR_ID
...@@ -84,6 +93,9 @@ abstract class GamepadMappings { ...@@ -84,6 +93,9 @@ abstract class GamepadMappings {
&& productId == XBOX_ONE_S_2016_FIRMWARE_PRODUCT_ID) { && productId == XBOX_ONE_S_2016_FIRMWARE_PRODUCT_ID) {
return new XboxOneS2016FirmwareMappings(); return new XboxOneS2016FirmwareMappings();
} }
if (vendorId == BROADCOM_VENDOR_ID && productId == SNAKEBYTE_IDROIDCON_PRODUCT_ID) {
return new SnakebyteIDroidConMappings(axes);
}
return null; return null;
} }
...@@ -225,6 +237,12 @@ abstract class GamepadMappings { ...@@ -225,6 +237,12 @@ abstract class GamepadMappings {
mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = rTrigger; mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = rTrigger;
} }
private static void mapZAxisToBottomShoulder(float[] mappedButtons, float[] rawAxes) {
float z = rawAxes[MotionEvent.AXIS_Z];
mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = z > BUTTON_AXIS_DEADZONE ? z : 0.0f;
mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = -z > BUTTON_AXIS_DEADZONE ? -z : 0.0f;
}
private static void mapLowerTriggerButtonsToBottomShoulder(float[] mappedButtons, private static void mapLowerTriggerButtonsToBottomShoulder(float[] mappedButtons,
float[] rawButtons) { float[] rawButtons) {
float l2 = rawButtons[KeyEvent.KEYCODE_BUTTON_L2]; float l2 = rawButtons[KeyEvent.KEYCODE_BUTTON_L2];
...@@ -294,6 +312,58 @@ abstract class GamepadMappings { ...@@ -294,6 +312,58 @@ abstract class GamepadMappings {
} }
} }
private static class SnakebyteIDroidConMappings extends GamepadMappings {
private final boolean mAnalogMode;
public SnakebyteIDroidConMappings(int[] axes) {
// Digital mode has X, Y, Z, RZ, HAT_X, HAT_Y
// Analog mode has X, Y, Z, RX, RY, HAT_X, HAT_Y
mAnalogMode = arrayContains(axes, MotionEvent.AXIS_RX);
}
private static boolean arrayContains(int[] array, int element) {
for (int e : array) {
if (e == element) {
return true;
}
}
return false;
}
@Override
public int getButtonsLength() {
// No meta button.
return CanonicalButtonIndex.COUNT - 1;
}
@Override
public void mapToStandardGamepad(
float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons) {
mapCommonXYABButtons(mappedButtons, rawButtons);
mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
mapXYAxes(mappedAxes, rawAxes);
mapHatAxisToDpadButtons(mappedButtons, rawAxes);
// On older versions of Android the thumbstick buttons are incorrectly mapped to C and
// Z. Support either.
float thumbL = rawButtons[KeyEvent.KEYCODE_BUTTON_THUMBL];
float thumbR = rawButtons[KeyEvent.KEYCODE_BUTTON_THUMBR];
float c = rawButtons[KeyEvent.KEYCODE_BUTTON_C];
float z = rawButtons[KeyEvent.KEYCODE_BUTTON_Z];
mappedButtons[CanonicalButtonIndex.LEFT_THUMBSTICK] = Math.max(thumbL, c);
mappedButtons[CanonicalButtonIndex.RIGHT_THUMBSTICK] = Math.max(thumbR, z);
if (mAnalogMode) {
mapZAxisToBottomShoulder(mappedButtons, rawAxes);
mapRXAndRYAxesToRightStick(mappedAxes, rawAxes);
} else {
mapLowerTriggerButtonsToBottomShoulder(mappedButtons, rawButtons);
mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
}
}
}
private static class XboxOneS2016FirmwareMappings extends GamepadMappings { private static class XboxOneS2016FirmwareMappings extends GamepadMappings {
private boolean mLeftTriggerActivated; private boolean mLeftTriggerActivated;
private boolean mRightTriggerActivated; private boolean mRightTriggerActivated;
......
...@@ -361,9 +361,19 @@ public class GamepadMappingsTest { ...@@ -361,9 +361,19 @@ public class GamepadMappingsTest {
@Test @Test
@Feature({"Gamepad"}) @Feature({"Gamepad"})
public void testPS4GamepadMappings() { public void testPS4GamepadMappings() {
int[] axes;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
axes = new int[] {MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z,
MotionEvent.AXIS_RZ, MotionEvent.AXIS_LTRIGGER, MotionEvent.AXIS_RTRIGGER,
MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y};
} else {
axes = new int[] {MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z,
MotionEvent.AXIS_RX, MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ,
MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y};
}
GamepadMappings mappings = GamepadMappings mappings =
GamepadMappings.getMappings(GamepadMappings.PS_DUALSHOCK_4_PRODUCT_ID, GamepadMappings.getMappings(GamepadMappings.PS_DUALSHOCK_4_PRODUCT_ID,
GamepadMappings.PS_DUALSHOCK_4_VENDOR_ID); GamepadMappings.PS_DUALSHOCK_4_VENDOR_ID, axes);
mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons); mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
...@@ -423,9 +433,12 @@ public class GamepadMappingsTest { ...@@ -423,9 +433,12 @@ public class GamepadMappingsTest {
@Feature({"Gamepad"}) @Feature({"Gamepad"})
public void testXboxOneSBluetooth2016FirmwareMappings() { public void testXboxOneSBluetooth2016FirmwareMappings() {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
int[] axes = new int[] {MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z,
MotionEvent.AXIS_RZ, MotionEvent.AXIS_LTRIGGER, MotionEvent.AXIS_RTRIGGER,
MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y};
GamepadMappings mappings = GamepadMappings mappings =
GamepadMappings.getMappings(GamepadMappings.XBOX_ONE_S_2016_FIRMWARE_PRODUCT_ID, GamepadMappings.getMappings(GamepadMappings.XBOX_ONE_S_2016_FIRMWARE_PRODUCT_ID,
GamepadMappings.XBOX_ONE_S_2016_FIRMWARE_VENDOR_ID); GamepadMappings.XBOX_ONE_S_2016_FIRMWARE_VENDOR_ID, axes);
mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons); mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);
Assert.assertEquals(mMappedButtons[CanonicalButtonIndex.PRIMARY], Assert.assertEquals(mMappedButtons[CanonicalButtonIndex.PRIMARY],
...@@ -475,8 +488,11 @@ public class GamepadMappingsTest { ...@@ -475,8 +488,11 @@ public class GamepadMappingsTest {
// Test that Xbox One S gamepads with updated firmware connected over Bluetooth use the // Test that Xbox One S gamepads with updated firmware connected over Bluetooth use the
// default mapping. // default mapping.
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
GamepadMappings deviceIdMappings = GamepadMappings.getMappings( int[] axes = new int[] {MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z,
XBOX_ONE_S_PRODUCT_ID, GamepadMappings.XBOX_ONE_S_2016_FIRMWARE_VENDOR_ID); MotionEvent.AXIS_RZ, MotionEvent.AXIS_LTRIGGER, MotionEvent.AXIS_RTRIGGER,
MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y};
GamepadMappings deviceIdMappings = GamepadMappings.getMappings(XBOX_ONE_S_PRODUCT_ID,
GamepadMappings.XBOX_ONE_S_2016_FIRMWARE_VENDOR_ID, axes);
Assert.assertNull(deviceIdMappings); Assert.assertNull(deviceIdMappings);
} }
...@@ -484,6 +500,74 @@ public class GamepadMappingsTest { ...@@ -484,6 +500,74 @@ public class GamepadMappingsTest {
Assert.assertNull(deviceNameMappings); Assert.assertNull(deviceNameMappings);
} }
@Test
@Feature({"Gamepad"})
public void testIDroidConGamepadMappingsDigital() {
int[] axes = {
MotionEvent.AXIS_X,
MotionEvent.AXIS_Y,
MotionEvent.AXIS_Z,
MotionEvent.AXIS_RZ,
MotionEvent.AXIS_HAT_X,
MotionEvent.AXIS_HAT_Y,
};
GamepadMappings mappings =
GamepadMappings.getMappings(GamepadMappings.SNAKEBYTE_IDROIDCON_PRODUCT_ID,
GamepadMappings.BROADCOM_VENDOR_ID, axes);
mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);
expectNoMetaButton(mappings);
assertMappedCommonXYABButtons();
assertMappedTriggerButtonsToTopShoulder();
assertMappedCommonThumbstickButtons();
assertMappedLowerTriggerButtonsToBottomShoulder();
assertMappedHatAxisToDpadButtons();
assertMappedXYAxes();
assertMappedZAndRZAxesToRightStick();
Assert.assertEquals(mMappedButtons[CanonicalButtonIndex.START],
mRawButtons[KeyEvent.KEYCODE_BUTTON_START], ERROR_TOLERANCE);
Assert.assertEquals(mMappedButtons[CanonicalButtonIndex.BACK_SELECT],
mRawButtons[KeyEvent.KEYCODE_BUTTON_SELECT], ERROR_TOLERANCE);
assertMapping(mappings);
}
@Test
@Feature({"Gamepad"})
public void testIDroidConGamepadMappingsAnalog() {
int[] axes = {
MotionEvent.AXIS_X,
MotionEvent.AXIS_Y,
MotionEvent.AXIS_Z,
MotionEvent.AXIS_RX,
MotionEvent.AXIS_RY,
MotionEvent.AXIS_HAT_X,
MotionEvent.AXIS_HAT_Y,
};
GamepadMappings mappings =
GamepadMappings.getMappings(GamepadMappings.SNAKEBYTE_IDROIDCON_PRODUCT_ID,
GamepadMappings.BROADCOM_VENDOR_ID, axes);
mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);
expectNoMetaButton(mappings);
assertMappedCommonXYABButtons();
assertMappedTriggerButtonsToTopShoulder();
assertMappedCommonThumbstickButtons();
assertMappedHatAxisToDpadButtons();
assertMappedXYAxes();
assertMappedRXAndRYAxesToRightStick();
Assert.assertEquals(mMappedButtons[CanonicalButtonIndex.START],
mRawButtons[KeyEvent.KEYCODE_BUTTON_START], ERROR_TOLERANCE);
Assert.assertEquals(mMappedButtons[CanonicalButtonIndex.BACK_SELECT],
mRawButtons[KeyEvent.KEYCODE_BUTTON_SELECT], ERROR_TOLERANCE);
Assert.assertEquals(
mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER], 0.0, ERROR_TOLERANCE);
Assert.assertEquals(mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER],
-mRawAxes[MotionEvent.AXIS_Z], ERROR_TOLERANCE);
assertMapping(mappings);
}
/** /**
* Asserts that the current gamepad mapping being tested matches the shield mappings. * Asserts that the current gamepad mapping being tested matches the shield mappings.
*/ */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment