Merge "Better IME transition while switching app with recents (3/N)"

This commit is contained in:
TreeHugger Robot
2020-12-28 18:34:32 +00:00
committed by Android (Google) Code Review
13 changed files with 160 additions and 26 deletions

View File

@@ -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

View File

@@ -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<SurfaceControl> 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();
}

View File

@@ -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<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() {
@@ -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);
}
}

View File

@@ -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 */);
});
}
}

View File

@@ -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,

View File

@@ -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}.

View File

@@ -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:

View File

@@ -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 */);
}
/**

View File

@@ -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;

View File

@@ -2927,7 +2927,7 @@ class WindowState extends WindowContainer<WindowState> 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() {

View File

@@ -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");
}

View File

@@ -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 */);
}
}
}

View File

@@ -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,