content: /*
   * Copyright (C) 2018 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 android.inputmethodservice;
  
  import static java.lang.annotation.RetentionPolicy.SOURCE;
  
  import android.annotation.IntDef;
  import android.content.Context;
  import android.content.Intent;
  import android.os.IBinder;
  import android.os.Looper;
  import android.util.Log;
  import android.view.InputChannel;
  import android.view.KeyEvent;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.inputmethod.IMultiClientInputMethod;
  import com.android.internal.inputmethod.IMultiClientInputMethodPrivilegedOperations;
  import com.android.internal.inputmethod.MultiClientInputMethodPrivilegedOperations;
  
  import java.lang.annotation.Retention;
  import java.lang.ref.WeakReference;
  
  final class MultiClientInputMethodServiceDelegateImpl {
      private static final String TAG = "MultiClientInputMethodServiceDelegateImpl";
  
      private final Object mLock = new Object();
  
      @Retention(SOURCE)
      @IntDef({InitializationPhase.INSTANTIATED,
              InitializationPhase.ON_BIND_CALLED,
              InitializationPhase.INITIALIZE_CALLED,
              InitializationPhase.ON_UNBIND_CALLED,
              InitializationPhase.ON_DESTROY_CALLED})
      private @interface InitializationPhase {
          int INSTANTIATED = 1;
          int ON_BIND_CALLED = 2;
          int INITIALIZE_CALLED = 3;
          int ON_UNBIND_CALLED  = 4;
          int ON_DESTROY_CALLED = 5;
      }
  
      @GuardedBy("mLock")
      @InitializationPhase
      private int mInitializationPhase;
  
      private final MultiClientInputMethodPrivilegedOperations mPrivOps =
              new MultiClientInputMethodPrivilegedOperations();
  
      private final MultiClientInputMethodServiceDelegate.ServiceCallback mServiceCallback;
  
      private final Context mContext;
  
      MultiClientInputMethodServiceDelegateImpl(Context context,
              MultiClientInputMethodServiceDelegate.ServiceCallback serviceCallback) {
          mInitializationPhase = InitializationPhase.INSTANTIATED;
          mContext = context;
          mServiceCallback = serviceCallback;
      }
  
      void onDestroy() {
          synchronized (mLock) {
              switch (mInitializationPhase) {
                  case InitializationPhase.INSTANTIATED:
                  case InitializationPhase.ON_UNBIND_CALLED:
                      mInitializationPhase = InitializationPhase.ON_DESTROY_CALLED;
                      break;
                  default:
                      Log.e(TAG, "unexpected state=" + mInitializationPhase);
                      break;
              }
          }
      }
  
      private static final class ServiceImpl extends IMultiClientInputMethod.Stub {
          private final WeakReference<MultiClientInputMethodServiceDelegateImpl> mImpl;
  
          ServiceImpl(MultiClientInputMethodServiceDelegateImpl service) {
              mImpl = new WeakReference<>(service);
          }
  
          @Override
          public void initialize(IMultiClientInputMethodPrivilegedOperations privOps) {
              final MultiClientInputMethodServiceDelegateImpl service = mImpl.get();
              if (service == null) {
                  return;
              }
              synchronized (service.mLock) {
                  switch (service.mInitializationPhase) {
                      case InitializationPhase.ON_BIND_CALLED:
                          service.mPrivOps.set(privOps);
                          service.mInitializationPhase = InitializationPhase.INITIALIZE_CALLED;
                          service.mServiceCallback.initialized();
                          break;
                      default:
                          Log.e(TAG, "unexpected state=" + service.mInitializationPhase);
                          break;
                  }
              }
          }
  
          @Override
          public void addClient(int clientId, int uid, int pid, int selfReportedDisplayId) {
              final MultiClientInputMethodServiceDelegateImpl service = mImpl.get();
              if (service == null) {
                  return;
              }
              service.mServiceCallback.addClient(clientId, uid, pid, selfReportedDisplayId);
          }
  
          @Override
          public void removeClient(int clientId) {
              final MultiClientInputMethodServiceDelegateImpl service = mImpl.get();
              if (service == null) {
                  return;
              }
              service.mServiceCallback.removeClient(clientId);
          }
      }
  
      IBinder onBind(Intent intent) {
          synchronized (mLock) {
              switch (mInitializationPhase) {
                  case InitializationPhase.INSTANTIATED:
                      mInitializationPhase = InitializationPhase.ON_BIND_CALLED;
                      return new ServiceImpl(this);
                  default:
                      Log.e(TAG, "unexpected state=" + mInitializationPhase);
                      break;
              }
          }
          return null;
      }
  
      boolean onUnbind(Intent intent) {
          synchronized (mLock) {
              switch (mInitializationPhase) {
                  case InitializationPhase.ON_BIND_CALLED:
                  case InitializationPhase.INITIALIZE_CALLED:
                      mInitializationPhase = InitializationPhase.ON_UNBIND_CALLED;
                      mPrivOps.dispose();
                      break;
                  default:
                      Log.e(TAG, "unexpected state=" + mInitializationPhase);
                      break;
              }
          }
          return false;
      }
  
      IBinder createInputMethodWindowToken(int displayId) {
          return mPrivOps.createInputMethodWindowToken(displayId);
      }
  
      void acceptClient(int clientId,
              MultiClientInputMethodServiceDelegate.ClientCallback clientCallback,
              KeyEvent.DispatcherState dispatcherState, Looper looper) {
          final InputChannel[] channels = InputChannel.openInputChannelPair("MSIMS-session");
          final InputChannel writeChannel = channels[0];
          final InputChannel readChannel = channels[1];
          try {
              final MultiClientInputMethodClientCallbackAdaptor callbackAdaptor =
                      new MultiClientInputMethodClientCallbackAdaptor(clientCallback, looper,
                              dispatcherState, readChannel);
              mPrivOps.acceptClient(clientId, callbackAdaptor.createIInputMethodSession(),
                      callbackAdaptor.createIMultiClientInputMethodSession(), writeChannel);
          } finally {
              writeChannel.dispose();
          }
      }
  
      void reportImeWindowTarget(int clientId, int targetWindowHandle, IBinder imeWindowToken) {
          mPrivOps.reportImeWindowTarget(clientId, targetWindowHandle, imeWindowToken);
      }
  
      boolean isUidAllowedOnDisplay(int displayId, int uid) {
          return mPrivOps.isUidAllowedOnDisplay(displayId, uid);
      }
  
      void setActive(int clientId, boolean active) {
          mPrivOps.setActive(clientId, active);
      }
  }
  