Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7dd4f1af authored by Andrei Litvin's avatar Andrei Litvin Committed by Android (Google) Code Review
Browse files

Merge "Add support for GamePad api in ITvRemoteServiceInput." into rvc-dev

parents 53e927f8 3b92b968
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
# 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 −0
Original line number Diff line number Diff line
# 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
+7 −1
Original line number Diff line number Diff line
@@ -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);
}
+155 −8
Original line number Diff line number Diff line
@@ -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) {
+48 −0
Original line number Diff line number Diff line
@@ -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());
    }
}
Loading