Defines Gamepad-specific keys and axis in UinputBridge.

Adds the ability to open gamepads in the JNI layer of server.tv.
Major changes:
  - defined gamepad specific keys and axes
  - centralized /dev/uinput file descriptor management during the
    open method (since gamepad and remote have similar open logic)
  - added gamepad open, keysend and axis send methods.

Test: Android remote service still works on a clean build & flash.
     (tested remote service on my phone and UI navigation).
Test: as part of a larger chage chain, opened gamepad devices
      and observed /dev/input/event* behaviour for axes and keys.
Bug: 150775713
Bug: 150776008
Bug: 150784056
Change-Id: I2eee6844dd85346753ec62430c234cc7e0179d3b
diff --git a/services/core/java/com/android/server/tv/UinputBridge.java b/services/core/java/com/android/server/tv/UinputBridge.java
index 752aa66..a2fe5fc 100644
--- a/services/core/java/com/android/server/tv/UinputBridge.java
+++ b/services/core/java/com/android/server/tv/UinputBridge.java
@@ -28,7 +28,7 @@
 public final class UinputBridge {
     private final CloseGuard mCloseGuard = CloseGuard.get();
     private long mPtr;
-    private IBinder mToken = null;
+    private IBinder mToken;
 
     private static native long nativeOpen(String name, String uniqueId, int width, int height,
                                           int maxPointers);
@@ -39,6 +39,25 @@
     private static native void nativeSendPointerUp(long ptr, int pointerId);
     private static native void nativeSendPointerSync(long ptr);
 
+    /** Opens a gamepad - will support gamepad key and axis sending */
+    private static native long nativeGamepadOpen(String name, String uniqueId);
+
+    /** Marks the specified key up/down for a gamepad */
+    private static native void nativeSendGamepadKey(long ptr, int keyIndex, boolean down);
+
+    /**
+     * Gamepads pre-define the following axes:
+     *   - Left joystick X, axis == ABS_X == 0, range [0, 254]
+     *   - Left joystick Y, axis == ABS_Y == 1, range [0, 254]
+     *   - Right joystick X, axis == ABS_RX == 3, range [0, 254]
+     *   - Right joystick Y, axis == ABS_RY == 4, range [0, 254]
+     *   - Left trigger, axis == ABS_Z == 2, range [0, 254]
+     *   - Right trigger, axis == ABS_RZ == 5, range [0, 254]
+     *   - DPad X, axis == ABS_HAT0X == 0x10, range [-1, 1]
+     *   - DPad Y, axis == ABS_HAT0Y == 0x11, range [-1, 1]
+     */
+    private static native void nativeSendGamepadAxisValue(long ptr, int axis, int value);
+
     public UinputBridge(IBinder token, String name, int width, int height, int maxPointers)
                         throws IOException {
         if (width < 1 || height < 1) {
@@ -58,12 +77,31 @@
         mCloseGuard.open("close");
     }
 
+    /** Constructor used by static factory methods */
+    private UinputBridge(IBinder token, long ptr) {
+        mPtr = ptr;
+        mToken = token;
+        mCloseGuard.open("close");
+    }
+
+    /** Opens a UinputBridge that supports gamepad buttons and axes. */
+    public static UinputBridge openGamepad(IBinder token, String name)
+            throws IOException {
+        if (token == null) {
+            throw new IllegalArgumentException("Token cannot be null");
+        }
+        long ptr = nativeGamepadOpen(name, token.toString());
+        if (ptr == 0) {
+            throw new IOException("Could not open uinput device " + name);
+        }
+
+        return new UinputBridge(token, ptr);
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mCloseGuard != null) {
-                mCloseGuard.warnIfOpen();
-            }
+            mCloseGuard.warnIfOpen();
             close(mToken);
         } finally {
             mToken = null;
@@ -119,7 +157,35 @@
         if (isTokenValid(token)) {
             nativeSendPointerSync(mPtr);
         }
+    }
 
+    /** Send a gamepad key
+     *  @param keyIndex - the index of the w3-spec key
+     *  @param down - is the key pressed ?
+     */
+    public void sendGamepadKey(IBinder token, int keyIndex, boolean down) {
+        if (isTokenValid(token)) {
+            nativeSendGamepadKey(mPtr, keyIndex, down);
+        }
+    }
+
+    /** Send a gamepad axis value.
+     *   - Left joystick X, axis == ABS_X == 0, range [0, 254]
+     *   - Left joystick Y, axis == ABS_Y == 1, range [0, 254]
+     *   - Right joystick X, axis == ABS_RX == 3, range [0, 254]
+     *   - Right joystick Y, axis == ABS_RY == 4, range [0, 254]
+     *   - Left trigger, axis == ABS_Z == 2, range [0, 254]
+     *   - Right trigger, axis == ABS_RZ == 5, range [0, 254]
+     *   - DPad X, axis == ABS_HAT0X == 0x10, range [-1, 1]
+     *   - DPad Y, axis == ABS_HAT0Y == 0x11, range [-1, 1]
+     *
+     * @param axis is the axis index
+     * @param value is the value to set for that axis
+     */
+    public void sendGamepadAxisValue(IBinder token, int axis, int value) {
+        if (isTokenValid(token)) {
+            nativeSendGamepadAxisValue(mPtr, axis, value);
+        }
     }
 
     public void clear(IBinder token) {
diff --git a/services/core/jni/com_android_server_tv_GamepadKeys.h b/services/core/jni/com_android_server_tv_GamepadKeys.h
new file mode 100644
index 0000000..11fc903
--- /dev/null
+++ b/services/core/jni/com_android_server_tv_GamepadKeys.h
@@ -0,0 +1,79 @@
+#ifndef ANDROIDTVREMOTE_SERVICE_JNI_GAMEPAD_KEYS_H_
+#define ANDROIDTVREMOTE_SERVICE_JNI_GAMEPAD_KEYS_H_
+
+#include <linux/input.h>
+
+namespace android {
+
+// Follows the W3 spec for gamepad buttons and their corresponding mapping into
+// Linux keycodes. Note that gamepads are generally not very well standardized
+// and various controllers will result in different buttons. This mapping tries
+// to be reasonable.
+//
+// W3 Button spec: https://www.w3.org/TR/gamepad/#remapping
+//
+// Standard gamepad keycodes are added plus 2 additional buttons (e.g. Stadia
+// has "Assistant" and "Share", PS4 has the touchpad button).
+//
+// To generate this list, PS4, XBox, Stadia and Nintendo Switch Pro were tested.
+static const int GAMEPAD_KEY_CODES[19] = {
+        // Right-side buttons. A/B/X/Y or circle/triangle/square/X or similar
+        BTN_A, // "South", A, GAMEPAD and SOUTH have the same constant
+        BTN_B, // "East", BTN_B, BTN_EAST have the same constant
+        BTN_X, // "West", Note that this maps to X and NORTH in constants
+        BTN_Y, // "North", Note that this maps to Y and WEST in constants
+
+        BTN_TL, // "Left Bumper" / "L1" - Nintendo sends BTN_WEST instead
+        BTN_TR, // "Right Bumper" / "R1" - Nintendo sends BTN_Z instead
+
+        // For triggers, gamepads vary:
+        //   - Stadia sends analog values over ABS_GAS/ABS_BRAKE and sends
+        //     TriggerHappy3/4 as digital presses
+        //   - PS4 and Xbox send analog values as ABS_Z/ABS_RZ
+        //   - Nintendo Pro sends BTN_TL/BTN_TR (since bumpers behave differently)
+        // As placeholders we chose the stadia trigger-happy values since TL/TR are
+        // sent for bumper button presses
+        BTN_TRIGGER_HAPPY4, // "Left Trigger" / "L2"
+        BTN_TRIGGER_HAPPY3, // "Right Trigger" / "R2"
+
+        BTN_SELECT, // "Select/Back". Often "options" or similar
+        BTN_START,  // "Start/forward". Often "hamburger" icon
+
+        BTN_THUMBL, // "Left Joystick Pressed"
+        BTN_THUMBR, // "Right Joystick Pressed"
+
+        // For DPads, gamepads generally only send axis changes
+        // on ABS_HAT0X and ABS_HAT0Y.
+        KEY_UP,    // "Digital Pad up"
+        KEY_DOWN,  // "Digital Pad down"
+        KEY_LEFT,  // "Digital Pad left"
+        KEY_RIGHT, // "Digital Pad right"
+
+        BTN_MODE, // "Main button" (Stadia/PS/XBOX/Home)
+
+        BTN_TRIGGER_HAPPY1, // Extra button: "Assistant" for Stadia
+        BTN_TRIGGER_HAPPY2, // Extra button: "Share" for Stadia
+};
+
+// Defines information for an axis.
+struct Axis {
+    int number;
+    int rangeMin;
+    int rangeMax;
+};
+
+// List of all axes supported by a gamepad
+static const Axis GAMEPAD_AXES[] = {
+        {ABS_X, 0, 254},    // Left joystick X
+        {ABS_Y, 0, 254},    // Left joystick Y
+        {ABS_RX, 0, 254},   // Right joystick X
+        {ABS_RY, 0, 254},   // Right joystick Y
+        {ABS_Z, 0, 254},    // Left trigger
+        {ABS_RZ, 0, 254},   // Right trigger
+        {ABS_HAT0X, -1, 1}, // DPad X
+        {ABS_HAT0Y, -1, 1}, // DPad Y
+};
+
+} // namespace android
+
+#endif // ANDROIDTVREMOTE_SERVICE_JNI_GAMEPAD_KEYS_H_
diff --git a/services/core/jni/com_android_server_tv_TvKeys.h b/services/core/jni/com_android_server_tv_TvKeys.h
index 4895f34..7eacdf6 100644
--- a/services/core/jni/com_android_server_tv_TvKeys.h
+++ b/services/core/jni/com_android_server_tv_TvKeys.h
@@ -110,4 +110,4 @@
 
 } // namespace android
 
-#endif // SERVICE_JNI_KEYS_H_
+#endif // ANDROIDTVREMOTE_SERVICE_JNI_KEYS_H_
diff --git a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
index c832c18..0e96bd7 100644
--- a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
+++ b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "TvRemote-native-uiBridge"
 
+#include "com_android_server_tv_GamepadKeys.h"
 #include "com_android_server_tv_TvKeys.h"
 
 #include "jni.h"
@@ -92,27 +93,156 @@
     }
 }
 
+static const int kInvalidFileDescriptor = -1;
+
+// Convenience class to manage an opened /dev/uinput device
+class UInputDescriptor {
+public:
+    UInputDescriptor() : mFd(kInvalidFileDescriptor) {
+        memset(&mUinputDescriptor, 0, sizeof(mUinputDescriptor));
+    }
+
+    // Auto-closes any open /dev/uinput descriptor unless detached.
+    ~UInputDescriptor();
+
+    // Open /dev/uinput and prepare to register
+    // the device with the given name and unique Id
+    bool Open(const char* name, const char* uniqueId);
+
+    // Checks if the current file descriptor is valid
+    bool IsValid() const { return mFd != kInvalidFileDescriptor; }
+
+    void EnableKey(int keyCode);
+
+    void EnableAxesEvents();
+    void EnableAxis(int axis, int rangeMin, int rangeMax);
+
+    bool Create();
+
+    // Detaches from the current file descriptor
+    // Returns the file descriptor for /dev/uniput
+    int Detach();
+
+private:
+    int mFd;
+    struct uinput_user_dev mUinputDescriptor;
+};
+
+UInputDescriptor::~UInputDescriptor() {
+    if (mFd != kInvalidFileDescriptor) {
+        close(mFd);
+        mFd = kInvalidFileDescriptor;
+    }
+}
+
+int UInputDescriptor::Detach() {
+    int fd = mFd;
+    mFd = kInvalidFileDescriptor;
+    return fd;
+}
+
+bool UInputDescriptor::Open(const char* name, const char* uniqueId) {
+    if (IsValid()) {
+        ALOGE("UInput device already open");
+        return false;
+    }
+
+    mFd = ::open("/dev/uinput", O_WRONLY | O_NDELAY);
+    if (mFd < 0) {
+        ALOGE("Cannot open /dev/uinput: %s.", strerror(errno));
+        mFd = kInvalidFileDescriptor;
+        return false;
+    }
+
+    // write device unique id to the phys property
+    ioctl(mFd, UI_SET_PHYS, uniqueId);
+
+    memset(&mUinputDescriptor, 0, sizeof(mUinputDescriptor));
+    strlcpy(mUinputDescriptor.name, name, UINPUT_MAX_NAME_SIZE);
+    mUinputDescriptor.id.version = 1;
+    mUinputDescriptor.id.bustype = BUS_VIRTUAL;
+
+    // All UInput devices we use process keys
+    ioctl(mFd, UI_SET_EVBIT, EV_KEY);
+
+    return true;
+}
+
+void UInputDescriptor::EnableKey(int keyCode) {
+    ioctl(mFd, UI_SET_KEYBIT, keyCode);
+}
+
+void UInputDescriptor::EnableAxesEvents() {
+    ioctl(mFd, UI_SET_EVBIT, EV_ABS);
+}
+
+void UInputDescriptor::EnableAxis(int axis, int rangeMin, int rangeMax) {
+    if ((axis < 0) || (axis >= NELEM(mUinputDescriptor.absmin))) {
+        ALOGE("Invalid axis number: %d", axis);
+        return;
+    }
+
+    if (ioctl(mFd, UI_SET_ABSBIT, axis) != 0) {
+        ALOGE("Failed to set absbit for %d", axis);
+    }
+
+    mUinputDescriptor.absmin[axis] = rangeMin;
+    mUinputDescriptor.absmax[axis] = rangeMax;
+    mUinputDescriptor.absfuzz[axis] = 0;
+    mUinputDescriptor.absflat[axis] = 0;
+}
+
+bool UInputDescriptor::Create() {
+    // register the input device
+    if (write(mFd, &mUinputDescriptor, sizeof(mUinputDescriptor)) != sizeof(mUinputDescriptor)) {
+        ALOGE("Cannot write uinput_user_dev to fd %d: %s.", mFd, strerror(errno));
+        return false;
+    }
+
+    if (ioctl(mFd, UI_DEV_CREATE) != 0) {
+        ALOGE("Unable to create uinput device: %s.", strerror(errno));
+        return false;
+    }
+
+    ALOGV("Created uinput device, fd=%d.", mFd);
+
+    return true;
+}
+
 class NativeConnection {
 public:
+    enum class ConnectionType {
+        kRemoteDevice,
+        kGamepadDevice,
+    };
+
     ~NativeConnection();
 
     static NativeConnection* open(const char* name, const char* uniqueId,
             int32_t width, int32_t height, int32_t maxPointerId);
 
+    static NativeConnection* openGamepad(const char* name, const char* uniqueId);
+
     void sendEvent(int32_t type, int32_t code, int32_t value);
 
     int32_t getMaxPointers() const { return mMaxPointers; }
 
+    ConnectionType getType() const { return mType; }
+
+    bool IsGamepad() const { return getType() == ConnectionType::kGamepadDevice; }
+
+    bool IsRemote() const { return getType() == ConnectionType::kRemoteDevice; }
+
 private:
-    NativeConnection(int fd, int32_t maxPointers);
+    NativeConnection(int fd, int32_t maxPointers, ConnectionType type);
 
     const int mFd;
     const int32_t mMaxPointers;
+    const ConnectionType mType;
 };
 
-NativeConnection::NativeConnection(int fd, int32_t maxPointers) :
-        mFd(fd), mMaxPointers(maxPointers) {
-}
+NativeConnection::NativeConnection(int fd, int32_t maxPointers, ConnectionType type)
+      : mFd(fd), mMaxPointers(maxPointers), mType(type) {}
 
 NativeConnection::~NativeConnection() {
     ALOGI("Un-Registering uinput device %d.", mFd);
@@ -125,44 +255,50 @@
     ALOGI("Registering uinput device %s: touch pad size %dx%d, "
             "max pointers %d.", name, width, height, maxPointers);
 
-    int fd = ::open("/dev/uinput", O_WRONLY | O_NDELAY);
-    if (fd < 0) {
-        ALOGE("Cannot open /dev/uinput: %s.", strerror(errno));
-        return nullptr;
-    }
-
-    struct uinput_user_dev uinp;
-    memset(&uinp, 0, sizeof(struct uinput_user_dev));
-    strlcpy(uinp.name, name, UINPUT_MAX_NAME_SIZE);
-    uinp.id.version = 1;
-    uinp.id.bustype = BUS_VIRTUAL;
-
-    // initialize keymap
     initKeysMap();
 
-    // write device unique id to the phys property
-    ioctl(fd, UI_SET_PHYS, uniqueId);
-
-    // set the keys mapped
-    ioctl(fd, UI_SET_EVBIT, EV_KEY);
-    for (size_t i = 0; i < NELEM(KEYS); i++) {
-        ioctl(fd, UI_SET_KEYBIT, KEYS[i].linuxKeyCode);
-    }
-
-    // register the input device
-    if (write(fd, &uinp, sizeof(uinp)) != sizeof(uinp)) {
-        ALOGE("Cannot write uinput_user_dev to fd %d: %s.", fd, strerror(errno));
-        close(fd);
-        return NULL;
-    }
-    if (ioctl(fd, UI_DEV_CREATE) != 0) {
-        ALOGE("Unable to create uinput device: %s.", strerror(errno));
-        close(fd);
+    UInputDescriptor descriptor;
+    if (!descriptor.Open(name, uniqueId)) {
         return nullptr;
     }
 
-    ALOGV("Created uinput device, fd=%d.", fd);
-    return new NativeConnection(fd, maxPointers);
+    // set the keys mapped
+    for (size_t i = 0; i < NELEM(KEYS); i++) {
+        descriptor.EnableKey(KEYS[i].linuxKeyCode);
+    }
+
+    if (!descriptor.Create()) {
+        return nullptr;
+    }
+
+    return new NativeConnection(descriptor.Detach(), maxPointers, ConnectionType::kRemoteDevice);
+}
+
+NativeConnection* NativeConnection::openGamepad(const char* name, const char* uniqueId) {
+    ALOGI("Registering uinput device %s: gamepad", name);
+
+    UInputDescriptor descriptor;
+    if (!descriptor.Open(name, uniqueId)) {
+        return nullptr;
+    }
+
+    // set the keys mapped for gamepads
+    for (size_t i = 0; i < NELEM(GAMEPAD_KEY_CODES); i++) {
+        descriptor.EnableKey(GAMEPAD_KEY_CODES[i]);
+    }
+
+    // define the axes that are required
+    descriptor.EnableAxesEvents();
+    for (size_t i = 0; i < NELEM(GAMEPAD_AXES); i++) {
+        const Axis& axis = GAMEPAD_AXES[i];
+        descriptor.EnableAxis(axis.number, axis.rangeMin, axis.rangeMax);
+    }
+
+    if (!descriptor.Create()) {
+        return nullptr;
+    }
+
+    return new NativeConnection(descriptor.Detach(), 0, ConnectionType::kGamepadDevice);
 }
 
 void NativeConnection::sendEvent(int32_t type, int32_t code, int32_t value) {
@@ -174,7 +310,6 @@
     write(mFd, &iev, sizeof(iev));
 }
 
-
 static jlong nativeOpen(JNIEnv* env, jclass clazz,
         jstring nameStr, jstring uniqueIdStr,
         jint width, jint height, jint maxPointers) {
@@ -186,6 +321,14 @@
     return reinterpret_cast<jlong>(connection);
 }
 
+static jlong nativeGamepadOpen(JNIEnv* env, jclass clazz, jstring nameStr, jstring uniqueIdStr) {
+    ScopedUtfChars name(env, nameStr);
+    ScopedUtfChars uniqueId(env, uniqueIdStr);
+
+    NativeConnection* connection = NativeConnection::openGamepad(name.c_str(), uniqueId.c_str());
+    return reinterpret_cast<jlong>(connection);
+}
+
 static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) {
     NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
     delete connection;
@@ -194,6 +337,12 @@
 static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jboolean down) {
     int32_t code = getLinuxKeyCode(keyCode);
     NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
+
+    if (connection->IsGamepad()) {
+        ALOGE("Invalid key even for a gamepad - need to send gamepad events");
+        return;
+    }
+
     if (code != KEY_UNKNOWN) {
         connection->sendEvent(EV_KEY, code, down ? 1 : 0);
     } else {
@@ -201,10 +350,44 @@
     }
 }
 
+static void nativeSendGamepadKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyIndex,
+                                 jboolean down) {
+    NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
+
+    if (!connection->IsGamepad()) {
+        ALOGE("Invalid gamepad key for non-gamepad device");
+        return;
+    }
+
+    if ((keyIndex < 0) || (keyIndex >= NELEM(GAMEPAD_KEY_CODES))) {
+        ALOGE("Invalid gamepad key index: %d", keyIndex);
+        return;
+    }
+
+    connection->sendEvent(EV_KEY, GAMEPAD_KEY_CODES[keyIndex], down ? 1 : 0);
+}
+
+static void nativeSendGamepadAxisValue(JNIEnv* env, jclass clazz, jlong ptr, jint axis,
+                                       jint value) {
+    NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
+
+    if (!connection->IsGamepad()) {
+        ALOGE("Invalid axis send for non-gamepad device");
+        return;
+    }
+
+    connection->sendEvent(EV_ABS, axis, value);
+}
+
 static void nativeSendPointerDown(JNIEnv* env, jclass clazz, jlong ptr,
         jint pointerId, jint x, jint y) {
     NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
 
+    if (connection->IsGamepad()) {
+        ALOGE("Invalid pointer down event for a gamepad.");
+        return;
+    }
+
     int32_t slot = findSlot(pointerId);
     if (slot == SLOT_UNKNOWN) {
         slot = assignSlot(pointerId);
@@ -221,6 +404,11 @@
         jint pointerId) {
     NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
 
+    if (connection->IsGamepad()) {
+        ALOGE("Invalid pointer up event for a gamepad.");
+        return;
+    }
+
     int32_t slot = findSlot(pointerId);
     if (slot != SLOT_UNKNOWN) {
         connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot);
@@ -238,17 +426,34 @@
     NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
 
     // Clear keys.
-    for (size_t i = 0; i < NELEM(KEYS); i++) {
-        connection->sendEvent(EV_KEY, KEYS[i].linuxKeyCode, 0);
-    }
+    if (connection->IsRemote()) {
+        for (size_t i = 0; i < NELEM(KEYS); i++) {
+            connection->sendEvent(EV_KEY, KEYS[i].linuxKeyCode, 0);
+        }
 
-    // Clear pointers.
-    int32_t slot = SLOT_UNKNOWN;
-    for (int32_t i = 0; i < connection->getMaxPointers(); i++) {
-        slot = findSlot(i);
-        if (slot != SLOT_UNKNOWN) {
-            connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot);
-            connection->sendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1);
+        // Clear pointers.
+        int32_t slot = SLOT_UNKNOWN;
+        for (int32_t i = 0; i < connection->getMaxPointers(); i++) {
+            slot = findSlot(i);
+            if (slot != SLOT_UNKNOWN) {
+                connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot);
+                connection->sendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1);
+            }
+        }
+    } else {
+        for (size_t i = 0; i < NELEM(GAMEPAD_KEY_CODES); i++) {
+            connection->sendEvent(EV_KEY, GAMEPAD_KEY_CODES[i], 0);
+        }
+
+        for (size_t i = 0; i < NELEM(GAMEPAD_AXES); i++) {
+            const Axis& axis = GAMEPAD_AXES[i];
+            if ((axis.number == ABS_Z) || (axis.number == ABS_RZ)) {
+                // Mark triggers unpressed
+                connection->sendEvent(EV_ABS, axis.number, 0);
+            } else {
+                // Joysticks and dpad rests on center
+                connection->sendEvent(EV_ABS, axis.number, (axis.rangeMin + axis.rangeMax) / 2);
+            }
         }
     }
 
@@ -261,20 +466,16 @@
  */
 
 static JNINativeMethod gUinputBridgeMethods[] = {
-    { "nativeOpen", "(Ljava/lang/String;Ljava/lang/String;III)J",
-        (void*)nativeOpen },
-    { "nativeClose", "(J)V",
-        (void*)nativeClose },
-    { "nativeSendKey", "(JIZ)V",
-        (void*)nativeSendKey },
-    { "nativeSendPointerDown", "(JIII)V",
-        (void*)nativeSendPointerDown },
-    { "nativeSendPointerUp", "(JI)V",
-        (void*)nativeSendPointerUp },
-    { "nativeClear", "(J)V",
-        (void*)nativeClear },
-    { "nativeSendPointerSync", "(J)V",
-        (void*)nativeSendPointerSync },
+        {"nativeOpen", "(Ljava/lang/String;Ljava/lang/String;III)J", (void*)nativeOpen},
+        {"nativeGamepadOpen", "(Ljava/lang/String;Ljava/lang/String;)J", (void*)nativeGamepadOpen},
+        {"nativeClose", "(J)V", (void*)nativeClose},
+        {"nativeSendKey", "(JIZ)V", (void*)nativeSendKey},
+        {"nativeSendPointerDown", "(JIII)V", (void*)nativeSendPointerDown},
+        {"nativeSendPointerUp", "(JI)V", (void*)nativeSendPointerUp},
+        {"nativeClear", "(J)V", (void*)nativeClear},
+        {"nativeSendPointerSync", "(J)V", (void*)nativeSendPointerSync},
+        {"nativeSendGamepadKey", "(JIZ)V", (void*)nativeSendGamepadKey},
+        {"nativeSendGamepadAxisValue", "(JII)V", (void*)nativeSendGamepadAxisValue},
 };
 
 int register_android_server_tv_TvUinputBridge(JNIEnv* env) {