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);
+                }
+            }
+        }
+    }
+}