diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 80437be1dcd4c..49cd3a6862a5f 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1334,6 +1334,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme) { + // TODO(b/166736352): We should only skip the animation of specific types, not all types. + boolean skipAnim = false; + if ((types & ime()) != 0) { + final InsetsSourceConsumer consumer = mSourceConsumers.get(ITYPE_IME); + final InsetsSourceControl imeControl = consumer != null ? consumer.getControl() : null; + // Skip showing animation once that made by system for some reason. + // (e.g. starting window with IME snapshot) + if (imeControl != null && show) { + skipAnim = imeControl.getAndClearSkipAnimationOnce(); + } + } + applyAnimation(types, show, fromIme, skipAnim); + } + + @VisibleForTesting + public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme, + boolean skipAnim) { if (types == 0) { // nothing to animate. if (DEBUG) Log.d(TAG, "applyAnimation, nothing to animate"); @@ -1342,7 +1359,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks(); final InternalAnimationControlListener listener = new InternalAnimationControlListener( - show, hasAnimationCallbacks, types, mAnimationsDisabled, + show, hasAnimationCallbacks, types, skipAnim || mAnimationsDisabled, mHost.dipToPx(InternalAnimationControlListener.FLOATING_IME_BOTTOM_INSET)); // Show/hide animations always need to be relative to the display frame, in order that shown diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 5ddbd029f1134..c717c2adc32d4 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -41,6 +41,7 @@ public class InsetsSourceControl implements Parcelable { private final @InternalInsetsType int mType; private final @Nullable SurfaceControl mLeash; private final Point mSurfacePosition; + private boolean mSkipAnimationOnce; public InsetsSourceControl(@InternalInsetsType int type, @Nullable SurfaceControl leash, Point surfacePosition) { @@ -57,6 +58,7 @@ public class InsetsSourceControl implements Parcelable { mLeash = null; } mSurfacePosition = new Point(other.mSurfacePosition); + mSkipAnimationOnce = other.getAndClearSkipAnimationOnce(); } public int getType() { @@ -77,6 +79,7 @@ public class InsetsSourceControl implements Parcelable { mType = in.readInt(); mLeash = in.readParcelable(null /* loader */); mSurfacePosition = in.readParcelable(null /* loader */); + mSkipAnimationOnce = in.readBoolean(); } public boolean setSurfacePosition(int left, int top) { @@ -87,10 +90,27 @@ public class InsetsSourceControl implements Parcelable { return true; } + public void setSkipAnimationOnce(boolean skipAnimation) { + mSkipAnimationOnce = skipAnimation; + } + public Point getSurfacePosition() { return mSurfacePosition; } + /** + * Get the state whether the current control needs to skip animation or not. + * + * Note that this is a one-time check that the state is only valid and can be called when + * {@link InsetsController#applyAnimation} to check if the current control can skip animation + * at this time, and then will clear the state value. + */ + public boolean getAndClearSkipAnimationOnce() { + final boolean result = mSkipAnimationOnce; + mSkipAnimationOnce = false; + return result; + } + @Override public int describeContents() { return 0; @@ -101,6 +121,7 @@ public class InsetsSourceControl implements Parcelable { dest.writeInt(mType); dest.writeParcelable(mLeash, 0 /* flags*/); dest.writeParcelable(mSurfacePosition, 0 /* flags*/); + dest.writeBoolean(mSkipAnimationOnce); } public void release(Consumer surfaceReleaseConsumer) { @@ -114,6 +135,7 @@ public class InsetsSourceControl implements Parcelable { pw.print("InsetsSourceControl type="); pw.print(InsetsState.typeToString(mType)); pw.print(" mLeash="); pw.print(mLeash); pw.print(" mSurfacePosition="); pw.print(mSurfacePosition); + pw.print(" mSkipAnimationOnce="); pw.print(mSkipAnimationOnce); pw.println(); } diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java index 9834aade61e86..dc07e44d4d98e 100644 --- a/core/java/android/window/TaskSnapshot.java +++ b/core/java/android/window/TaskSnapshot.java @@ -60,6 +60,7 @@ public class TaskSnapshot implements Parcelable { private final @WindowInsetsController.Appearance int mAppearance; private final boolean mIsTranslucent; + private final boolean mHasImeSurface; // Must be one of the named color spaces, otherwise, always use SRGB color space. private final ColorSpace mColorSpace; @@ -68,7 +69,7 @@ public class TaskSnapshot implements Parcelable { @NonNull ColorSpace colorSpace, int orientation, int rotation, Point taskSize, Rect contentInsets, boolean isLowResolution, boolean isRealSnapshot, int windowingMode, @WindowInsetsController.Appearance int appearance, - boolean isTranslucent) { + boolean isTranslucent, boolean hasImeSurface) { mId = id; mTopActivityComponent = topActivityComponent; mSnapshot = snapshot; @@ -83,6 +84,7 @@ public class TaskSnapshot implements Parcelable { mWindowingMode = windowingMode; mAppearance = appearance; mIsTranslucent = isTranslucent; + mHasImeSurface = hasImeSurface; } private TaskSnapshot(Parcel source) { @@ -102,6 +104,7 @@ public class TaskSnapshot implements Parcelable { mWindowingMode = source.readInt(); mAppearance = source.readInt(); mIsTranslucent = source.readBoolean(); + mHasImeSurface = source.readBoolean(); } /** @@ -200,6 +203,13 @@ public class TaskSnapshot implements Parcelable { return mIsTranslucent; } + /** + * @return Whether or not the snapshot has the IME surface. + */ + public boolean hasImeSurface() { + return mHasImeSurface; + } + /** * @return The windowing mode of the task when this snapshot was taken. */ @@ -237,6 +247,7 @@ public class TaskSnapshot implements Parcelable { dest.writeInt(mWindowingMode); dest.writeInt(mAppearance); dest.writeBoolean(mIsTranslucent); + dest.writeBoolean(mHasImeSurface); } @Override @@ -256,7 +267,8 @@ public class TaskSnapshot implements Parcelable { + " mIsRealSnapshot=" + mIsRealSnapshot + " mWindowingMode=" + mWindowingMode + " mAppearance=" + mAppearance - + " mIsTranslucent=" + mIsTranslucent; + + " mIsTranslucent=" + mIsTranslucent + + " mHasImeSurface=" + mHasImeSurface; } public static final @NonNull Creator CREATOR = new Creator() { @@ -283,6 +295,7 @@ public class TaskSnapshot implements Parcelable { private @WindowInsetsController.Appearance int mAppearance; private boolean mIsTranslucent; + private boolean mHasImeSurface; private int mPixelFormat; public Builder setId(long id) { @@ -290,8 +303,7 @@ public class TaskSnapshot implements Parcelable { return this; } - public Builder setTopActivityComponent( - ComponentName name) { + public Builder setTopActivityComponent(ComponentName name) { mTopActivity = name; return this; } @@ -329,8 +341,7 @@ public class TaskSnapshot implements Parcelable { return this; } - public Builder setIsRealSnapshot( - boolean realSnapshot) { + public Builder setIsRealSnapshot(boolean realSnapshot) { mIsRealSnapshot = realSnapshot; return this; } @@ -340,18 +351,24 @@ public class TaskSnapshot implements Parcelable { return this; } - public Builder setAppearance( - @WindowInsetsController.Appearance int appearance) { + public Builder setAppearance(@WindowInsetsController.Appearance int appearance) { mAppearance = appearance; return this; } - public Builder setIsTranslucent( - boolean isTranslucent) { + public Builder setIsTranslucent(boolean isTranslucent) { mIsTranslucent = isTranslucent; return this; } + /** + * Sets the IME visibility when taking the snapshot of the task. + */ + public Builder setHasImeSurface(boolean hasImeSurface) { + mHasImeSurface = hasImeSurface; + return this; + } + public int getPixelFormat() { return mPixelFormat; } @@ -378,7 +395,8 @@ public class TaskSnapshot implements Parcelable { mIsRealSnapshot, mWindowingMode, mAppearance, - mIsTranslucent); + mIsTranslucent, + mHasImeSurface); } } diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index db838e81eb288..970ab790ba73f 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -128,4 +128,26 @@ public class ImeInsetsSourceConsumerTest { eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */); }); } + + @Test + public void testImeGetAndClearSkipAnimationOnce() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // Request IME visible before control is available. + mImeConsumer.onWindowFocusGained(); + mImeConsumer.applyImeVisibility(true /* setVisible */); + + // set control and verify visibility is applied. + InsetsSourceControl control = Mockito.spy( + new InsetsSourceControl(ITYPE_IME, mLeash, new Point())); + // Simulate IME source control set this flag when the target has starting window. + control.setSkipAnimationOnce(true); + mController.onControlsChanged(new InsetsSourceControl[] { control }); + // Verify IME show animation should be triggered when control becomes available and + // the animation will be skipped by getAndClearSkipAnimationOnce invoked. + verify(control).getAndClearSkipAnimationOnce(); + verify(mController).applyAnimation( + eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, + eq(true) /* skipAnim */); + }); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java index 94af329997342..414a0a778d937 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java @@ -96,7 +96,7 @@ public class TaskSnapshotWindowTest { ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, Surface.ROTATION_0, taskSize, contentInsets, false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, - 0 /* systemUiVisibility */, false /* isTranslucent */); + 0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */); } private static TaskDescription createTaskDescription(int background, int statusBar, diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 17c3b20c9e409..0320a349a97a1 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -30,7 +30,9 @@ import android.annotation.NonNull; import android.os.Trace; import android.util.proto.ProtoOutputStream; import android.view.InsetsSource; +import android.view.InsetsSourceControl; import android.view.WindowInsets; +import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; @@ -53,6 +55,26 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { super(source, stateController, displayContent); } + @Override + InsetsSourceControl getControl(InsetsControlTarget target) { + final InsetsSourceControl control = super.getControl(target); + if (control != null && target != null && target.getWindow() != null) { + final WindowState targetWin = target.getWindow(); + // If the control target changes during the app transition with the task snapshot + // starting window and the IME snapshot is visible, in case not have duplicated IME + // showing animation during transitioning, use a flag to inform IME source control to + // skip showing animation once. + final TaskSnapshot snapshot = targetWin.getRootTask() != null + ? targetWin.mWmService.getTaskSnapshot(targetWin.getRootTask().mTaskId, + 0 /* userId */, false /* isLowResolution */, false /* restoreFromDisk */) + : null; + control.setSkipAnimationOnce(targetWin.mActivityRecord != null + && targetWin.mActivityRecord.hasStartingWindow() + && snapshot != null && snapshot.hasImeSurface()); + } + return control; + } + /** * Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService} * requests to show IME on {@param imeTarget}. diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 28a99825d0b49..ff5b356d16ad3 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -103,8 +103,7 @@ class InsetsSourceProvider { mSource = source; mDisplayContent = displayContent; mStateController = stateController; - mFakeControl = new InsetsSourceControl(source.getType(), null /* leash */, - new Point()); + mFakeControl = new InsetsSourceControl(source.getType(), null /* leash */, new Point()); switch (source.getType()) { case ITYPE_STATUS_BAR: diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index de9fb6aaefe21..ed90cc7594957 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -24,7 +24,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.annotation.Nullable; -import android.window.TaskSnapshot; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.PixelFormat; @@ -43,6 +42,7 @@ import android.view.ThreadedRenderer; import android.view.WindowInsets.Type; import android.view.WindowInsetsController.Appearance; import android.view.WindowManager.LayoutParams; +import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; @@ -344,20 +344,20 @@ class TaskSnapshotController { TaskSnapshot.Builder builder) { Point taskSize = new Point(); final SurfaceControl.ScreenshotHardwareBuffer taskSnapshot = createTaskSnapshot(task, - mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize); + mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder); builder.setTaskSize(taskSize); return taskSnapshot; } @Nullable SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task, - float scaleFraction) { - return createTaskSnapshot(task, scaleFraction, PixelFormat.RGBA_8888, null); + float scaleFraction, TaskSnapshot.Builder builder) { + return createTaskSnapshot(task, scaleFraction, PixelFormat.RGBA_8888, null, builder); } @Nullable SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task, - float scaleFraction, int pixelFormat, Point outTaskSize) { + float scaleFraction, int pixelFormat, Point outTaskSize, TaskSnapshot.Builder builder) { if (task.getSurfaceControl() == null) { if (DEBUG_SCREENSHOT) { Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task); @@ -376,6 +376,7 @@ class TaskSnapshotController { excludeLayers[0] = imeWindow.getSurfaceControl(); } else { excludeLayers = new SurfaceControl[0]; + builder.setHasImeSurface(imeWindow != null && imeWindow.isDrawn()); } final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = SurfaceControl.captureLayersExcluding( @@ -510,7 +511,8 @@ class TaskSnapshotController { hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation, mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight), contentInsets, false /* isLowResolution */, false /* isRealSnapshot */, - task.getWindowingMode(), getAppearance(task), false); + task.getWindowingMode(), getAppearance(task), false /* isTranslucent */, + false /* hasImeSurface */); } /** diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java index cfdb6b3577327..d3bfbab8106ff 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java @@ -197,7 +197,7 @@ class TaskSnapshotLoader { hwBitmap.getColorSpace(), proto.orientation, proto.rotation, taskSize, new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom), loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode, - proto.appearance, proto.isTranslucent); + proto.appearance, proto.isTranslucent, false /* hasImeSurface */); } catch (IOException e) { Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId); return null; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e872c1010f78f..a8f4bae1d519a 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2927,7 +2927,7 @@ class WindowState extends WindowContainer implements WindowManagerP /** * Returns {@code true} if this window has been shown on screen at some time in the past. * - * @deprecated Use {@link #isDrawnLw} or any of the other drawn/visibility methods. + * @deprecated Use {@link #isDrawn} or any of the other drawn/visibility methods. */ @Deprecated boolean hasDrawn() { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java index 4d37eb25744da..81712c6897c4b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -31,6 +31,8 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.WindowConfiguration; @@ -196,8 +198,37 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { mDisplayContent.mInputMethodWindow.setSurfaceControl(null); // Verify no NPE happens when calling createTaskSnapshot. try { + final TaskSnapshot.Builder builder = new TaskSnapshot.Builder(); mWm.mTaskSnapshotController.createTaskSnapshot(mAppWindow.mActivityRecord.getTask(), - 1f /* scaleFraction */, PixelFormat.UNKNOWN, null /* outTaskSize */); + 1f /* scaleFraction */, PixelFormat.UNKNOWN, null /* outTaskSize */, builder); + } catch (NullPointerException e) { + fail("There should be no exception when calling createTaskSnapshot"); + } + } + + @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD}) + @Test + public void testCreateTaskSnapshotWithIncludingIme() { + Task task = mAppWindow.mActivityRecord.getTask(); + spyOn(task); + spyOn(mDisplayContent); + spyOn(mDisplayContent.mInputMethodWindow); + when(task.getDisplayContent().isImeAttachedToApp()).thenReturn(true); + // Intentionally set the IME window is in drawn state. + doReturn(true).when(mDisplayContent.mInputMethodWindow).isDrawn(); + // Verify no NPE happens when calling createTaskSnapshot. + try { + final TaskSnapshot.Builder builder = new TaskSnapshot.Builder(); + spyOn(builder); + mWm.mTaskSnapshotController.createTaskSnapshot( + mAppWindow.mActivityRecord.getTask(), 1f /* scaleFraction */, + PixelFormat.UNKNOWN, null /* outTaskSize */, builder); + // Verify the builder should includes IME surface. + verify(builder).setHasImeSurface(eq(true)); + builder.setColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)); + builder.setTaskSize(new Point(100, 100)); + final TaskSnapshot snapshot = builder.build(); + assertTrue(snapshot.hasImeSurface()); } catch (NullPointerException e) { fail("There should be no exception when calling createTaskSnapshot"); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java index d9a794a24bc56..edf70567b2325 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java @@ -207,7 +207,8 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase { // is always false. Low-res snapshots are only created when loading from // disk. false /* isLowResolution */, - mIsRealSnapshot, mWindowingMode, mSystemUiVisibility, mIsTranslucent); + mIsRealSnapshot, mWindowingMode, mSystemUiVisibility, mIsTranslucent, + false /* hasImeSurface */); } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java index aa63869037463..eb3a5e073440a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -102,7 +102,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, Surface.ROTATION_0, taskSize, contentInsets, false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, - 0 /* systemUiVisibility */, false /* isTranslucent */); + 0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */); } private static TaskDescription createTaskDescription(int background, int statusBar,