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