Fix IME snapshot applied on non-IME input target window

In case when the activity shows IME, transitioning from non-IME input
targeted dialog activity will see a weird IME snapshot sliding down
animation during the dialog dismissing.

Also added a flicker test to verify this behavior.

Fix: 210868010
Test: manual as below steps:
   1) Launch keep note app
   2) Tap on any url link to popup "open with" dialog
   3) Tap anywhere else on the screen to dismiss the dialog
   4) Expect no IME sliding down animation
Test: atest LaunchAppShowImeAndDialogThemeAppTest
Test: atest DisplayContentTests#testAttachAndShowImeScreenshotOnTarget

Change-Id: Ibccaea46547e57b3439c5e787f97a261fbb10125
This commit is contained in:
Ming-Shin Lu
2021-12-30 23:36:22 +08:00
parent f5a7230cbf
commit 531604d4a7
10 changed files with 248 additions and 6 deletions

View File

@@ -3987,11 +3987,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (target == mImeLayeringTarget) {
return;
}
// Prepare the IME screenshot for the last IME target when its task is applying app
// transition. This is for the better IME transition to keep IME visibility when
// transitioning to the next task.
// If the IME target is the input target, before it changes, prepare the IME screenshot
// for the last IME target when its task is applying app transition. This is for the
// better IME transition to keep IME visibility when transitioning to the next task.
if (mImeLayeringTarget != null && mImeLayeringTarget.isAnimating(PARENTS | TRANSITION,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
&& mImeLayeringTarget == mImeInputTarget) {
attachAndShowImeScreenshotOnTarget();
}

View File

@@ -1982,6 +1982,7 @@ public class DisplayContentTests extends WindowTestsBase {
// Test step 1: appWin1 is the current IME target and soft-keyboard is visible.
mDisplayContent.computeImeTarget(true);
assertEquals(appWin1, mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
mDisplayContent.setImeInputTarget(appWin1);
spyOn(mDisplayContent.mInputMethodWindow);
doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible();
mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true);
@@ -1998,7 +1999,6 @@ public class DisplayContentTests extends WindowTestsBase {
// be shown at this time.
final Transaction t = mDisplayContent.getPendingTransaction();
spyOn(t);
mDisplayContent.setImeInputTarget(appWin2);
mDisplayContent.computeImeTarget(true);
assertEquals(appWin2, mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
assertTrue(mDisplayContent.shouldImeAttachedToApp());

View File

@@ -17,7 +17,9 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -47,4 +49,29 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor(
}
launcherStrategy.launch(appName, expectedPackage)
}
fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
"start_dialog_themed_activity_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()
wmHelper.waitForAppTransitionIdle()
wmHelper.waitForFullScreenApp(
ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent())
}
fun dismissDialog(wmHelper: WindowManagerStateHelper) {
val dialog = uiDevice.wait(
Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
// Tapping outside of the dialog to dismiss
if (dialog != null) {
val dialogBounds = dialog.visibleBounds
uiDevice.click(dialogBounds.left, dialogBounds.top - 300)
wmHelper.waitForAppTransitionIdle()
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2021 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.Presubmit
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.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
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 snapshot mechanism won't apply when transitioning from non-IME focused dialog activity.
* To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class LaunchAppShowImeAndDialogThemeAppTest(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)
wmHelper.waitImeShown()
testApp.startDialogThemedActivity(wmHelper)
}
}
teardown {
eachRun {
testApp.exit()
}
}
transitions {
testApp.dismissDialog(wmHelper)
}
}
}
/**
* Checks that [FlickerComponentName.IME] layer becomes visible during the transition
*/
@Presubmit
@Test
fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
/**
* Checks that [FlickerComponentName.IME] layer is visible at the end of the transition
*/
@Presubmit
@Test
fun imeLayerExistsEnd() {
testSpec.assertLayersEnd {
this.isVisible(FlickerComponentName.IME)
}
}
/**
* Checks that [FlickerComponentName.IME_SNAPSHOT] layer is invisible always.
*/
@Presubmit
@Test
fun imeSnapshotNotVisible() {
testSpec.assertLayers {
this.isInvisible(FlickerComponentName.IME_SNAPSHOT)
}
}
companion object {
/**
* Creates the test configurations.
*
* See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
* repetitions, screen orientation and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(
repetitions = 3,
supportedRotations = listOf(Surface.ROTATION_0),
supportedNavigationModes = listOf(
WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
)
)
}
}
}

View File

@@ -97,5 +97,16 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".DialogThemedActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity"
android:configChanges="orientation|screenSize"
android:theme="@style/DialogTheme"
android:label="DialogThemedActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -31,4 +31,9 @@
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" />
</LinearLayout>

View File

@@ -27,4 +27,14 @@
<style name="CutoutNever">
<item name="android:windowLayoutInDisplayCutoutMode">never</item>
</style>
</resources>
<style name="DialogTheme" parent="@android:style/Theme.DeviceDefault">
<item name="android:windowAnimationStyle">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@null</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources>

View File

@@ -56,4 +56,8 @@ public class ActivityOptions {
public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME =
new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity";
public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME =
new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".DialogThemedActivity");
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 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.testapp;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import android.app.Activity;
import android.app.AlertDialog;
import android.graphics.Color;
import android.os.Bundle;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
public class DialogThemedActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
getWindow().getDecorView().setBackgroundColor(Color.TRANSPARENT);
TextView textView = new TextView(this);
textView.setText("This is a test dialog");
textView.setTextColor(Color.BLACK);
LinearLayout layout = new LinearLayout(this);
layout.setBackgroundColor(Color.GREEN);
layout.addView(textView);
// Create a dialog with dialog-themed activity
AlertDialog dialog = new AlertDialog.Builder(this)
.setView(layout)
.setTitle("Dialog for test")
.create();
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(MATCH_PARENT,
MATCH_PARENT);
attrs.flags = FLAG_DIM_BEHIND | FLAG_ALT_FOCUSABLE_IM;
dialog.getWindow().getDecorView().setLayoutParams(attrs);
dialog.setCanceledOnTouchOutside(true);
dialog.show();
dialog.setOnDismissListener((d) -> finish());
}
}

View File

@@ -16,6 +16,8 @@
package com.android.server.wm.flicker.testapp;
import android.content.Intent;
import android.widget.Button;
import android.widget.EditText;
public class ImeActivityAutoFocus extends ImeActivity {
@@ -26,5 +28,9 @@ public class ImeActivityAutoFocus extends ImeActivity {
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)));
}
}