Regression test for bug 226033399.

This CL adds a test to verify that keyboard remains visible
when a notification comes.

The test is skipped on the following cases, in case the
device-under-test has a non-standard notification UI.
(1) on TV, Automotive, and Wear devices.
(2) on small-screen devices.

Bug: 226033399
Bug: 230027493
Test: atest CtsInputMethodTestCases:KeyboardVisibilityControlTest
Change-Id: I23c6449ec2ec76079c55948b8adff7828c095499
diff --git a/tests/inputmethod/AndroidManifest.xml b/tests/inputmethod/AndroidManifest.xml
index 5d7f57e..57aa4cd 100644
--- a/tests/inputmethod/AndroidManifest.xml
+++ b/tests/inputmethod/AndroidManifest.xml
@@ -20,6 +20,7 @@
      android:targetSandboxVersion="2">
 
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
 
     <queries>
         <intent>
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 18f6c5c..9b0ce85 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -55,10 +55,14 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeNotNull;
 import static org.junit.Assume.assumeTrue;
 
 import android.app.AlertDialog;
 import android.app.Instrumentation;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.compat.CompatChanges;
 import android.content.Context;
 import android.content.Intent;
@@ -1718,6 +1722,81 @@
         }
     }
 
+    /**
+     * A regression Test for Bug 226033399.
+     *
+     * <p>This test verifies that the keyboard remains visible when a notification comes.
+     * This test runs only when the screen is big enough. If the screen is small, it may make sense
+     * to dismiss the keyboard while a notification is being displayed. We also skip this test on
+     * non-phone, non-tablet form factors.
+     */
+    @Test
+    public void testIMEVisibleWhenNotificationComes() throws Throwable {
+        final Context targetContext = mInstrumentation.getTargetContext();
+        final PackageManager pm = targetContext.getPackageManager();
+        // Exclude major known non-phone, non-tablet form factors which have different notification
+        // UIs.
+        assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY));
+        assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+        assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_WATCH));
+        final NotificationManager notificationManager =
+                targetContext.getSystemService(NotificationManager.class);
+        assumeNotNull(notificationManager);
+        // Usually notification permission should be auto-granted by the test runner.
+        // Skip the test if notification is disabled for some other reason.
+        assumeTrue(notificationManager.areNotificationsEnabled());
+
+        final InputMethodManager imm = targetContext.getSystemService(InputMethodManager.class);
+        final int notificationId = 12345;
+        try (MockImeSession imeSession = MockImeSession.create(
+                mInstrumentation.getContext(),
+                mInstrumentation.getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final EditText editText = launchTestActivity(marker);
+
+            // Skip the test if the screen size is small.
+            final int smallestScreenWidthDp =
+                    editText.getContext().getResources().getConfiguration().smallestScreenWidthDp;
+            Log.d(TAG, "smallestScreenWidthDp = " + smallestScreenWidthDp);
+            assumeTrue(smallestScreenWidthDp >= 400);
+
+            // 1. Show keyboard.
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            assertTrue("showSoftInput must success if the View has IME focus",
+                    getOnMainSync(() -> imm.showSoftInput(editText, 0)));
+            expectImeVisible(TIMEOUT);
+
+            // 2. Post a notification and verify that the keyboard is still visible.
+            final NotificationChannel channel = new NotificationChannel("test" /* id */,
+                    "Test Channel" /* name */, NotificationManager.IMPORTANCE_HIGH);
+            notificationManager.createNotificationChannel(channel);
+            final String notificationTitle = "notification-" + marker;
+            notificationManager.notify(
+                    notificationId,
+                    new Notification.Builder(targetContext, channel.getId())
+                            .setContentTitle(notificationTitle)
+                            .setContentText("testIMEVisibleWhenNotificationComes")
+                            .setSmallIcon(android.R.drawable.ic_info)
+                            .build());
+            UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
+            // Wait until the notification is visible. If TIMEOUT has passed and the notification
+            // is not visible, it's fine - the keyboard should remain visible in that case too.
+            uiDevice.wait(Until.hasObject(By.text(notificationTitle)), TIMEOUT);
+            expectImeVisible(TIMEOUT);
+
+            // 3. Dismiss the notification and verify that the keyboard is still visible.
+            notificationManager.cancel(notificationId);
+            uiDevice.wait(Until.gone(By.text(notificationTitle)), TIMEOUT);
+            expectImeVisible(TIMEOUT);
+        } finally {
+            // Make sure to dismiss the notification even if the test failed.
+            notificationManager.cancel(notificationId);
+        }
+    }
+
     private void setAutoRotateScreen(boolean enable) {
         try {
             final Instrumentation instrumentation = mInstrumentation;