chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadMappings.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 android.os.Build;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;

import androidx.annotation.VisibleForTesting;

import org.jni_zero.JNINamespace;

import java.util.BitSet;

/** Class to manage mapping information related to each supported gamepad controller device. */
@JNINamespace("content")
abstract class GamepadMappings {
    @VisibleForTesting
    static final String NVIDIA_SHIELD_DEVICE_NAME_PREFIX = "NVIDIA Corporation NVIDIA Controller";

    @VisibleForTesting
    static final String MICROSOFT_XBOX_PAD_DEVICE_NAME = "Microsoft X-Box 360 pad";

    @VisibleForTesting
    static final String PS_DUALSHOCK_3_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";

    @VisibleForTesting static final int SONY_VENDOR_ID = 0x054c;
    @VisibleForTesting static final int PS_DUALSHOCK_4_PRODUCT_ID = 0x05c4;
    @VisibleForTesting static final int PS_DUALSHOCK_4_SLIM_PRODUCT_ID = 0x09cc;
    @VisibleForTesting static final int PS_DUALSHOCK_4_USB_RECEIVER_PRODUCT_ID = 0x0ba0;
    static final int PS_DUAL_SENSE_PRODUCT_ID = 0x0ce6;

    @VisibleForTesting static final int MICROSOFT_VENDOR_ID = 0x045e;
    @VisibleForTesting static final int XBOX_ONE_S_2016_FIRMWARE_PRODUCT_ID = 0x02e0;
    @VisibleForTesting static final int XBOX_SERIES_X_BLUETOOTH_PRODUCT_ID = 0x0b13;

    @VisibleForTesting static final int BROADCOM_VENDOR_ID = 0x0a5c;
    @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) {
        GamepadMappings mappings = getMappings(device.getVendorId(), device.getProductId(), axes);
        if (mappings == null) {
            mappings = getMappings(device.getName());
        }
        if (mappings == null) {
            mappings = new UnknownGamepadMappings(axes, buttons);
        }
        return mappings;
    }

    @VisibleForTesting
    static GamepadMappings getMappings(int vendorId, int productId, int[] axes) {
        if (vendorId == SONY_VENDOR_ID) {
            if (productId == PS_DUALSHOCK_4_PRODUCT_ID
                    || productId == PS_DUALSHOCK_4_SLIM_PRODUCT_ID
                    || productId == PS_DUALSHOCK_4_USB_RECEIVER_PRODUCT_ID) {
                // Android 9 included improvements for PS3 and PS4 gamepads that changed the
                // KeyEvent and MotionEvent codes for some buttons and axes. Use an alternate
                // mapping for versions of Android that include these improvements.
                if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                    return new XboxCompatibleGamepadMappings();
                }
                return new Ps4Ps5GamepadMappings();
            }
            if (productId == PS_DUAL_SENSE_PRODUCT_ID) {
                // Android 12 includes a new driver for PS5 gamepads. Use an alternate mapping for
                // versions of Android without this driver.
                if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
                    return new Ps4Ps5GamepadMappings();
                }
            }
        }
        if (vendorId == MICROSOFT_VENDOR_ID) {
            // Microsoft released a firmware update for the Xbox One S gamepad that modified the
            // button and axis assignments. With the new firmware, these gamepads work correctly in
            // Android using the default mapping, but a custom mapping is still required for the old
            // firmware. Both gamepads return the same device name, so we must compare hardware IDs
            // to distinguish them.
            if (productId == XBOX_ONE_S_2016_FIRMWARE_PRODUCT_ID) {
                return new XboxOneS2016FirmwareMappings();
            }
            if (productId == XBOX_SERIES_X_BLUETOOTH_PRODUCT_ID) {
                return new XboxSeriesXBluetoothMappings();
            }
        }
        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;
    }

    @VisibleForTesting
    static GamepadMappings getMappings(String deviceName) {
        if (deviceName.startsWith(NVIDIA_SHIELD_DEVICE_NAME_PREFIX)
                || deviceName.equals(MICROSOFT_XBOX_PAD_DEVICE_NAME)) {
            return new XboxCompatibleGamepadMappings();
        } else if (deviceName.equals(PS_DUALSHOCK_3_SIXAXIS_DEVICE_NAME)) {
            // Android 9 included improvements for Sony PlayStation gamepads that changed the
            // KeyEvent and MotionEvent codes for some buttons and axes. Use an alternate mapping
            // for versions of Android that include these improvements.
            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                return new Dualshock3SixAxisGamepadMappings();
            }
            return new Dualshock3SixAxisGamepadMappingsPreP();
        } else if (deviceName.equals(SAMSUNG_EI_GP20_DEVICE_NAME)) {
            return new SamsungEIGP20GamepadMappings();
        } else if (deviceName.equals(AMAZON_FIRE_DEVICE_NAME)) {
            return new AmazonFireGamepadMappings();
        }
        return null;
    }

    @VisibleForTesting
    static GamepadMappings getUnknownGamepadMappings(int[] axes, BitSet buttons) {
        return new UnknownGamepadMappings(axes, buttons);
    }

    /**
     * Method that specifies whether the mappings are standard or not.
     * It should be overridden in subclasses that don't provide standard
     * mappings.
     */
    public boolean isStandard() {
        return true;
    }

    /**
     * Returns the number of mapped buttons. Subclasses which support more or fewer buttons (e.g. no
     * meta button) should override this.
     */
    public int getButtonsLength() {
        return CanonicalButtonIndex.COUNT;
    }

    /**
     * Method implemented by subclasses to perform mapping from raw axes and buttons
     * to canonical axes and buttons.
     */
    public abstract void mapToStandardGamepad(
            float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons);

    private static void mapCommonXYABButtons(float[] mappedButtons, float[] rawButtons) {
        float a = rawButtons[KeyEvent.KEYCODE_BUTTON_A];
        float b = rawButtons[KeyEvent.KEYCODE_BUTTON_B];
        float x = rawButtons[KeyEvent.KEYCODE_BUTTON_X];
        float y = rawButtons[KeyEvent.KEYCODE_BUTTON_Y];
        mappedButtons[CanonicalButtonIndex.PRIMARY] = a;
        mappedButtons[CanonicalButtonIndex.SECONDARY] = b;
        mappedButtons[CanonicalButtonIndex.TERTIARY] = x;
        mappedButtons[CanonicalButtonIndex.QUATERNARY] = y;
    }

    private static void mapCommonStartSelectMetaButtons(float[] mappedButtons, float[] rawButtons) {
        float start = rawButtons[KeyEvent.KEYCODE_BUTTON_START];
        float select = rawButtons[KeyEvent.KEYCODE_BUTTON_SELECT];
        float mode = rawButtons[KeyEvent.KEYCODE_BUTTON_MODE];
        mappedButtons[CanonicalButtonIndex.START] = start;
        mappedButtons[CanonicalButtonIndex.BACK_SELECT] = select;
        mappedButtons[CanonicalButtonIndex.META] = mode;
    }

    private static void mapCommonThumbstickButtons(float[] mappedButtons, float[] rawButtons) {
        float thumbL = rawButtons[KeyEvent.KEYCODE_BUTTON_THUMBL];
        float thumbR = rawButtons[KeyEvent.KEYCODE_BUTTON_THUMBR];
        mappedButtons[CanonicalButtonIndex.LEFT_THUMBSTICK] = thumbL;
        mappedButtons[CanonicalButtonIndex.RIGHT_THUMBSTICK] = thumbR;
    }

    /**
     * Method for mapping the L1/R1 buttons to lower shoulder buttons, rather than
     * upper shoulder as the user would normally expect. Please think twice before
     * using this, as it can easily confuse the user. It is only really useful if
     * the controller completely lacks a second set of shoulder buttons.
     */
    private static void mapUpperTriggerButtonsToBottomShoulder(
            float[] mappedButtons, float[] rawButtons) {
        float l1 = rawButtons[KeyEvent.KEYCODE_BUTTON_L1];
        float r1 = rawButtons[KeyEvent.KEYCODE_BUTTON_R1];
        mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = l1;
        mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = r1;
    }

    private static void mapTriggerButtonsToTopShoulder(float[] mappedButtons, float[] rawButtons) {
        float l1 = rawButtons[KeyEvent.KEYCODE_BUTTON_L1];
        float r1 = rawButtons[KeyEvent.KEYCODE_BUTTON_R1];
        mappedButtons[CanonicalButtonIndex.LEFT_SHOULDER] = l1;
        mappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER] = r1;
    }

    private static void mapCommonDpadButtons(float[] mappedButtons, float[] rawButtons) {
        float dpadDown = rawButtons[KeyEvent.KEYCODE_DPAD_DOWN];
        float dpadUp = rawButtons[KeyEvent.KEYCODE_DPAD_UP];
        float dpadLeft = rawButtons[KeyEvent.KEYCODE_DPAD_LEFT];
        float dpadRight = rawButtons[KeyEvent.KEYCODE_DPAD_RIGHT];
        mappedButtons[CanonicalButtonIndex.DPAD_DOWN] = dpadDown;
        mappedButtons[CanonicalButtonIndex.DPAD_UP] = dpadUp;
        mappedButtons[CanonicalButtonIndex.DPAD_LEFT] = dpadLeft;
        mappedButtons[CanonicalButtonIndex.DPAD_RIGHT] = dpadRight;
    }

    private static void mapXYAxes(float[] mappedAxes, float[] rawAxes) {
        mappedAxes[CanonicalAxisIndex.LEFT_STICK_X] = rawAxes[MotionEvent.AXIS_X];
        mappedAxes[CanonicalAxisIndex.LEFT_STICK_Y] = rawAxes[MotionEvent.AXIS_Y];
    }

    private static void mapRXAndRYAxesToRightStick(float[] mappedAxes, float[] rawAxes) {
        mappedAxes[CanonicalAxisIndex.RIGHT_STICK_X] = rawAxes[MotionEvent.AXIS_RX];
        mappedAxes[CanonicalAxisIndex.RIGHT_STICK_Y] = rawAxes[MotionEvent.AXIS_RY];
    }

    private static void mapZAndRZAxesToRightStick(float[] mappedAxes, float[] rawAxes) {
        mappedAxes[CanonicalAxisIndex.RIGHT_STICK_X] = rawAxes[MotionEvent.AXIS_Z];
        mappedAxes[CanonicalAxisIndex.RIGHT_STICK_Y] = rawAxes[MotionEvent.AXIS_RZ];
    }

    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) {
        float lTrigger = rawAxes[MotionEvent.AXIS_LTRIGGER];
        float rTrigger = rawAxes[MotionEvent.AXIS_RTRIGGER];
        mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = lTrigger;
        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, float[] rawButtons) {
        float l2 = rawButtons[KeyEvent.KEYCODE_BUTTON_L2];
        float r2 = rawButtons[KeyEvent.KEYCODE_BUTTON_R2];
        mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = l2;
        mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = r2;
    }

    @VisibleForTesting
    static float negativeAxisValueAsButton(float input) {
        return (input < -0.5f) ? 1.f : 0.f;
    }

    @VisibleForTesting
    static float positiveAxisValueAsButton(float input) {
        return (input > 0.5f) ? 1.f : 0.f;
    }

    private static void mapHatAxisToDpadButtons(float[] mappedButtons, float[] rawAxes) {
        float hatX = rawAxes[MotionEvent.AXIS_HAT_X];
        float hatY = rawAxes[MotionEvent.AXIS_HAT_Y];
        mappedButtons[CanonicalButtonIndex.DPAD_LEFT] = negativeAxisValueAsButton(hatX);
        mappedButtons[CanonicalButtonIndex.DPAD_RIGHT] = positiveAxisValueAsButton(hatX);
        mappedButtons[CanonicalButtonIndex.DPAD_UP] = negativeAxisValueAsButton(hatY);
        mappedButtons[CanonicalButtonIndex.DPAD_DOWN] = positiveAxisValueAsButton(hatY);
    }

    private static class AmazonFireGamepadMappings extends GamepadMappings {

        /**
         * Method for mapping Amazon Fire gamepad 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);
            mapCommonThumbstickButtons(mappedButtons, rawButtons);
            mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
            mapPedalAxesToBottomShoulder(mappedButtons, rawAxes);
            mapHatAxisToDpadButtons(mappedButtons, rawAxes);

            mapXYAxes(mappedAxes, rawAxes);
            mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
        }
    }

    private static class XboxCompatibleGamepadMappings extends GamepadMappings {

        /**
         * Method for mapping Xbox 360-compatible gamepad 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);
            mapCommonThumbstickButtons(mappedButtons, rawButtons);
            mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
            mapTriggerAxesToBottomShoulder(mappedButtons, rawAxes);
            mapHatAxisToDpadButtons(mappedButtons, rawAxes);

            mapXYAxes(mappedAxes, rawAxes);
            mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
        }
    }

    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 boolean mLeftTriggerActivated;
        private boolean mRightTriggerActivated;

        /**
         * Method for mapping Xbox One S controller (in Bluetooth mode) to
         * standard gamepad button and axes values.
         */
        @Override
        public void mapToStandardGamepad(
                float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons) {
            mappedButtons[CanonicalButtonIndex.PRIMARY] = rawButtons[KeyEvent.KEYCODE_BUTTON_A];
            mappedButtons[CanonicalButtonIndex.SECONDARY] = rawButtons[KeyEvent.KEYCODE_BUTTON_B];
            mappedButtons[CanonicalButtonIndex.TERTIARY] = rawButtons[KeyEvent.KEYCODE_BUTTON_C];
            mappedButtons[CanonicalButtonIndex.QUATERNARY] = rawButtons[KeyEvent.KEYCODE_BUTTON_X];

            mappedButtons[CanonicalButtonIndex.LEFT_SHOULDER] =
                    rawButtons[KeyEvent.KEYCODE_BUTTON_Y];
            mappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER] =
                    rawButtons[KeyEvent.KEYCODE_BUTTON_Z];

            mappedButtons[CanonicalButtonIndex.BACK_SELECT] =
                    rawButtons[KeyEvent.KEYCODE_BUTTON_L1];
            mappedButtons[CanonicalButtonIndex.START] = rawButtons[KeyEvent.KEYCODE_BUTTON_R1];

            mappedButtons[CanonicalButtonIndex.LEFT_THUMBSTICK] =
                    rawButtons[KeyEvent.KEYCODE_BUTTON_L2];
            mappedButtons[CanonicalButtonIndex.RIGHT_THUMBSTICK] =
                    rawButtons[KeyEvent.KEYCODE_BUTTON_R2];

            // The left and right triggers on the Xbox One S controller
            // are exposed as AXIS_Z and AXIS_RZ respectively. However,
            // these nominally idle at -1 rather than 0, like other triggers.
            // Unfortunately, the -1 value is only reported upon the first
            // activation of each trigger axis. In order to prevent idling at
            // 0.5 before trigger activation, we only expose trigger values
            // when we've seen them report a non-zero value at least once.
            if (rawAxes[MotionEvent.AXIS_Z] != 0) {
                mLeftTriggerActivated = true;
            }
            if (rawAxes[MotionEvent.AXIS_RZ] != 0) {
                mRightTriggerActivated = true;
            }
            if (mLeftTriggerActivated) {
                mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] =
                        (rawAxes[MotionEvent.AXIS_Z] + 1) / 2;
            } else {
                mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = 0.f;
            }
            if (mRightTriggerActivated) {
                mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] =
                        (rawAxes[MotionEvent.AXIS_RZ] + 1) / 2;
            } else {
                mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = 0.f;
            }

            mapHatAxisToDpadButtons(mappedButtons, rawAxes);
            mapXYAxes(mappedAxes, rawAxes);
            mapRXAndRYAxesToRightStick(mappedAxes, rawAxes);
        }

        @Override
        public int getButtonsLength() {
            // No meta button.
            return CanonicalButtonIndex.COUNT - 1;
        }
    }

    private static class XboxSeriesXBluetoothMappings extends GamepadMappings {
        private static final int BUTTON_INDEX_SHARE = CanonicalButtonIndex.COUNT;

        /**
         * Method for mapping Xbox Series X controller (in Bluetooth mode) 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);
            mapCommonThumbstickButtons(mappedButtons, rawButtons);
            mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
            mapHatAxisToDpadButtons(mappedButtons, rawAxes);
            mapXYAxes(mappedAxes, rawAxes);
            mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
            mapPedalAxesToBottomShoulder(mappedButtons, rawAxes);
            mappedButtons[BUTTON_INDEX_SHARE] = rawButtons[KeyEvent.KEYCODE_MEDIA_RECORD];
        }

        @Override
        public int getButtonsLength() {
            // Include the Share button.
            return CanonicalButtonIndex.COUNT + 1;
        }
    }

    private static class Dualshock3SixAxisGamepadMappingsPreP extends GamepadMappings {
        /**
         * Method for mapping DualShock 3 and SIXAXIS gamepad inputs to standard gamepad button and
         * axis values. This mapping function should only be used on Android 8 and earlier.
         */
        @Override
        public void mapToStandardGamepad(
                float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons) {
            // On DualShock 3 and SIXAXIS, X/Y has higher priority.
            float a = rawButtons[KeyEvent.KEYCODE_BUTTON_A];
            float b = rawButtons[KeyEvent.KEYCODE_BUTTON_B];
            float x = rawButtons[KeyEvent.KEYCODE_BUTTON_X];
            float y = rawButtons[KeyEvent.KEYCODE_BUTTON_Y];
            mappedButtons[CanonicalButtonIndex.PRIMARY] = x;
            mappedButtons[CanonicalButtonIndex.SECONDARY] = y;
            mappedButtons[CanonicalButtonIndex.TERTIARY] = a;
            mappedButtons[CanonicalButtonIndex.QUATERNARY] = b;

            mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
            mapCommonThumbstickButtons(mappedButtons, rawButtons);
            mapCommonDpadButtons(mappedButtons, rawButtons);
            mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
            mapTriggerAxesToBottomShoulder(mappedButtons, rawAxes);

            mapXYAxes(mappedAxes, rawAxes);
            mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
        }
    }

    private static class Dualshock3SixAxisGamepadMappings extends GamepadMappings {
        /**
         * Method for mapping DualShock 3 and SIXAXIS gamepad inputs to standard gamepad button and
         * axis values. This mapping function should only be used on Android 10+.
         */
        @Override
        public void mapToStandardGamepad(
                float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons) {
            mapCommonXYABButtons(mappedButtons, rawButtons);
            mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
            mapCommonThumbstickButtons(mappedButtons, rawButtons);
            mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
            mapCommonDpadButtons(mappedButtons, rawButtons);
            mapXYAxes(mappedAxes, rawAxes);
            mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
            mapTriggerAxesToBottomShoulder(mappedButtons, rawAxes);
        }
    }

    static class Ps4Ps5GamepadMappings extends GamepadMappings {
        // Scale input from [-1, 1] to [0, 1] uniformly.
        private static float scaleRxRy(float input) {
            return 1.f - ((1.f - input) / 2.f);
        }

        /**
         * Method for mapping DualShock 4 and DualSense gamepad inputs to standard gamepad button
         * and axis values. This mapping function should only be used for DualShock 4 on Android 9
         * and earlier and DualSense on Android 11 and earlier.
         */
        @Override
        public void mapToStandardGamepad(
                float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons) {
            float a = rawButtons[KeyEvent.KEYCODE_BUTTON_A];
            float b = rawButtons[KeyEvent.KEYCODE_BUTTON_B];
            float c = rawButtons[KeyEvent.KEYCODE_BUTTON_C];
            float x = rawButtons[KeyEvent.KEYCODE_BUTTON_X];
            mappedButtons[CanonicalButtonIndex.PRIMARY] = b;
            mappedButtons[CanonicalButtonIndex.SECONDARY] = c;
            mappedButtons[CanonicalButtonIndex.TERTIARY] = a;
            mappedButtons[CanonicalButtonIndex.QUATERNARY] = x;

            float y = rawButtons[KeyEvent.KEYCODE_BUTTON_Y];
            float z = rawButtons[KeyEvent.KEYCODE_BUTTON_Z];
            mappedButtons[CanonicalButtonIndex.LEFT_SHOULDER] = y;
            mappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER] = z;

            float rx = rawAxes[MotionEvent.AXIS_RX];
            float ry = rawAxes[MotionEvent.AXIS_RY];
            mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = scaleRxRy(rx);
            mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = scaleRxRy(ry);

            float share = rawButtons[KeyEvent.KEYCODE_BUTTON_L2];
            float options = rawButtons[KeyEvent.KEYCODE_BUTTON_R2];
            mappedButtons[CanonicalButtonIndex.BACK_SELECT] = share;
            mappedButtons[CanonicalButtonIndex.START] = options;

            float thumbL = rawButtons[KeyEvent.KEYCODE_BUTTON_SELECT];
            float thumbR = rawButtons[KeyEvent.KEYCODE_BUTTON_START];
            mappedButtons[CanonicalButtonIndex.LEFT_THUMBSTICK] = thumbL;
            mappedButtons[CanonicalButtonIndex.RIGHT_THUMBSTICK] = thumbR;

            float mode = rawButtons[KeyEvent.KEYCODE_BUTTON_MODE];
            mappedButtons[CanonicalButtonIndex.META] = mode;

            mapHatAxisToDpadButtons(mappedButtons, rawAxes);
            mapXYAxes(mappedAxes, rawAxes);
            mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
        }
    }

    private static class SamsungEIGP20GamepadMappings extends GamepadMappings {
        /**
         * Method for mapping Samsung GamePad EI-GP20 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);
            mapUpperTriggerButtonsToBottomShoulder(mappedButtons, rawButtons);
            mapCommonThumbstickButtons(mappedButtons, rawButtons);
            mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
            mapHatAxisToDpadButtons(mappedButtons, rawAxes);

            mapXYAxes(mappedAxes, rawAxes);
            mapRXAndRYAxesToRightStick(mappedAxes, rawAxes);
        }
    }

    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;
        private int mRightStickXAxis = -1;
        private int mRightStickYAxis = -1;
        private boolean mUseHatAxes;
        private final boolean mHasMetaButton;

        UnknownGamepadMappings(int[] axes, BitSet buttons) {
            mHasMetaButton = buttons.get(KeyEvent.KEYCODE_BUTTON_MODE);

            int hatAxesFound = 0;

            for (int axis : axes) {
                switch (axis) {
                    case MotionEvent.AXIS_LTRIGGER:
                    case MotionEvent.AXIS_BRAKE:
                        mLeftTriggerAxis = axis;
                        break;
                    case MotionEvent.AXIS_RTRIGGER:
                    case MotionEvent.AXIS_GAS:
                    case MotionEvent.AXIS_THROTTLE:
                        mRightTriggerAxis = axis;
                        break;
                    case MotionEvent.AXIS_RX:
                    case MotionEvent.AXIS_Z:
                        mRightStickXAxis = axis;
                        break;
                    case MotionEvent.AXIS_RY:
                    case MotionEvent.AXIS_RZ:
                        mRightStickYAxis = axis;
                        break;
                    case MotionEvent.AXIS_HAT_X:
                        hatAxesFound++;
                        break;
                    case MotionEvent.AXIS_HAT_Y:
                        hatAxesFound++;
                        break;
                    default:
                        break;
                }
            }

            if (hatAxesFound == 2) {
                mUseHatAxes = true;
            }
        }

        @Override
        public boolean isStandard() {
            // These mappings should not be considered standard
            return false;
        }

        @Override
        public int getButtonsLength() {
            return mHasMetaButton ? CanonicalButtonIndex.COUNT : CanonicalButtonIndex.COUNT - 1;
        }

        @Override
        public void mapToStandardGamepad(
                float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons) {
            // These are shared among all gamepads intended for use with Android
            // that we tested so far.
            mapCommonXYABButtons(mappedButtons, rawButtons);
            mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
            mapCommonThumbstickButtons(mappedButtons, rawButtons);
            mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
            mapXYAxes(mappedAxes, rawAxes);

            if (mLeftTriggerAxis != -1 && mRightTriggerAxis != -1) {
                float lTrigger = rawAxes[mLeftTriggerAxis];
                float rTrigger = rawAxes[mRightTriggerAxis];
                mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = lTrigger;
                mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = rTrigger;
            } else {
                // Devices without analog triggers use digital buttons
                mapLowerTriggerButtonsToBottomShoulder(mappedButtons, rawButtons);
            }

            if (mRightStickXAxis != -1 && mRightStickYAxis != -1) {
                float rX = rawAxes[mRightStickXAxis];
                float rY = rawAxes[mRightStickYAxis];
                mappedAxes[CanonicalAxisIndex.RIGHT_STICK_X] = rX;
                mappedAxes[CanonicalAxisIndex.RIGHT_STICK_Y] = rY;
            }

            if (mUseHatAxes) {
                mapHatAxisToDpadButtons(mappedButtons, rawAxes);
            } else {
                mapCommonDpadButtons(mappedButtons, rawButtons);
            }
        }
    }
}