chromium/device/gamepad/android/junit/src/org/chromium/device/gamepad/GamepadMappingsTest.java

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.device.gamepad;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThan;

import android.os.Build;
import android.view.KeyEvent;
import android.view.MotionEvent;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;

import java.util.Arrays;
import java.util.BitSet;

/** Verify no regressions in gamepad mappings. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class GamepadMappingsTest {
    private static final float ERROR_TOLERANCE = 0.000001f;

    /**
     * Product ID for Xbox One S gamepads with updated firmware connected over Bluetooth.
     * Microsoft released a firmware update for this gamepad that changes the button and axis
     * assignments. We distinguish between them by comparing the product ID.
     */
    private static final int XBOX_ONE_S_PRODUCT_ID = 0x02fd;

    /** The device ID string for Xbox One S gamepads connected over Bluetooth. */
    private static final String XBOX_WIRELESS_DEVICE_NAME = "Xbox Wireless Controller";

    /** 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 + 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];

    @Before
    public void 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;
        }
    }

    @Test
    @Feature({"Gamepad"})
    public void testShieldGamepadMappings() {
        GamepadMappings mappings =
                GamepadMappings.getMappings(GamepadMappings.NVIDIA_SHIELD_DEVICE_NAME_PREFIX);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        assertShieldGamepadMappings(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    public void testXBox360GamepadMappings() {
        GamepadMappings mappings =
                GamepadMappings.getMappings(GamepadMappings.MICROSOFT_XBOX_PAD_DEVICE_NAME);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        assertShieldGamepadMappings(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    public void testPS3SixAxisGamepadMappings() {
        GamepadMappings mappings =
                GamepadMappings.getMappings(GamepadMappings.PS_DUALSHOCK_3_SIXAXIS_DEVICE_NAME);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            // Changes in Android 9 caused the DualShock 3 and SIXAXIS mappings to change.
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.PRIMARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_A],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.SECONDARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_B],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.TERTIARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_X],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.QUATERNARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_Y],
                    ERROR_TOLERANCE);
        } else {
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.PRIMARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_X],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.SECONDARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_Y],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.TERTIARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_A],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.QUATERNARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_B],
                    ERROR_TOLERANCE);
        }

        assertMappedTriggerButtonsToTopShoulder();
        assertMappedCommonThumbstickButtons();
        assertMappedCommonDpadButtons();
        assertMappedCommonStartSelectMetaButtons();
        assertMappedTriggerAxesToBottomShoulder();
        assertMappedXYAxes();
        assertMappedZAndRZAxesToRightStick();

        assertMapping(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    public void testSamsungEIGP20GamepadMappings() {
        GamepadMappings mappings =
                GamepadMappings.getMappings(GamepadMappings.SAMSUNG_EI_GP20_DEVICE_NAME);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        assertMappedCommonXYABButtons();
        assertMappedUpperTriggerButtonsToBottomShoulder();
        assertMappedCommonThumbstickButtons();
        assertMappedCommonStartSelectMetaButtons();
        assertMappedHatAxisToDpadButtons();
        assertMappedXYAxes();
        assertMappedRXAndRYAxesToRightStick();

        expectNoShoulderButtons();
        assertMapping(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    public void testAmazonFireGamepadMappings() {
        GamepadMappings mappings =
                GamepadMappings.getMappings(GamepadMappings.AMAZON_FIRE_DEVICE_NAME);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        assertMappedCommonXYABButtons();
        assertMappedPedalAxesToBottomShoulder();
        assertMappedCommonThumbstickButtons();
        assertMappedCommonStartSelectMetaButtons();
        assertMappedTriggerButtonsToTopShoulder();
        assertMappedHatAxisToDpadButtons();
        assertMappedXYAxes();
        assertMappedZAndRZAxesToRightStick();

        assertMapping(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    public void testUnknownXBox360GamepadMappings() {
        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
                };
        BitSet buttons = new BitSet();
        buttons.set(KeyEvent.KEYCODE_BUTTON_A);
        buttons.set(KeyEvent.KEYCODE_BUTTON_B);
        buttons.set(KeyEvent.KEYCODE_BUTTON_X);
        buttons.set(KeyEvent.KEYCODE_BUTTON_Y);
        buttons.set(KeyEvent.KEYCODE_BUTTON_L1);
        buttons.set(KeyEvent.KEYCODE_BUTTON_R1);
        buttons.set(KeyEvent.KEYCODE_BUTTON_THUMBL);
        buttons.set(KeyEvent.KEYCODE_BUTTON_THUMBR);
        buttons.set(KeyEvent.KEYCODE_BUTTON_START);
        buttons.set(KeyEvent.KEYCODE_BUTTON_SELECT);
        buttons.set(KeyEvent.KEYCODE_BUTTON_MODE);

        GamepadMappings mappings = GamepadMappings.getUnknownGamepadMappings(axes, buttons);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        assertMappedCommonXYABButtons();
        assertMappedTriggerButtonsToTopShoulder();
        assertMappedCommonThumbstickButtons();
        assertMappedCommonStartSelectMetaButtons();
        assertMappedTriggerAxesToBottomShoulder();
        assertMappedHatAxisToDpadButtons();
        assertMappedXYAxes();
        assertMappedZAndRZAxesToRightStick();

        assertMapping(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    public void testUnknownMogaProGamepadMappings() {
        int[] axes =
                new int[] {
                    MotionEvent.AXIS_X,
                    MotionEvent.AXIS_Y,
                    MotionEvent.AXIS_Z,
                    MotionEvent.AXIS_RZ,
                    MotionEvent.AXIS_BRAKE,
                    MotionEvent.AXIS_GAS,
                    MotionEvent.AXIS_HAT_X,
                    MotionEvent.AXIS_HAT_Y
                };
        BitSet buttons = new BitSet();
        buttons.set(KeyEvent.KEYCODE_BUTTON_A);
        buttons.set(KeyEvent.KEYCODE_BUTTON_B);
        buttons.set(KeyEvent.KEYCODE_BUTTON_X);
        buttons.set(KeyEvent.KEYCODE_BUTTON_Y);
        buttons.set(KeyEvent.KEYCODE_BUTTON_L1);
        buttons.set(KeyEvent.KEYCODE_BUTTON_R1);
        buttons.set(KeyEvent.KEYCODE_BUTTON_THUMBL);
        buttons.set(KeyEvent.KEYCODE_BUTTON_THUMBR);
        buttons.set(KeyEvent.KEYCODE_BUTTON_START);
        buttons.set(KeyEvent.KEYCODE_BUTTON_SELECT);
        buttons.set(KeyEvent.KEYCODE_BUTTON_MODE);

        GamepadMappings mappings = GamepadMappings.getUnknownGamepadMappings(axes, buttons);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        assertMappedCommonXYABButtons();
        assertMappedTriggerButtonsToTopShoulder();
        assertMappedCommonThumbstickButtons();
        assertMappedCommonStartSelectMetaButtons();
        assertMappedPedalAxesToBottomShoulder();
        assertMappedHatAxisToDpadButtons();
        assertMappedXYAxes();
        assertMappedZAndRZAxesToRightStick();

        assertMapping(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    public void testUnknownXiaomiGamepadMappings() {
        int[] axes =
                new int[] {
                    MotionEvent.AXIS_X,
                    MotionEvent.AXIS_Y,
                    MotionEvent.AXIS_RX,
                    MotionEvent.AXIS_RY,
                    MotionEvent.AXIS_BRAKE,
                    MotionEvent.AXIS_THROTTLE,
                    MotionEvent.AXIS_HAT_X,
                    MotionEvent.AXIS_HAT_Y
                };
        BitSet buttons = new BitSet();
        buttons.set(KeyEvent.KEYCODE_BUTTON_A);
        buttons.set(KeyEvent.KEYCODE_BUTTON_B);
        buttons.set(KeyEvent.KEYCODE_BUTTON_X);
        buttons.set(KeyEvent.KEYCODE_BUTTON_Y);
        buttons.set(KeyEvent.KEYCODE_BUTTON_L1);
        buttons.set(KeyEvent.KEYCODE_BUTTON_R1);
        buttons.set(KeyEvent.KEYCODE_BUTTON_THUMBL);
        buttons.set(KeyEvent.KEYCODE_BUTTON_THUMBR);
        buttons.set(KeyEvent.KEYCODE_BUTTON_START);
        buttons.set(KeyEvent.KEYCODE_BUTTON_SELECT);
        buttons.set(KeyEvent.KEYCODE_BUTTON_MODE);

        GamepadMappings mappings = GamepadMappings.getUnknownGamepadMappings(axes, buttons);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        assertMappedCommonXYABButtons();
        assertMappedTriggerButtonsToTopShoulder();
        assertMappedCommonThumbstickButtons();
        assertMappedCommonStartSelectMetaButtons();
        assertMappedAltPedalAxesToBottomShoulder();
        assertMappedHatAxisToDpadButtons();
        assertMappedXYAxes();
        assertMappedRXAndRYAxesToRightStick();

        assertMapping(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    public void testUnknownGpdXdGamepadMappings() {
        int[] axes =
                new int[] {
                    MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ
                };
        BitSet buttons = new BitSet();
        buttons.set(KeyEvent.KEYCODE_BUTTON_A);
        buttons.set(KeyEvent.KEYCODE_BUTTON_B);
        buttons.set(KeyEvent.KEYCODE_BUTTON_X);
        buttons.set(KeyEvent.KEYCODE_BUTTON_Y);
        buttons.set(KeyEvent.KEYCODE_BUTTON_L1);
        buttons.set(KeyEvent.KEYCODE_BUTTON_R1);
        buttons.set(KeyEvent.KEYCODE_BUTTON_L2);
        buttons.set(KeyEvent.KEYCODE_BUTTON_R2);
        buttons.set(KeyEvent.KEYCODE_BUTTON_THUMBL);
        buttons.set(KeyEvent.KEYCODE_BUTTON_THUMBR);
        buttons.set(KeyEvent.KEYCODE_BUTTON_START);
        buttons.set(KeyEvent.KEYCODE_BUTTON_SELECT);
        buttons.set(KeyEvent.KEYCODE_BUTTON_MODE);

        GamepadMappings mappings = GamepadMappings.getUnknownGamepadMappings(axes, buttons);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        assertMappedCommonXYABButtons();
        assertMappedTriggerButtonsToTopShoulder();
        assertMappedCommonThumbstickButtons();
        assertMappedCommonStartSelectMetaButtons();
        assertMappedLowerTriggerButtonsToBottomShoulder();
        assertMappedCommonDpadButtons();
        assertMappedXYAxes();
        assertMappedZAndRZAxesToRightStick();

        assertMapping(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    public void testUnknownGamepadMappingsNoMeta() {
        int[] axes =
                new int[] {
                    MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ
                };
        BitSet buttons = new BitSet();
        buttons.set(KeyEvent.KEYCODE_BUTTON_A);
        buttons.set(KeyEvent.KEYCODE_BUTTON_B);
        buttons.set(KeyEvent.KEYCODE_BUTTON_X);
        buttons.set(KeyEvent.KEYCODE_BUTTON_Y);
        buttons.set(KeyEvent.KEYCODE_BUTTON_L1);
        buttons.set(KeyEvent.KEYCODE_BUTTON_R1);
        buttons.set(KeyEvent.KEYCODE_BUTTON_THUMBL);
        buttons.set(KeyEvent.KEYCODE_BUTTON_THUMBR);
        buttons.set(KeyEvent.KEYCODE_BUTTON_START);
        buttons.set(KeyEvent.KEYCODE_BUTTON_SELECT);

        mRawButtons[KeyEvent.KEYCODE_BUTTON_MODE] = 0.0f;
        GamepadMappings mappings = GamepadMappings.getUnknownGamepadMappings(axes, buttons);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        expectNoMetaButton(mappings);

        assertMapping(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    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.getMappings(
                        GamepadMappings.SONY_VENDOR_ID,
                        GamepadMappings.PS_DUALSHOCK_4_PRODUCT_ID,
                        axes);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            // Changes in Android 9 caused the DualShock 4 mapping to change.
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.PRIMARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_A],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.SECONDARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_B],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.TERTIARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_X],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.QUATERNARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_Y],
                    ERROR_TOLERANCE);

            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.LEFT_SHOULDER],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_L1],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_R1],
                    ERROR_TOLERANCE);

            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER],
                    mRawAxes[MotionEvent.AXIS_LTRIGGER],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER],
                    mRawAxes[MotionEvent.AXIS_RTRIGGER],
                    ERROR_TOLERANCE);

            assertMappedCommonThumbstickButtons();
        } else {
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.PRIMARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_B],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.SECONDARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_C],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.TERTIARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_A],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.QUATERNARY],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_X],
                    ERROR_TOLERANCE);

            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.LEFT_SHOULDER],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_Z],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER],
                    mRawButtons[KeyEvent.KEYCODE_BUTTON_Y],
                    ERROR_TOLERANCE);

            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER],
                    mRawAxes[MotionEvent.AXIS_RX],
                    ERROR_TOLERANCE);
            Assert.assertEquals(
                    mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER],
                    mRawAxes[MotionEvent.AXIS_RY],
                    ERROR_TOLERANCE);

            expectNoThumbstickButtons();
        }

        assertMappedCommonStartSelectMetaButtons();
        assertMappedXYAxes();
        assertMappedHatAxisToDpadButtons();
        assertMappedZAndRZAxesToRightStick();

        assertMapping(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    public void testXboxOneSBluetooth2016FirmwareMappings() {
        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.getMappings(
                        GamepadMappings.MICROSOFT_VENDOR_ID,
                        GamepadMappings.XBOX_ONE_S_2016_FIRMWARE_PRODUCT_ID,
                        axes);
        mappings.mapToStandardGamepad(mMappedAxes, mMappedButtons, mRawAxes, mRawButtons);

        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.PRIMARY],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_A],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.SECONDARY],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_B],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.TERTIARY],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_C],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.QUATERNARY],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_X],
                ERROR_TOLERANCE);

        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.LEFT_SHOULDER],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_Y],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_Z],
                ERROR_TOLERANCE);

        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.LEFT_THUMBSTICK],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_L2],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.RIGHT_THUMBSTICK],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_R2],
                ERROR_TOLERANCE);

        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.BACK_SELECT],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_L1],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.START],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_R1],
                ERROR_TOLERANCE);

        // The triggers range from -1 to 1 with -1 as the idle value.
        float leftTriggerValue = (mRawAxes[MotionEvent.AXIS_Z] + 1.0f) / 2.0f;
        float rightTriggerValue = (mRawAxes[MotionEvent.AXIS_RZ] + 1.0f) / 2.0f;
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER],
                leftTriggerValue,
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER],
                rightTriggerValue,
                ERROR_TOLERANCE);

        assertMappedHatAxisToDpadButtons();
        assertMappedXYAxes();
        assertMappedRXAndRYAxesToRightStick();
        expectNoMetaButton(mappings);

        assertMapping(mappings);
    }

    @Test
    @Feature({"Gamepad"})
    public void testXboxOneSBluetoothUsesDefaultMappings() {
        // Test that Xbox One S gamepads with updated firmware connected over Bluetooth use the
        // default mapping.
        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 deviceIdMappings =
                GamepadMappings.getMappings(
                        GamepadMappings.MICROSOFT_VENDOR_ID, XBOX_ONE_S_PRODUCT_ID, axes);
        Assert.assertNull(deviceIdMappings);

        GamepadMappings deviceNameMappings = GamepadMappings.getMappings(XBOX_WIRELESS_DEVICE_NAME);
        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.BROADCOM_VENDOR_ID,
                        GamepadMappings.SNAKEBYTE_IDROIDCON_PRODUCT_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.BROADCOM_VENDOR_ID,
                        GamepadMappings.SNAKEBYTE_IDROIDCON_PRODUCT_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);
    }

    @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. */
    public void assertShieldGamepadMappings(GamepadMappings mappings) {
        assertMappedCommonXYABButtons();
        assertMappedTriggerButtonsToTopShoulder();
        assertMappedCommonThumbstickButtons();
        assertMappedCommonStartSelectMetaButtons();
        assertMappedTriggerAxesToBottomShoulder();
        assertMappedHatAxisToDpadButtons();
        assertMappedXYAxes();
        assertMappedZAndRZAxesToRightStick();

        assertMapping(mappings);
    }

    public void expectNoShoulderButtons() {
        mUnmappedButtons.set(CanonicalButtonIndex.LEFT_SHOULDER);
        mUnmappedButtons.set(CanonicalButtonIndex.RIGHT_SHOULDER);
    }

    public void expectNoMetaButton(GamepadMappings mappings) {
        mUnmappedButtons.set(CanonicalButtonIndex.META);
        assertThat(mappings.getButtonsLength(), lessThan(CanonicalButtonIndex.COUNT));
    }

    public void expectNoThumbstickButtons() {
        mUnmappedButtons.set(CanonicalButtonIndex.LEFT_THUMBSTICK);
        mUnmappedButtons.set(CanonicalButtonIndex.RIGHT_THUMBSTICK);
    }

    public void assertMapping(GamepadMappings mappings) {
        for (int i = 0; i < mMappedAxes.length; i++) {
            if (mUnmappedAxes.get(i)) {
                Assert.assertTrue(
                        "An unexpected axis was mapped at index " + i, Float.isNaN(mMappedAxes[i]));
            } else {
                Assert.assertFalse(
                        "An axis was not mapped at index " + i, Float.isNaN(mMappedAxes[i]));
            }
        }
        assertThat(mMappedButtons.length, greaterThanOrEqualTo(mappings.getButtonsLength()));
        for (int i = 0; i < mappings.getButtonsLength(); i++) {
            if (mUnmappedButtons.get(i)) {
                Assert.assertTrue(
                        "An unexpected button was mapped at index " + i,
                        Float.isNaN(mMappedButtons[i]));
            } else {
                Assert.assertFalse(
                        "A button was not mapped at index " + i, Float.isNaN(mMappedButtons[i]));
            }
        }
    }

    private void assertMappedUpperTriggerButtonsToBottomShoulder() {
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_L1],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_R1],
                ERROR_TOLERANCE);
    }

    private void assertMappedLowerTriggerButtonsToBottomShoulder() {
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_L2],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_R2],
                ERROR_TOLERANCE);
    }

    private void assertMappedCommonDpadButtons() {
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.DPAD_DOWN],
                mRawButtons[KeyEvent.KEYCODE_DPAD_DOWN],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.DPAD_UP],
                mRawButtons[KeyEvent.KEYCODE_DPAD_UP],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.DPAD_LEFT],
                mRawButtons[KeyEvent.KEYCODE_DPAD_LEFT],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.DPAD_RIGHT],
                mRawButtons[KeyEvent.KEYCODE_DPAD_RIGHT],
                ERROR_TOLERANCE);
    }

    private void assertMappedTriggerButtonsToTopShoulder() {
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.LEFT_SHOULDER],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_L1],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_R1],
                ERROR_TOLERANCE);
    }

    private void assertMappedCommonXYABButtons() {
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.PRIMARY],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_A],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.SECONDARY],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_B],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.TERTIARY],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_X],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.QUATERNARY],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_Y],
                ERROR_TOLERANCE);
    }

    private void assertMappedCommonThumbstickButtons() {
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.LEFT_THUMBSTICK],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_THUMBL],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.RIGHT_THUMBSTICK],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_THUMBR],
                ERROR_TOLERANCE);
    }

    private void assertMappedCommonStartSelectMetaButtons() {
        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.META],
                mRawButtons[KeyEvent.KEYCODE_BUTTON_MODE],
                ERROR_TOLERANCE);
    }

    private void assertMappedPedalAxesToBottomShoulder() {
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER],
                mRawAxes[MotionEvent.AXIS_BRAKE],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER],
                mRawAxes[MotionEvent.AXIS_GAS],
                ERROR_TOLERANCE);
    }

    private void assertMappedAltPedalAxesToBottomShoulder() {
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER],
                mRawAxes[MotionEvent.AXIS_BRAKE],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER],
                mRawAxes[MotionEvent.AXIS_THROTTLE],
                ERROR_TOLERANCE);
    }

    private void assertMappedTriggerAxesToBottomShoulder() {
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.LEFT_TRIGGER],
                mRawAxes[MotionEvent.AXIS_LTRIGGER],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER],
                mRawAxes[MotionEvent.AXIS_RTRIGGER],
                ERROR_TOLERANCE);
    }

    private void assertMappedHatAxisToDpadButtons() {
        float hatX = mRawAxes[MotionEvent.AXIS_HAT_X];
        float hatY = mRawAxes[MotionEvent.AXIS_HAT_Y];
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.DPAD_LEFT],
                GamepadMappings.negativeAxisValueAsButton(hatX),
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.DPAD_RIGHT],
                GamepadMappings.positiveAxisValueAsButton(hatX),
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.DPAD_UP],
                GamepadMappings.negativeAxisValueAsButton(hatY),
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedButtons[CanonicalButtonIndex.DPAD_DOWN],
                GamepadMappings.positiveAxisValueAsButton(hatY),
                ERROR_TOLERANCE);
    }

    private void assertMappedXYAxes() {
        Assert.assertEquals(
                mMappedAxes[CanonicalAxisIndex.LEFT_STICK_X],
                mRawAxes[MotionEvent.AXIS_X],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedAxes[CanonicalAxisIndex.LEFT_STICK_Y],
                mRawAxes[MotionEvent.AXIS_Y],
                ERROR_TOLERANCE);
    }

    private void assertMappedRXAndRYAxesToRightStick() {
        Assert.assertEquals(
                mMappedAxes[CanonicalAxisIndex.RIGHT_STICK_X],
                mRawAxes[MotionEvent.AXIS_RX],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedAxes[CanonicalAxisIndex.RIGHT_STICK_Y],
                mRawAxes[MotionEvent.AXIS_RY],
                ERROR_TOLERANCE);
    }

    private void assertMappedZAndRZAxesToRightStick() {
        Assert.assertEquals(
                mMappedAxes[CanonicalAxisIndex.RIGHT_STICK_X],
                mRawAxes[MotionEvent.AXIS_Z],
                ERROR_TOLERANCE);
        Assert.assertEquals(
                mMappedAxes[CanonicalAxisIndex.RIGHT_STICK_Y],
                mRawAxes[MotionEvent.AXIS_RZ],
                ERROR_TOLERANCE);
    }
}