blob: b97ac26bb91520ec11b8848d1ff0a6f7b6751a54 [file] [log] [blame]
/*
* Copyright (C) 2016 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.
*/
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;
import android.os.IBinder;
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.
* <p/>
* This object is not thread safe. It is only intended to be accessed on the
* {@link Context#getMainLooper main looper thread} of an application.
* The callback {@link #onInputBridgeConnected()} may be called from a different thread.
* </p><p>
* IMPORTANT: This class is effectively a system API for unbundled emote service, and
* must remain API stable. See README.txt in the root of this package for more information.
* </p>
*/
public abstract class TvRemoteProvider {
/**
* The {@link Intent} that must be declared as handled by the service.
* The service must also require the {@link android.Manifest.permission#BIND_TV_REMOTE_SERVICE}
* permission so that other applications cannot abuse it.
*/
public static final String SERVICE_INTERFACE =
"com.android.media.tv.remoteprovider.TvRemoteProvider";
private static final String TAG = "TvRemoteProvider";
private static final boolean DEBUG_KEYS = false;
private final Context mContext;
private final ProviderStub mStub;
private final LinkedList<Runnable> mOpenBridgeRunnables;
private ITvRemoteServiceInput mRemoteServiceInput;
/**
* Creates a provider for an unbundled emote controller
* service allowing it to interface with the tv remote controller
* system service.
*
* @param context The application context for the remote provider.
*/
public TvRemoteProvider(Context context) {
mContext = context.getApplicationContext();
mStub = new ProviderStub();
mOpenBridgeRunnables = new LinkedList<Runnable>();
}
/**
* Gets the context of the remote service provider.
*/
public final Context getContext() {
return mContext;
}
/**
* Gets the Binder associated with the provider.
* <p>
* This is intended to be used for the onBind() method of a service that implements
* a remote provider service.
* </p>
*
* @return The IBinder instance associated with the provider.
*/
public IBinder getBinder() {
return mStub;
}
/**
* Information about the InputBridge connected status.
*
* @param token Identifier for the connection. Null, if failed.
*/
public void onInputBridgeConnected(IBinder token) {
}
/**
* Set a sink for sending events to framework service.
*
* @param tvServiceInput sink defined in framework service
*/
private void setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput) {
synchronized (mOpenBridgeRunnables) {
mRemoteServiceInput = tvServiceInput;
}
mOpenBridgeRunnables.forEach(Runnable::run);
mOpenBridgeRunnables.clear();
}
/**
* openRemoteInputBridge : Open an input bridge for a particular 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 name Device name
* @param token Identifier for this connection
* @param width Width of the device's virtual touchpad
* @param height Height of the device's virtual touchpad
* @param maxPointers Maximum supported pointers
* @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 " + finalName);
mOpenBridgeRunnables.add(() -> {
try {
mRemoteServiceInput.openInputBridge(
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 openGamepadBridge() for " + finalName + ": failure",
re);
}
});
return;
}
}
try {
mRemoteServiceInput.openGamepadBridge(token, finalName);
Log.d(TAG, "openGamepadBridge() for " + finalName + ": success");
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* closeInputBridge : Close input bridge for a device
*
* @param token identifier for this connection
* @throws RuntimeException
*/
public void closeInputBridge(IBinder token) throws RuntimeException {
Objects.requireNonNull(token);
try {
mRemoteServiceInput.closeInputBridge(token);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* clearInputBridge : Clear out any existing key or pointer events in queue for this device by
* dropping them on the floor and sending an UP to all keys and pointer
* slots.
*
* @param token identifier for this connection
* @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);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* sendTimestamp : Send a timestamp for a set of pointer events
*
* @param token identifier for the device
* @param timestamp Timestamp to be used in
* {@link android.os.SystemClock#uptimeMillis} time base
* @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 {
mRemoteServiceInput.sendTimestamp(token, timestamp);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* sendKeyUp : Send key up event for a device
*
* @param token identifier for this connection
* @param keyCode Key code to be sent
* @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);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* sendKeyDown : Send key down event for a device
*
* @param token identifier for this connection
* @param keyCode Key code to be sent
* @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 {
mRemoteServiceInput.sendKeyDown(token, keyCode);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* sendPointerUp : Send pointer up event for a device
*
* @param token identifier for the device
* @param pointerId Pointer id to be used. Value may be from 0
* to {@link MotionEvent#getPointerCount()} -1
* @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 {
mRemoteServiceInput.sendPointerUp(token, pointerId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* sendPointerDown : Send pointer down event for a device
*
* @param token identifier for the device
* @param pointerId Pointer id to be used. Value may be from 0
* to {@link MotionEvent#getPointerCount()} -1
* @param x X co-ordinates in display pixels
* @param y Y co-ordinates in display pixels
* @throws RuntimeException
*/
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 {
mRemoteServiceInput.sendPointerDown(token, pointerId, x, y);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* sendPointerSync : Send pointer sync event for a device
*
* @param token identifier for the device
* @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);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* 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) {
TvRemoteProvider.this.setRemoteServiceInputSink(tvServiceInput);
}
@Override
public void onInputBridgeConnected(IBinder token) {
TvRemoteProvider.this.onInputBridgeConnected(token);
}
}
}