From 8b139a417930751434ca1db4cd6673e08a55538a Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Mon, 10 Apr 2023 19:05:27 +0000 Subject: [PATCH] Fix the IME flicker when the launching activity in fixed-rotation Previously, when synchronizing IME visibility in between removing tasksnapshot with IME starting window and showing the real IME during the activity launching, we schedule a runnable for removing the starting window with a timeout (600ms) if IME will be shown when AR#onFirstWindowDrawn comes, and then remove it immediately once the IME has actually drawn before that timeout. But this approch doesn't accurate when the activity was in fixed rotation. Since the IME client visiblity change from InsetsController does not aware the IME orientation was still in changing (portrait -> landcape), in this case, starting window will be removed in earlier stage and seeing IME fade-out / fade-in animation during AsyncRotationController controlling IME surface in fixed rotation. To more accurate deciding whether to defer the starting window removal during the rotation, in this CL 1) Modified StartingWindowRemovalInfo#deferRemoveForIme with deferRemoveForImeMode to diffenciate scenarios for giving adaquate waiting IME drawn timeout: - DEFER_MODE_NONE: no need wait for IME drawn. - DEFER_MODE_NORMAL: wait IME drawn at most 600ms - DEFER_MODE_ROTATION: wait IME rotation and drawn at most 3 secs. 2) Fix StartingWindowRecordManager#removeStartingWindow removes the record from mStartingWindowRecords too early before calling onImeDrawnOnTask leads to the the IME snapshot starting window always be defered to remove after the timeout. 3) Modified ImeInsetsSourceProvider and AsyncRotationController to skip reporting IME drawn state to WMShell when the system was in fixed-rotation, and report it once AsyncRotationController receiving onAnimationFinished callback from IME token's surfaceAnimator. 4) Added a flicker test to verify this CL works as expected for this use case. Fix: 268627602 Test: atest FlickerTests:\ ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest Test: atest ActivityRecordTests Change-Id: Ie476e89a57f2f64d4d66e722fedeeb1719d9de55 --- .../window/StartingWindowRemovalInfo.java | 29 +++++++++-- .../SplashscreenWindowCreator.java | 7 +-- .../StartingSurfaceDrawer.java | 51 +++++++++++++++---- .../WindowlessSplashWindowCreator.java | 3 +- .../StartingSurfaceDrawerTests.java | 8 +-- .../com/android/server/wm/ActivityRecord.java | 10 ++-- .../server/wm/AsyncRotationController.java | 31 +++++++---- .../com/android/server/wm/DisplayContent.java | 9 +++- .../server/wm/FadeAnimationController.java | 12 +++-- .../server/wm/ImeInsetsSourceProvider.java | 40 ++++++++++++--- .../server/wm/TaskOrganizerController.java | 18 ++++++- ...henLaunchingAppFromFixedOrientationTest.kt | 50 +++++++++++++++++- 12 files changed, 216 insertions(+), 52 deletions(-) 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 78c066bdc212f..526998753cb33 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; @@ -5607,11 +5612,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.