diff options
author | Andrei Litvin <andreilitvin@google.com> | 2020-03-16 10:26:14 -0400 |
---|---|---|
committer | Andrei Litvin <andreilitvin@google.com> | 2020-04-20 10:42:58 -0400 |
commit | 3b92b9682d9dbf8d6420432394218ef703c022d3 (patch) | |
tree | 983528fa8ae248c55cbf50cb9e8b902ef71f76d1 /media/lib | |
parent | 4e177518045bbc430bd7a467b207420e54db422d (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.java | 163 | ||||
-rw-r--r-- | media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java | 48 |
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()); + } } |