Loading services/core/java/com/android/server/tv/UinputBridge.java +70 −4 Original line number Original line Diff line number Diff line Loading @@ -28,7 +28,7 @@ import java.io.IOException; public final class UinputBridge { public final class UinputBridge { private final CloseGuard mCloseGuard = CloseGuard.get(); private final CloseGuard mCloseGuard = CloseGuard.get(); private long mPtr; private long mPtr; private IBinder mToken = null; private IBinder mToken; private static native long nativeOpen(String name, String uniqueId, int width, int height, private static native long nativeOpen(String name, String uniqueId, int width, int height, int maxPointers); int maxPointers); Loading @@ -39,6 +39,25 @@ public final class UinputBridge { private static native void nativeSendPointerUp(long ptr, int pointerId); private static native void nativeSendPointerUp(long ptr, int pointerId); private static native void nativeSendPointerSync(long ptr); 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) public UinputBridge(IBinder token, String name, int width, int height, int maxPointers) throws IOException { throws IOException { if (width < 1 || height < 1) { if (width < 1 || height < 1) { Loading @@ -58,12 +77,31 @@ public final class UinputBridge { mCloseGuard.open("close"); 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 @Override protected void finalize() throws Throwable { protected void finalize() throws Throwable { try { try { if (mCloseGuard != null) { mCloseGuard.warnIfOpen(); mCloseGuard.warnIfOpen(); } close(mToken); close(mToken); } finally { } finally { mToken = null; mToken = null; Loading Loading @@ -119,7 +157,35 @@ public final class UinputBridge { if (isTokenValid(token)) { if (isTokenValid(token)) { nativeSendPointerSync(mPtr); 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) { public void clear(IBinder token) { Loading services/core/jni/com_android_server_tv_GamepadKeys.h 0 → 100644 +79 −0 Original line number Original line Diff line number Diff line #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_ services/core/jni/com_android_server_tv_TvKeys.h +1 −1 Original line number Original line Diff line number Diff line Loading @@ -110,4 +110,4 @@ static Key KEYS[] = { } // namespace android } // namespace android #endif // SERVICE_JNI_KEYS_H_ #endif // ANDROIDTVREMOTE_SERVICE_JNI_KEYS_H_ services/core/jni/com_android_server_tv_TvUinputBridge.cpp +256 −55 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ #define LOG_TAG "TvRemote-native-uiBridge" #define LOG_TAG "TvRemote-native-uiBridge" #include "com_android_server_tv_GamepadKeys.h" #include "com_android_server_tv_TvKeys.h" #include "com_android_server_tv_TvKeys.h" #include "jni.h" #include "jni.h" Loading Loading @@ -92,27 +93,156 @@ static void unassignSlot(int32_t pointerId) { } } } } 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 { class NativeConnection { public: public: enum class ConnectionType { kRemoteDevice, kGamepadDevice, }; ~NativeConnection(); ~NativeConnection(); static NativeConnection* open(const char* name, const char* uniqueId, static NativeConnection* open(const char* name, const char* uniqueId, int32_t width, int32_t height, int32_t maxPointerId); 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); void sendEvent(int32_t type, int32_t code, int32_t value); int32_t getMaxPointers() const { return mMaxPointers; } 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: private: NativeConnection(int fd, int32_t maxPointers); NativeConnection(int fd, int32_t maxPointers, ConnectionType type); const int mFd; const int mFd; const int32_t mMaxPointers; const int32_t mMaxPointers; const ConnectionType mType; }; }; NativeConnection::NativeConnection(int fd, int32_t maxPointers) : NativeConnection::NativeConnection(int fd, int32_t maxPointers, ConnectionType type) mFd(fd), mMaxPointers(maxPointers) { : mFd(fd), mMaxPointers(maxPointers), mType(type) {} } NativeConnection::~NativeConnection() { NativeConnection::~NativeConnection() { ALOGI("Un-Registering uinput device %d.", mFd); ALOGI("Un-Registering uinput device %d.", mFd); Loading @@ -125,44 +255,50 @@ NativeConnection* NativeConnection::open(const char* name, const char* uniqueId, ALOGI("Registering uinput device %s: touch pad size %dx%d, " ALOGI("Registering uinput device %s: touch pad size %dx%d, " "max pointers %d.", name, width, height, maxPointers); "max pointers %d.", name, width, height, maxPointers); int fd = ::open("/dev/uinput", O_WRONLY | O_NDELAY); initKeysMap(); if (fd < 0) { ALOGE("Cannot open /dev/uinput: %s.", strerror(errno)); UInputDescriptor descriptor; if (!descriptor.Open(name, uniqueId)) { return nullptr; return nullptr; } } struct uinput_user_dev uinp; // set the keys mapped memset(&uinp, 0, sizeof(struct uinput_user_dev)); for (size_t i = 0; i < NELEM(KEYS); i++) { strlcpy(uinp.name, name, UINPUT_MAX_NAME_SIZE); descriptor.EnableKey(KEYS[i].linuxKeyCode); uinp.id.version = 1; } uinp.id.bustype = BUS_VIRTUAL; // initialize keymap if (!descriptor.Create()) { initKeysMap(); return nullptr; } // write device unique id to the phys property return new NativeConnection(descriptor.Detach(), maxPointers, ConnectionType::kRemoteDevice); ioctl(fd, UI_SET_PHYS, uniqueId); } // set the keys mapped NativeConnection* NativeConnection::openGamepad(const char* name, const char* uniqueId) { ioctl(fd, UI_SET_EVBIT, EV_KEY); ALOGI("Registering uinput device %s: gamepad", name); for (size_t i = 0; i < NELEM(KEYS); i++) { ioctl(fd, UI_SET_KEYBIT, KEYS[i].linuxKeyCode); UInputDescriptor descriptor; if (!descriptor.Open(name, uniqueId)) { return nullptr; } } // register the input device // set the keys mapped for gamepads if (write(fd, &uinp, sizeof(uinp)) != sizeof(uinp)) { for (size_t i = 0; i < NELEM(GAMEPAD_KEY_CODES); i++) { ALOGE("Cannot write uinput_user_dev to fd %d: %s.", fd, strerror(errno)); descriptor.EnableKey(GAMEPAD_KEY_CODES[i]); close(fd); return NULL; } } if (ioctl(fd, UI_DEV_CREATE) != 0) { ALOGE("Unable to create uinput device: %s.", strerror(errno)); // define the axes that are required close(fd); 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 nullptr; } } ALOGV("Created uinput device, fd=%d.", fd); return new NativeConnection(descriptor.Detach(), 0, ConnectionType::kGamepadDevice); return new NativeConnection(fd, maxPointers); } } void NativeConnection::sendEvent(int32_t type, int32_t code, int32_t value) { void NativeConnection::sendEvent(int32_t type, int32_t code, int32_t value) { Loading @@ -174,7 +310,6 @@ void NativeConnection::sendEvent(int32_t type, int32_t code, int32_t value) { write(mFd, &iev, sizeof(iev)); write(mFd, &iev, sizeof(iev)); } } static jlong nativeOpen(JNIEnv* env, jclass clazz, static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring nameStr, jstring uniqueIdStr, jstring nameStr, jstring uniqueIdStr, jint width, jint height, jint maxPointers) { jint width, jint height, jint maxPointers) { Loading @@ -186,6 +321,14 @@ static jlong nativeOpen(JNIEnv* env, jclass clazz, return reinterpret_cast<jlong>(connection); 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) { static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); delete connection; delete connection; Loading @@ -194,6 +337,12 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) { static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jboolean down) { static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jboolean down) { int32_t code = getLinuxKeyCode(keyCode); int32_t code = getLinuxKeyCode(keyCode); NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); 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) { if (code != KEY_UNKNOWN) { connection->sendEvent(EV_KEY, code, down ? 1 : 0); connection->sendEvent(EV_KEY, code, down ? 1 : 0); } else { } else { Loading @@ -201,10 +350,44 @@ static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jb } } } } 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, static void nativeSendPointerDown(JNIEnv* env, jclass clazz, jlong ptr, jint pointerId, jint x, jint y) { jint pointerId, jint x, jint y) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); if (connection->IsGamepad()) { ALOGE("Invalid pointer down event for a gamepad."); return; } int32_t slot = findSlot(pointerId); int32_t slot = findSlot(pointerId); if (slot == SLOT_UNKNOWN) { if (slot == SLOT_UNKNOWN) { slot = assignSlot(pointerId); slot = assignSlot(pointerId); Loading @@ -221,6 +404,11 @@ static void nativeSendPointerUp(JNIEnv* env, jclass clazz, jlong ptr, jint pointerId) { jint pointerId) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); if (connection->IsGamepad()) { ALOGE("Invalid pointer up event for a gamepad."); return; } int32_t slot = findSlot(pointerId); int32_t slot = findSlot(pointerId); if (slot != SLOT_UNKNOWN) { if (slot != SLOT_UNKNOWN) { connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot); connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot); Loading @@ -238,6 +426,7 @@ static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); // Clear keys. // Clear keys. if (connection->IsRemote()) { for (size_t i = 0; i < NELEM(KEYS); i++) { for (size_t i = 0; i < NELEM(KEYS); i++) { connection->sendEvent(EV_KEY, KEYS[i].linuxKeyCode, 0); connection->sendEvent(EV_KEY, KEYS[i].linuxKeyCode, 0); } } Loading @@ -251,6 +440,22 @@ static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) { connection->sendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1); 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); } } } // Sync pointer events // Sync pointer events connection->sendEvent(EV_SYN, SYN_REPORT, 0); connection->sendEvent(EV_SYN, SYN_REPORT, 0); Loading @@ -261,20 +466,16 @@ static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) { */ */ static JNINativeMethod gUinputBridgeMethods[] = { static JNINativeMethod gUinputBridgeMethods[] = { { "nativeOpen", "(Ljava/lang/String;Ljava/lang/String;III)J", {"nativeOpen", "(Ljava/lang/String;Ljava/lang/String;III)J", (void*)nativeOpen}, (void*)nativeOpen }, {"nativeGamepadOpen", "(Ljava/lang/String;Ljava/lang/String;)J", (void*)nativeGamepadOpen}, { "nativeClose", "(J)V", {"nativeClose", "(J)V", (void*)nativeClose}, (void*)nativeClose }, {"nativeSendKey", "(JIZ)V", (void*)nativeSendKey}, { "nativeSendKey", "(JIZ)V", {"nativeSendPointerDown", "(JIII)V", (void*)nativeSendPointerDown}, (void*)nativeSendKey }, {"nativeSendPointerUp", "(JI)V", (void*)nativeSendPointerUp}, { "nativeSendPointerDown", "(JIII)V", {"nativeClear", "(J)V", (void*)nativeClear}, (void*)nativeSendPointerDown }, {"nativeSendPointerSync", "(J)V", (void*)nativeSendPointerSync}, { "nativeSendPointerUp", "(JI)V", {"nativeSendGamepadKey", "(JIZ)V", (void*)nativeSendGamepadKey}, (void*)nativeSendPointerUp }, {"nativeSendGamepadAxisValue", "(JII)V", (void*)nativeSendGamepadAxisValue}, { "nativeClear", "(J)V", (void*)nativeClear }, { "nativeSendPointerSync", "(J)V", (void*)nativeSendPointerSync }, }; }; int register_android_server_tv_TvUinputBridge(JNIEnv* env) { int register_android_server_tv_TvUinputBridge(JNIEnv* env) { Loading Loading
services/core/java/com/android/server/tv/UinputBridge.java +70 −4 Original line number Original line Diff line number Diff line Loading @@ -28,7 +28,7 @@ import java.io.IOException; public final class UinputBridge { public final class UinputBridge { private final CloseGuard mCloseGuard = CloseGuard.get(); private final CloseGuard mCloseGuard = CloseGuard.get(); private long mPtr; private long mPtr; private IBinder mToken = null; private IBinder mToken; private static native long nativeOpen(String name, String uniqueId, int width, int height, private static native long nativeOpen(String name, String uniqueId, int width, int height, int maxPointers); int maxPointers); Loading @@ -39,6 +39,25 @@ public final class UinputBridge { private static native void nativeSendPointerUp(long ptr, int pointerId); private static native void nativeSendPointerUp(long ptr, int pointerId); private static native void nativeSendPointerSync(long ptr); 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) public UinputBridge(IBinder token, String name, int width, int height, int maxPointers) throws IOException { throws IOException { if (width < 1 || height < 1) { if (width < 1 || height < 1) { Loading @@ -58,12 +77,31 @@ public final class UinputBridge { mCloseGuard.open("close"); 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 @Override protected void finalize() throws Throwable { protected void finalize() throws Throwable { try { try { if (mCloseGuard != null) { mCloseGuard.warnIfOpen(); mCloseGuard.warnIfOpen(); } close(mToken); close(mToken); } finally { } finally { mToken = null; mToken = null; Loading Loading @@ -119,7 +157,35 @@ public final class UinputBridge { if (isTokenValid(token)) { if (isTokenValid(token)) { nativeSendPointerSync(mPtr); 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) { public void clear(IBinder token) { Loading
services/core/jni/com_android_server_tv_GamepadKeys.h 0 → 100644 +79 −0 Original line number Original line Diff line number Diff line #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_
services/core/jni/com_android_server_tv_TvKeys.h +1 −1 Original line number Original line Diff line number Diff line Loading @@ -110,4 +110,4 @@ static Key KEYS[] = { } // namespace android } // namespace android #endif // SERVICE_JNI_KEYS_H_ #endif // ANDROIDTVREMOTE_SERVICE_JNI_KEYS_H_
services/core/jni/com_android_server_tv_TvUinputBridge.cpp +256 −55 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ #define LOG_TAG "TvRemote-native-uiBridge" #define LOG_TAG "TvRemote-native-uiBridge" #include "com_android_server_tv_GamepadKeys.h" #include "com_android_server_tv_TvKeys.h" #include "com_android_server_tv_TvKeys.h" #include "jni.h" #include "jni.h" Loading Loading @@ -92,27 +93,156 @@ static void unassignSlot(int32_t pointerId) { } } } } 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 { class NativeConnection { public: public: enum class ConnectionType { kRemoteDevice, kGamepadDevice, }; ~NativeConnection(); ~NativeConnection(); static NativeConnection* open(const char* name, const char* uniqueId, static NativeConnection* open(const char* name, const char* uniqueId, int32_t width, int32_t height, int32_t maxPointerId); 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); void sendEvent(int32_t type, int32_t code, int32_t value); int32_t getMaxPointers() const { return mMaxPointers; } 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: private: NativeConnection(int fd, int32_t maxPointers); NativeConnection(int fd, int32_t maxPointers, ConnectionType type); const int mFd; const int mFd; const int32_t mMaxPointers; const int32_t mMaxPointers; const ConnectionType mType; }; }; NativeConnection::NativeConnection(int fd, int32_t maxPointers) : NativeConnection::NativeConnection(int fd, int32_t maxPointers, ConnectionType type) mFd(fd), mMaxPointers(maxPointers) { : mFd(fd), mMaxPointers(maxPointers), mType(type) {} } NativeConnection::~NativeConnection() { NativeConnection::~NativeConnection() { ALOGI("Un-Registering uinput device %d.", mFd); ALOGI("Un-Registering uinput device %d.", mFd); Loading @@ -125,44 +255,50 @@ NativeConnection* NativeConnection::open(const char* name, const char* uniqueId, ALOGI("Registering uinput device %s: touch pad size %dx%d, " ALOGI("Registering uinput device %s: touch pad size %dx%d, " "max pointers %d.", name, width, height, maxPointers); "max pointers %d.", name, width, height, maxPointers); int fd = ::open("/dev/uinput", O_WRONLY | O_NDELAY); initKeysMap(); if (fd < 0) { ALOGE("Cannot open /dev/uinput: %s.", strerror(errno)); UInputDescriptor descriptor; if (!descriptor.Open(name, uniqueId)) { return nullptr; return nullptr; } } struct uinput_user_dev uinp; // set the keys mapped memset(&uinp, 0, sizeof(struct uinput_user_dev)); for (size_t i = 0; i < NELEM(KEYS); i++) { strlcpy(uinp.name, name, UINPUT_MAX_NAME_SIZE); descriptor.EnableKey(KEYS[i].linuxKeyCode); uinp.id.version = 1; } uinp.id.bustype = BUS_VIRTUAL; // initialize keymap if (!descriptor.Create()) { initKeysMap(); return nullptr; } // write device unique id to the phys property return new NativeConnection(descriptor.Detach(), maxPointers, ConnectionType::kRemoteDevice); ioctl(fd, UI_SET_PHYS, uniqueId); } // set the keys mapped NativeConnection* NativeConnection::openGamepad(const char* name, const char* uniqueId) { ioctl(fd, UI_SET_EVBIT, EV_KEY); ALOGI("Registering uinput device %s: gamepad", name); for (size_t i = 0; i < NELEM(KEYS); i++) { ioctl(fd, UI_SET_KEYBIT, KEYS[i].linuxKeyCode); UInputDescriptor descriptor; if (!descriptor.Open(name, uniqueId)) { return nullptr; } } // register the input device // set the keys mapped for gamepads if (write(fd, &uinp, sizeof(uinp)) != sizeof(uinp)) { for (size_t i = 0; i < NELEM(GAMEPAD_KEY_CODES); i++) { ALOGE("Cannot write uinput_user_dev to fd %d: %s.", fd, strerror(errno)); descriptor.EnableKey(GAMEPAD_KEY_CODES[i]); close(fd); return NULL; } } if (ioctl(fd, UI_DEV_CREATE) != 0) { ALOGE("Unable to create uinput device: %s.", strerror(errno)); // define the axes that are required close(fd); 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 nullptr; } } ALOGV("Created uinput device, fd=%d.", fd); return new NativeConnection(descriptor.Detach(), 0, ConnectionType::kGamepadDevice); return new NativeConnection(fd, maxPointers); } } void NativeConnection::sendEvent(int32_t type, int32_t code, int32_t value) { void NativeConnection::sendEvent(int32_t type, int32_t code, int32_t value) { Loading @@ -174,7 +310,6 @@ void NativeConnection::sendEvent(int32_t type, int32_t code, int32_t value) { write(mFd, &iev, sizeof(iev)); write(mFd, &iev, sizeof(iev)); } } static jlong nativeOpen(JNIEnv* env, jclass clazz, static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring nameStr, jstring uniqueIdStr, jstring nameStr, jstring uniqueIdStr, jint width, jint height, jint maxPointers) { jint width, jint height, jint maxPointers) { Loading @@ -186,6 +321,14 @@ static jlong nativeOpen(JNIEnv* env, jclass clazz, return reinterpret_cast<jlong>(connection); 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) { static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); delete connection; delete connection; Loading @@ -194,6 +337,12 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) { static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jboolean down) { static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jboolean down) { int32_t code = getLinuxKeyCode(keyCode); int32_t code = getLinuxKeyCode(keyCode); NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); 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) { if (code != KEY_UNKNOWN) { connection->sendEvent(EV_KEY, code, down ? 1 : 0); connection->sendEvent(EV_KEY, code, down ? 1 : 0); } else { } else { Loading @@ -201,10 +350,44 @@ static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jb } } } } 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, static void nativeSendPointerDown(JNIEnv* env, jclass clazz, jlong ptr, jint pointerId, jint x, jint y) { jint pointerId, jint x, jint y) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); if (connection->IsGamepad()) { ALOGE("Invalid pointer down event for a gamepad."); return; } int32_t slot = findSlot(pointerId); int32_t slot = findSlot(pointerId); if (slot == SLOT_UNKNOWN) { if (slot == SLOT_UNKNOWN) { slot = assignSlot(pointerId); slot = assignSlot(pointerId); Loading @@ -221,6 +404,11 @@ static void nativeSendPointerUp(JNIEnv* env, jclass clazz, jlong ptr, jint pointerId) { jint pointerId) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); if (connection->IsGamepad()) { ALOGE("Invalid pointer up event for a gamepad."); return; } int32_t slot = findSlot(pointerId); int32_t slot = findSlot(pointerId); if (slot != SLOT_UNKNOWN) { if (slot != SLOT_UNKNOWN) { connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot); connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot); Loading @@ -238,6 +426,7 @@ static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) { NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr); // Clear keys. // Clear keys. if (connection->IsRemote()) { for (size_t i = 0; i < NELEM(KEYS); i++) { for (size_t i = 0; i < NELEM(KEYS); i++) { connection->sendEvent(EV_KEY, KEYS[i].linuxKeyCode, 0); connection->sendEvent(EV_KEY, KEYS[i].linuxKeyCode, 0); } } Loading @@ -251,6 +440,22 @@ static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) { connection->sendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1); 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); } } } // Sync pointer events // Sync pointer events connection->sendEvent(EV_SYN, SYN_REPORT, 0); connection->sendEvent(EV_SYN, SYN_REPORT, 0); Loading @@ -261,20 +466,16 @@ static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) { */ */ static JNINativeMethod gUinputBridgeMethods[] = { static JNINativeMethod gUinputBridgeMethods[] = { { "nativeOpen", "(Ljava/lang/String;Ljava/lang/String;III)J", {"nativeOpen", "(Ljava/lang/String;Ljava/lang/String;III)J", (void*)nativeOpen}, (void*)nativeOpen }, {"nativeGamepadOpen", "(Ljava/lang/String;Ljava/lang/String;)J", (void*)nativeGamepadOpen}, { "nativeClose", "(J)V", {"nativeClose", "(J)V", (void*)nativeClose}, (void*)nativeClose }, {"nativeSendKey", "(JIZ)V", (void*)nativeSendKey}, { "nativeSendKey", "(JIZ)V", {"nativeSendPointerDown", "(JIII)V", (void*)nativeSendPointerDown}, (void*)nativeSendKey }, {"nativeSendPointerUp", "(JI)V", (void*)nativeSendPointerUp}, { "nativeSendPointerDown", "(JIII)V", {"nativeClear", "(J)V", (void*)nativeClear}, (void*)nativeSendPointerDown }, {"nativeSendPointerSync", "(J)V", (void*)nativeSendPointerSync}, { "nativeSendPointerUp", "(JI)V", {"nativeSendGamepadKey", "(JIZ)V", (void*)nativeSendGamepadKey}, (void*)nativeSendPointerUp }, {"nativeSendGamepadAxisValue", "(JII)V", (void*)nativeSendGamepadAxisValue}, { "nativeClear", "(J)V", (void*)nativeClear }, { "nativeSendPointerSync", "(J)V", (void*)nativeSendPointerSync }, }; }; int register_android_server_tv_TvUinputBridge(JNIEnv* env) { int register_android_server_tv_TvUinputBridge(JNIEnv* env) { Loading