Unbundle RemoteService on TV - part 3
- New service TVRemoteService triggered by SystemServer
- Provider service proxy and watcher for maintaining connections to unbundled
services which have the BIND_TV_REMOTE_SERVICE permission.
- Shared library to facilitate connections between unbundled service and
TVRemoteService.
- Unbundled service needs TV_VIRTUAL_REMOTE_CONTROLLER
permission to be fully functional.
b/23792608
Change-Id: Ief5c6995883d1f7268a73bdd0c920c4c3f42cddb
diff --git a/services/core/java/com/android/server/tv/TvRemoteService.java b/services/core/java/com/android/server/tv/TvRemoteService.java
new file mode 100644
index 0000000..961c992
--- /dev/null
+++ b/services/core/java/com/android/server/tv/TvRemoteService.java
@@ -0,0 +1,388 @@
+/*
+ * 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);
+ }
+ }
+ }
+ }
+}