summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/keyboards/Vendor_18d1_Product_0200.kcm48
-rw-r--r--data/keyboards/Vendor_18d1_Product_0200.kl71
-rw-r--r--media/java/android/media/tv/ITvRemoteServiceInput.aidl8
-rw-r--r--media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java163
-rw-r--r--media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java48
-rw-r--r--services/core/java/com/android/server/tv/TvRemoteServiceInput.java111
-rw-r--r--services/core/java/com/android/server/tv/UinputBridge.java51
-rw-r--r--services/core/jni/com_android_server_tv_GamepadKeys.h131
-rw-r--r--services/core/jni/com_android_server_tv_TvUinputBridge.cpp133
9 files changed, 645 insertions, 119 deletions
diff --git a/data/keyboards/Vendor_18d1_Product_0200.kcm b/data/keyboards/Vendor_18d1_Product_0200.kcm
new file mode 100644
index 000000000000..231fac6b48b7
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_0200.kcm
@@ -0,0 +1,48 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+type FULL
+
+key BUTTON_A {
+ base: fallback DPAD_CENTER
+}
+
+key BUTTON_B {
+ base: fallback BACK
+}
+
+key BUTTON_X {
+ base: fallback DPAD_CENTER
+}
+
+key BUTTON_Y {
+ base: fallback BACK
+}
+
+key BUTTON_THUMBL {
+ base: fallback DPAD_CENTER
+}
+
+key BUTTON_THUMBR {
+ base: fallback DPAD_CENTER
+}
+
+key BUTTON_SELECT {
+ base: fallback MENU
+}
+
+key BUTTON_MODE {
+ base: fallback MENU
+}
+
diff --git a/data/keyboards/Vendor_18d1_Product_0200.kl b/data/keyboards/Vendor_18d1_Product_0200.kl
new file mode 100644
index 000000000000..d30bcc60e663
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_0200.kl
@@ -0,0 +1,71 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Keyboard map for the android virtual remote running as a gamepad
+#
+
+key 0x130 BUTTON_A
+key 0x131 BUTTON_B
+key 0x133 BUTTON_X
+key 0x134 BUTTON_Y
+
+key 0x136 BUTTON_L2
+key 0x137 BUTTON_R2
+key 0x138 BUTTON_L1
+key 0x139 BUTTON_R1
+
+key 0x13a BUTTON_SELECT
+key 0x13b BUTTON_START
+key 0x13c BUTTON_MODE
+
+key 0x13d BUTTON_THUMBL
+key 0x13e BUTTON_THUMBR
+
+key 103 DPAD_UP
+key 108 DPAD_DOWN
+key 105 DPAD_LEFT
+key 106 DPAD_RIGHT
+
+# Generic usage buttons
+key 0x2c0 BUTTON_1
+key 0x2c1 BUTTON_2
+key 0x2c2 BUTTON_3
+key 0x2c3 BUTTON_4
+key 0x2c4 BUTTON_5
+key 0x2c5 BUTTON_6
+key 0x2c6 BUTTON_7
+key 0x2c7 BUTTON_8
+key 0x2c8 BUTTON_9
+key 0x2c9 BUTTON_10
+key 0x2ca BUTTON_11
+key 0x2cb BUTTON_12
+key 0x2cc BUTTON_13
+key 0x2cd BUTTON_14
+key 0x2ce BUTTON_15
+key 0x2cf BUTTON_16
+
+# assistant buttons
+key 0x246 VOICE_ASSIST
+key 0x247 ASSIST
+
+axis 0x00 X
+axis 0x01 Y
+axis 0x02 Z
+axis 0x05 RZ
+axis 0x09 RTRIGGER
+axis 0x0a LTRIGGER
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
diff --git a/media/java/android/media/tv/ITvRemoteServiceInput.aidl b/media/java/android/media/tv/ITvRemoteServiceInput.aidl
index a0b6c9bfc8d8..0e6563a1ab13 100644
--- a/media/java/android/media/tv/ITvRemoteServiceInput.aidl
+++ b/media/java/android/media/tv/ITvRemoteServiceInput.aidl
@@ -39,4 +39,10 @@ oneway interface ITvRemoteServiceInput {
void sendPointerUp(IBinder token, int pointerId);
@UnsupportedAppUsage
void sendPointerSync(IBinder token);
-} \ No newline at end of file
+
+ // API specific to gamepads. Close gamepads with closeInputBridge
+ void openGamepadBridge(IBinder token, String name);
+ void sendGamepadKeyDown(IBinder token, int keyCode);
+ void sendGamepadKeyUp(IBinder token, int keyCode);
+ void sendGamepadAxisValue(IBinder token, int axis, float value);
+}
diff --git a/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java b/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java
index 0bf0f97d2c5e..b97ac26bb915 100644
--- a/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java
+++ b/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java
@@ -16,6 +16,8 @@
package com.android.media.tv.remoteprovider;
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
import android.content.Context;
import android.media.tv.ITvRemoteProvider;
import android.media.tv.ITvRemoteServiceInput;
@@ -24,6 +26,7 @@ import android.os.RemoteException;
import android.util.Log;
import java.util.LinkedList;
+import java.util.Objects;
/**
* Base class for emote providers implemented in unbundled service.
@@ -124,27 +127,75 @@ public abstract class TvRemoteProvider {
* @param maxPointers Maximum supported pointers
* @throws RuntimeException
*/
- public void openRemoteInputBridge(IBinder token, String name, int width, int height,
- int maxPointers) throws RuntimeException {
+ public void openRemoteInputBridge(
+ IBinder token, String name, int width, int height, int maxPointers)
+ throws RuntimeException {
+ final IBinder finalToken = Objects.requireNonNull(token);
+ final String finalName = Objects.requireNonNull(name);
+
synchronized (mOpenBridgeRunnables) {
if (mRemoteServiceInput == null) {
- Log.d(TAG, "Delaying openRemoteInputBridge() for " + name);
+ Log.d(TAG, "Delaying openRemoteInputBridge() for " + finalName);
mOpenBridgeRunnables.add(() -> {
try {
mRemoteServiceInput.openInputBridge(
- token, name, width, height, maxPointers);
- Log.d(TAG, "Delayed openRemoteInputBridge() for " + name + ": success");
+ finalToken, finalName, width, height, maxPointers);
+ Log.d(TAG, "Delayed openRemoteInputBridge() for " + finalName
+ + ": success");
+ } catch (RemoteException re) {
+ Log.e(TAG, "Delayed openRemoteInputBridge() for " + finalName
+ + ": failure", re);
+ }
+ });
+ return;
+ }
+ }
+ try {
+ mRemoteServiceInput.openInputBridge(finalToken, finalName, width, height, maxPointers);
+ Log.d(TAG, "openRemoteInputBridge() for " + finalName + ": success");
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Opens an input bridge as a gamepad device.
+ * Clients should pass in a token that can be used to match this request with a token that
+ * will be returned by {@link TvRemoteProvider#onInputBridgeConnected(IBinder token)}
+ * <p>
+ * The token should be used for subsequent calls.
+ * </p>
+ *
+ * @param token Identifier for this connection
+ * @param name Device name
+ * @throws RuntimeException
+ *
+ * @hide
+ */
+ public void openGamepadBridge(@NonNull IBinder token, @NonNull String name)
+ throws RuntimeException {
+ final IBinder finalToken = Objects.requireNonNull(token);
+ final String finalName = Objects.requireNonNull(name);
+ synchronized (mOpenBridgeRunnables) {
+ if (mRemoteServiceInput == null) {
+ Log.d(TAG, "Delaying openGamepadBridge() for " + finalName);
+
+ mOpenBridgeRunnables.add(() -> {
+ try {
+ mRemoteServiceInput.openGamepadBridge(finalToken, finalName);
+ Log.d(TAG, "Delayed openGamepadBridge() for " + finalName + ": success");
} catch (RemoteException re) {
- Log.e(TAG, "Delayed openRemoteInputBridge() for " + name + ": failure", re);
+ Log.e(TAG, "Delayed openGamepadBridge() for " + finalName + ": failure",
+ re);
}
});
return;
}
}
try {
- mRemoteServiceInput.openInputBridge(token, name, width, height, maxPointers);
- Log.d(TAG, "openRemoteInputBridge() for " + name + ": success");
+ mRemoteServiceInput.openGamepadBridge(token, finalName);
+ Log.d(TAG, "openGamepadBridge() for " + finalName + ": success");
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -157,6 +208,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void closeInputBridge(IBinder token) throws RuntimeException {
+ Objects.requireNonNull(token);
try {
mRemoteServiceInput.closeInputBridge(token);
} catch (RemoteException re) {
@@ -173,6 +225,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void clearInputBridge(IBinder token) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "clearInputBridge() token " + token);
try {
mRemoteServiceInput.clearInputBridge(token);
@@ -190,6 +243,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void sendTimestamp(IBinder token, long timestamp) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendTimestamp() token: " + token +
", timestamp: " + timestamp);
try {
@@ -207,6 +261,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void sendKeyUp(IBinder token, int keyCode) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendKeyUp() token: " + token + ", keyCode: " + keyCode);
try {
mRemoteServiceInput.sendKeyUp(token, keyCode);
@@ -223,6 +278,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void sendKeyDown(IBinder token, int keyCode) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendKeyDown() token: " + token +
", keyCode: " + keyCode);
try {
@@ -241,6 +297,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void sendPointerUp(IBinder token, int pointerId) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendPointerUp() token: " + token +
", pointerId: " + pointerId);
try {
@@ -262,6 +319,7 @@ public abstract class TvRemoteProvider {
*/
public void sendPointerDown(IBinder token, int pointerId, int x, int y)
throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendPointerDown() token: " + token +
", pointerId: " + pointerId);
try {
@@ -278,6 +336,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void sendPointerSync(IBinder token) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendPointerSync() token: " + token);
try {
mRemoteServiceInput.sendPointerSync(token);
@@ -286,6 +345,94 @@ public abstract class TvRemoteProvider {
}
}
+ /**
+ * Send a notification that a gamepad key was pressed.
+ *
+ * Supported buttons are:
+ * <ul>
+ * <li> Right-side buttons: BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y
+ * <li> Digital Triggers and bumpers: BUTTON_L1, BUTTON_R1, BUTTON_L2, BUTTON_R2
+ * <li> Thumb buttons: BUTTON_THUMBL, BUTTON_THUMBR
+ * <li> DPad buttons: DPAD_UP, DPAD_DOWN, DPAD_LEFT, DPAD_RIGHT
+ * <li> Gamepad buttons: BUTTON_SELECT, BUTTON_START, BUTTON_MODE
+ * <li> Generic buttons: BUTTON_1, BUTTON_2, ...., BUTTON16
+ * <li> Assistant: ASSIST, VOICE_ASSIST
+ * </ul>
+ *
+ * @param token identifier for the device
+ * @param keyCode the gamepad key that was pressed (like BUTTON_A)
+ *
+ * @hide
+ */
+ public void sendGamepadKeyDown(@NonNull IBinder token, int keyCode) throws RuntimeException {
+ Objects.requireNonNull(token);
+ if (DEBUG_KEYS) {
+ Log.d(TAG, "sendGamepadKeyDown() token: " + token);
+ }
+
+ try {
+ mRemoteServiceInput.sendGamepadKeyDown(token, keyCode);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Send a notification that a gamepad key was released.
+ *
+ * @see sendGamepadKeyDown for supported key codes.
+ *
+ * @param token identifier for the device
+ * @param keyCode the gamepad key that was pressed
+ *
+ * @hide
+ */
+ public void sendGamepadKeyUp(@NonNull IBinder token, int keyCode) throws RuntimeException {
+ Objects.requireNonNull(token);
+ if (DEBUG_KEYS) {
+ Log.d(TAG, "sendGamepadKeyUp() token: " + token);
+ }
+
+ try {
+ mRemoteServiceInput.sendGamepadKeyUp(token, keyCode);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Send a gamepad axis value.
+ *
+ * Supported axes:
+ * <li> Left Joystick: AXIS_X, AXIS_Y
+ * <li> Right Joystick: AXIS_Z, AXIS_RZ
+ * <li> Triggers: AXIS_LTRIGGER, AXIS_RTRIGGER
+ * <li> DPad: AXIS_HAT_X, AXIS_HAT_Y
+ *
+ * For non-trigger axes, the range of acceptable values is [-1, 1]. The trigger axes support
+ * values [0, 1].
+ *
+ * @param token identifier for the device
+ * @param axis MotionEvent axis
+ * @param value the value to send
+ *
+ * @hide
+ */
+ public void sendGamepadAxisValue(
+ @NonNull IBinder token, int axis, @FloatRange(from = -1.0f, to = 1.0f) float value)
+ throws RuntimeException {
+ Objects.requireNonNull(token);
+ if (DEBUG_KEYS) {
+ Log.d(TAG, "sendGamepadAxisValue() token: " + token);
+ }
+
+ try {
+ mRemoteServiceInput.sendGamepadAxisValue(token, axis, value);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
private final class ProviderStub extends ITvRemoteProvider.Stub {
@Override
public void setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput) {
diff --git a/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java b/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java
index c9ce56138217..e6e39390962e 100644
--- a/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java
+++ b/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java
@@ -83,4 +83,52 @@ public class TvRemoteProviderTest extends AndroidTestCase {
assertTrue(tvProvider.verifyTokens());
}
+
+ @SmallTest
+ public void testOpenGamepadRemoteInputBridge() throws Exception {
+ Binder tokenA = new Binder();
+ Binder tokenB = new Binder();
+ Binder tokenC = new Binder();
+
+ class LocalTvRemoteProvider extends TvRemoteProvider {
+ private final ArrayList<IBinder> mTokens = new ArrayList<IBinder>();
+
+ LocalTvRemoteProvider(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onInputBridgeConnected(IBinder token) {
+ mTokens.add(token);
+ }
+
+ public boolean verifyTokens() {
+ return mTokens.size() == 3 && mTokens.contains(tokenA) && mTokens.contains(tokenB)
+ && mTokens.contains(tokenC);
+ }
+ }
+
+ LocalTvRemoteProvider tvProvider = new LocalTvRemoteProvider(getContext());
+ ITvRemoteProvider binder = (ITvRemoteProvider) tvProvider.getBinder();
+
+ ITvRemoteServiceInput tvServiceInput = mock(ITvRemoteServiceInput.class);
+ doAnswer((i) -> {
+ binder.onInputBridgeConnected(i.getArgument(0));
+ return null;
+ })
+ .when(tvServiceInput)
+ .openGamepadBridge(any(), any());
+
+ tvProvider.openGamepadBridge(tokenA, "A");
+ tvProvider.openGamepadBridge(tokenB, "B");
+ binder.setRemoteServiceInputSink(tvServiceInput);
+ tvProvider.openGamepadBridge(tokenC, "C");
+
+ verify(tvServiceInput).openGamepadBridge(tokenA, "A");
+ verify(tvServiceInput).openGamepadBridge(tokenB, "B");
+ verify(tvServiceInput).openGamepadBridge(tokenC, "C");
+ verifyNoMoreInteractions(tvServiceInput);
+
+ assertTrue(tvProvider.verifyTokens());
+ }
}
diff --git a/services/core/java/com/android/server/tv/TvRemoteServiceInput.java b/services/core/java/com/android/server/tv/TvRemoteServiceInput.java
index 8fe6da5e8dbe..390340a13e51 100644
--- a/services/core/java/com/android/server/tv/TvRemoteServiceInput.java
+++ b/services/core/java/com/android/server/tv/TvRemoteServiceInput.java
@@ -88,6 +88,47 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
}
@Override
+ public void openGamepadBridge(IBinder token, String name) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, String.format("openGamepadBridge(), token: %s, name: %s", token, name));
+ }
+
+ synchronized (mLock) {
+ if (mBridgeMap.containsKey(token)) {
+ if (DEBUG) {
+ Slog.d(TAG, "InputBridge already exists");
+ }
+ } else {
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ mBridgeMap.put(token, UinputBridge.openGamepad(token, name));
+ token.linkToDeath(new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ closeInputBridge(token);
+ }
+ }, 0);
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot create device for " + name);
+ return;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Token is already dead");
+ closeInputBridge(token);
+ return;
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ try {
+ mProvider.onInputBridgeConnected(token);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed remote call to onInputBridgeConnected");
+ }
+ }
+
+ @Override
public void closeInputBridge(IBinder token) {
if (DEBUG) {
Slog.d(TAG, "closeInputBridge(), token: " + token);
@@ -96,6 +137,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.remove(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -117,6 +159,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -145,6 +188,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -166,6 +210,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -188,6 +233,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -209,6 +255,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -230,6 +277,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -241,4 +289,67 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
}
}
}
+
+ @Override
+ public void sendGamepadKeyUp(IBinder token, int keyIndex) {
+ if (DEBUG_KEYS) {
+ Slog.d(TAG, String.format("sendGamepadKeyUp(), token: %s", token));
+ }
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.sendGamepadKey(token, keyIndex, false);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ @Override
+ public void sendGamepadKeyDown(IBinder token, int keyCode) {
+ if (DEBUG_KEYS) {
+ Slog.d(TAG, String.format("sendGamepadKeyDown(), token: %s", token));
+ }
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.sendGamepadKey(token, keyCode, true);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ @Override
+ public void sendGamepadAxisValue(IBinder token, int axis, float value) {
+ if (DEBUG_KEYS) {
+ Slog.d(TAG, String.format("sendGamepadAxisValue(), token: %s", token));
+ }
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.sendGamepadAxisValue(token, axis, value);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/tv/UinputBridge.java b/services/core/java/com/android/server/tv/UinputBridge.java
index a2fe5fcde8c2..1dc201d4ee6b 100644
--- a/services/core/java/com/android/server/tv/UinputBridge.java
+++ b/services/core/java/com/android/server/tv/UinputBridge.java
@@ -42,21 +42,27 @@ public final class UinputBridge {
/** 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);
+ /**
+ * Marks the specified key up/down for a gamepad.
+ *
+ * @param keyCode - a code like BUTTON_MODE, BUTTON_A, BUTTON_B, ...
+ */
+ private static native void nativeSendGamepadKey(long ptr, int keyCode, 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]
+ * Send an axis value.
+ *
+ * Available axes are:
+ * <li> Left joystick: AXIS_X, AXIS_Y
+ * <li> Right joystick: AXIS_Z, AXIS_RZ
+ * <li> Analog triggers: AXIS_LTRIGGER, AXIS_RTRIGGER
+ * <li> DPad: AXIS_HAT_X, AXIS_HAT_Y
+ *
+ * @param axis is a MotionEvent.AXIS_* value.
+ * @param value is a value between -1 and 1 (inclusive)
+ *
*/
- private static native void nativeSendGamepadAxisValue(long ptr, int axis, int value);
+ private static native void nativeSendGamepadAxisValue(long ptr, int axis, float value);
public UinputBridge(IBinder token, String name, int width, int height, int maxPointers)
throws IOException {
@@ -163,26 +169,19 @@ public final class UinputBridge {
* @param keyIndex - the index of the w3-spec key
* @param down - is the key pressed ?
*/
- public void sendGamepadKey(IBinder token, int keyIndex, boolean down) {
+ public void sendGamepadKey(IBinder token, int keyCode, boolean down) {
if (isTokenValid(token)) {
- nativeSendGamepadKey(mPtr, keyIndex, down);
+ nativeSendGamepadKey(mPtr, keyCode, 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]
+ /**
+ * Send a gamepad axis value.
*
- * @param axis is the axis index
- * @param value is the value to set for that axis
+ * @param axis is the axis code (MotionEvent.AXIS_*)
+ * @param value is the value to set for that axis in [-1, 1]
*/
- public void sendGamepadAxisValue(IBinder token, int axis, int value) {
+ public void sendGamepadAxisValue(IBinder token, int axis, float value) {
if (isTokenValid(token)) {
nativeSendGamepadAxisValue(mPtr, axis, value);
}
diff --git a/services/core/jni/com_android_server_tv_GamepadKeys.h b/services/core/jni/com_android_server_tv_GamepadKeys.h
index 11fc9031da3b..127010f907ff 100644
--- a/services/core/jni/com_android_server_tv_GamepadKeys.h
+++ b/services/core/jni/com_android_server_tv_GamepadKeys.h
@@ -1,77 +1,104 @@
#ifndef ANDROIDTVREMOTE_SERVICE_JNI_GAMEPAD_KEYS_H_
#define ANDROIDTVREMOTE_SERVICE_JNI_GAMEPAD_KEYS_H_
+#include <android/input.h>
+#include <android/keycodes.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.
+// The constant array below defines a mapping between "Android" IDs (key code
+// within events) and what is being sent through /dev/uinput.
//
-// W3 Button spec: https://www.w3.org/TR/gamepad/#remapping
+// The translation back from uinput key codes into android key codes is done through
+// the corresponding key layout files. This file and
//
-// Standard gamepad keycodes are added plus 2 additional buttons (e.g. Stadia
-// has "Assistant" and "Share", PS4 has the touchpad button).
+// data/keyboards/Vendor_18d1_Product_0200.kl
//
-// 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
+// MUST be kept in sync.
+//
+// see https://source.android.com/devices/input/key-layout-files for documentation.
- BTN_TL, // "Left Bumper" / "L1" - Nintendo sends BTN_WEST instead
- BTN_TR, // "Right Bumper" / "R1" - Nintendo sends BTN_Z instead
+// Defines axis mapping information between android and
+// uinput axis.
+struct GamepadKey {
+ int32_t androidKeyCode;
+ int linuxUinputKeyCode;
+};
+
+static const GamepadKey GAMEPAD_KEYS[] = {
+ // Right-side buttons. A/B/X/Y or circle/triangle/square/X or similar
+ {AKEYCODE_BUTTON_A, BTN_A},
+ {AKEYCODE_BUTTON_B, BTN_B},
+ {AKEYCODE_BUTTON_X, BTN_X},
+ {AKEYCODE_BUTTON_Y, BTN_Y},
- // 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"
+ // Bumper buttons and digital triggers. Triggers generally have
+ // both analog versions (GAS and BRAKE output) and digital ones
+ {AKEYCODE_BUTTON_L1, BTN_TL2},
+ {AKEYCODE_BUTTON_L2, BTN_TL},
+ {AKEYCODE_BUTTON_R1, BTN_TR2},
+ {AKEYCODE_BUTTON_R2, BTN_TR},
- BTN_SELECT, // "Select/Back". Often "options" or similar
- BTN_START, // "Start/forward". Often "hamburger" icon
+ // general actions for controllers
+ {AKEYCODE_BUTTON_SELECT, BTN_SELECT}, // Options or "..."
+ {AKEYCODE_BUTTON_START, BTN_START}, // Menu/Hamburger menu
+ {AKEYCODE_BUTTON_MODE, BTN_MODE}, // "main" button
- BTN_THUMBL, // "Left Joystick Pressed"
- BTN_THUMBR, // "Right Joystick Pressed"
+ // Pressing on the joyticks themselves
+ {AKEYCODE_BUTTON_THUMBL, BTN_THUMBL},
+ {AKEYCODE_BUTTON_THUMBR, BTN_THUMBR},
- // 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"
+ // DPAD digital keys. HAT axis events are generally also sent.
+ {AKEYCODE_DPAD_UP, KEY_UP},
+ {AKEYCODE_DPAD_DOWN, KEY_DOWN},
+ {AKEYCODE_DPAD_LEFT, KEY_LEFT},
+ {AKEYCODE_DPAD_RIGHT, KEY_RIGHT},
- BTN_MODE, // "Main button" (Stadia/PS/XBOX/Home)
+ // "Extra" controller buttons: some devices have "share" and "assistant"
+ {AKEYCODE_BUTTON_1, BTN_TRIGGER_HAPPY1},
+ {AKEYCODE_BUTTON_2, BTN_TRIGGER_HAPPY2},
+ {AKEYCODE_BUTTON_3, BTN_TRIGGER_HAPPY3},
+ {AKEYCODE_BUTTON_4, BTN_TRIGGER_HAPPY4},
+ {AKEYCODE_BUTTON_5, BTN_TRIGGER_HAPPY5},
+ {AKEYCODE_BUTTON_6, BTN_TRIGGER_HAPPY6},
+ {AKEYCODE_BUTTON_7, BTN_TRIGGER_HAPPY7},
+ {AKEYCODE_BUTTON_8, BTN_TRIGGER_HAPPY8},
+ {AKEYCODE_BUTTON_9, BTN_TRIGGER_HAPPY9},
+ {AKEYCODE_BUTTON_10, BTN_TRIGGER_HAPPY10},
+ {AKEYCODE_BUTTON_11, BTN_TRIGGER_HAPPY11},
+ {AKEYCODE_BUTTON_12, BTN_TRIGGER_HAPPY12},
+ {AKEYCODE_BUTTON_13, BTN_TRIGGER_HAPPY13},
+ {AKEYCODE_BUTTON_14, BTN_TRIGGER_HAPPY14},
+ {AKEYCODE_BUTTON_15, BTN_TRIGGER_HAPPY15},
+ {AKEYCODE_BUTTON_16, BTN_TRIGGER_HAPPY16},
- BTN_TRIGGER_HAPPY1, // Extra button: "Assistant" for Stadia
- BTN_TRIGGER_HAPPY2, // Extra button: "Share" for Stadia
+ // Assignment to support global assistant for devices that support it.
+ {AKEYCODE_ASSIST, KEY_ASSISTANT},
+ {AKEYCODE_VOICE_ASSIST, KEY_VOICECOMMAND},
};
-// Defines information for an axis.
-struct Axis {
- int number;
- int rangeMin;
- int rangeMax;
+// Defines axis mapping information between android and
+// uinput axis.
+struct GamepadAxis {
+ int32_t androidAxis;
+ float androidRangeMin;
+ float androidRangeMax;
+ int linuxUinputAxis;
+ int linuxUinputRangeMin;
+ int linuxUinputRangeMax;
};
// 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
+static const GamepadAxis GAMEPAD_AXES[] = {
+ {AMOTION_EVENT_AXIS_X, -1, 1, ABS_X, 0, 254}, // Left joystick X
+ {AMOTION_EVENT_AXIS_Y, -1, 1, ABS_Y, 0, 254}, // Left joystick Y
+ {AMOTION_EVENT_AXIS_Z, -1, 1, ABS_Z, 0, 254}, // Right joystick X
+ {AMOTION_EVENT_AXIS_RZ, -1, 1, ABS_RZ, 0, 254}, // Right joystick Y
+ {AMOTION_EVENT_AXIS_LTRIGGER, 0, 1, ABS_GAS, 0, 254}, // Left trigger
+ {AMOTION_EVENT_AXIS_RTRIGGER, 0, 1, ABS_BRAKE, 0, 254}, // Right trigger
+ {AMOTION_EVENT_AXIS_HAT_X, -1, 1, ABS_HAT0X, -1, 1}, // DPad X
+ {AMOTION_EVENT_AXIS_HAT_Y, -1, 1, ABS_HAT0Y, -1, 1}, // DPad Y
};
} // namespace android
diff --git a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
index 0e96bd7ae47e..6e2e2c54518b 100644
--- a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
+++ b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
@@ -31,27 +31,38 @@
#include <utils/String8.h>
#include <ctype.h>
-#include <linux/input.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include <time.h>
-#include <stdint.h>
-#include <map>
#include <fcntl.h>
+#include <linux/input.h>
#include <linux/uinput.h>
#include <signal.h>
+#include <stdint.h>
#include <sys/inotify.h>
#include <sys/stat.h>
+#include <sys/time.h>
#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <unordered_map>
#define SLOT_UNKNOWN -1
namespace android {
-static std::map<int32_t,int> keysMap;
-static std::map<int32_t,int32_t> slotsMap;
+#define GOOGLE_VENDOR_ID 0x18d1
+
+#define GOOGLE_VIRTUAL_REMOTE_PRODUCT_ID 0x0100
+#define GOOGLE_VIRTUAL_GAMEPAD_PROUCT_ID 0x0200
+
+static std::unordered_map<int32_t, int> keysMap;
+static std::unordered_map<int32_t, int32_t> slotsMap;
static BitSet32 mtSlots;
+// Maps android key code to linux key code.
+static std::unordered_map<int32_t, int> gamepadAndroidToLinuxKeyMap;
+
+// Maps an android gamepad axis to the index within the GAMEPAD_AXES array.
+static std::unordered_map<int32_t, int> gamepadAndroidAxisToIndexMap;
+
static void initKeysMap() {
if (keysMap.empty()) {
for (size_t i = 0; i < NELEM(KEYS); i++) {
@@ -60,16 +71,49 @@ static void initKeysMap() {
}
}
+static void initGamepadKeyMap() {
+ if (gamepadAndroidToLinuxKeyMap.empty()) {
+ for (size_t i = 0; i < NELEM(GAMEPAD_KEYS); i++) {
+ gamepadAndroidToLinuxKeyMap[GAMEPAD_KEYS[i].androidKeyCode] =
+ GAMEPAD_KEYS[i].linuxUinputKeyCode;
+ }
+ }
+
+ if (gamepadAndroidAxisToIndexMap.empty()) {
+ for (size_t i = 0; i < NELEM(GAMEPAD_AXES); i++) {
+ gamepadAndroidAxisToIndexMap[GAMEPAD_AXES[i].androidAxis] = i;
+ }
+ }
+}
+
static int32_t getLinuxKeyCode(int32_t androidKeyCode) {
- std::map<int,int>::iterator it = keysMap.find(androidKeyCode);
+ std::unordered_map<int, int>::iterator it = keysMap.find(androidKeyCode);
if (it != keysMap.end()) {
return it->second;
}
return KEY_UNKNOWN;
}
+static int getGamepadkeyCode(int32_t androidKeyCode) {
+ std::unordered_map<int32_t, int>::iterator it =
+ gamepadAndroidToLinuxKeyMap.find(androidKeyCode);
+ if (it != gamepadAndroidToLinuxKeyMap.end()) {
+ return it->second;
+ }
+ return KEY_UNKNOWN;
+}
+
+static const GamepadAxis* getGamepadAxis(int32_t androidAxisCode) {
+ std::unordered_map<int32_t, int>::iterator it =
+ gamepadAndroidAxisToIndexMap.find(androidAxisCode);
+ if (it == gamepadAndroidToLinuxKeyMap.end()) {
+ return nullptr;
+ }
+ return &GAMEPAD_AXES[it->second];
+}
+
static int findSlot(int32_t pointerId) {
- std::map<int,int>::iterator it = slotsMap.find(pointerId);
+ std::unordered_map<int, int>::iterator it = slotsMap.find(pointerId);
if (it != slotsMap.end()) {
return it->second;
}
@@ -107,7 +151,7 @@ public:
// Open /dev/uinput and prepare to register
// the device with the given name and unique Id
- bool Open(const char* name, const char* uniqueId);
+ bool Open(const char* name, const char* uniqueId, uint16_t product);
// Checks if the current file descriptor is valid
bool IsValid() const { return mFd != kInvalidFileDescriptor; }
@@ -141,7 +185,7 @@ int UInputDescriptor::Detach() {
return fd;
}
-bool UInputDescriptor::Open(const char* name, const char* uniqueId) {
+bool UInputDescriptor::Open(const char* name, const char* uniqueId, uint16_t product) {
if (IsValid()) {
ALOGE("UInput device already open");
return false;
@@ -161,6 +205,8 @@ bool UInputDescriptor::Open(const char* name, const char* uniqueId) {
strlcpy(mUinputDescriptor.name, name, UINPUT_MAX_NAME_SIZE);
mUinputDescriptor.id.version = 1;
mUinputDescriptor.id.bustype = BUS_VIRTUAL;
+ mUinputDescriptor.id.vendor = GOOGLE_VENDOR_ID;
+ mUinputDescriptor.id.product = product;
// All UInput devices we use process keys
ioctl(mFd, UI_SET_EVBIT, EV_KEY);
@@ -258,7 +304,7 @@ NativeConnection* NativeConnection::open(const char* name, const char* uniqueId,
initKeysMap();
UInputDescriptor descriptor;
- if (!descriptor.Open(name, uniqueId)) {
+ if (!descriptor.Open(name, uniqueId, GOOGLE_VIRTUAL_REMOTE_PRODUCT_ID)) {
return nullptr;
}
@@ -277,21 +323,24 @@ NativeConnection* NativeConnection::open(const char* name, const char* uniqueId,
NativeConnection* NativeConnection::openGamepad(const char* name, const char* uniqueId) {
ALOGI("Registering uinput device %s: gamepad", name);
+ initGamepadKeyMap();
+
UInputDescriptor descriptor;
- if (!descriptor.Open(name, uniqueId)) {
+ if (!descriptor.Open(name, uniqueId, GOOGLE_VIRTUAL_GAMEPAD_PROUCT_ID)) {
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]);
+ for (size_t i = 0; i < NELEM(GAMEPAD_KEYS); i++) {
+ descriptor.EnableKey(GAMEPAD_KEYS[i].linuxUinputKeyCode);
}
// 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);
+ const GamepadAxis& axis = GAMEPAD_AXES[i];
+ descriptor.EnableAxis(axis.linuxUinputAxis, axis.linuxUinputRangeMin,
+ axis.linuxUinputRangeMax);
}
if (!descriptor.Create()) {
@@ -350,7 +399,7 @@ static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jb
}
}
-static void nativeSendGamepadKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyIndex,
+static void nativeSendGamepadKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode,
jboolean down) {
NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
@@ -359,16 +408,16 @@ static void nativeSendGamepadKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyI
return;
}
- if ((keyIndex < 0) || (keyIndex >= NELEM(GAMEPAD_KEY_CODES))) {
- ALOGE("Invalid gamepad key index: %d", keyIndex);
+ int linuxKeyCode = getGamepadkeyCode(keyCode);
+ if (linuxKeyCode == KEY_UNKNOWN) {
+ ALOGE("Gamepad: received an unknown keycode of %d.", keyCode);
return;
}
-
- connection->sendEvent(EV_KEY, GAMEPAD_KEY_CODES[keyIndex], down ? 1 : 0);
+ connection->sendEvent(EV_KEY, linuxKeyCode, down ? 1 : 0);
}
static void nativeSendGamepadAxisValue(JNIEnv* env, jclass clazz, jlong ptr, jint axis,
- jint value) {
+ jfloat value) {
NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
if (!connection->IsGamepad()) {
@@ -376,7 +425,25 @@ static void nativeSendGamepadAxisValue(JNIEnv* env, jclass clazz, jlong ptr, jin
return;
}
- connection->sendEvent(EV_ABS, axis, value);
+ const GamepadAxis* axisInfo = getGamepadAxis(axis);
+ if (axisInfo == nullptr) {
+ ALOGE("Invalid axis: %d", axis);
+ return;
+ }
+
+ if (value > axisInfo->androidRangeMax) {
+ value = axisInfo->androidRangeMax;
+ } else if (value < axisInfo->androidRangeMin) {
+ value = axisInfo->androidRangeMin;
+ }
+
+ // Converts the android range into the device range
+ float movementPercent = (value - axisInfo->androidRangeMin) /
+ (axisInfo->androidRangeMax - axisInfo->androidRangeMin);
+ int axisRawValue = axisInfo->linuxUinputRangeMin +
+ movementPercent * (axisInfo->linuxUinputRangeMax - axisInfo->linuxUinputRangeMin);
+
+ connection->sendEvent(EV_ABS, axisInfo->linuxUinputAxis, axisRawValue);
}
static void nativeSendPointerDown(JNIEnv* env, jclass clazz, jlong ptr,
@@ -441,18 +508,20 @@ static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) {
}
}
} 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_KEYS); i++) {
+ connection->sendEvent(EV_KEY, GAMEPAD_KEYS[i].linuxUinputKeyCode, 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)) {
+ const GamepadAxis& axis = GAMEPAD_AXES[i];
+
+ if ((axis.linuxUinputAxis == ABS_Z) || (axis.linuxUinputAxis == ABS_RZ)) {
// Mark triggers unpressed
- connection->sendEvent(EV_ABS, axis.number, 0);
+ connection->sendEvent(EV_ABS, axis.linuxUinputAxis, axis.linuxUinputRangeMin);
} else {
// Joysticks and dpad rests on center
- connection->sendEvent(EV_ABS, axis.number, (axis.rangeMin + axis.rangeMax) / 2);
+ connection->sendEvent(EV_ABS, axis.linuxUinputAxis,
+ (axis.linuxUinputRangeMin + axis.linuxUinputRangeMax) / 2);
}
}
}
@@ -475,7 +544,7 @@ static JNINativeMethod gUinputBridgeMethods[] = {
{"nativeClear", "(J)V", (void*)nativeClear},
{"nativeSendPointerSync", "(J)V", (void*)nativeSendPointerSync},
{"nativeSendGamepadKey", "(JIZ)V", (void*)nativeSendGamepadKey},
- {"nativeSendGamepadAxisValue", "(JII)V", (void*)nativeSendGamepadAxisValue},
+ {"nativeSendGamepadAxisValue", "(JIF)V", (void*)nativeSendGamepadAxisValue},
};
int register_android_server_tv_TvUinputBridge(JNIEnv* env) {