Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9053d728 authored by Ming-Shin Lu's avatar Ming-Shin Lu
Browse files

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
Merged-In: I7a66fd84e4094be249714c2597706ee25938adbe
Change-Id: I7a66fd84e4094be249714c2597706ee25938adbe
parent 8165fae2
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -2395,7 +2395,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 */);
        }
    }

+3 −1
Original line number Diff line number Diff line
@@ -1119,7 +1119,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());
    }

+14 −0
Original line number Diff line number Diff line
@@ -113,4 +113,18 @@ class ImeAppAutoFocusHelper @JvmOverloads 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
        wmHelper.waitForAppTransitionIdle()
        wmHelper.waitImeShown()
    }
}
+129 −0
Original line number Diff line number Diff line
/*
 * 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.app.Instrumentation
import android.platform.test.annotations.Postsubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
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.FlickerComponentName
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)
@Group2
class OpenImeWindowToFixedPortraitAppTest (private val testSpec: FlickerTestParameter) {
    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)

    @FlickerBuilderProvider
    fun buildFlicker(): FlickerBuilder {
        return FlickerBuilder(instrumentation).apply {
            setup {
                eachRun {
                    testApp.launchViaIntent(wmHelper)
                    testApp.openIME(device, wmHelper)
                    // Enable letterbox when the app calls setRequestedOrientation
                    device.executeShellCommand("cmd window set-ignore-orientation-request true")
                }
            }
            transitions {
                testApp.toggleFixPortraitOrientation(wmHelper)
            }
            teardown {
                eachRun {
                    testApp.exit()
                    device.executeShellCommand("cmd window set-ignore-orientation-request false")
                }
            }
        }
    }

    @Postsubmit
    @Test
    fun imeLayerVisibleStart() {
        testSpec.assertLayersStart {
            this.isVisible(FlickerComponentName.IME)
        }
    }

    @Postsubmit
    @Test
    fun imeLayerExistsEnd() {
        testSpec.assertLayersEnd {
            this.isVisible(FlickerComponentName.IME)
        }
    }

    @Postsubmit
    @Test
    fun imeLayerVisibleRegionKeepsTheSame() {
        var imeLayerVisibleRegionBeforeTransition: RegionSubject? = null
        testSpec.assertLayersStart {
            imeLayerVisibleRegionBeforeTransition = this.visibleRegion(FlickerComponentName.IME)
        }
        testSpec.assertLayersEnd {
            this.visibleRegion(FlickerComponentName.IME)
                    .coversExactly(imeLayerVisibleRegionBeforeTransition!!.region)
        }
    }

    @Postsubmit
    @Test
    fun appWindowWithLetterboxCoversExactlyOnScreen() {
        val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
        testSpec.assertLayersEnd {
            this.visibleRegion(testApp.component, FlickerComponentName.LETTERBOX)
                    .coversExactly(displayBounds)
        }
    }

    companion object {
        @Parameterized.Parameters(name = "{0}")
        @JvmStatic
        fun getParams(): Collection<FlickerTestParameter> {
            return FlickerTestParameterFactory.getInstance()
                    .getConfigNonRotationTests(
                            supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270),
                            supportedNavigationModes = listOf(
                                    WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
                                    WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
                            )
                    )
        }
    }
}
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -45,7 +45,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>
Loading