Merge "Fix IME being shifted when the app setRequestedOrientation"
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 33af563..31f30af 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2374,7 +2374,11 @@
         // IME parent may failed to attach to the app during rotating the screen.
         // See DisplayContent#shouldImeAttachedToApp, DisplayContent#isImeControlledByApp
         if (windowConfigChanged) {
-            getDisplayContent().updateImeControlTarget();
+            // If the window was the IME layering target, updates the IME surface parent in case
+            // the IME surface may be wrongly positioned when the window configuration affects the
+            // IME surface association. (e.g. Attach IME surface on the display instead of the
+            // app when the app bounds being letterboxed.)
+            mDisplayContent.updateImeControlTarget(isImeLayeringTarget() /* updateImeParent */);
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 1f60e79..d3e5a8a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -1145,7 +1145,9 @@
         spyOn(app.getDisplayContent());
         app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
 
-        verify(app.getDisplayContent()).updateImeControlTarget();
+        // Expect updateImeParent will be invoked when the configuration of the IME control
+        // target has changed.
+        verify(app.getDisplayContent()).updateImeControlTarget(eq(true) /* updateImeParent */);
         assertEquals(mAppWindow, mDisplayContent.getImeTarget(IME_TARGET_CONTROL).getWindow());
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index b7eea1b..fed953c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -119,4 +119,17 @@
         }
         return false
     }
+
+    fun toggleFixPortraitOrientation(wmHelper: WindowManagerStateHelper) {
+        val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
+                "toggle_fixed_portrait_btn")), FIND_TIMEOUT)
+        require(button != null) {
+            "Button not found, this usually happens when the device " +
+                    "was left in an unknown state (e.g. Screen turned off)"
+        }
+        button.click()
+        mInstrumentation.waitForIdleSync()
+        // Ensure app relaunching transition finish and the IME has shown
+        waitIMEShown(wmHelper)
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
new file mode 100644
index 0000000..806ce99
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.wm.flicker.ime
+
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.traces.region.RegionSubject
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test IME window shown on the app with fixing portrait orientation.
+ * To run this test: `atest FlickerTests:OpenImeWindowToFixedPortraitAppTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenImeWindowToFixedPortraitAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            testApp.launchViaIntent(wmHelper)
+            testApp.openIME(wmHelper)
+            // Enable letterbox when the app calls setRequestedOrientation
+            device.executeShellCommand("cmd window set-ignore-orientation-request true")
+        }
+        transitions {
+            testApp.toggleFixPortraitOrientation(wmHelper)
+        }
+        teardown {
+            testApp.exit()
+            device.executeShellCommand("cmd window set-ignore-orientation-request false")
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun imeLayerVisibleStart() {
+        flicker.assertLayersStart {
+            this.isVisible(ComponentNameMatcher.IME)
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun imeLayerExistsEnd() {
+        flicker.assertLayersEnd {
+            this.isVisible(ComponentNameMatcher.IME)
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun imeLayerVisibleRegionKeepsTheSame() {
+        var imeLayerVisibleRegionBeforeTransition: RegionSubject? = null
+        flicker.assertLayersStart {
+            imeLayerVisibleRegionBeforeTransition = this.visibleRegion(ComponentNameMatcher.IME)
+        }
+        flicker.assertLayersEnd {
+            this.visibleRegion(ComponentNameMatcher.IME)
+                    .coversExactly(imeLayerVisibleRegionBeforeTransition!!.region)
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun appWindowWithLetterboxCoversExactlyOnScreen() {
+        val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
+        flicker.assertLayersEnd {
+            this.visibleRegion(testApp.or(ComponentNameMatcher.LETTERBOX))
+                    .coversExactly(displayBounds)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                            supportedRotations = listOf(
+                                PlatformConsts.Rotation.ROTATION_90,
+                            ),
+                            supportedNavigationModes = listOf(
+                                PlatformConsts.NavBar.MODE_3BUTTON,
+                                PlatformConsts.NavBar.MODE_GESTURAL
+                            )
+                    )
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 83823ea..cd47f60 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -47,7 +47,7 @@
                   android:theme="@style/CutoutShortEdges"
                   android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
                   android:windowSoftInputMode="stateVisible"
-                  android:configChanges="orientation|screenSize"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
                   android:label="ImeAppAutoFocus"
                   android:exported="true">
             <intent-filter>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
index baaf707..fa73e2c 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -26,14 +26,27 @@
               android:layout_width="match_parent"
 	      android:imeOptions="flagNoExtractUi"
               android:inputType="text"/>
-    <Button
-        android:id="@+id/finish_activity_btn"
+    <LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="Finish activity" />
-    <Button
-        android:id="@+id/start_dialog_themed_activity_btn"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="Start dialog themed activity" />
+        android:layout_height="match_parent"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/finish_activity_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Finish activity" />
+        <Button
+            android:id="@+id/start_dialog_themed_activity_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Dialog themed activity" />
+        <ToggleButton
+            android:id="@+id/toggle_fixed_portrait_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textOn="Portrait (On)"
+            android:textOff="Portrait (Off)"
+        />
+    </LinearLayout>
 </LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
index bb200f1..7ee8deb 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
@@ -16,21 +16,29 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
 import android.content.Intent;
 import android.widget.Button;
 import android.widget.EditText;
+import android.widget.ToggleButton;
 
 public class ImeActivityAutoFocus extends ImeActivity {
-
     @Override
     protected void onStart() {
         super.onStart();
 
-        EditText editTextField = findViewById(R.id.plain_text_input);
-        editTextField.requestFocus();
-
         Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn);
         startThemedActivityButton.setOnClickListener(
                 button -> startActivity(new Intent(this, DialogThemedActivity.class)));
+
+        ToggleButton toggleFixedPortraitButton = findViewById(R.id.toggle_fixed_portrait_btn);
+        toggleFixedPortraitButton.setOnCheckedChangeListener(
+                (button, isChecked) -> setRequestedOrientation(
+                        isChecked ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_UNSPECIFIED));
+
+        EditText editTextField = findViewById(R.id.plain_text_input);
+        editTextField.requestFocus();
     }
 }