/* * 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); } } } } }