Controllers have two kinds of actions:

- [`KeyEvent`](https://developer.android.com/reference/android/view/KeyEvent) used for any button with a binary state of "on" and "off"
- [`MotionEvent`](https://developer.android.com/reference/android/view/MotionEvent) used for any axis that returns a range of values. Such as -1 to 1 for analog sticks or 0 to 1 for analog triggers.

You can read these inputs from the [`View`](https://developer.android.com/reference/android/view/View) that has [`focus`](https://developer.android.com/reference/android/view/View#focus-handling).

- [`onGenericMotionEvent`](https://developer.android.com/reference/android/view/View.OnGenericMotionListener) is raised for any [`MotionEvent`](https://developer.android.com/reference/android/view/MotionEvent).
- [`onKeyDown`](https://developer.android.com/reference/android/view/KeyEvent.Callback#onKeyDown(int,%20android.view.KeyEvent)) and [`onKeyUp`](https://developer.android.com/reference/android/view/KeyEvent.Callback#onKeyUp(int,%20android.view.KeyEvent)) are raised for [`KeyEvent`](https://developer.android.com/reference/android/view/KeyEvent) when
  buttons are pressed and released.

### Kotlin

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
      if (event.isFromSource(SOURCE_GAMEPAD)
          && event.repeatCount == 0
      ) {
          Log.d("GameView", "Gamepad key pressed: $keyCode")
          return true
      }

      return super.onKeyDown(keyCode, event)
    }

    override fun onGenericMotionEvent(event: MotionEvent): Boolean {
      if (event.isFromSource(SOURCE_JOYSTICK)) {
          Log.d("GameView", "Gamepad event: $event")
          return true
      }

      return super.onGenericMotionEvent(event)
    }

### Java

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
      if (event.isFromSource(SOURCE_GAMEPAD)
              && event.getRepeatCount() == 0
      ) {
          Log.d("GameView", "Gamepad key pressed: " + keyCode);
          return true;
      }

      return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {
      if (event.isFromSource(SOURCE_JOYSTICK)) {
          Log.d("GameView", "Gamepad event: " + event);
          return true;
      }
      return super.onGenericMotionEvent(event);
    }

If needed, you can read events from the [`Activity`](https://developer.android.com/reference/android/app/Activity) directly instead.

- [`dispatchGenericMotionEvent`](https://developer.android.com/reference/android/app/Activity#dispatchGenericMotionEvent(android.view.MotionEvent)) is raised for any [`MotionEvent`](https://developer.android.com/reference/android/view/MotionEvent)
- [`dispatchKeyEvent`](https://developer.android.com/reference/android/app/Activity#dispatchKeyEvent(android.view.KeyEvent)). is raised for any [`KeyEvent`](https://developer.android.com/reference/android/view/KeyEvent).

> [!NOTE]
> **Note:** Android has compatibility layers to help games and applications by mapping newer or less common input types into more common or legacy input types. When receiving events, avoid marking events as handled (returning true) unless that event is used by gameplay so these layers can help maximize your game's compatibility.

## Verify a game controller is connected

When reporting input events, Android will reuse the same key or axis ids for
different input device types. For example, a touchscreen action generates an
[`AXIS_X`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_X) event that represents the X
coordinate of the touch surface, but a gamepad generates an
[`AXIS_X`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_X) event that represents the X
position of the left stick. This means that you must check the source type to
properly interpret input events.

To verify that a connected [`InputDevice`](https://developer.android.com/reference/android/view/InputDevice) is a game controller, use the
[`supportsSource(int)`](https://developer.android.com/reference/android/view/InputDevice#supportsSource(int)) function:

- A source type of [`SOURCE_GAMEPAD`](https://developer.android.com/reference/android/view/InputDevice#SOURCE_GAMEPAD) indicates that the input device has controller buttons (for example, [`KEYCODE_BUTTON_A`](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_A)). Note that this source type does not strictly indicate if the game controller has D-pad buttons, although most controllers typically have directional controls.
- A source type of [`SOURCE_DPAD`](https://developer.android.com/reference/android/view/InputDevice#SOURCE_DPAD) indicates that the input device has D-pad buttons (for example, [`DPAD_UP`](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_DPAD_UP)).
- A source type of [`SOURCE_JOYSTICK`](https://developer.android.com/reference/android/view/InputDevice#SOURCE_JOYSTICK) indicates that the input device has analog control sticks (for example, a joystick that records movements along [`AXIS_X`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_X) and [`AXIS_Y`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_Y)).

The following code snippet shows a helper method that lets you check whether the
connected input devices are game controllers. If so, the method retrieves the
device IDs for the game controllers. You can then associate each device ID with
a player in your game, and process game actions for each connected player
separately. To learn more about supporting multiple game controllers that are
simultaneously connected on the same Android-powered device, see
[Support multiple game controllers](https://developer.android.com/games/sdk/game-controller/multiple-controllers).

### Kotlin

    fun getGameControllerIds(): List<Int> {
      val gameControllerDeviceIds = mutableListOf<Int>()
      val deviceIds = InputDevice.getDeviceIds()
      deviceIds.forEach { deviceId ->
          InputDevice.getDevice(deviceId)?.apply {

              // Verify that the device has gamepad buttons, control sticks, or both.
              if (supportsSource(SOURCE_GAMEPAD)
                  || supportsSource(SOURCE_JOYSTICK)) {
                  // This device is a game controller. Store its device ID.
                  gameControllerDeviceIds
                      .takeIf { !it.contains(deviceId) }
                      ?.add(deviceId)
              }
          }
      }
      return gameControllerDeviceIds
    }

### Java

     public ArrayList<Integer> getGameControllerIds() {
      ArrayList<Integer> gameControllerDeviceIds = new ArrayList<Integer>();
      int[] deviceIds = InputDevice.getDeviceIds();
      for (int deviceId : deviceIds) {
          InputDevice dev = InputDevice.getDevice(deviceId);

          if (dev == null) {
              continue;
          }

          // Verify that the device has gamepad buttons, control sticks, or both.
          if (dev.supportsSource(SOURCE_GAMEPAD) || dev.supportsSource(SOURCE_JOYSTICK)) {
              // This device is a game controller. Store its device ID.
              if (!gameControllerDeviceIds.contains(deviceId)) {
                  gameControllerDeviceIds.add(deviceId);
              }
          }
      }
      return gameControllerDeviceIds;
    }

## Process controller inputs

This section describes the types for game controllers that are supported on
Android.

C++ developers should use the [Game Controller Library](https://developer.android.com/games/sdk/game-controller). It unifies all
controllers to the most common subset of features and provides a consistent
interface between them, including the ability to detect the button layout.

This figure shows what an Android game developer can expect a common controller
to look like on Android.
![Generic game controller with labeled inputs including D-Pad, analog sticks, and buttons](https://developer.android.com/static/images/games/game-controller/img_1_no bg.svg) **Figure 1.** Profile for a generic game controller.

The table lists the standard event names and types for game controllers. For a
complete list of events, see [Common variants](https://developer.android.com/games/sdk/game-controller/controller-input#common-variants). The system
sends `MotionEvent` events through `onGenericMotionEvent` and `KeyEvent` events
through `onKeyDown` and `onKeyUp`.

| Controller Input | KeyEvent | MotionEvent |
|---|---|---|
| 1. D-Pad |   | `https://developer.android.com/reference/android/view/MotionEvent#AXIS_HAT_X` *(horizontal input)* `https://developer.android.com/reference/android/view/MotionEvent#AXIS_HAT_Y` *(vertical input)* |
| 2. Left Analog Stick | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_THUMBL` *(when pressed in)* | `https://developer.android.com/reference/android/view/MotionEvent#AXIS_X` *(horizontal movement)* `https://developer.android.com/reference/android/view/MotionEvent#AXIS_Y` *(vertical movement)* |
| 3. Right Analog Stick | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_THUMBR` *(when pressed in)* | `https://developer.android.com/reference/android/view/MotionEvent#AXIS_Z` *(horizontal movement)* `https://developer.android.com/reference/android/view/MotionEvent#AXIS_RZ` *(vertical movement)* |
| 4. X Button | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_X` |   |
| 5. A Button | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_A` |   |
| 6. Y Button | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_Y` |   |
| 7. B Button | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_B` |   |
| 8. Right Bumper | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_R1` |   |
| 9. Right Trigger |   | `https://developer.android.com/reference/android/view/MotionEvent#AXIS_RTRIGGER` |
| 10. Left Trigger |   | `https://developer.android.com/reference/android/view/MotionEvent#AXIS_LTRIGGER` |
| 11. Left Bumper | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_L1` |   |
| 12. Start | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_START` |   |
| 13. Select | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_SELECT` |   |

## Handle button presses

Since Android reports controller button presses identically to keyboard button
presses, you need to:

- Validate that the event is coming from a [`SOURCE_GAMEPAD`](https://developer.android.com/reference/android/view/InputDevice#SOURCE_GAMEPAD).
- Make sure you only receive the button once with [`KeyEvent.getRepeatCount()`](https://developer.android.com/reference/android/view/KeyEvent#getRepeatCount()), Android will send repeat key events just like if you held down a keyboard key.
- Indicate that an event is handled by returning `true`.
- Pass unhandled events to `super` to verify that Android's various
  compatibility layers function appropriately.

  ### Kotlin

      class GameView : View {
      // ...
      override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
          event.apply {
              var handled = false

              // make sure we're handling gamepad events
              if (isFromSource(SOURCE_GAMEPAD)) {

                  // avoid processing the keycode repeatedly
                  if (repeatCount == 0) {
                      when (keyCode) {
                          // handle the "A" button
                          KEYCODE_BUTTON_A -> {
                            handled = true
                          }
                      }
                      // ...
                  }
              }
              if (handled) {
                  return true
              }
         }
         return super.onKeyDown(keyCode, event)
        }
      }

  ### Java

      public class GameView extends View {
      // ...

      @Override
      public boolean onKeyDown(int keyCode, KeyEvent event) {
          boolean handled = false;
          // make sure we're handling gamepad events
          if (event.isFromSource(SOURCE_GAMEPAD)) {
              // avoid processing the keycode repeatedly
              if (event.getRepeatCount() == 0) {
                  switch (keyCode) {
                      case KEYCODE_BUTTON_A:
                          // handle the "A" button
                          handled = true;
                          break;
                      // ...
                  }
              }
              // mark this event as handled
              if (handled) {
                  return true;
              }
          }
          // Always do this instead of "return false"
          // it allows Android's input compatibility layers to work
          return super.onKeyDown(keyCode, event);
        }
      }

## Process directional pad input

The 4-way directional pad, or D-pad, is a common physical control in many game
controllers. Android reports D-pad UP and DOWN presses as `AXIS_HAT_Y` events,
with -1.0 indicating up and 1.0 indicating down. It reports D-pad LEFT or RIGHT
presses as `AXIS_HAT_X` events, with -1.0 indicating left and 1.0 indicating
right.

Some controllers instead report D-pad presses with a key code. If your game
cares about D-pad presses, you should treat the hat axis events and the D-pad
key codes as the same input events, as recommended in table 2.


**Table 2.** Recommended default game actions for D-pad key
codes and hat axis values.

| Game Action | D-pad Key Code | Hat Axis Code |
|---|---|---|
| Move Up | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_DPAD_UP` | `https://developer.android.com/reference/android/view/MotionEvent#AXIS_HAT_Y` (for values 0 to -1.0) |
| Move Down | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_DPAD_DOWN` | `https://developer.android.com/reference/android/view/MotionEvent#AXIS_HAT_Y` (for values 0 to 1.0) |
| Move Left | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_DPAD_LEFT` | `https://developer.android.com/reference/android/view/MotionEvent#AXIS_HAT_X` (for values 0 to -1.0) |
| Move Right | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_DPAD_RIGHT` | `https://developer.android.com/reference/android/view/MotionEvent#AXIS_HAT_X` (for values 0 to 1.0) |

The following code snippet shows a helper class that lets you check the hat axis
and key code values from an input event to determine the D-pad direction.

### Kotlin

    class Dpad {

        private var directionPressed = -1 // initialized to -1

        fun getDirectionPressed(event: InputEvent): Int {
            if (!isDpadDevice(event)) {
                return -1
            }

            // If the input event is a MotionEvent, check its hat axis values.
            (event as? MotionEvent)?.apply {

                // Use the hat axis value to find the D-pad direction
                val xaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_X)
                val yaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_Y)

                directionPressed = when {
                    // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
                    // LEFT and RIGHT direction accordingly.
                    xaxis.compareTo(-1.0f) == 0 -> Dpad.LEFT
                    xaxis.compareTo(1.0f) == 0 -> Dpad.RIGHT
                    // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
                    // UP and DOWN direction accordingly.
                    yaxis.compareTo(-1.0f) == 0 -> Dpad.UP
                    yaxis.compareTo(1.0f) == 0 -> Dpad.DOWN
                    else -> directionPressed
                }
            }
            // If the input event is a KeyEvent, check its key code.
            (event as? KeyEvent)?.apply {

                // Use the key code to find the D-pad direction.
                directionPressed = when(event.keyCode) {
                    KeyEvent.KEYCODE_DPAD_LEFT -> Dpad.LEFT
                    KeyEvent.KEYCODE_DPAD_RIGHT -> Dpad.RIGHT
                    KeyEvent.KEYCODE_DPAD_UP -> Dpad.UP
                    KeyEvent.KEYCODE_DPAD_DOWN -> Dpad.DOWN
                    KeyEvent.KEYCODE_DPAD_CENTER ->  Dpad.CENTER
                    else -> directionPressed
                }
            }
            return directionPressed
        }

        companion object {
            internal const val UP = 0
            internal const val LEFT = 1
            internal const val RIGHT = 2
            internal const val DOWN = 3
            internal const val CENTER = 4

            fun isDpadDevice(event: InputEvent): Boolean =
                // Check that input comes from a device with directional pads.
                return event.isFromSource(InputDevice.SOURCE_DPAD)
        }
    }

### Java

    public class Dpad {
        final static int UP       = 0;
        final static int LEFT     = 1;
        final static int RIGHT    = 2;
        final static int DOWN     = 3;
        final static int CENTER   = 4;

        int directionPressed = -1; // initialized to -1

        public int getDirectionPressed(InputEvent event) {
            if (!isDpadDevice(event)) {
               return -1;
            }

            // If the input event is a MotionEvent, check its hat axis values.
            if (event instanceof MotionEvent) {

                // Use the hat axis value to find the D-pad direction
                MotionEvent motionEvent = (MotionEvent) event;
                float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
                float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);

                // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
                // LEFT and RIGHT direction accordingly.
                if (Float.compare(xaxis, -1.0f) == 0) {
                    directionPressed =  Dpad.LEFT;
                } else if (Float.compare(xaxis, 1.0f) == 0) {
                    directionPressed =  Dpad.RIGHT;
                }
                // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
                // UP and DOWN direction accordingly.
                else if (Float.compare(yaxis, -1.0f) == 0) {
                    directionPressed =  Dpad.UP;
                } else if (Float.compare(yaxis, 1.0f) == 0) {
                    directionPressed =  Dpad.DOWN;
                }
            }

            // If the input event is a KeyEvent, check its key code.
            else if (event instanceof KeyEvent) {

               // Use the key code to find the D-pad direction.
                KeyEvent keyEvent = (KeyEvent) event;
                if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
                    directionPressed = Dpad.LEFT;
                } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                    directionPressed = Dpad.RIGHT;
                } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
                    directionPressed = Dpad.UP;
                } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
                    directionPressed = Dpad.DOWN;
                } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
                    directionPressed = Dpad.CENTER;
                }
            }
            return directionPressed;
        }

        public static boolean isDpadDevice(InputEvent event) {
            // Check that input comes from a device with directional pads.
            return event.isFromSource(InputDevice.SOURCE_DPAD);
         }
    }

You can use this helper class in your game wherever you want to process D-pad
input (for example, in the
[`onGenericMotionEvent()`](https://developer.android.com/reference/android/view/View#onGenericMotionEvent(android.view.MotionEvent))
or
[`onKeyDown()`](https://developer.android.com/reference/android/view/View#onKeyDown(int,%20android.view.KeyEvent))
callbacks).

For example:

### Kotlin

    private val dpad = Dpad()
    ...
    override fun onGenericMotionEvent(event: MotionEvent): Boolean {
        if (Dpad.isDpadDevice(event)) {
            when (dpad.getDirectionPressed(event)) {
                Dpad.LEFT -> {
                    // Do something for LEFT direction press
                    ...
                    return true
                }
                Dpad.RIGHT -> {
                    // Do something for RIGHT direction press
                    ...
                    return true
                }
                Dpad.UP -> {
                    // Do something for UP direction press
                    ...
                    return true
                }
                ...
            }
        }

        // Check if this event is from a joystick movement and process accordingly.
        ...
    }

### Java

    Dpad dpad = new Dpad();
    ...
    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {

        // Check if this event if from a D-pad and process accordingly.
        if (Dpad.isDpadDevice(event)) {

           int press = dpad.getDirectionPressed(event);
           switch (press) {
                case LEFT:
                    // Do something for LEFT direction press
                    ...
                    return true;
                case RIGHT:
                    // Do something for RIGHT direction press
                    ...
                    return true;
                case UP:
                    // Do something for UP direction press
                    ...
                    return true;
                ...
            }
        }

        // Check if this event is from a joystick movement and process accordingly.
        ...
    }

## Process joystick movements

When players move a joystick on their game controllers, Android reports a
[`MotionEvent`](https://developer.android.com/reference/android/view/MotionEvent) that contains the
[`ACTION_MOVE`](https://developer.android.com/reference/android/view/MotionEvent#ACTION_MOVE) action
code and the updated positions of the joystick's axes. Your game can use the
data provided by the [`MotionEvent`](https://developer.android.com/reference/android/view/MotionEvent)
to determine if a joystick movement it cares about happened.

Note that joystick motion events may batch multiple movement samples together
within a single object. The
[`MotionEvent`](https://developer.android.com/reference/android/view/MotionEvent) object contains the
current position for each joystick axis as well as multiple historical positions
for each axis. When reporting motion events with action code
[`ACTION_MOVE`](https://developer.android.com/reference/android/view/MotionEvent#ACTION_MOVE) (such as
joystick movements), Android batches up the axis values for efficiency. The
historical values for an axis consists of the set of distinct values older than
the current axis value, and more recent than values reported in any previous
motion events. See the [`MotionEvent`](https://developer.android.com/reference/android/view/MotionEvent)
reference for details.

To accurately render a game object's movement based on joystick input, you can
use the historical information provided by `MotionEvent` objects.

You can retrieve current and historical values using the following methods:

- [`getAxisValue()`](https://developer.android.com/reference/android/view/MotionEvent#getAxisValue(int))
- [`getHistoricalAxisValue()`](https://developer.android.com/reference/android/view/MotionEvent#getHistoricalAxisValue(int,%20int))
- [`getHistorySize()`](https://developer.android.com/reference/android/view/MotionEvent#getHistorySize()) (to find the number of historical points in the joystick event)

The following snippet shows how you might override the
[`onGenericMotionEvent()`](https://developer.android.com/reference/android/view/View#onGenericMotionEvent(android.view.MotionEvent))
callback to process joystick input. You should first process the historical
values for an axis, then process its current position.

### Kotlin

    class GameView(...) : View(...) {

        override fun onGenericMotionEvent(event: MotionEvent): Boolean {

            // Check that the event came from a game controller
            return if (event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
                    && event.action == MotionEvent.ACTION_MOVE) {

                // Process the movements starting from the
                // earliest historical position in the batch
                (0 until event.historySize).forEach { i ->
                    // Process the event at historical position i
                    processJoystickInput(event, i)
                }

                // Process the current movement sample in the batch (position -1)
                processJoystickInput(event, -1)
                true
            } else {
                super.onGenericMotionEvent(event)
            }
        }
    }

### Java

    public class GameView extends View {

        @Override
        public boolean onGenericMotionEvent(MotionEvent event) {

            // Check that the event came from a game controller
            if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) ==
                    InputDevice.SOURCE_JOYSTICK &&
                    event.getAction() == MotionEvent.ACTION_MOVE) {

                // Process all historical movement samples in the batch
                final int historySize = event.getHistorySize();

                // Process the movements starting from the
                // earliest historical position in the batch
                for (int i = 0; i < historySize; i++) {
                    // Process the event at historical position i
                    processJoystickInput(event, i);
                }

                // Process the current movement sample in the batch (position -1)
                processJoystickInput(event, -1);
                return true;
            }
            return super.onGenericMotionEvent(event);
        }
    }

Before using joystick input, you need to determine if the joystick is centered,
then calculate its axis movements accordingly. Joysticks typically have a *flat*
area, that is, a range of values near the (0,0) coordinate at which the axis is
considered to be centered. If the axis value reported by Android falls within
the flat area, you should treat the controller to be at rest (that is,
motionless along both axes).

The snippet shows a helper method that calculates the movement along each
axis. You invoke this helper in the `processJoystickInput()` method described
further in the following sample:

### Kotlin

    private fun getCenteredAxis(
            event: MotionEvent,
            device: InputDevice,
            axis: Int,
            historyPos: Int
    ): Float {
        val range: InputDevice.MotionRange? = device.getMotionRange(axis, event.source)

        // A joystick at rest does not always report an absolute position of
        // (0,0). Use the getFlat() method to determine the range of values
        // bounding the joystick axis center.
        range?.apply {
            val value: Float = if (historyPos < 0) {
                event.getAxisValue(axis)
            } else {
                event.getHistoricalAxisValue(axis, historyPos)
            }

            // Ignore axis values that are within the 'flat' region of the
            // joystick axis center.
            if (Math.abs(value) > flat) {
                return value
            }
        }
        return 0f
    }

### Java

    private static float getCenteredAxis(MotionEvent event,
            InputDevice device, int axis, int historyPos) {
        final InputDevice.MotionRange range =
                device.getMotionRange(axis, event.getSource());

        // A joystick at rest does not always report an absolute position of
        // (0,0). Use the getFlat() method to determine the range of values
        // bounding the joystick axis center.
        if (range != null) {
            final float flat = range.getFlat();
            final float value =
                    historyPos < 0 ? event.getAxisValue(axis):
                    event.getHistoricalAxisValue(axis, historyPos);

            // Ignore axis values that are within the 'flat' region of the
            // joystick axis center.
            if (Math.abs(value) > flat) {
                return value;
            }
        }
        return 0;
    }

Putting it all together, here is how you might process joystick movements in
your game:

### Kotlin

    private fun processJoystickInput(event: MotionEvent, historyPos: Int) {

        val inputDevice = event.device

        // Calculate the horizontal distance to move by
        // using the input value from one of these physical controls:
        // the left control stick, hat axis, or the right control stick.
        var x: Float = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_X, historyPos)
        if (x == 0f) {
            x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_X, historyPos)
        }
        if (x == 0f) {
            x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Z, historyPos)
        }

        // Calculate the vertical distance to move by
        // using the input value from one of these physical controls:
        // the left control stick, hat switch, or the right control stick.
        var y: Float = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Y, historyPos)
        if (y == 0f) {
            y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_Y, historyPos)
        }
        if (y == 0f) {
            y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_RZ, historyPos)
        }

        // Update the ship object based on the new x and y values
    }

### Java

    private void processJoystickInput(MotionEvent event,
            int historyPos) {

        InputDevice inputDevice = event.getDevice();

        // Calculate the horizontal distance to move by
        // using the input value from one of these physical controls:
        // the left control stick, hat axis, or the right control stick.
        float x = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_X, historyPos);
        if (x == 0) {
            x = getCenteredAxis(event, inputDevice,
                    MotionEvent.AXIS_HAT_X, historyPos);
        }
        if (x == 0) {
            x = getCenteredAxis(event, inputDevice,
                    MotionEvent.AXIS_Z, historyPos);
        }

        // Calculate the vertical distance to move by
        // using the input value from one of these physical controls:
        // the left control stick, hat switch, or the right control stick.
        float y = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_Y, historyPos);
        if (y == 0) {
            y = getCenteredAxis(event, inputDevice,
                    MotionEvent.AXIS_HAT_Y, historyPos);
        }
        if (y == 0) {
            y = getCenteredAxis(event, inputDevice,
                    MotionEvent.AXIS_RZ, historyPos);
        }

        // Update the ship object based on the new x and y values
    }

To support game controllers that have more sophisticated features beyond a
single joystick, follow these best practices:

- **Handle dual controller sticks.** Many game controllers have both a left and right joystick. For the left stick, Android reports horizontal movements as [`AXIS_X`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_X) events and vertical movements as [`AXIS_Y`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_Y) events. For the right stick, Android reports horizontal movements as [`AXIS_Z`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_Z) events and vertical movements as [`AXIS_RZ`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_RZ) events. Make sure to handle both controller sticks in your code.
- **Handle shoulder trigger presses (and make sure your game works with
  `AXIS_` and `KEYCODE_BUTTON_` events).** Some controllers have left and right shoulder triggers. When these triggers are present, they emit an `AXIS_*TRIGGER` or `KEYCODE_BUTTON_*2` event or both. For the left trigger, this would be [`AXIS_LTRIGGER`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_LTRIGGER) and [`KEYCODE_BUTTON_L2`](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_L2). For the right trigger this would be [`AXIS_RTRIGGER`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_RTRIGGER) and [`KEYCODE_BUTTON_R2`](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_R2). Axis events only occur if the trigger emits a range of values between 0 and 1, and some controllers with analog output emit button events in addition to axis events. Games must support both `AXIS_` and `KEYCODE_BUTTON_` events to remain compatible with all common game controllers, but prefer the event that makes the most sense for your gameplay if a controller reports both. On Android 4.3 (API level 18) and higher, a controller that produces a [`AXIS_LTRIGGER`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_LTRIGGER) also reports an identical value for the [`AXIS_BRAKE`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_BRAKE) axis. The same is true for [`AXIS_RTRIGGER`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_RTRIGGER) and [`AXIS_GAS`](https://developer.android.com/reference/android/view/MotionEvent#AXIS_GAS). Android reports all analog trigger presses with a normalized value from 0.0 (released) to 1.0 (fully pressed).
- **Specific behaviors and support may differ in emulated environments** . Emulated platforms, such as [Google Play Games](https://developer.android.com/games/playgames/overview), may differ slightly in behaviour based on the capabilities of the host operating system. For example, some controllers that emit both `AXIS_` and `KEYCODE_BUTTON_` events only emit `AXIS_` events, and support for some controllers may be missing entirely.

## Common variants

With the wide variety of support Android has for controllers, it may be unclear
how to build and test to verify your game works bug-free among your playerbase.
We find that despite this apparent variety, controller manufacturers around the
world tend to consistently adhere to three different styles of controller. Some
provide hardware toggles between two or more of these.

This means that you can test with as few as three controllers among your dev
team and remain confident that your game is playable without resorting to allow
and deny lists.

### Common Controller Types

The most common style of controllers tend to mimic the layouts of popular game
consoles. This is both aesthetic in the button labels and layout and functional
by what events are raised. Controllers with hardware toggles between different
console types will change the events they send and often even their logical
button layout.

When testing we recommend you validate that your game works with one controller
in each of the categories. You may choose to test with first party controllers,
or popular third party manufacturers. Generally we will map the most
popular controllers to the [definition above](https://developer.android.com/games/sdk/game-controller/controller-input#process-inputs) by best effort.

| Controller Type | Behavioural Differences | Labelling Variations |
|---|---|---|
| **Xbox Style Controllers** These are controllers typically made for the Microsoft Xbox and Windows\* platform. | These controllers match the feature set outlined in [Process controller inputs](https://developer.android.com/games/sdk/game-controller/controller-input#process-inputs) | The L2/R2 buttons on these controllers are labelled LT/RT |
| **Switch Style Controllers** These controllers typically are designed for the Nintendo Switch\* family of consoles. | These controllers send the `https://developer.android.com/reference/android/view/KeyEvent` `https://developer.android.com/reference/android/view/KeyEvent` `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_L2` `https://developer.android.com/reference/android/view/MotionEvent`s | The L2/R2 buttons on these controllers are labelled ZL/ZR. These controllers also swap the **A** and **B** buttons and the **X** and **Y** buttons, so `KEYCODE_BUTTON_A` is the button labeled **B** and conversely. |
| **PlayStation Style Controllers** These controllers typically are designed for the Sony PlayStation\* family of consoles. | These controllers send `https://developer.android.com/reference/android/view/MotionEvent`s like the **Xbox Style Controllers** , but also send `https://developer.android.com/reference/android/view/KeyEvent`s like the **Switch Style Controllers** when fully depressed. | These controllers use a different set of glyphs for face buttons. |

^\*^
Microsoft, Xbox, and Windows are registered trademarks of Microsoft;
Nintendo Switch is a registered trademark of Nintendo of America Inc.;
PlayStation is a registered trademark of Sony Interactive Entertainment Inc.

> [!NOTE]
> **Note:** This is a list of controllers styles, not vendors. You shouldn't gate functionality on device name or Vendor ID, although it is recommended to auto-adapt UI prompts.

> [!NOTE]
> **Note:** The button positions on a controller will remain consistent across controllers (For example, `KEYCODE_BUTTON_A` is always the south most button even if it's labelled "B") as long as you use `keyCode`.

### Disambiguate trigger buttons

Some controllers send `AXIS_LTRIGGER` and `AXIS_RTRIGGER`, some send
`KEYCODE_BUTTON_L2` and `KEYCODE_BUTTON_R2`, and others send all these events
based on their hardware capabilities. Maximize compatibility by supporting all
of these events.

All controllers that send `AXIS_LTRIGGER` will also send `AXIS_BRAKE`, similarly
for `AXIS_RTRIGGER` and `AXIS_GAS` to help maximize compatibility between racing
wheels and typical game controllers. Generally this won't cause issues, but be
aware for features like key remapping screens.

| Trigger | `MotionEvent` | `KeyEvent` |
|---|---|---|
| Left Trigger | `https://developer.android.com/reference/android/view/MotionEvent#AXIS_LTRIGGER` `https://developer.android.com/reference/android/view/MotionEvent#AXIS_BRAKE` | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_L2` |
| Right Trigger | `https://developer.android.com/reference/android/view/MotionEvent#AXIS_RTRIGGER` `https://developer.android.com/reference/android/view/MotionEvent#AXIS_GAS` | `https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_BUTTON_R2` |

Care should be taken to verify your game can handle both `KeyEvent` and
`MotionEvent` to maintain compatibility with as many controllers as possible,
and that events are de-duplicated.

### Supported Controllers

When testing we recommend you validate that your game works with one controller
in each of the categories.

- Xbox Style
- Nintendo Switch Style
- PlayStation Style

You can test with first-party controllers or popular third-party manufacturers,
and we generally map the most popular controllers to the definition as closely
as possible.