Commit 983206f1 authored by mstrum's avatar mstrum Committed by Commit bot

Gamepad: Add support for the Amazon Fire Game Controller

Manual verification of the mapping was done using:
http://www.html5rocks.com/en/tutorials/doodles/gamepad/gamepad-tester/tester.html

This also adds tests to check for mapping regressions for all gamepads.

BUG=453178,373942
TEST=GamepadMappingsTest

Review URL: https://codereview.chromium.org/875813003

Cr-Commit-Position: refs/heads/master@{#313994}
parent 3e48c23a
...@@ -10,6 +10,8 @@ import android.view.InputDevice.MotionRange; ...@@ -10,6 +10,8 @@ import android.view.InputDevice.MotionRange;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import org.chromium.base.VisibleForTesting;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -17,6 +19,16 @@ import java.util.List; ...@@ -17,6 +19,16 @@ import java.util.List;
* Manages information related to each connected gamepad device. * Manages information related to each connected gamepad device.
*/ */
class GamepadDevice { class GamepadDevice {
// Axis ids are used as indices which are empirically always smaller than 256 so this allows
// us to create cheap associative arrays.
@VisibleForTesting
static final int MAX_RAW_AXIS_VALUES = 256;
// Keycodes are used as indices which are empirically always smaller than 256 so this allows
// us to create cheap associative arrays.
@VisibleForTesting
static final int MAX_RAW_BUTTON_VALUES = 256;
// An id for the gamepad. // An id for the gamepad.
private int mDeviceId; private int mDeviceId;
// The index of the gamepad in the Navigator. // The index of the gamepad in the Navigator.
...@@ -38,8 +50,8 @@ class GamepadDevice { ...@@ -38,8 +50,8 @@ class GamepadDevice {
// that it be remapped to a canonical ordering when possible. Devices that are // that it be remapped to a canonical ordering when possible. Devices that are
// not recognized should still be exposed in their raw form. Therefore we must // not recognized should still be exposed in their raw form. Therefore we must
// pass the raw Button and raw Axis values. // pass the raw Button and raw Axis values.
private final float[] mRawButtons = new float[256]; private final float[] mRawButtons = new float[MAX_RAW_BUTTON_VALUES];
private final float[] mRawAxes = new float[256]; private final float[] mRawAxes = new float[MAX_RAW_AXIS_VALUES];
// An identification string for the gamepad. // An identification string for the gamepad.
private String mDeviceName; private String mDeviceName;
...@@ -59,7 +71,7 @@ class GamepadDevice { ...@@ -59,7 +71,7 @@ class GamepadDevice {
for (MotionRange range : ranges) { for (MotionRange range : ranges) {
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
int axis = range.getAxis(); int axis = range.getAxis();
assert axis < 256; assert axis < MAX_RAW_AXIS_VALUES;
mAxes[i++] = axis; mAxes[i++] = axis;
} }
} }
...@@ -141,7 +153,7 @@ class GamepadDevice { ...@@ -141,7 +153,7 @@ class GamepadDevice {
// Ignore event if it is not for standard gamepad key. // Ignore event if it is not for standard gamepad key.
if (!GamepadList.isGamepadEvent(event)) return false; if (!GamepadList.isGamepadEvent(event)) return false;
int keyCode = event.getKeyCode(); int keyCode = event.getKeyCode();
assert keyCode < 256; assert keyCode < MAX_RAW_BUTTON_VALUES;
// Button value 0.0 must mean fully unpressed, and 1.0 must mean fully pressed. // Button value 0.0 must mean fully unpressed, and 1.0 must mean fully pressed.
if (event.getAction() == KeyEvent.ACTION_DOWN) { if (event.getAction() == KeyEvent.ACTION_DOWN) {
mRawButtons[keyCode] = 1.0f; mRawButtons[keyCode] = 1.0f;
......
// Copyright 2014 The Chromium Authors. All rights reserved. // Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
...@@ -8,17 +8,23 @@ import android.view.KeyEvent; ...@@ -8,17 +8,23 @@ import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import org.chromium.base.JNINamespace; import org.chromium.base.JNINamespace;
import org.chromium.base.VisibleForTesting;
/** /**
* Class to manage mapping information related to each supported gamepad controller device. * Class to manage mapping information related to each supported gamepad controller device.
*/ */
@JNINamespace("content") @JNINamespace("content")
class GamepadMappings { class GamepadMappings {
private static final String NVIDIA_SHIELD_DEVICE_NAME_PREFIX = @VisibleForTesting
"NVIDIA Corporation NVIDIA Controller"; static final String NVIDIA_SHIELD_DEVICE_NAME_PREFIX = "NVIDIA Corporation NVIDIA Controller";
private static final String MICROSOFT_XBOX_PAD_DEVICE_NAME = "Microsoft X-Box 360 pad"; @VisibleForTesting
private static final String PS3_SIXAXIS_DEVICE_NAME = "Sony PLAYSTATION(R)3 Controller"; static final String MICROSOFT_XBOX_PAD_DEVICE_NAME = "Microsoft X-Box 360 pad";
private static final String SAMSUNG_EI_GP20_DEVICE_NAME = "Samsung Game Pad EI-GP20"; @VisibleForTesting
static final String PS3_SIXAXIS_DEVICE_NAME = "Sony PLAYSTATION(R)3 Controller";
@VisibleForTesting
static final String SAMSUNG_EI_GP20_DEVICE_NAME = "Samsung Game Pad EI-GP20";
@VisibleForTesting
static final String AMAZON_FIRE_DEVICE_NAME = "Amazon Fire Game Controller";
public static boolean mapToStandardGamepad(float[] mappedAxes, float[] mappedButtons, public static boolean mapToStandardGamepad(float[] mappedAxes, float[] mappedButtons,
float[] rawAxes, float[] rawButtons, String deviceName) { float[] rawAxes, float[] rawButtons, String deviceName) {
...@@ -34,6 +40,9 @@ class GamepadMappings { ...@@ -34,6 +40,9 @@ class GamepadMappings {
} else if (deviceName.equals(SAMSUNG_EI_GP20_DEVICE_NAME)) { } else if (deviceName.equals(SAMSUNG_EI_GP20_DEVICE_NAME)) {
mapSamsungEIGP20Gamepad(mappedButtons, rawButtons, mappedAxes, rawAxes); mapSamsungEIGP20Gamepad(mappedButtons, rawButtons, mappedAxes, rawAxes);
return true; return true;
} else if (deviceName.equals(AMAZON_FIRE_DEVICE_NAME)) {
mapAmazonFireGamepad(mappedButtons, rawButtons, mappedAxes, rawAxes);
return true;
} }
mapUnknownGamepad(mappedButtons, rawButtons, mappedAxes, rawAxes); mapUnknownGamepad(mappedButtons, rawButtons, mappedAxes, rawAxes);
...@@ -115,6 +124,13 @@ class GamepadMappings { ...@@ -115,6 +124,13 @@ class GamepadMappings {
mappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER] = rTrigger; mappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER] = rTrigger;
} }
private static void mapPedalAxesToBottomShoulder(float[] mappedButtons, float[] rawAxes) {
float lTrigger = rawAxes[MotionEvent.AXIS_BRAKE];
float rTrigger = rawAxes[MotionEvent.AXIS_GAS];
mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = lTrigger;
mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = rTrigger;
}
private static void mapTriggerAxesToBottomShoulder(float[] mappedButtons, float[] rawAxes) { private static void mapTriggerAxesToBottomShoulder(float[] mappedButtons, float[] rawAxes) {
float lTrigger = rawAxes[MotionEvent.AXIS_LTRIGGER]; float lTrigger = rawAxes[MotionEvent.AXIS_LTRIGGER];
float rTrigger = rawAxes[MotionEvent.AXIS_RTRIGGER]; float rTrigger = rawAxes[MotionEvent.AXIS_RTRIGGER];
...@@ -122,11 +138,13 @@ class GamepadMappings { ...@@ -122,11 +138,13 @@ class GamepadMappings {
mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = rTrigger; mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = rTrigger;
} }
private static float negativeAxisValueAsButton(float input) { @VisibleForTesting
static float negativeAxisValueAsButton(float input) {
return (input < -0.5f) ? 1.f : 0.f; return (input < -0.5f) ? 1.f : 0.f;
} }
private static float positiveAxisValueAsButton(float input) { @VisibleForTesting
static float positiveAxisValueAsButton(float input) {
return (input > 0.5f) ? 1.f : 0.f; return (input > 0.5f) ? 1.f : 0.f;
} }
...@@ -139,6 +157,23 @@ class GamepadMappings { ...@@ -139,6 +157,23 @@ class GamepadMappings {
mappedButtons[CanonicalButtonIndex.DPAD_DOWN] = positiveAxisValueAsButton(hatY); mappedButtons[CanonicalButtonIndex.DPAD_DOWN] = positiveAxisValueAsButton(hatY);
} }
/**
* Method for mapping Amazon Fire gamepad axis and button values
* to standard gamepad button and axes values.
*/
private static void mapAmazonFireGamepad(
float[] mappedButtons, float[] rawButtons, float[] mappedAxes, float[] rawAxes) {
mapCommonXYABButtons(mappedButtons, rawButtons);
mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
mapCommonThumbstickButtons(mappedButtons, rawButtons);
mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
mapPedalAxesToBottomShoulder(mappedButtons, rawAxes);
mapHatAxisToDpadButtons(mappedButtons, rawAxes);
mapXYAxes(mappedAxes, rawAxes);
mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
}
/** /**
* Method for mapping Nvidia gamepad axis and button values * Method for mapping Nvidia gamepad axis and button values
* to standard gamepad button and axes values. * to standard gamepad button and axes values.
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser.input;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
import android.view.MotionEvent;
import org.chromium.base.test.util.Feature;
import java.util.Arrays;
import java.util.BitSet;
/**
* Verify no regressions in gamepad mappings.
*/
public class GamepadMappingsTest extends InstrumentationTestCase {
/**
* Set bits indicate that we don't expect the button at mMappedButtons[index] to be mapped.
*/
private BitSet mUnmappedButtons = new BitSet(CanonicalButtonIndex.COUNT);
/**
* 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[] 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];
@Override
protected void setUp() throws Exception {
super.setUp();
// By default, we expect every button and axis to be mapped.
mUnmappedButtons.clear();
mUnmappedAxes.clear();
// Start with all the mapped values as unmapped.
Arrays.fill(mMappedButtons, Float.NaN);
Arrays.fill(mMappedAxes, Float.NaN);
// Set each raw value to something unique.
for (int i = 0; i < GamepadDevice.MAX_RAW_AXIS_VALUES; i++) {
mRawAxes[i] = -i - 1.0f;
}
for (int i = 0; i < GamepadDevice.MAX_RAW_BUTTON_VALUES; i++) {
mRawButtons[i] = i + 1.0f;
}
}
@SmallTest
@Feature({"Gamepad"})
public void testShieldGamepadMappings() throws Exception {
GamepadMappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons,
GamepadMappings.NVIDIA_SHIELD_DEVICE_NAME_PREFIX);
assertShieldGamepadMappings();
}
@SmallTest
@Feature({"Gamepad"})
public void testXBox360GamepadMappings() throws Exception {
GamepadMappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons,
GamepadMappings.MICROSOFT_XBOX_PAD_DEVICE_NAME);
assertShieldGamepadMappings();
}
@SmallTest
@Feature({"Gamepad"})
public void testPS3SixAxisGamepadMappings() throws Exception {
GamepadMappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons,
GamepadMappings.PS3_SIXAXIS_DEVICE_NAME);
assertEquals(mMappedButtons[CanonicalButtonIndex.PRIMARY],
mRawButtons[KeyEvent.KEYCODE_BUTTON_X]);
assertEquals(mMappedButtons[CanonicalButtonIndex.SECONDARY],
mRawButtons[KeyEvent.KEYCODE_BUTTON_Y]);
assertEquals(mMappedButtons[CanonicalButtonIndex.TERTIARY],
mRawButtons[KeyEvent.KEYCODE_BUTTON_A]);
assertEquals(mMappedButtons[CanonicalButtonIndex.QUATERNARY],
mRawButtons[KeyEvent.KEYCODE_BUTTON_B]);
assertMappedCommonTriggerButtons();
assertMappedCommonThumbstickButtons();
assertMappedCommonDpadButtons();
assertMappedCommonStartSelectMetaButtons();
assertMappedTriggerAxexToShoulderButtons();
assertMappedXYAxes();
assertMappedZAndRZAxesToRightStick();
assertMapping();
}
@SmallTest
@Feature({"Gamepad"})
public void testSamsungEIGP20GamepadMappings() throws Exception {
GamepadMappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons,
GamepadMappings.SAMSUNG_EI_GP20_DEVICE_NAME);
assertMappedCommonXYABButtons();
assertMappedCommonTriggerButtons();
assertMappedCommonThumbstickButtons();
assertMappedCommonStartSelectMetaButtons();
assertMappedHatAxisToDpadButtons();
assertMappedXYAxes();
assertMappedRXAndRYAxesToRightStick();
expectNoShoulderButtons();
assertMapping();
}
@SmallTest
@Feature({"Gamepad"})
public void testAmazonFireGamepadMappings() throws Exception {
GamepadMappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons,
GamepadMappings.AMAZON_FIRE_DEVICE_NAME);
assertMappedCommonXYABButtons();
assertMappedPedalAxesToBottomShoulder();
assertMappedCommonThumbstickButtons();
assertMappedCommonStartSelectMetaButtons();
assertMappedTriggerButtonsToTopShoulder();
assertMappedHatAxisToDpadButtons();
assertMappedXYAxes();
assertMappedZAndRZAxesToRightStick();
assertMapping();
}
@SmallTest
@Feature({"Gamepad"})
public void testUnknownGamepadMappings() throws Exception {
GamepadMappings.mapToStandardGamepad(
mMappedAxes, mMappedButtons, mRawAxes, mRawButtons, "");
assertMappedCommonXYABButtons();
assertMappedCommonTriggerButtons();
assertMappedCommonThumbstickButtons();
assertMappedCommonStartSelectMetaButtons();
assertMappedTriggerAxexToShoulderButtons();
assertMappedCommonDpadButtons();
assertMappedXYAxes();
assertMappedRXAndRYAxesToRightStick();
assertMapping();
}
/**
* Asserts that the current gamepad mapping being tested matches the shield mappings.
*/
public void assertShieldGamepadMappings() {
assertMappedCommonXYABButtons();
assertMappedTriggerButtonsToTopShoulder();
assertMappedCommonThumbstickButtons();
assertMappedCommonStartSelectMetaButtons();
assertMappedTriggerAxesToBottomShoulder();
assertMappedHatAxisToDpadButtons();
assertMappedXYAxes();
assertMappedZAndRZAxesToRightStick();
assertMapping();
}
public void expectNoShoulderButtons() {
mUnmappedButtons.set(CanonicalButtonIndex.LEFT_SHOULDER);
mUnmappedButtons.set(CanonicalButtonIndex.RIGHT_SHOULDER);
}
public void assertMapping() {
for (int i = 0; i < mMappedAxes.length; i++) {
if (mUnmappedAxes.get(i)) {
assertTrue(
"An unexpected axis was mapped at index " + i, Float.isNaN(mMappedAxes[i]));
} else {
assertFalse("An axis was not mapped at index " + i, Float.isNaN(mMappedAxes[i]));
}
}
for (int i = 0; i < mMappedButtons.length; i++) {
if (mUnmappedButtons.get(i)) {
assertTrue("An unexpected button was mapped at index " + i,
Float.isNaN(mMappedButtons[i]));
} else {
assertFalse(
"A button was not mapped at index " + i, Float.isNaN(mMappedButtons[i]));
}
}
}
private void assertMappedCommonTriggerButtons() {
assertEquals(mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER],
mRawButtons[KeyEvent.KEYCODE_BUTTON_L1]);
assertEquals(mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER],
mRawButtons[KeyEvent.KEYCODE_BUTTON_R1]);
}
private void assertMappedCommonDpadButtons() {
assertEquals(mMappedButtons[CanonicalButtonIndex.DPAD_DOWN],
mRawButtons[KeyEvent.KEYCODE_DPAD_DOWN]);
assertEquals(mMappedButtons[CanonicalButtonIndex.DPAD_UP],
mRawButtons[KeyEvent.KEYCODE_DPAD_UP]);
assertEquals(mMappedButtons[CanonicalButtonIndex.DPAD_LEFT],
mRawButtons[KeyEvent.KEYCODE_DPAD_LEFT]);
assertEquals(mMappedButtons[CanonicalButtonIndex.DPAD_RIGHT],
mRawButtons[KeyEvent.KEYCODE_DPAD_RIGHT]);
}
private void assertMappedTriggerAxexToShoulderButtons() {
assertEquals(mMappedButtons[CanonicalButtonIndex.LEFT_SHOULDER],
mRawAxes[MotionEvent.AXIS_LTRIGGER]);
assertEquals(mMappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER],
mRawAxes[MotionEvent.AXIS_RTRIGGER]);
}
private void assertMappedTriggerButtonsToTopShoulder() {
assertEquals(mMappedButtons[CanonicalButtonIndex.LEFT_SHOULDER],
mRawButtons[KeyEvent.KEYCODE_BUTTON_L1]);
assertEquals(mMappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER],
mRawButtons[KeyEvent.KEYCODE_BUTTON_R1]);
}
private void assertMappedCommonXYABButtons() {
assertEquals(mMappedButtons[CanonicalButtonIndex.PRIMARY],
mRawButtons[KeyEvent.KEYCODE_BUTTON_A]);
assertEquals(mMappedButtons[CanonicalButtonIndex.SECONDARY],
mRawButtons[KeyEvent.KEYCODE_BUTTON_B]);
assertEquals(mMappedButtons[CanonicalButtonIndex.TERTIARY],
mRawButtons[KeyEvent.KEYCODE_BUTTON_X]);
assertEquals(mMappedButtons[CanonicalButtonIndex.QUATERNARY],
mRawButtons[KeyEvent.KEYCODE_BUTTON_Y]);
}
private void assertMappedCommonThumbstickButtons() {
assertEquals(mMappedButtons[CanonicalButtonIndex.LEFT_THUMBSTICK],
mRawButtons[KeyEvent.KEYCODE_BUTTON_THUMBL]);
assertEquals(mMappedButtons[CanonicalButtonIndex.RIGHT_THUMBSTICK],
mRawButtons[KeyEvent.KEYCODE_BUTTON_THUMBR]);
}
private void assertMappedCommonStartSelectMetaButtons() {
assertEquals(mMappedButtons[CanonicalButtonIndex.START],
mRawButtons[KeyEvent.KEYCODE_BUTTON_START]);
assertEquals(mMappedButtons[CanonicalButtonIndex.BACK_SELECT],
mRawButtons[KeyEvent.KEYCODE_BUTTON_SELECT]);
assertEquals(mMappedButtons[CanonicalButtonIndex.META],
mRawButtons[KeyEvent.KEYCODE_BUTTON_MODE]);
}
private void assertMappedPedalAxesToBottomShoulder() {
assertEquals(mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER],
mRawAxes[MotionEvent.AXIS_BRAKE]);
assertEquals(
mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER], mRawAxes[MotionEvent.AXIS_GAS]);
}
private void assertMappedTriggerAxesToBottomShoulder() {
assertEquals(mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER],
mRawAxes[MotionEvent.AXIS_LTRIGGER]);
assertEquals(mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER],
mRawAxes[MotionEvent.AXIS_RTRIGGER]);
}
private void assertMappedHatAxisToDpadButtons() {
float hatX = mRawAxes[MotionEvent.AXIS_HAT_X];
float hatY = mRawAxes[MotionEvent.AXIS_HAT_Y];
assertEquals(mMappedButtons[CanonicalButtonIndex.DPAD_LEFT],
GamepadMappings.negativeAxisValueAsButton(hatX));
assertEquals(mMappedButtons[CanonicalButtonIndex.DPAD_RIGHT],
GamepadMappings.positiveAxisValueAsButton(hatX));
assertEquals(mMappedButtons[CanonicalButtonIndex.DPAD_UP],
GamepadMappings.negativeAxisValueAsButton(hatY));
assertEquals(mMappedButtons[CanonicalButtonIndex.DPAD_DOWN],
GamepadMappings.positiveAxisValueAsButton(hatY));
}
private void assertMappedXYAxes() {
assertEquals(mMappedAxes[CanonicalAxisIndex.LEFT_STICK_X], mRawAxes[MotionEvent.AXIS_X]);
assertEquals(mMappedAxes[CanonicalAxisIndex.LEFT_STICK_Y], mRawAxes[MotionEvent.AXIS_Y]);
}
private void assertMappedRXAndRYAxesToRightStick() {
assertEquals(mMappedAxes[CanonicalAxisIndex.RIGHT_STICK_X], mRawAxes[MotionEvent.AXIS_RX]);
assertEquals(mMappedAxes[CanonicalAxisIndex.RIGHT_STICK_Y], mRawAxes[MotionEvent.AXIS_RY]);
}
private void assertMappedZAndRZAxesToRightStick() {
assertEquals(mMappedAxes[CanonicalAxisIndex.RIGHT_STICK_X], mRawAxes[MotionEvent.AXIS_Z]);
assertEquals(mMappedAxes[CanonicalAxisIndex.RIGHT_STICK_Y], mRawAxes[MotionEvent.AXIS_RZ]);
}
}
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