| /* |
| * 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.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; |
| |
| /** |
| * 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 { |
| synchronized (mOpenBridgeRunnables) { |
| if (mRemoteServiceInput == null) { |
| Log.d(TAG, "Delaying openRemoteInputBridge() for " + name); |
| |
| mOpenBridgeRunnables.add(() -> { |
| try { |
| mRemoteServiceInput.openInputBridge( |
| token, name, width, height, maxPointers); |
| Log.d(TAG, "Delayed openRemoteInputBridge() for " + name + ": success"); |
| } catch (RemoteException re) { |
| Log.e(TAG, "Delayed openRemoteInputBridge() for " + name + ": failure", re); |
| } |
| }); |
| return; |
| } |
| } |
| try { |
| mRemoteServiceInput.openInputBridge(token, name, width, height, maxPointers); |
| Log.d(TAG, "openRemoteInputBridge() for " + name + ": 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 { |
| 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 { |
| 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 { |
| 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 { |
| 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 { |
| 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 { |
| 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 { |
| 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 { |
| if (DEBUG_KEYS) Log.d(TAG, "sendPointerSync() token: " + token); |
| try { |
| mRemoteServiceInput.sendPointerSync(token); |
| } 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); |
| } |
| } |
| } |