diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java index 518123600b9ae..6999e5bdc527a 100644 --- a/core/java/android/window/StartingWindowRemovalInfo.java +++ b/core/java/android/window/StartingWindowRemovalInfo.java @@ -16,6 +16,7 @@ package android.window; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; @@ -23,6 +24,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.view.SurfaceControl; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Information when removing a starting window of a particular task. * @hide @@ -55,11 +59,28 @@ public final class StartingWindowRemovalInfo implements Parcelable { */ public boolean playRevealAnimation; + /** The mode is no need to defer removing the starting window for IME */ + public static final int DEFER_MODE_NONE = 0; + + /** The mode to defer removing the starting window until IME has drawn */ + public static final int DEFER_MODE_NORMAL = 1; + + /** The mode to defer the starting window removal until IME drawn and finished the rotation */ + public static final int DEFER_MODE_ROTATION = 2; + + @IntDef(prefix = { "DEFER_MODE_" }, value = { + DEFER_MODE_NONE, + DEFER_MODE_NORMAL, + DEFER_MODE_ROTATION, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeferMode {} + /** * Whether need to defer removing the starting window for IME. * @hide */ - public boolean deferRemoveForIme; + public @DeferMode int deferRemoveForImeMode; /** * The rounded corner radius @@ -95,7 +116,7 @@ public final class StartingWindowRemovalInfo implements Parcelable { windowAnimationLeash = source.readTypedObject(SurfaceControl.CREATOR); mainFrame = source.readTypedObject(Rect.CREATOR); playRevealAnimation = source.readBoolean(); - deferRemoveForIme = source.readBoolean(); + deferRemoveForImeMode = source.readInt(); roundedCornerRadius = source.readFloat(); windowlessSurface = source.readBoolean(); removeImmediately = source.readBoolean(); @@ -107,7 +128,7 @@ public final class StartingWindowRemovalInfo implements Parcelable { dest.writeTypedObject(windowAnimationLeash, flags); dest.writeTypedObject(mainFrame, flags); dest.writeBoolean(playRevealAnimation); - dest.writeBoolean(deferRemoveForIme); + dest.writeInt(deferRemoveForImeMode); dest.writeFloat(roundedCornerRadius); dest.writeBoolean(windowlessSurface); dest.writeBoolean(removeImmediately); @@ -119,7 +140,7 @@ public final class StartingWindowRemovalInfo implements Parcelable { + " frame=" + mainFrame + " playRevealAnimation=" + playRevealAnimation + " roundedCornerRadius=" + roundedCornerRadius - + " deferRemoveForIme=" + deferRemoveForIme + + " deferRemoveForImeMode=" + deferRemoveForImeMode + " windowlessSurface=" + windowlessSurface + " removeImmediately=" + removeImmediately + "}"; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java index 8a4d4c21194af..ae722208782e5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java @@ -477,15 +477,15 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator { } @Override - public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { + public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { if (mRootView == null) { - return; + return true; } if (mSplashView == null) { // shouldn't happen, the app window may be drawn earlier than starting window? Slog.e(TAG, "Found empty splash screen, remove!"); removeWindowInner(mRootView, false); - return; + return true; } clearSystemBarColor(); if (immediately @@ -503,6 +503,7 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator { removeWindowInner(mRootView, true); } } + return true; } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index ff06db370d1ae..7cbf263f7cb1e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -18,6 +18,8 @@ package com.android.wm.shell.startingsurface; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.view.Display.DEFAULT_DISPLAY; +import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL; +import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION; import android.annotation.CallSuper; import android.app.TaskInfo; @@ -216,7 +218,17 @@ public class StartingSurfaceDrawer { } abstract static class StartingWindowRecord { protected int mBGColor; - abstract void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately); + + /** + * Remove the starting window with the given {@link StartingWindowRemovalInfo} if possible. + * @param info The removal info sent from the task organizer controller in the WM core. + * @param immediately {@code true} means removing the starting window immediately, + * {@code false} otherwise. + * @return {@code true} means {@link StartingWindowRecordManager} can safely remove the + * record itself. {@code false} means {@link StartingWindowRecordManager} requires + * to manage the record reference and remove it later. + */ + abstract boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately); int getBGColor() { return mBGColor; } @@ -231,6 +243,15 @@ public class StartingSurfaceDrawer { * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}. */ private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600; + + /** + * The max delay time in milliseconds for removing the task snapshot window with IME + * visible after the fixed rotation finished. + * Ideally the delay time will be shorter when receiving + * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}. + */ + private static final long MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION = 3000; + private final Runnable mScheduledRunnable = this::removeImmediately; @WindowConfiguration.ActivityType protected final int mActivityType; @@ -242,24 +263,34 @@ public class StartingSurfaceDrawer { } @Override - public final void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { + public final boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { if (immediately) { removeImmediately(); } else { - scheduleRemove(info.deferRemoveForIme); + scheduleRemove(info.deferRemoveForImeMode); + return false; } + return true; } - void scheduleRemove(boolean deferRemoveForIme) { + void scheduleRemove(@StartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode) { // Show the latest content as soon as possible for unlocking to home. if (mActivityType == ACTIVITY_TYPE_HOME) { removeImmediately(); return; } mRemoveExecutor.removeCallbacks(mScheduledRunnable); - final long delayRemovalTime = hasImeSurface() && deferRemoveForIme - ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE - : DELAY_REMOVAL_TIME_GENERAL; + final long delayRemovalTime; + switch (deferRemoveForImeMode) { + case DEFER_MODE_ROTATION: + delayRemovalTime = MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION; + break; + case DEFER_MODE_NORMAL: + delayRemovalTime = MAX_DELAY_REMOVAL_TIME_IME_VISIBLE; + break; + default: + delayRemovalTime = DELAY_REMOVAL_TIME_GENERAL; + } mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "Defer removing snapshot surface in %d", delayRemovalTime); @@ -297,8 +328,10 @@ public class StartingSurfaceDrawer { final int taskId = removeInfo.taskId; final StartingWindowRecord record = mStartingWindowRecords.get(taskId); if (record != null) { - record.removeIfPossible(removeInfo, immediately); - mStartingWindowRecords.remove(taskId); + final boolean canRemoveRecord = record.removeIfPossible(removeInfo, immediately); + if (canRemoveRecord) { + mStartingWindowRecords.remove(taskId); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java index 12a0d4054b4d1..98a8031285876 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java @@ -124,7 +124,7 @@ class WindowlessSplashWindowCreator extends AbsSplashWindowCreator { } @Override - public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { + public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { if (!immediately) { mSplashscreenContentDrawer.applyExitAnimation(mSplashView, info.windowAnimationLeash, info.mainFrame, @@ -132,6 +132,7 @@ class WindowlessSplashWindowCreator extends AbsSplashWindowCreator { } else { release(); } + return true; } void release() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index bf62acfc47a1d..8115a5d4e89cd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -265,17 +265,17 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { mStartingSurfaceDrawer.mWindowRecords.addRecord(taskId, new StartingSurfaceDrawer.StartingWindowRecord() { @Override - public void removeIfPossible(StartingWindowRemovalInfo info, + public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { - + return true; } }); mStartingSurfaceDrawer.mWindowlessRecords.addRecord(taskId, new StartingSurfaceDrawer.StartingWindowRecord() { @Override - public void removeIfPossible(StartingWindowRemovalInfo info, + public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { - + return true; } }); mStartingSurfaceDrawer.clearAllWindows(); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c3cd3eca84f21..25e5dacb25e3b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5220,6 +5220,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } logAppCompatState(); if (!visible) { + final InputTarget imeInputTarget = mDisplayContent.getImeInputTarget(); + mLastImeShown = imeInputTarget != null && imeInputTarget.getWindowState() != null + && imeInputTarget.getWindowState().mActivityRecord == this + && mDisplayContent.mInputMethodWindow != null + && mDisplayContent.mInputMethodWindow.isVisible(); finishOrAbortReplacingWindow(); } return true; @@ -5609,11 +5614,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (!visible) { - final InputTarget imeInputTarget = mDisplayContent.getImeInputTarget(); - mLastImeShown = imeInputTarget != null && imeInputTarget.getWindowState() != null - && imeInputTarget.getWindowState().mActivityRecord == this - && mDisplayContent.mInputMethodWindow != null - && mDisplayContent.mInputMethodWindow.isVisible(); mImeInsetsFrozenUntilStartInput = true; } diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index 7a11120132bdc..7e783938d30af 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -267,7 +267,12 @@ class AsyncRotationController extends FadeAnimationController implements Consume op.mDrawTransaction = null; if (DEBUG) Slog.d(TAG, "finishOp merge transaction " + windowToken.getTopChild()); } - if (op.mAction == Operation.ACTION_FADE) { + if (op.mAction == Operation.ACTION_TOGGLE_IME) { + if (DEBUG) Slog.d(TAG, "finishOp fade-in IME " + windowToken.getTopChild()); + fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM, + (type, anim) -> mDisplayContent.getInsetsStateController() + .getImeSourceProvider().reportImeDrawnForOrganizer()); + } else if (op.mAction == Operation.ACTION_FADE) { if (DEBUG) Slog.d(TAG, "finishOp fade-in " + windowToken.getTopChild()); // The previous animation leash will be dropped when preparing fade-in animation, so // simply apply new animation without restoring the transformation. @@ -344,7 +349,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { final WindowToken windowToken = mTargetWindowTokens.keyAt(i); final Operation op = mTargetWindowTokens.valueAt(i); - if (op.mAction == Operation.ACTION_FADE) { + if (op.mAction == Operation.ACTION_FADE || op.mAction == Operation.ACTION_TOGGLE_IME) { fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM); op.mLeash = windowToken.getAnimationLeash(); if (DEBUG) Slog.d(TAG, "Start fade-out " + windowToken.getTopChild()); @@ -374,17 +379,19 @@ class AsyncRotationController extends FadeAnimationController implements Consume WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION); } - /** Hides the window immediately until it is drawn in new rotation. */ - void hideImmediately(WindowToken windowToken) { - if (isTargetToken(windowToken)) return; + /** Hides the IME window immediately until it is drawn in new rotation. */ + void hideImeImmediately() { + if (mDisplayContent.mInputMethodWindow == null) return; + final WindowToken imeWindowToken = mDisplayContent.mInputMethodWindow.mToken; + if (isTargetToken(imeWindowToken)) return; final boolean original = mHideImmediately; mHideImmediately = true; - final Operation op = new Operation(Operation.ACTION_FADE); - mTargetWindowTokens.put(windowToken, op); - fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM); - op.mLeash = windowToken.getAnimationLeash(); + final Operation op = new Operation(Operation.ACTION_TOGGLE_IME); + mTargetWindowTokens.put(imeWindowToken, op); + fadeWindowToken(false /* show */, imeWindowToken, ANIMATION_TYPE_TOKEN_TRANSFORM); + op.mLeash = imeWindowToken.getAnimationLeash(); mHideImmediately = original; - if (DEBUG) Slog.d(TAG, "hideImmediately " + windowToken.getTopChild()); + if (DEBUG) Slog.d(TAG, "hideImeImmediately " + imeWindowToken.getTopChild()); } /** Returns {@code true} if the window will rotate independently. */ @@ -586,11 +593,13 @@ class AsyncRotationController extends FadeAnimationController implements Consume /** The operation to control the rotation appearance associated with window token. */ private static class Operation { @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { ACTION_SEAMLESS, ACTION_FADE }) + @IntDef(value = { ACTION_SEAMLESS, ACTION_FADE, ACTION_TOGGLE_IME }) @interface Action {} static final int ACTION_SEAMLESS = 1; static final int ACTION_FADE = 2; + /** The action to toggle the IME window appearance */ + static final int ACTION_TOGGLE_IME = 3; final @Action int mAction; /** The leash of window token. It can be animation leash or the token itself. */ SurfaceControl mLeash; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8bca1067de8c5..57812c1d604c4 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1896,7 +1896,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp case SOFT_INPUT_STATE_HIDDEN: return false; } - return r.mLastImeShown; + final boolean useIme = r.getWindow( + w -> WindowManager.LayoutParams.mayUseInputMethod(w.mAttrs.flags)) != null; + if (!useIme) { + return false; + } + return r.mLastImeShown || (r.mStartingData != null && r.mStartingData.hasImeSurface()); } /** Returns {@code true} if the top activity is transformed with the new rotation of display. */ @@ -4219,7 +4224,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Hide the window until the rotation is done to avoid intermediate artifacts if the // parent surface of IME container is changed. if (mAsyncRotationController != null) { - mAsyncRotationController.hideImmediately(mInputMethodWindow.mToken); + mAsyncRotationController.hideImeImmediately(); } } } diff --git a/services/core/java/com/android/server/wm/FadeAnimationController.java b/services/core/java/com/android/server/wm/FadeAnimationController.java index 561a07061bb45..7af67e63f469c 100644 --- a/services/core/java/com/android/server/wm/FadeAnimationController.java +++ b/services/core/java/com/android/server/wm/FadeAnimationController.java @@ -57,14 +57,21 @@ public class FadeAnimationController { return AnimationUtils.loadAnimation(mContext, R.anim.fade_out); } + /** Run the fade in/out animation for a window token. */ + public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) { + fadeWindowToken(show, windowToken, animationType, null); + } + /** * Run the fade in/out animation for a window token. * * @param show true for fade-in, otherwise for fade-out. * @param windowToken the window token to run the animation. * @param animationType the animation type defined in SurfaceAnimator. + * @param finishedCallback the callback after the animation finished. */ - public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) { + public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType, + SurfaceAnimator.OnAnimationFinishedCallback finishedCallback) { if (windowToken == null || windowToken.getParent() == null) { return; } @@ -75,9 +82,8 @@ public class FadeAnimationController { if (animationAdapter == null) { return; } - windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter, - show /* hidden */, animationType, null /* finishedCallback */); + show /* hidden */, animationType, finishedCallback); } protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec, diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index b4dffdcba243c..ff2985c984216 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -145,18 +145,44 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { } boolean changed = super.updateClientVisibility(caller); if (changed && caller.isRequestedVisible(mSource.getType())) { - reportImeDrawnForOrganizer(caller); + reportImeDrawnForOrganizerIfNeeded(caller); } changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate(); return changed; } - private void reportImeDrawnForOrganizer(InsetsControlTarget caller) { - if (caller.getWindow() != null && caller.getWindow().getTask() != null) { - if (caller.getWindow().getTask().isOrganized()) { - mWindowContainer.mWmService.mAtmService.mTaskOrganizerController - .reportImeDrawnOnTask(caller.getWindow().getTask()); - } + private void reportImeDrawnForOrganizerIfNeeded(@NonNull InsetsControlTarget caller) { + final WindowState callerWindow = caller.getWindow(); + if (callerWindow == null) { + return; + } + WindowToken imeToken = mWindowContainer.asWindowState() != null + ? mWindowContainer.asWindowState().mToken : null; + if (mDisplayContent.getAsyncRotationController() != null + && mDisplayContent.getAsyncRotationController().isTargetToken(imeToken)) { + // Skip reporting IME drawn state when the control target is in fixed + // rotation, AsyncRotationController will report after the animation finished. + return; + } + reportImeDrawnForOrganizer(caller); + } + + private void reportImeDrawnForOrganizer(@NonNull InsetsControlTarget caller) { + final WindowState callerWindow = caller.getWindow(); + if (callerWindow == null || callerWindow.getTask() == null) { + return; + } + if (callerWindow.getTask().isOrganized()) { + mWindowContainer.mWmService.mAtmService.mTaskOrganizerController + .reportImeDrawnOnTask(caller.getWindow().getTask()); + } + } + + /** Report the IME has drawn on the current IME control target for its task organizer */ + void reportImeDrawnForOrganizer() { + final InsetsControlTarget imeControlTarget = getControlTarget(); + if (imeControlTarget != null) { + reportImeDrawnForOrganizer(imeControlTarget); } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 5626aa7f075f4..cdb4ad645dc33 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -18,6 +18,9 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.TaskInfo.cameraCompatControlStateToString; +import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NONE; +import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL; +import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission; @@ -686,8 +689,19 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { final boolean playShiftUpAnimation = !task.inMultiWindowMode(); final ActivityRecord topActivity = task.topActivityContainsStartingWindow(); if (topActivity != null) { - removalInfo.deferRemoveForIme = topActivity.mDisplayContent - .mayImeShowOnLaunchingActivity(topActivity); + // Set defer remove mode for IME + final DisplayContent dc = topActivity.getDisplayContent(); + final WindowState imeWindow = dc.mInputMethodWindow; + if (topActivity.isVisibleRequested() && imeWindow != null + && dc.mayImeShowOnLaunchingActivity(topActivity) + && dc.isFixedRotationLaunchingApp(topActivity)) { + removalInfo.deferRemoveForImeMode = DEFER_MODE_ROTATION; + } else if (dc.mayImeShowOnLaunchingActivity(topActivity)) { + removalInfo.deferRemoveForImeMode = DEFER_MODE_NORMAL; + } else { + removalInfo.deferRemoveForImeMode = DEFER_MODE_NONE; + } + final WindowState mainWindow = topActivity.findMainWindow(false/* includeStartingApp */); // No app window for this activity, app might be crashed. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt index a3fb73bad25f1..496165ab5b09a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt @@ -18,6 +18,11 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.Presubmit import android.tools.common.Rotation +import android.platform.test.annotations.Postsubmit +import android.tools.common.Timestamp +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.common.flicker.subject.exceptions.ExceptionMessageBuilder +import android.tools.common.flicker.subject.exceptions.InvalidPropertyException import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.FlickerTest @@ -36,7 +41,7 @@ import org.junit.runners.Parameterized /** * Test IME window layer will become visible when switching from the fixed orientation activity * (e.g. Launcher activity). To run this test: `atest - * FlickerTests:OpenImeWindowFromFixedOrientationAppTest` + * FlickerTests:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest` */ @RequiresDevice @RunWith(Parameterized::class) @@ -77,6 +82,49 @@ open class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: Fl flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp) } + @Postsubmit + @Test + fun imeLayerAlphaOneAfterSnapshotStartingWindowRemoval() { + // Check if the snapshot appeared during the trace + var imeSnapshotRemovedTimestamp: Timestamp? = null + + val layerTrace = flicker.reader.readLayersTrace() + val layerTraceEntries = layerTrace?.entries?.toList() ?: emptyList() + + layerTraceEntries.zipWithNext { prev, next -> + val prevSnapshotLayerVisible = + ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(prev.visibleLayers) + val nextSnapshotLayerVisible = + ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(next.visibleLayers) + + if (imeSnapshotRemovedTimestamp == null && + (prevSnapshotLayerVisible && !nextSnapshotLayerVisible)) { + imeSnapshotRemovedTimestamp = next.timestamp + } + } + + // if so, make an assertion + imeSnapshotRemovedTimestamp?.let { timestamp -> + val stateAfterSnapshot = layerTrace?.getEntryAt(timestamp) + ?: error("State not found for $timestamp") + + val imeLayers = ComponentNameMatcher.IME + .filterLayers(stateAfterSnapshot.visibleLayers.toList()) + + require(imeLayers.isNotEmpty()) { "IME layer not found" } + if (imeLayers.any { it.color.a != 1.0f }) { + val errorMsgBuilder = ExceptionMessageBuilder() + .setTimestamp(timestamp) + .forInvalidProperty("IME layer alpha") + .setExpected("is 1.0") + .setActual("not 1.0") + .addExtraDescription("Filter", + ComponentNameMatcher.IME.toLayerIdentifier()) + throw InvalidPropertyException(errorMsgBuilder) + } + } + } + companion object { /** * Creates the test configurations.