/*
|
* 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.server.tv;
|
|
import android.content.Context;
|
import android.os.Handler;
|
import android.os.IBinder;
|
import android.os.Looper;
|
import android.os.Message;
|
import android.util.ArrayMap;
|
import android.util.Slog;
|
|
import com.android.server.SystemService;
|
import com.android.server.Watchdog;
|
|
import java.io.IOException;
|
import java.util.ArrayList;
|
import java.util.Map;
|
|
/**
|
* TvRemoteService represents a system service that allows a connected
|
* remote control (emote) service to inject white-listed input events
|
* and call other specified methods for functioning as an emote service.
|
* <p/>
|
* This service is intended for use only by white-listed packages.
|
*/
|
public class TvRemoteService extends SystemService implements Watchdog.Monitor {
|
private static final String TAG = "TvRemoteService";
|
private static final boolean DEBUG = false;
|
private static final boolean DEBUG_KEYS = false;
|
|
private Map<IBinder, UinputBridge> mBridgeMap = new ArrayMap();
|
private Map<IBinder, TvRemoteProviderProxy> mProviderMap = new ArrayMap();
|
private ArrayList<TvRemoteProviderProxy> mProviderList = new ArrayList<>();
|
|
/**
|
* State guarded by mLock.
|
* This is the second lock in sequence for an incoming call.
|
* The first lock is always {@link TvRemoteProviderProxy#mLock}
|
*
|
* There are currently no methods that break this sequence.
|
* Special note:
|
* Outgoing call informInputBridgeConnected(), which is called from
|
* openInputBridgeInternalLocked() uses a handler thereby relinquishing held locks.
|
*/
|
private final Object mLock = new Object();
|
|
public final UserHandler mHandler;
|
|
public TvRemoteService(Context context) {
|
super(context);
|
mHandler = new UserHandler(new UserProvider(TvRemoteService.this), context);
|
Watchdog.getInstance().addMonitor(this);
|
}
|
|
@Override
|
public void onStart() {
|
if (DEBUG) Slog.d(TAG, "onStart()");
|
}
|
|
@Override
|
public void monitor() {
|
synchronized (mLock) { /* check for deadlock */ }
|
}
|
|
@Override
|
public void onBootPhase(int phase) {
|
if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
|
if (DEBUG) Slog.d(TAG, "PHASE_THIRD_PARTY_APPS_CAN_START");
|
mHandler.sendEmptyMessage(UserHandler.MSG_START);
|
}
|
}
|
|
//Outgoing calls.
|
private void informInputBridgeConnected(IBinder token) {
|
mHandler.obtainMessage(UserHandler.MSG_INPUT_BRIDGE_CONNECTED, 0, 0, token).sendToTarget();
|
}
|
|
// Incoming calls.
|
private void openInputBridgeInternalLocked(TvRemoteProviderProxy provider, IBinder token,
|
String name, int width, int height,
|
int maxPointers) {
|
if (DEBUG) {
|
Slog.d(TAG, "openInputBridgeInternalLocked(), token: " + token + ", name: " + name +
|
", width: " + width + ", height: " + height + ", maxPointers: " + maxPointers);
|
}
|
|
try {
|
//Create a new bridge, if one does not exist already
|
if (mBridgeMap.containsKey(token)) {
|
if (DEBUG) Slog.d(TAG, "RemoteBridge already exists");
|
// Respond back with success.
|
informInputBridgeConnected(token);
|
return;
|
}
|
|
UinputBridge inputBridge = new UinputBridge(token, name, width, height, maxPointers);
|
|
mBridgeMap.put(token, inputBridge);
|
mProviderMap.put(token, provider);
|
|
// Respond back with success.
|
informInputBridgeConnected(token);
|
|
} catch (IOException ioe) {
|
Slog.e(TAG, "Cannot create device for " + name);
|
}
|
}
|
|
private void closeInputBridgeInternalLocked(IBinder token) {
|
if (DEBUG) {
|
Slog.d(TAG, "closeInputBridgeInternalLocked(), token: " + token);
|
}
|
|
// Close an existing RemoteBridge
|
UinputBridge inputBridge = mBridgeMap.get(token);
|
if (inputBridge != null) {
|
inputBridge.close(token);
|
}
|
|
mBridgeMap.remove(token);
|
}
|
|
|
private void clearInputBridgeInternalLocked(IBinder token) {
|
if (DEBUG) {
|
Slog.d(TAG, "clearInputBridgeInternalLocked(), token: " + token);
|
}
|
|
UinputBridge inputBridge = mBridgeMap.get(token);
|
if (inputBridge != null) {
|
inputBridge.clear(token);
|
}
|
}
|
|
private void sendTimeStampInternalLocked(IBinder token, long timestamp) {
|
UinputBridge inputBridge = mBridgeMap.get(token);
|
if (inputBridge != null) {
|
inputBridge.sendTimestamp(token, timestamp);
|
}
|
}
|
|
private void sendKeyDownInternalLocked(IBinder token, int keyCode) {
|
if (DEBUG_KEYS) {
|
Slog.d(TAG, "sendKeyDownInternalLocked(), token: " + token + ", keyCode: " + keyCode);
|
}
|
|
UinputBridge inputBridge = mBridgeMap.get(token);
|
if (inputBridge != null) {
|
inputBridge.sendKeyDown(token, keyCode);
|
}
|
}
|
|
private void sendKeyUpInternalLocked(IBinder token, int keyCode) {
|
if (DEBUG_KEYS) {
|
Slog.d(TAG, "sendKeyUpInternalLocked(), token: " + token + ", keyCode: " + keyCode);
|
}
|
|
UinputBridge inputBridge = mBridgeMap.get(token);
|
if (inputBridge != null) {
|
inputBridge.sendKeyUp(token, keyCode);
|
}
|
}
|
|
private void sendPointerDownInternalLocked(IBinder token, int pointerId, int x, int y) {
|
if (DEBUG_KEYS) {
|
Slog.d(TAG, "sendPointerDownInternalLocked(), token: " + token + ", pointerId: " +
|
pointerId + ", x: " + x + ", y: " + y);
|
}
|
|
UinputBridge inputBridge = mBridgeMap.get(token);
|
if (inputBridge != null) {
|
inputBridge.sendPointerDown(token, pointerId, x, y);
|
}
|
}
|
|
private void sendPointerUpInternalLocked(IBinder token, int pointerId) {
|
if (DEBUG_KEYS) {
|
Slog.d(TAG, "sendPointerUpInternalLocked(), token: " + token + ", pointerId: " +
|
pointerId);
|
}
|
|
UinputBridge inputBridge = mBridgeMap.get(token);
|
if (inputBridge != null) {
|
inputBridge.sendPointerUp(token, pointerId);
|
}
|
}
|
|
private void sendPointerSyncInternalLocked(IBinder token) {
|
if (DEBUG_KEYS) {
|
Slog.d(TAG, "sendPointerSyncInternalLocked(), token: " + token);
|
}
|
|
UinputBridge inputBridge = mBridgeMap.get(token);
|
if (inputBridge != null) {
|
inputBridge.sendPointerSync(token);
|
}
|
}
|
|
private final class UserHandler extends Handler {
|
|
public static final int MSG_START = 1;
|
public static final int MSG_INPUT_BRIDGE_CONNECTED = 2;
|
|
private final TvRemoteProviderWatcher mWatcher;
|
private boolean mRunning;
|
|
public UserHandler(UserProvider provider, Context context) {
|
super(Looper.getMainLooper(), null, true);
|
mWatcher = new TvRemoteProviderWatcher(context, provider, this);
|
}
|
|
@Override
|
public void handleMessage(Message msg) {
|
switch (msg.what) {
|
case MSG_START: {
|
start();
|
break;
|
}
|
case MSG_INPUT_BRIDGE_CONNECTED: {
|
IBinder token = (IBinder) msg.obj;
|
TvRemoteProviderProxy provider = mProviderMap.get(token);
|
if (provider != null) {
|
provider.inputBridgeConnected(token);
|
}
|
break;
|
}
|
}
|
}
|
|
private void start() {
|
if (!mRunning) {
|
mRunning = true;
|
mWatcher.start(); // also starts all providers
|
}
|
}
|
}
|
|
private final class UserProvider implements TvRemoteProviderWatcher.ProviderMethods,
|
TvRemoteProviderProxy.ProviderMethods {
|
|
private final TvRemoteService mService;
|
|
public UserProvider(TvRemoteService service) {
|
mService = service;
|
}
|
|
@Override
|
public void openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
|
int width, int height, int maxPointers) {
|
if (DEBUG) {
|
Slog.d(TAG, "openInputBridge(), token: " + token +
|
", name: " + name + ", width: " + width +
|
", height: " + height + ", maxPointers: " + maxPointers);
|
}
|
|
synchronized (mLock) {
|
if (mProviderList.contains(provider)) {
|
mService.openInputBridgeInternalLocked(provider, token, name, width, height,
|
maxPointers);
|
}
|
}
|
}
|
|
@Override
|
public void closeInputBridge(TvRemoteProviderProxy provider, IBinder token) {
|
if (DEBUG) Slog.d(TAG, "closeInputBridge(), token: " + token);
|
synchronized (mLock) {
|
if (mProviderList.contains(provider)) {
|
mService.closeInputBridgeInternalLocked(token);
|
mProviderMap.remove(token);
|
}
|
}
|
}
|
|
@Override
|
public void clearInputBridge(TvRemoteProviderProxy provider, IBinder token) {
|
if (DEBUG) Slog.d(TAG, "clearInputBridge(), token: " + token);
|
synchronized (mLock) {
|
if (mProviderList.contains(provider)) {
|
mService.clearInputBridgeInternalLocked(token);
|
}
|
}
|
}
|
|
@Override
|
public void sendTimeStamp(TvRemoteProviderProxy provider, IBinder token, long timestamp) {
|
synchronized (mLock) {
|
if (mProviderList.contains(provider)) {
|
mService.sendTimeStampInternalLocked(token, timestamp);
|
}
|
}
|
}
|
|
@Override
|
public void sendKeyDown(TvRemoteProviderProxy provider, IBinder token, int keyCode) {
|
if (DEBUG_KEYS) {
|
Slog.d(TAG, "sendKeyDown(), token: " + token + ", keyCode: " + keyCode);
|
}
|
synchronized (mLock) {
|
if (mProviderList.contains(provider)) {
|
mService.sendKeyDownInternalLocked(token, keyCode);
|
}
|
}
|
}
|
|
@Override
|
public void sendKeyUp(TvRemoteProviderProxy provider, IBinder token, int keyCode) {
|
if (DEBUG_KEYS) {
|
Slog.d(TAG, "sendKeyUp(), token: " + token + ", keyCode: " + keyCode);
|
}
|
synchronized (mLock) {
|
if (mProviderList.contains(provider)) {
|
mService.sendKeyUpInternalLocked(token, keyCode);
|
}
|
}
|
}
|
|
@Override
|
public void sendPointerDown(TvRemoteProviderProxy provider, IBinder token, int pointerId,
|
int x, int y) {
|
if (DEBUG_KEYS) {
|
Slog.d(TAG, "sendPointerDown(), token: " + token + ", pointerId: " + pointerId);
|
}
|
synchronized (mLock) {
|
if (mProviderList.contains(provider)) {
|
mService.sendPointerDownInternalLocked(token, pointerId, x, y);
|
}
|
}
|
}
|
|
@Override
|
public void sendPointerUp(TvRemoteProviderProxy provider, IBinder token, int pointerId) {
|
if (DEBUG_KEYS) {
|
Slog.d(TAG, "sendPointerUp(), token: " + token + ", pointerId: " + pointerId);
|
}
|
synchronized (mLock) {
|
if (mProviderList.contains(provider)) {
|
mService.sendPointerUpInternalLocked(token, pointerId);
|
}
|
}
|
}
|
|
@Override
|
public void sendPointerSync(TvRemoteProviderProxy provider, IBinder token) {
|
if (DEBUG_KEYS) Slog.d(TAG, "sendPointerSync(), token: " + token);
|
synchronized (mLock) {
|
if (mProviderList.contains(provider)) {
|
mService.sendPointerSyncInternalLocked(token);
|
}
|
}
|
}
|
|
@Override
|
public void addProvider(TvRemoteProviderProxy provider) {
|
if (DEBUG) Slog.d(TAG, "addProvider " + provider);
|
synchronized (mLock) {
|
provider.setProviderSink(this);
|
mProviderList.add(provider);
|
Slog.d(TAG, "provider: " + provider.toString());
|
}
|
}
|
|
@Override
|
public void removeProvider(TvRemoteProviderProxy provider) {
|
if (DEBUG) Slog.d(TAG, "removeProvider " + provider);
|
synchronized (mLock) {
|
if (mProviderList.remove(provider) == false) {
|
Slog.e(TAG, "Unknown provider " + provider);
|
}
|
}
|
}
|
}
|
}
|