summaryrefslogtreecommitdiff
path: root/media/lib
diff options
context:
space:
mode:
authorAndrei Litvin <andreilitvin@google.com>2020-03-16 10:26:14 -0400
committerAndrei Litvin <andreilitvin@google.com>2020-04-20 10:42:58 -0400
commit3b92b9682d9dbf8d6420432394218ef703c022d3 (patch)
tree983528fa8ae248c55cbf50cb9e8b902ef71f76d1 /media/lib
parent4e177518045bbc430bd7a467b207420e54db422d (diff)
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 9b9f556af1f53a6ae29d5560240b96fdc151978a)
Diffstat (limited to 'media/lib')
-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
2 files changed, 203 insertions, 8 deletions
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());
+ }
}