Introduce IMMS ClientController component

ClientController is the component responsible for storing and managing
IMMS clients.

This is a pure refactoring, no feature flag is required.

Bug: 314150112
Bug: 315227580
Test: atest CtsInputMethodTestCases CtsInputMethodInstallTestCases
Test: atest FrameworksInputMethodSystemServerTests
Test: atest --host FrameworksInputMethodSystemServerTests_host
Change-Id: I117396b145f098f7b852328a2f1dd3df7a9f3b28
Signed-off-by: Antonio Kantek <kanant@google.com>
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index ffe6dc5..56423b9 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -40,6 +40,7 @@
         "frameworks-base-testutils",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
+        "ravenwood-junit",
         "services.core",
         "service-permission.stubs.system_server",
         "servicestests-core-utils",
@@ -66,6 +67,28 @@
     },
 }
 
+android_ravenwood_test {
+    name: "FrameworksInputMethodSystemServerTests_host",
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.rules",
+        "framework",
+        "mockito_ravenwood",
+        "ravenwood-runtime",
+        "ravenwood-utils",
+        "services",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    srcs: [
+        "src/com/android/server/inputmethod/**/ClientControllerTest.java",
+    ],
+    sdk_version: "test_current",
+    auto_gen_config: true,
+}
+
 android_test {
     name: "FrameworksImeTests",
     defaults: [
@@ -88,6 +111,7 @@
         "frameworks-base-testutils",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
+        "ravenwood-junit",
         "services.core",
         "service-permission.stubs.system_server",
         "servicestests-core-utils",
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
new file mode 100644
index 0000000..3c8f5c9
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageManagerInternal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.view.Display;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+// This test is designed to run on both device and host (Ravenwood) side.
+public final class ClientControllerTest {
+    private static final int ANY_DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final int ANY_CALLER_UID = 1;
+    private static final int ANY_CALLER_PID = 1;
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true).build();
+
+    @Mock
+    private PackageManagerInternal mMockPackageManagerInternal;
+
+    @Mock(extraInterfaces = IBinder.class)
+    private IInputMethodClient mClient;
+
+    @Mock
+    private IRemoteInputConnection mConnection;
+
+    @Mock
+    private IBinder.DeathRecipient mDeathRecipient;
+
+    private Handler mHandler;
+
+    private ClientController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mHandler = new Handler(Looper.getMainLooper());
+        mController = new ClientController(mMockPackageManagerInternal);
+        when(mClient.asBinder()).thenReturn((IBinder) mClient);
+    }
+
+    @Test
+    // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+    @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
+    public void testAddClient_cannotAddTheSameClientTwice() {
+        var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+
+        synchronized (ImfLock.class) {
+            mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, mDeathRecipient,
+                    ANY_CALLER_UID, ANY_CALLER_PID);
+
+            SecurityException thrown = assertThrows(SecurityException.class,
+                    () -> {
+                        synchronized (ImfLock.class) {
+                            mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
+                                    mDeathRecipient, ANY_CALLER_UID, ANY_CALLER_PID);
+                        }
+                    });
+            assertThat(thrown.getMessage()).isEqualTo(
+                    "uid=1/pid=1/displayId=0 is already registered");
+        }
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 3199e06..438bea4 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -22,6 +22,7 @@
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
+import static com.android.server.inputmethod.ClientController.ClientState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
@@ -68,8 +69,7 @@
         super.setUp();
         mVisibilityApplier =
                 (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
-        mInputMethodManagerService.setAttachedClientForTesting(
-                mock(InputMethodManagerService.ClientState.class));
+        mInputMethodManagerService.setAttachedClientForTesting(mock(ClientState.class));
     }
 
     @Test