gamepad: Add an Android mapping function for Stadia Controller
The default mapping function maps all buttons and axes correctly except for the Assistant and Capture buttons. This CL adds a new mapping function that maps these buttons correctly. On Linux, HID Button usages are mapped to scancodes in the BTN_GAMEPAD and BTN_TRIGGER_HAPPY ranges. Android translates the BTN_GAMEPAD scancodes into KeyEvent.KEYCODE_BUTTON_* keycodes, but BTN_TRIGGER_HAPPY scancodes do not have equivalent Android keycodes and are translated to KeyEvent.KEYCODE_UNKNOWN. This CL allows KeyEvents with BTN_TRIGGER_HAPPY scancodes to be handled as if they were generic gamepad keys. Bug: 1208042 Change-Id: Ie3b6850206cce43ec0d4dbd25d325673c9a2cf11 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2907058 Commit-Queue: Matt Reynolds <mattreynolds@chromium.org> Reviewed-by: James Hollyer <jameshollyer@chromium.org> Cr-Commit-Position: refs/heads/master@{#885748}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
3bfae5c832
commit
76734f014a
device/gamepad/android
java
src
org
chromium
junit
src
org
chromium
device
gamepad
@ -33,6 +33,11 @@ class GamepadDevice {
|
||||
// Allow for devices that have more buttons than the Standard Gamepad.
|
||||
static final int MAX_BUTTON_INDEX = CanonicalButtonIndex.COUNT;
|
||||
|
||||
// Minimum and maximum scancodes for extra gamepad buttons. Android does not assign KeyEvent
|
||||
// keycodes for these buttons.
|
||||
static final int MIN_BTN_TRIGGER_HAPPY = 0x2c0;
|
||||
static final int MAX_BTN_TRIGGER_HAPPY = 0x2cf;
|
||||
|
||||
/** Keycodes which might be mapped by {@link GamepadMappings}. Keep sorted by keycode. */
|
||||
@VisibleForTesting
|
||||
static final int RELEVANT_KEYCODES[] = {
|
||||
@ -76,7 +81,12 @@ class GamepadDevice {
|
||||
// should correspond to "down" or "right".
|
||||
private final float[] mAxisValues = new float[CanonicalAxisIndex.COUNT];
|
||||
|
||||
private final float[] mButtonsValues = new float[MAX_BUTTON_INDEX + 1];
|
||||
// Array of values for all buttons of the gamepad. All button values must be
|
||||
// linearly normalized to the range [0.0 .. 1.0]. 0.0 should correspond to
|
||||
// a neutral, unpressed state and 1.0 should correspond to a pressed state.
|
||||
// Allocate enough room for all Standard Gamepad buttons plus two extra
|
||||
// buttons.
|
||||
private final float[] mButtonsValues = new float[MAX_BUTTON_INDEX + 2];
|
||||
|
||||
// When the user agent recognizes the attached inputDevice, it is recommended
|
||||
// that it be remapped to a canonical ordering when possible. Devices that are
|
||||
@ -221,9 +231,18 @@ class GamepadDevice {
|
||||
* @return True if the key event from the gamepad device has been consumed.
|
||||
*/
|
||||
public boolean handleKeyEvent(KeyEvent event) {
|
||||
// Ignore event if it is not for standard gamepad key.
|
||||
if (!GamepadList.isGamepadEvent(event)) return false;
|
||||
// Extra gamepad and joystick buttons use Linux scancodes starting from BTN_TRIGGER_HAPPY
|
||||
// but don't have specific Android keycodes and are mapped as KEYCODE_UNKNOWN. Handle the
|
||||
// first 16 extra buttons as if they had KEYCODE_BUTTON_# keycodes.
|
||||
int keyCode = event.getKeyCode();
|
||||
int scanCode = event.getScanCode();
|
||||
if (keyCode == KeyEvent.KEYCODE_UNKNOWN && scanCode >= MIN_BTN_TRIGGER_HAPPY
|
||||
&& scanCode <= MAX_BTN_TRIGGER_HAPPY) {
|
||||
keyCode = KeyEvent.KEYCODE_BUTTON_1 + scanCode - MIN_BTN_TRIGGER_HAPPY;
|
||||
}
|
||||
|
||||
// Ignore the event if it is not for a gamepad key.
|
||||
if (!GamepadList.isGamepadEvent(event)) return false;
|
||||
assert keyCode < MAX_RAW_BUTTON_VALUES;
|
||||
// Button value 0.0 must mean fully unpressed, and 1.0 must mean fully pressed.
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
|
@ -289,8 +289,17 @@ public class GamepadList {
|
||||
case KeyEvent.KEYCODE_MEDIA_RECORD:
|
||||
return true;
|
||||
default:
|
||||
return KeyEvent.isGamepadButton(keyCode);
|
||||
break;
|
||||
}
|
||||
|
||||
// If the scancode is in the BTN_TRIGGER_HAPPY range it is an extra gamepad button.
|
||||
int scanCode = event.getScanCode();
|
||||
if (keyCode == KeyEvent.KEYCODE_UNKNOWN && scanCode >= GamepadDevice.MIN_BTN_TRIGGER_HAPPY
|
||||
&& scanCode <= GamepadDevice.MAX_BTN_TRIGGER_HAPPY) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return KeyEvent.isGamepadButton(keyCode);
|
||||
}
|
||||
|
||||
@CalledByNative
|
||||
|
@ -53,6 +53,11 @@ abstract class GamepadMappings {
|
||||
@VisibleForTesting
|
||||
static final int SNAKEBYTE_IDROIDCON_PRODUCT_ID = 0x8502;
|
||||
|
||||
@VisibleForTesting
|
||||
static final int GOOGLE_VENDOR_ID = 0x18d1;
|
||||
@VisibleForTesting
|
||||
static final int STADIA_CONTROLLER_PRODUCT_ID = 0x9400;
|
||||
|
||||
private static final float BUTTON_AXIS_DEADZONE = 0.01f;
|
||||
|
||||
public static GamepadMappings getMappings(InputDevice device, int[] axes, BitSet buttons) {
|
||||
@ -104,6 +109,9 @@ abstract class GamepadMappings {
|
||||
if (vendorId == BROADCOM_VENDOR_ID && productId == SNAKEBYTE_IDROIDCON_PRODUCT_ID) {
|
||||
return new SnakebyteIDroidConMappings(axes);
|
||||
}
|
||||
if (vendorId == GOOGLE_VENDOR_ID && productId == STADIA_CONTROLLER_PRODUCT_ID) {
|
||||
return new StadiaControllerMappings();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -587,6 +595,36 @@ abstract class GamepadMappings {
|
||||
}
|
||||
}
|
||||
|
||||
private static class StadiaControllerMappings extends GamepadMappings {
|
||||
private static final int BUTTON_INDEX_ASSISTANT = CanonicalButtonIndex.COUNT;
|
||||
private static final int BUTTON_INDEX_CAPTURE = CanonicalButtonIndex.COUNT + 1;
|
||||
/**
|
||||
* Method for mapping Stadia Controller axis and button values to
|
||||
* standard gamepad button and axes values.
|
||||
*/
|
||||
@Override
|
||||
public void mapToStandardGamepad(
|
||||
float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons) {
|
||||
mapCommonXYABButtons(mappedButtons, rawButtons);
|
||||
mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
|
||||
mapPedalAxesToBottomShoulder(mappedButtons, rawAxes);
|
||||
mapCommonThumbstickButtons(mappedButtons, rawButtons);
|
||||
mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
|
||||
mapHatAxisToDpadButtons(mappedButtons, rawAxes);
|
||||
mappedButtons[BUTTON_INDEX_ASSISTANT] = rawButtons[KeyEvent.KEYCODE_BUTTON_1];
|
||||
mappedButtons[BUTTON_INDEX_CAPTURE] = rawButtons[KeyEvent.KEYCODE_BUTTON_2];
|
||||
|
||||
mapXYAxes(mappedAxes, rawAxes);
|
||||
mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getButtonsLength() {
|
||||
// Include the Assistant and Capture buttons.
|
||||
return CanonicalButtonIndex.COUNT + 2;
|
||||
}
|
||||
}
|
||||
|
||||
private static class UnknownGamepadMappings extends GamepadMappings {
|
||||
private int mLeftTriggerAxis = -1;
|
||||
private int mRightTriggerAxis = -1;
|
||||
|
@ -49,7 +49,7 @@ public class GamepadMappingsTest {
|
||||
* Set bits indicate that we don't expect the axis at mMappedAxes[index] to be mapped.
|
||||
*/
|
||||
private BitSet mUnmappedAxes = new BitSet(CanonicalAxisIndex.COUNT);
|
||||
private float[] mMappedButtons = new float[CanonicalButtonIndex.COUNT];
|
||||
private float[] mMappedButtons = new float[CanonicalButtonIndex.COUNT + 2];
|
||||
private float[] mMappedAxes = new float[CanonicalAxisIndex.COUNT];
|
||||
private float[] mRawButtons = new float[GamepadDevice.MAX_RAW_BUTTON_VALUES];
|
||||
private float[] mRawAxes = new float[GamepadDevice.MAX_RAW_AXIS_VALUES];
|
||||
@ -560,6 +560,43 @@ public class GamepadMappingsTest {
|
||||
assertMapping(mappings);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Feature({"Gamepad"})
|
||||
public void testStadiaControllerMappings() {
|
||||
int[] axes = {
|
||||
MotionEvent.AXIS_X,
|
||||
MotionEvent.AXIS_Y,
|
||||
MotionEvent.AXIS_Z,
|
||||
MotionEvent.AXIS_RZ,
|
||||
MotionEvent.AXIS_HAT_X,
|
||||
MotionEvent.AXIS_HAT_Y,
|
||||
MotionEvent.AXIS_GAS,
|
||||
MotionEvent.AXIS_BRAKE,
|
||||
};
|
||||
GamepadMappings mappings = GamepadMappings.getMappings(GamepadMappings.GOOGLE_VENDOR_ID,
|
||||
GamepadMappings.STADIA_CONTROLLER_PRODUCT_ID, axes);
|
||||
mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);
|
||||
|
||||
assertMappedCommonXYABButtons();
|
||||
assertMappedTriggerButtonsToTopShoulder();
|
||||
assertMappedPedalAxesToBottomShoulder();
|
||||
assertMappedCommonStartSelectMetaButtons();
|
||||
assertMappedCommonThumbstickButtons();
|
||||
assertMappedHatAxisToDpadButtons();
|
||||
assertMappedXYAxes();
|
||||
assertMappedZAndRZAxesToRightStick();
|
||||
|
||||
// The Assistant and Capture buttons should be mapped after the last
|
||||
// Standard Gamepad button index.
|
||||
Assert.assertEquals(mappings.getButtonsLength(), CanonicalButtonIndex.COUNT + 2);
|
||||
Assert.assertEquals(mMappedButtons[CanonicalButtonIndex.COUNT],
|
||||
mRawButtons[KeyEvent.KEYCODE_BUTTON_1], ERROR_TOLERANCE);
|
||||
Assert.assertEquals(mMappedButtons[CanonicalButtonIndex.COUNT + 1],
|
||||
mRawButtons[KeyEvent.KEYCODE_BUTTON_2], ERROR_TOLERANCE);
|
||||
|
||||
assertMapping(mappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the current gamepad mapping being tested matches the shield mappings.
|
||||
*/
|
||||
|
Reference in New Issue
Block a user