Fix IME being shifted when the app setRequestedOrientation

Fix an edge case happens on large-screen devices that
DisplayContent#updateImeParent may not be called to update IME surface
parent from the activity to the display area when the app actvitity is
letterboxed by
 1) activity handles configChange without being relaunched
    by window configuration change
 2) calling setRequestedOrientation to fix orientation as portrait

In this case, if the activity bounds size is not suitable to attach
IME surface, we need to recompute the IME surface parent in
WindowState#onConfigurationChanged to make IME surface can be placed on
the display area to avoid IME position being shifted by
letterboxed activity.

Fix: 249081451
Test: atest FlickerTests:OpenImeWindowToFixedPortraitAppTest
Change-Id: I7a66fd84e4094be249714c2597706ee25938adbe
This commit is contained in:
Ming-Shin Lu
2023-01-13 08:19:01 +00:00
parent 5975f9dc72
commit 45537b7567
7 changed files with 180 additions and 16 deletions

View File

@@ -2374,7 +2374,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// 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 */);
}
}

View File

@@ -1145,7 +1145,9 @@ public class WindowStateTests extends WindowTestsBase {
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());
}

View File

@@ -119,4 +119,17 @@ constructor(
}
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)
}
}

View File

@@ -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
)
)
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();
}
}