Add support for GamePad api in ITvRemoteServiceInput.
Gamepad-specific API is a separtate input path from standard "remote"
service. Specifically it adds:
- openGamepad that creates a virtual input device with
gamepad-specific suport
- send gamepad keys
- send gamepad axis updates, which support joysticks, analog triggers
and HAT axis (as an alternative to DPAD buttons).
Bug: 150764186
Test: atest media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java
Test: flashed a ADT-3 device after the changes. Android TV Remote
on my phone still worked in controlling the UI.
Merged-In: I49612fce5e74c4e00ca60c715c6c72954e73b7a3
Change-Id: I49612fce5e74c4e00ca60c715c6c72954e73b7a3
(cherry picked from commit 9b9f556af1)
This commit is contained in:
48
data/keyboards/Vendor_18d1_Product_0200.kcm
Normal file
48
data/keyboards/Vendor_18d1_Product_0200.kcm
Normal file
@@ -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
|
||||
}
|
||||
|
||||
71
data/keyboards/Vendor_18d1_Product_0200.kl
Normal file
71
data/keyboards/Vendor_18d1_Product_0200.kl
Normal file
@@ -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
|
||||
|
||||
@@ -39,4 +39,10 @@ oneway interface ITvRemoteServiceInput {
|
||||
void sendPointerUp(IBinder token, int pointerId);
|
||||
@UnsupportedAppUsage
|
||||
void sendPointerSync(IBinder token);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -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 " + name + ": failure", re);
|
||||
Log.e(TAG, "Delayed openRemoteInputBridge() for " + finalName
|
||||
+ ": failure", re);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
mRemoteServiceInput.openInputBridge(token, name, width, height, maxPointers);
|
||||
Log.d(TAG, "openRemoteInputBridge() for " + name + ": success");
|
||||
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 openGamepadBridge() for " + finalName + ": failure",
|
||||
re);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,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) {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
// 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 axis mapping information between android and
|
||||
// uinput axis.
|
||||
struct GamepadKey {
|
||||
int32_t androidKeyCode;
|
||||
int linuxUinputKeyCode;
|
||||
};
|
||||
|
||||
// Defines information for an axis.
|
||||
struct Axis {
|
||||
int number;
|
||||
int rangeMin;
|
||||
int rangeMax;
|
||||
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},
|
||||
|
||||
// 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},
|
||||
|
||||
// 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
|
||||
|
||||
// Pressing on the joyticks themselves
|
||||
{AKEYCODE_BUTTON_THUMBL, BTN_THUMBL},
|
||||
{AKEYCODE_BUTTON_THUMBR, BTN_THUMBR},
|
||||
|
||||
// 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},
|
||||
|
||||
// "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},
|
||||
|
||||
// Assignment to support global assistant for devices that support it.
|
||||
{AKEYCODE_ASSIST, KEY_ASSISTANT},
|
||||
{AKEYCODE_VOICE_ASSIST, KEY_VOICECOMMAND},
|
||||
};
|
||||
|
||||
// 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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user