Add the first test for multi-session IME

Bug: 327704045
Test: atest ConcurrentMultiSessionImeTest --iteration 100

Change-Id: I9d6f4a724bffc24068b1a92e97a9ca87f335dade
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
index 0e0d212..4c531b8 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
@@ -20,9 +20,12 @@
 android_test {
     name: "ConcurrentMultiSessionImeTest",
     srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
     libs: ["android.test.runner"],
     static_libs: [
         "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
         "platform-test-annotations",
         "platform-test-rules",
         "truth",
@@ -35,7 +38,11 @@
     test_suites: [
         "general-tests",
     ],
-    sdk_version: "current",
+    sdk_version: "test_current",
+
+    data: [
+        ":CtsMockInputMethod",
+    ],
 
     // Store test artifacts in separated directories for easier debugging.
     per_testcase_directory: true,
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml
index 0defe5b..2e336ca 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidManifest.xml
@@ -17,6 +17,17 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.server.inputmethod.multisessiontest">
 
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".MainActivity"
+                  android:theme="@android:style/Theme.Material.NoActionBar"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+    </application>
+
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.server.inputmethod.multisessiontest"></instrumentation>
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml
index fd598c5..d5ed203 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/AndroidTest.xml
@@ -17,13 +17,28 @@
 <configuration description="Config for Concurrent Multi-Session IME tests">
     <object class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController"
         type="module_controller">
-        <option name="required-feature" value="android.software.input_methods" />
+        <!-- TODO(b/323372972): require this feature once the bug is fixed. -->
+        <!-- option name="required-feature" value="android.software.input_methods" -->
 
         <!-- Currently enabled to automotive only -->
         <option name="required-feature" value="android.hardware.type.automotive" />
     </object>
     <option name="test-suite-tag" value="apct" />
 
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="force-install-mode" value="FULL" />
+        <option name="test-file-name" value="ConcurrentMultiSessionImeTest.apk" />
+        <option name="test-file-name" value="CtsMockInputMethod.apk" />
+    </target_preparer>
+
+    <!-- RunOnSecondaryUserTargetPreparer must run after SuiteApkInstaller. -->
+    <target_preparer class="com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer">
+        <option name="start-background-user" value="true" />
+        <option name="test-package-name" value="com.android.server.inputmethod.multisessiontest" />
+        <option name="test-package-name" value="com.android.cts.mockime" />
+    </target_preparer>
+
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" />
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
@@ -31,12 +46,6 @@
             value="settings delete secure show_ime_with_hard_keyboard" />
     </target_preparer>
 
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="force-install-mode" value="FULL" />
-        <option name="test-file-name" value="ConcurrentMultiSessionImeTest.apk" />
-    </target_preparer>
-
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="com.android.server.inputmethod.multisessiontest" />
     </test>
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/res/layout/main_activity.xml b/tests/inputmethod/ConcurrentMultiSessionImeTest/res/layout/main_activity.xml
new file mode 100644
index 0000000..e16d286
--- /dev/null
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/res/layout/main_activity.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2024 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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+    <EditText
+        android:id="@+id/edit_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:hint="Input text here"/>
+</FrameLayout>
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index a5ce69d..56dbde0 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -16,16 +16,27 @@
 
 package com.android.server.inputmethod.multisessiontest;
 
-import static com.google.common.truth.Truth.assertThat;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import android.content.Context;
-import android.content.pm.PackageManager;
+import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.getResponderUserId;
+import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.launchActivityAsUserSync;
+import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.sendBundleAndWaitForReply;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_REQUEST_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_RESULT_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_HIDDEN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_IME_STATUS;
 
-import androidx.test.platform.app.InstrumentationRegistry;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.os.Bundle;
+
+import androidx.test.core.app.ActivityScenario;
 
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
@@ -39,19 +50,56 @@
     @Rule
     public static final DeviceState sDeviceState = new DeviceState();
 
+    private static final ComponentName TEST_ACTIVITY = new ComponentName(
+            getInstrumentation().getTargetContext().getPackageName(),
+            MainActivity.class.getName());
+
+    private ActivityScenario<MainActivity> mActivityScenario;
+    private MainActivity mActivity;
+    private int mPeerUserId;
+
     @Before
-    public void doBeforeEachTest() {
-        // No op
+    public void setUp() {
+        // Launch passenger activity.
+        mPeerUserId = getResponderUserId();
+        launchActivityAsUserSync(TEST_ACTIVITY, mPeerUserId);
+
+        // Launch driver activity.
+        mActivityScenario = ActivityScenario.launch(MainActivity.class);
+        mActivityScenario.onActivity(activity -> mActivity = activity);
+    }
+
+    @After
+    public void tearDown() {
+        if (mActivityScenario != null) {
+            mActivityScenario.close();
+        }
     }
 
     @Test
-    public void behaviorBeingTested_expectedResult() {
-        // Sample test
-        Context context =
-                InstrumentationRegistry.getInstrumentation().getTargetContext();
-        assertThat(context.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE)).isTrue();
-        assertThat(context.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_INPUT_METHODS)).isTrue();
+    public void driverShowImeNotAffectPassenger() {
+        assertDriverImeHidden();
+        assertPassengerImeHidden();
+
+        showDriverImeAndAssert();
+        assertPassengerImeHidden();
+    }
+
+    private void assertDriverImeHidden() {
+        assertWithMessage("Driver IME should be hidden")
+                .that(mActivity.isMyImeVisible()).isFalse();
+    }
+
+    private void assertPassengerImeHidden() {
+        final Bundle bundleToSend = new Bundle();
+        bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_IME_STATUS);
+        Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+                mPeerUserId, bundleToSend);
+        assertWithMessage("Passenger IME should be hidden")
+                .that(receivedBundle.getInt(KEY_RESULT_CODE)).isEqualTo(REPLY_IME_HIDDEN);
+    }
+
+    private void showDriverImeAndAssert() {
+        mActivity.showMyImeAndWait();
     }
 }
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
new file mode 100644
index 0000000..e3f84c1
--- /dev/null
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.multisessiontest;
+
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_REQUEST_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_RESULT_CODE;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_HIDDEN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_SHOWN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_IME_STATUS;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityBase;
+
+/**
+ * An {@link Activity} to test multiple concurrent session IME.
+ */
+public final class MainActivity extends ConcurrentUserActivityBase {
+    private static final String TAG = ConcurrentMultiUserTest.class.getSimpleName();
+    private static final long WAIT_IME_TIMEOUT_MS = 3000;
+
+    private EditText mEditor;
+    private InputMethodManager mImm;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.v(TAG, "Create MainActivity as user " + getUserId() + " on display "
+                + getDisplayId());
+        setContentView(R.layout.main_activity);
+        mImm = getSystemService(InputMethodManager.class);
+        mEditor = requireViewById(R.id.edit_text);
+    }
+
+    @Override
+    protected Bundle onBundleReceived(Bundle receivedBundle) {
+        final int requestCode = receivedBundle.getInt(KEY_REQUEST_CODE);
+        Log.v(TAG, "onBundleReceived() with request code:" + requestCode);
+        final Bundle replyBundle = new Bundle();
+        switch (requestCode) {
+            case REQUEST_IME_STATUS:
+                replyBundle.putInt(KEY_RESULT_CODE,
+                        isMyImeVisible() ? REPLY_IME_SHOWN : REPLY_IME_HIDDEN);
+                break;
+            default:
+                throw new RuntimeException("Received undefined request code:" + requestCode);
+        }
+        return replyBundle;
+    }
+
+    boolean isMyImeVisible() {
+        final WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(mEditor);
+        return insets == null ? false : insets.isVisible(WindowInsetsCompat.Type.ime());
+    }
+
+    void showMyImeAndWait() {
+        Log.v(TAG, "showSoftInput");
+        runOnUiThread(() -> {
+            // requestFocus() must run on UI thread.
+            if (!mEditor.requestFocus()) {
+                Log.e(TAG, "Failed to focus on mEditor");
+                return;
+            }
+            if (!mImm.showSoftInput(mEditor, /* flags= */ 0)) {
+                Log.e(TAG, String.format("Failed to show my IME as user %d, "
+                                + "mEditor:focused=%b,hasWindowFocus=%b", getUserId(),
+                        mEditor.isFocused(), mEditor.hasWindowFocus()));
+            }
+        });
+        PollingCheck.waitFor(WAIT_IME_TIMEOUT_MS, () -> isMyImeVisible(),
+                String.format("My IME (user %d) didn't show up", getUserId()));
+    }
+}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
new file mode 100644
index 0000000..1501bfb
--- /dev/null
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.multisessiontest;
+
+final class TestRequestConstants {
+    private TestRequestConstants() {
+    }
+
+    public static final String KEY_REQUEST_CODE = "key_request_code";
+    public static final String KEY_RESULT_CODE = "key_result_code";
+
+    public static final int REQUEST_IME_STATUS = 1;
+    public static final int REPLY_IME_SHOWN = 2;
+    public static final int REPLY_IME_HIDDEN = 3;
+}