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) {