Fix the transition from Split to PiP
- Do not bring split to top on entering PiP When swiping up to home (or other cases) triggers one of the split-screen children enters PiP, do not try to bring the other task to top since the pair should be considered invisible. - When entering PiP from split screen or non-auto PiP case, use the alpha information in PictureInPictureSurfaceTransaction to hide the task which would later be turned back on to avoid flicker. Bug: 190855091 Bug: 205894095 Video: http://recall/-/aaaaaabFQoRHlzixHdtY/42RWtayanp2qG0mHSf4Q5 Test: manual, enter PiP from split-screen, see Video Change-Id: I1559748cd583c20d79ee458822d66e7801733d3a
This commit is contained in:
@@ -19,6 +19,7 @@ package android.window;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@@ -34,9 +35,10 @@ import java.util.Objects;
|
||||
* @hide
|
||||
*/
|
||||
public final class PictureInPictureSurfaceTransaction implements Parcelable {
|
||||
private static final float NOT_SET = -1f;
|
||||
|
||||
public final float mPositionX;
|
||||
public final float mPositionY;
|
||||
public final float mAlpha;
|
||||
public final PointF mPosition;
|
||||
|
||||
public final float[] mFloat9;
|
||||
|
||||
@@ -45,33 +47,37 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
|
||||
|
||||
public final float mCornerRadius;
|
||||
|
||||
private final Rect mWindowCrop = new Rect();
|
||||
private final Rect mWindowCrop;
|
||||
|
||||
public PictureInPictureSurfaceTransaction(Parcel in) {
|
||||
mPositionX = in.readFloat();
|
||||
mPositionY = in.readFloat();
|
||||
private PictureInPictureSurfaceTransaction(Parcel in) {
|
||||
mAlpha = in.readFloat();
|
||||
mPosition = in.readTypedObject(PointF.CREATOR);
|
||||
mFloat9 = new float[9];
|
||||
in.readFloatArray(mFloat9);
|
||||
mRotation = in.readFloat();
|
||||
mCornerRadius = in.readFloat();
|
||||
mWindowCrop.set(Objects.requireNonNull(in.readTypedObject(Rect.CREATOR)));
|
||||
mWindowCrop = in.readTypedObject(Rect.CREATOR);
|
||||
}
|
||||
|
||||
public PictureInPictureSurfaceTransaction(float positionX, float positionY,
|
||||
float[] float9, float rotation, float cornerRadius,
|
||||
private PictureInPictureSurfaceTransaction(float alpha, @Nullable PointF position,
|
||||
@Nullable float[] float9, float rotation, float cornerRadius,
|
||||
@Nullable Rect windowCrop) {
|
||||
mPositionX = positionX;
|
||||
mPositionY = positionY;
|
||||
mFloat9 = Arrays.copyOf(float9, 9);
|
||||
mRotation = rotation;
|
||||
mCornerRadius = cornerRadius;
|
||||
if (windowCrop != null) {
|
||||
mWindowCrop.set(windowCrop);
|
||||
mAlpha = alpha;
|
||||
mPosition = position;
|
||||
if (float9 == null) {
|
||||
mFloat9 = new float[9];
|
||||
Matrix.IDENTITY_MATRIX.getValues(mFloat9);
|
||||
mRotation = 0;
|
||||
} else {
|
||||
mFloat9 = Arrays.copyOf(float9, 9);
|
||||
mRotation = rotation;
|
||||
}
|
||||
mCornerRadius = cornerRadius;
|
||||
mWindowCrop = (windowCrop == null) ? null : new Rect(windowCrop);
|
||||
}
|
||||
|
||||
public PictureInPictureSurfaceTransaction(PictureInPictureSurfaceTransaction other) {
|
||||
this(other.mPositionX, other.mPositionY,
|
||||
this(other.mAlpha, other.mPosition,
|
||||
other.mFloat9, other.mRotation, other.mCornerRadius, other.mWindowCrop);
|
||||
}
|
||||
|
||||
@@ -82,13 +88,18 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
|
||||
return matrix;
|
||||
}
|
||||
|
||||
/** @return {@code true} if this transaction contains setting corner radius. */
|
||||
public boolean hasCornerRadiusSet() {
|
||||
return mCornerRadius > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof PictureInPictureSurfaceTransaction)) return false;
|
||||
PictureInPictureSurfaceTransaction that = (PictureInPictureSurfaceTransaction) o;
|
||||
return Objects.equals(mPositionX, that.mPositionX)
|
||||
&& Objects.equals(mPositionY, that.mPositionY)
|
||||
return Objects.equals(mAlpha, that.mAlpha)
|
||||
&& Objects.equals(mPosition, that.mPosition)
|
||||
&& Arrays.equals(mFloat9, that.mFloat9)
|
||||
&& Objects.equals(mRotation, that.mRotation)
|
||||
&& Objects.equals(mCornerRadius, that.mCornerRadius)
|
||||
@@ -97,7 +108,7 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mPositionX, mPositionY, Arrays.hashCode(mFloat9),
|
||||
return Objects.hash(mAlpha, mPosition, Arrays.hashCode(mFloat9),
|
||||
mRotation, mCornerRadius, mWindowCrop);
|
||||
}
|
||||
|
||||
@@ -108,8 +119,8 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeFloat(mPositionX);
|
||||
out.writeFloat(mPositionY);
|
||||
out.writeFloat(mAlpha);
|
||||
out.writeTypedObject(mPosition, 0 /* flags */);
|
||||
out.writeFloatArray(mFloat9);
|
||||
out.writeFloat(mRotation);
|
||||
out.writeFloat(mCornerRadius);
|
||||
@@ -120,8 +131,8 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
|
||||
public String toString() {
|
||||
final Matrix matrix = getMatrix();
|
||||
return "PictureInPictureSurfaceTransaction("
|
||||
+ " posX=" + mPositionX
|
||||
+ " posY=" + mPositionY
|
||||
+ " alpha=" + mAlpha
|
||||
+ " position=" + mPosition
|
||||
+ " matrix=" + matrix.toShortString()
|
||||
+ " rotation=" + mRotation
|
||||
+ " cornerRadius=" + mCornerRadius
|
||||
@@ -134,11 +145,20 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
|
||||
@NonNull SurfaceControl surfaceControl,
|
||||
@NonNull SurfaceControl.Transaction tx) {
|
||||
final Matrix matrix = surfaceTransaction.getMatrix();
|
||||
tx.setMatrix(surfaceControl, matrix, new float[9])
|
||||
.setPosition(surfaceControl,
|
||||
surfaceTransaction.mPositionX, surfaceTransaction.mPositionY)
|
||||
.setWindowCrop(surfaceControl, surfaceTransaction.mWindowCrop)
|
||||
.setCornerRadius(surfaceControl, surfaceTransaction.mCornerRadius);
|
||||
tx.setMatrix(surfaceControl, matrix, new float[9]);
|
||||
if (surfaceTransaction.mPosition != null) {
|
||||
tx.setPosition(surfaceControl,
|
||||
surfaceTransaction.mPosition.x, surfaceTransaction.mPosition.y);
|
||||
}
|
||||
if (surfaceTransaction.mWindowCrop != null) {
|
||||
tx.setWindowCrop(surfaceControl, surfaceTransaction.mWindowCrop);
|
||||
}
|
||||
if (surfaceTransaction.hasCornerRadiusSet()) {
|
||||
tx.setCornerRadius(surfaceControl, surfaceTransaction.mCornerRadius);
|
||||
}
|
||||
if (surfaceTransaction.mAlpha != NOT_SET) {
|
||||
tx.setAlpha(surfaceControl, surfaceTransaction.mAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
public static final @android.annotation.NonNull Creator<PictureInPictureSurfaceTransaction>
|
||||
@@ -151,4 +171,44 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
|
||||
return new PictureInPictureSurfaceTransaction[size];
|
||||
}
|
||||
};
|
||||
|
||||
public static class Builder {
|
||||
private float mAlpha = NOT_SET;
|
||||
private PointF mPosition;
|
||||
private float[] mFloat9;
|
||||
private float mRotation;
|
||||
private float mCornerRadius = NOT_SET;
|
||||
private Rect mWindowCrop;
|
||||
|
||||
public Builder setAlpha(float alpha) {
|
||||
mAlpha = alpha;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPosition(float x, float y) {
|
||||
mPosition = new PointF(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTransform(@NonNull float[] float9, float rotation) {
|
||||
mFloat9 = Arrays.copyOf(float9, 9);
|
||||
mRotation = rotation;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCornerRadius(float cornerRadius) {
|
||||
mCornerRadius = cornerRadius;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setWindowCrop(@NonNull Rect windowCrop) {
|
||||
mWindowCrop = new Rect(windowCrop);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PictureInPictureSurfaceTransaction build() {
|
||||
return new PictureInPictureSurfaceTransaction(mAlpha, mPosition,
|
||||
mFloat9, mRotation, mCornerRadius, mWindowCrop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
|
||||
static final int EXIT_REASON_ROOT_TASK_VANISHED = 6;
|
||||
static final int EXIT_REASON_SCREEN_LOCKED = 7;
|
||||
static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
|
||||
static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
|
||||
@IntDef(value = {
|
||||
EXIT_REASON_UNKNOWN,
|
||||
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
|
||||
@@ -112,6 +113,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
|
||||
EXIT_REASON_ROOT_TASK_VANISHED,
|
||||
EXIT_REASON_SCREEN_LOCKED,
|
||||
EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
|
||||
EXIT_REASON_CHILD_TASK_ENTER_PIP,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface ExitReason{}
|
||||
@@ -406,6 +408,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
|
||||
return "APP_FINISHED";
|
||||
case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
|
||||
return "APP_DOES_NOT_SUPPORT_MULTIWINDOW";
|
||||
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
|
||||
return "CHILD_TASK_ENTER_PIP";
|
||||
default:
|
||||
return "unknown reason, reason int = " + exitReason;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
|
||||
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
|
||||
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
|
||||
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
|
||||
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
|
||||
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
|
||||
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
|
||||
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
|
||||
@@ -629,8 +630,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|
||||
});
|
||||
mShouldUpdateRecents = false;
|
||||
|
||||
mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
|
||||
mMainStage.deactivate(wct, childrenToTop == mMainStage);
|
||||
// When the exit split-screen is caused by one of the task enters auto pip,
|
||||
// we want the tasks to be put to bottom instead of top, otherwise it will end up
|
||||
// a fullscreen plus a pinned task instead of pinned only at the end of the transition.
|
||||
final boolean fromEnteringPip = exitReason == EXIT_REASON_CHILD_TASK_ENTER_PIP;
|
||||
mSideStage.removeAllTasks(wct, !fromEnteringPip && childrenToTop == mSideStage);
|
||||
mMainStage.deactivate(wct, !fromEnteringPip && childrenToTop == mMainStage);
|
||||
mTaskOrganizer.applyTransaction(wct);
|
||||
mSyncQueue.runInSync(t -> t
|
||||
.setWindowCrop(mMainStage.mRootLeash, null)
|
||||
@@ -660,6 +665,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|
||||
case EXIT_REASON_DRAG_DIVIDER:
|
||||
// Either of the split apps have finished
|
||||
case EXIT_REASON_APP_FINISHED:
|
||||
// One of the children enters PiP
|
||||
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -749,6 +756,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|
||||
}
|
||||
}
|
||||
|
||||
private void onStageChildTaskEnterPip(StageListenerImpl stageListener, int taskId) {
|
||||
exitSplitScreen(stageListener == mMainStageListener ? mMainStage : mSideStage,
|
||||
EXIT_REASON_CHILD_TASK_ENTER_PIP);
|
||||
}
|
||||
|
||||
private void updateRecentTasksSplitPair() {
|
||||
if (!mShouldUpdateRecents) {
|
||||
return;
|
||||
@@ -1436,6 +1448,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|
||||
StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildTaskEnterPip(int taskId) {
|
||||
StageCoordinator.this.onStageChildTaskEnterPip(this, taskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRootTaskVanished() {
|
||||
reset();
|
||||
|
||||
@@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
|
||||
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
|
||||
@@ -73,6 +74,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
|
||||
void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
|
||||
|
||||
void onChildTaskEnterPip(int taskId);
|
||||
|
||||
void onRootTaskVanished();
|
||||
|
||||
void onNoLongerSupportMultiWindow();
|
||||
@@ -256,6 +259,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
mChildrenTaskInfo.remove(taskId);
|
||||
mChildrenLeashes.remove(taskId);
|
||||
mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
|
||||
if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
|
||||
mCallbacks.onChildTaskEnterPip(taskId);
|
||||
}
|
||||
if (ENABLE_SHELL_TRANSITIONS) {
|
||||
// Status is managed/synchronized by the transition lifecycle.
|
||||
return;
|
||||
|
||||
@@ -53,8 +53,8 @@ public class PipSurfaceTransactionHelper {
|
||||
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
|
||||
.setPosition(leash, positionX, positionY)
|
||||
.setCornerRadius(leash, cornerRadius);
|
||||
return new PictureInPictureSurfaceTransaction(
|
||||
positionX, positionY, mTmpFloat9, 0 /* rotation */, cornerRadius, sourceBounds);
|
||||
return newPipSurfaceTransaction(positionX, positionY,
|
||||
mTmpFloat9, 0 /* rotation */, cornerRadius, sourceBounds);
|
||||
}
|
||||
|
||||
public PictureInPictureSurfaceTransaction scale(
|
||||
@@ -70,8 +70,8 @@ public class PipSurfaceTransactionHelper {
|
||||
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
|
||||
.setPosition(leash, positionX, positionY)
|
||||
.setCornerRadius(leash, cornerRadius);
|
||||
return new PictureInPictureSurfaceTransaction(
|
||||
positionX, positionY, mTmpFloat9, degree, cornerRadius, sourceBounds);
|
||||
return newPipSurfaceTransaction(positionX, positionY,
|
||||
mTmpFloat9, degree, cornerRadius, sourceBounds);
|
||||
}
|
||||
|
||||
public PictureInPictureSurfaceTransaction scaleAndCrop(
|
||||
@@ -93,8 +93,8 @@ public class PipSurfaceTransactionHelper {
|
||||
.setWindowCrop(leash, mTmpDestinationRect)
|
||||
.setPosition(leash, left, top)
|
||||
.setCornerRadius(leash, cornerRadius);
|
||||
return new PictureInPictureSurfaceTransaction(
|
||||
left, top, mTmpFloat9, 0 /* rotation */, cornerRadius, mTmpDestinationRect);
|
||||
return newPipSurfaceTransaction(left, top,
|
||||
mTmpFloat9, 0 /* rotation */, cornerRadius, mTmpDestinationRect);
|
||||
}
|
||||
|
||||
public PictureInPictureSurfaceTransaction scaleAndRotate(
|
||||
@@ -125,8 +125,7 @@ public class PipSurfaceTransactionHelper {
|
||||
.setWindowCrop(leash, mTmpDestinationRect)
|
||||
.setPosition(leash, adjustedPositionX, adjustedPositionY)
|
||||
.setCornerRadius(leash, cornerRadius);
|
||||
return new PictureInPictureSurfaceTransaction(
|
||||
adjustedPositionX, adjustedPositionY,
|
||||
return newPipSurfaceTransaction(adjustedPositionX, adjustedPositionY,
|
||||
mTmpFloat9, degree, cornerRadius, mTmpDestinationRect);
|
||||
}
|
||||
|
||||
@@ -137,6 +136,17 @@ public class PipSurfaceTransactionHelper {
|
||||
return mCornerRadius * scale;
|
||||
}
|
||||
|
||||
private static PictureInPictureSurfaceTransaction newPipSurfaceTransaction(
|
||||
float posX, float posY, float[] float9, float rotation, float cornerRadius,
|
||||
Rect windowCrop) {
|
||||
return new PictureInPictureSurfaceTransaction.Builder()
|
||||
.setPosition(posX, posY)
|
||||
.setTransform(float9, rotation)
|
||||
.setCornerRadius(cornerRadius)
|
||||
.setWindowCrop(windowCrop)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** @return {@link SurfaceControl.Transaction} instance with vsync-id */
|
||||
public static SurfaceControl.Transaction newSurfaceControlTransaction() {
|
||||
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
|
||||
|
||||
@@ -255,25 +255,27 @@ class PinnedTaskController {
|
||||
mDestRotatedBounds = null;
|
||||
mPipTransaction = null;
|
||||
final Rect areaBounds = taskArea.getBounds();
|
||||
if (pipTx != null) {
|
||||
if (pipTx != null && pipTx.mPosition != null) {
|
||||
// The transaction from recents animation is in old rotation. So the position needs to
|
||||
// be rotated.
|
||||
float dx = pipTx.mPositionX;
|
||||
float dy = pipTx.mPositionY;
|
||||
float dx = pipTx.mPosition.x;
|
||||
float dy = pipTx.mPosition.y;
|
||||
final Matrix matrix = pipTx.getMatrix();
|
||||
if (pipTx.mRotation == 90) {
|
||||
dx = pipTx.mPositionY;
|
||||
dy = areaBounds.right - pipTx.mPositionX;
|
||||
dx = pipTx.mPosition.y;
|
||||
dy = areaBounds.right - pipTx.mPosition.x;
|
||||
matrix.postRotate(-90);
|
||||
} else if (pipTx.mRotation == -90) {
|
||||
dx = areaBounds.bottom - pipTx.mPositionY;
|
||||
dy = pipTx.mPositionX;
|
||||
dx = areaBounds.bottom - pipTx.mPosition.y;
|
||||
dy = pipTx.mPosition.x;
|
||||
matrix.postRotate(90);
|
||||
}
|
||||
matrix.postTranslate(dx, dy);
|
||||
final SurfaceControl leash = pinnedTask.getSurfaceControl();
|
||||
t.setMatrix(leash, matrix, new float[9])
|
||||
.setCornerRadius(leash, pipTx.mCornerRadius);
|
||||
t.setMatrix(leash, matrix, new float[9]);
|
||||
if (pipTx.hasCornerRadiusSet()) {
|
||||
t.setCornerRadius(leash, pipTx.mCornerRadius);
|
||||
}
|
||||
Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user