Loading core/java/android/window/TransitionInfo.java +23 −3 Original line number Diff line number Diff line Loading @@ -364,8 +364,13 @@ public final class TransitionInfo implements Parcelable { private final Point mEndRelOffset = new Point(); private ActivityManager.RunningTaskInfo mTaskInfo = null; private boolean mAllowEnterPip; private int mStartRotation = ROTATION_UNDEFINED; private int mEndRotation = ROTATION_UNDEFINED; private @Surface.Rotation int mStartRotation = ROTATION_UNDEFINED; private @Surface.Rotation int mEndRotation = ROTATION_UNDEFINED; /** * The end rotation of the top activity after fixed rotation is finished. If the top * activity is not in fixed rotation, it will be {@link ROTATION_UNDEFINED}. */ private @Surface.Rotation int mEndFixedRotation = ROTATION_UNDEFINED; private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED; private @ColorInt int mBackgroundColor; Loading @@ -388,6 +393,7 @@ public final class TransitionInfo implements Parcelable { mAllowEnterPip = in.readBoolean(); mStartRotation = in.readInt(); mEndRotation = in.readInt(); mEndFixedRotation = in.readInt(); mRotationAnimation = in.readInt(); mBackgroundColor = in.readInt(); } Loading Loading @@ -441,6 +447,11 @@ public final class TransitionInfo implements Parcelable { mEndRotation = end; } /** Sets end rotation that top activity will be launched to after fixed rotation. */ public void setEndFixedRotation(@Surface.Rotation int endFixedRotation) { mEndFixedRotation = endFixedRotation; } /** * Sets the app-requested animation type for rotation. Will be one of the * ROTATION_ANIMATION_ values in {@link android.view.WindowManager.LayoutParams}; Loading Loading @@ -521,14 +532,21 @@ public final class TransitionInfo implements Parcelable { return mAllowEnterPip; } @Surface.Rotation public int getStartRotation() { return mStartRotation; } @Surface.Rotation public int getEndRotation() { return mEndRotation; } @Surface.Rotation public int getEndFixedRotation() { return mEndFixedRotation; } /** @return the rotation animation. */ public int getRotationAnimation() { return mRotationAnimation; Loading @@ -555,6 +573,7 @@ public final class TransitionInfo implements Parcelable { dest.writeBoolean(mAllowEnterPip); dest.writeInt(mStartRotation); dest.writeInt(mEndRotation); dest.writeInt(mEndFixedRotation); dest.writeInt(mRotationAnimation); dest.writeInt(mBackgroundColor); } Loading Loading @@ -584,7 +603,8 @@ public final class TransitionInfo implements Parcelable { return "{" + mContainer + "(" + mParent + ") leash=" + mLeash + " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb=" + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r=" + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + "}"; + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + " endFixedRotation=" + mEndFixedRotation + "}"; } } Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +21 −9 Original line number Diff line number Diff line Loading @@ -579,6 +579,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Log.d(TAG, "Alpha animation is expired. Use bounds animation."); mOneShotAnimationType = ANIM_TYPE_BOUNDS; } if (Transitions.ENABLE_SHELL_TRANSITIONS) { // For Shell transition, we will animate the window in PipTransition#startAnimation // instead of #onTaskAppeared. return; } if (mWaitForFixedRotation) { onTaskAppearedWithFixedRotation(); return; Loading @@ -588,15 +595,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Objects.requireNonNull(destinationBounds, "Missing destination bounds"); final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); if (Transitions.ENABLE_SHELL_TRANSITIONS) { if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { mPipMenuController.attach(mLeash); } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { mOneShotAnimationType = ANIM_TYPE_BOUNDS; } return; } if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { mPipMenuController.attach(mLeash); final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( Loading Loading @@ -829,6 +827,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mNextRotation = newRotation; mWaitForFixedRotation = true; if (Transitions.ENABLE_SHELL_TRANSITIONS) { // The fixed rotation will also be included in the transition info. However, if it is // not a PIP transition (such as open another app to different orientation), // PIP transition handler may not be aware of the fixed rotation start. // Notify the PIP transition handler so that it can fade out the PIP window early for // fixed transition of other windows. mPipTransitionController.onFixedRotationStarted(); return; } if (mPipTransitionState.isInPip()) { // Fade out the existing PiP to avoid jump cut during seamless rotation. fadeExistingPip(false /* show */); Loading @@ -840,6 +848,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (!mWaitForFixedRotation) { return; } if (Transitions.ENABLE_SHELL_TRANSITIONS) { clearWaitForFixedRotation(); return; } if (mPipTransitionState.getTransitionState() == PipTransitionState.TASK_APPEARED) { if (mPipTransitionState.getInSwipePipToHomeTransition()) { onEndOfSwipePipToHomeTransition(); Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +150 −25 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell.pip; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.util.RotationUtils.deltaRotation; Loading @@ -32,9 +33,11 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT; import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; Loading @@ -47,6 +50,7 @@ import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.util.Log; import android.view.Surface; import android.view.SurfaceControl; import android.window.TransitionInfo; Loading Loading @@ -86,6 +90,16 @@ public class PipTransition extends PipTransitionController { /** The Task window that is currently in PIP windowing mode. */ @Nullable private WindowContainerToken mCurrentPipTaskToken; /** Whether display is in fixed rotation. */ private boolean mInFixedRotation; /** * The rotation that the display will apply after expanding PiP to fullscreen. This is only * meaningful if {@link #mInFixedRotation} is true. */ @Surface.Rotation private int mFixedRotation; /** Whether the PIP window has fade out for fixed rotation. */ private boolean mHasFadeOut; public PipTransition(Context context, PipBoundsState pipBoundsState, Loading Loading @@ -136,35 +150,41 @@ public class PipTransition extends PipTransitionController { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { final TransitionInfo.Change currentPipChange = findCurrentPipChange(info); final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); mInFixedRotation = fixedRotationChange != null; mFixedRotation = mInFixedRotation ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; // Exiting PIP. final int type = info.getType(); if (transition.equals(mExitTransition)) { mExitDestinationBounds.setEmpty(); mExitTransition = null; mHasFadeOut = false; if (mFinishCallback != null) { mFinishCallback.onTransitionFinished(null, null); mFinishCallback = null; throw new RuntimeException("Previous callback not called, aborting exit PIP."); } final TransitionInfo.Change exitPipChange = findCurrentPipChange(info); if (exitPipChange == null) { if (currentPipChange == null) { throw new RuntimeException("Cannot find the pip window for exit-pip transition."); } switch (type) { case TRANSIT_EXIT_PIP: startExitAnimation(info, startTransaction, finishTransaction, finishCallback, exitPipChange); currentPipChange); break; case TRANSIT_EXIT_PIP_TO_SPLIT: startExitToSplitAnimation(info, startTransaction, finishTransaction, finishCallback, exitPipChange); finishCallback, currentPipChange); break; case TRANSIT_REMOVE_PIP: removePipImmediately(info, startTransaction, finishTransaction, finishCallback, exitPipChange); currentPipChange); break; default: throw new IllegalStateException("mExitTransition with unexpected transit type=" Loading @@ -177,7 +197,6 @@ public class PipTransition extends PipTransitionController { // The previous PIP Task is no longer in PIP, but this is not an exit transition (This can // happen when a new activity requests enter PIP). In this case, we just show this Task in // its end state, and play other animation as normal. final TransitionInfo.Change currentPipChange = findCurrentPipChange(info); if (currentPipChange != null && currentPipChange.getTaskInfo().getWindowingMode() != WINDOWING_MODE_PINNED) { resetPrevPip(currentPipChange, startTransaction); Loading @@ -193,6 +212,12 @@ public class PipTransition extends PipTransitionController { if (currentPipChange != null) { updatePipForUnhandledTransition(currentPipChange, startTransaction, finishTransaction); } // Fade in the fadeout PIP when the fixed rotation is finished. if (mPipTransitionState.isInPip() && !mInFixedRotation && mHasFadeOut) { fadeExistingPip(true /* show */); } return false; } Loading Loading @@ -242,9 +267,8 @@ public class PipTransition extends PipTransitionController { public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, @Nullable SurfaceControl.Transaction tx) { if (isInPipDirection(direction)) { mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP); mPipTransitionState.setTransitionState(ENTERED_PIP); } // If there is an expected exit transition, then the exit will be "merged" into this // transition so don't fire the finish-callback in that case. Loading @@ -268,6 +292,16 @@ public class PipTransition extends PipTransitionController { mFinishCallback = null; } @Override public void onFixedRotationStarted() { // The transition with this fixed rotation may be handled by other handler before reaching // PipTransition, so we cannot do this in #startAnimation. if (mPipTransitionState.getTransitionState() == ENTERED_PIP && !mHasFadeOut) { // Fade out the existing PiP to avoid jump cut during seamless rotation. fadeExistingPip(false /* show */); } } @Nullable private TransitionInfo.Change findCurrentPipChange(@NonNull TransitionInfo info) { if (mCurrentPipTaskToken == null) { Loading @@ -282,6 +316,17 @@ public class PipTransition extends PipTransitionController { return null; } @Nullable private TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change.getEndFixedRotation() != ROTATION_UNDEFINED) { return change; } } return null; } private void startExitAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, Loading Loading @@ -453,6 +498,7 @@ public class PipTransition extends PipTransitionController { } // Keep track of the PIP task. mCurrentPipTaskToken = enterPip.getContainer(); mHasFadeOut = false; if (mFinishCallback != null) { mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */); Loading @@ -465,12 +511,25 @@ public class PipTransition extends PipTransitionController { startTransaction.show(wallpaper.getLeash()); startTransaction.setAlpha(wallpaper.getLeash(), 1.f); } // Make sure other open changes are visible as entering PIP. Some may be hidden in // Transitions#setupStartState because the transition type is OPEN (such as auto-enter). for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change == enterPip || change == wallpaper) { continue; } if (isOpeningType(change.getMode())) { final SurfaceControl leash = change.getLeash(); startTransaction.show(leash).setAlpha(leash, 1.f); } } mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); mFinishCallback = finishCallback; final int endRotation = mInFixedRotation ? mFixedRotation : enterPip.getEndRotation(); return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(), startTransaction, finishTransaction, enterPip.getStartRotation(), enterPip.getEndRotation()); endRotation); } private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash, Loading @@ -481,25 +540,36 @@ public class PipTransition extends PipTransitionController { taskInfo.topActivityInfo); final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds(); int rotationDelta = deltaRotation(startRotation, endRotation); Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( taskInfo.pictureInPictureParams, currentBounds); if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { // Need to get the bounds of new rotation in old rotation for fixed rotation, sourceHintRect = computeRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo, TRANSITION_DIRECTION_TO_PIP, destinationBounds, sourceHintRect); } PipAnimationController.PipTransitionAnimator animator; // Set corner radius for entering pip. mSurfaceTransactionHelper .crop(finishTransaction, leash, destinationBounds) .round(finishTransaction, leash, true /* applyCornerRadius */); mPipMenuController.attach(leash); if (taskInfo.pictureInPictureParams != null && taskInfo.pictureInPictureParams.isAutoEnterEnabled() && mPipTransitionState.getInSwipePipToHomeTransition()) { mOneShotAnimationType = ANIM_TYPE_BOUNDS; // PiP menu is attached late in the process here to avoid any artifacts on the leash // caused by addShellRoot when in gesture navigation mode. mPipMenuController.attach(leash); SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9]) .setPosition(leash, destinationBounds.left, destinationBounds.top) .setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()); startTransaction.merge(tx); startTransaction.apply(); if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { // For fixed rotation, set the destination bounds to the new rotation coordinates // at the end. destinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds()); } mPipBoundsState.setBounds(destinationBounds); onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */); sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); Loading @@ -507,17 +577,14 @@ public class PipTransition extends PipTransitionController { return true; } int rotationDelta = deltaRotation(endRotation, startRotation); if (rotationDelta != Surface.ROTATION_0) { Matrix tmpTransform = new Matrix(); tmpTransform.postRotate(rotationDelta == Surface.ROTATION_90 ? Surface.ROTATION_270 : Surface.ROTATION_90); tmpTransform.postRotate(rotationDelta); startTransaction.setMatrix(leash, tmpTransform, new float[9]); } if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( taskInfo.pictureInPictureParams, currentBounds); // Reverse the rotation for Shell transition animation. rotationDelta = deltaRotation(rotationDelta, 0); animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, rotationDelta); Loading @@ -528,9 +595,6 @@ public class PipTransition extends PipTransitionController { } } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { startTransaction.setAlpha(leash, 0f); // PiP menu is attached late in the process here to avoid any artifacts on the leash // caused by addShellRoot when in gesture navigation mode. mPipMenuController.attach(leash); animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 0f, 1f); mOneShotAnimationType = ANIM_TYPE_BOUNDS; Loading @@ -541,12 +605,47 @@ public class PipTransition extends PipTransitionController { startTransaction.apply(); animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration) .start(); .setDuration(mEnterExitAnimationDuration); if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { // For fixed rotation, the animation destination bounds is in old rotation coordinates. // Set the destination bounds to new coordinates after the animation is finished. // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation. animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds()); } animator.start(); return true; } /** Computes destination bounds in old rotation and returns source hint rect if available. */ @Nullable private Rect computeRotatedBounds(int rotationDelta, int startRotation, int endRotation, TaskInfo taskInfo, int direction, Rect outDestinationBounds, @Nullable Rect sourceHintRect) { if (direction == TRANSITION_DIRECTION_TO_PIP) { mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation); final Rect displayBounds = mPipBoundsState.getDisplayBounds(); outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds()); // Transform the destination bounds to current display coordinates. rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation); // When entering PiP (from button navigation mode), adjust the source rect hint by // display cutout if applicable. if (sourceHintRect != null && taskInfo.displayCutoutInsets != null) { if (rotationDelta == Surface.ROTATION_270) { sourceHintRect.offset(taskInfo.displayCutoutInsets.left, taskInfo.displayCutoutInsets.top); } } } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) { final Rect rotatedDestinationBounds = new Rect(outDestinationBounds); rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(), rotationDelta); return PipBoundsAlgorithm.getValidSourceHintRect(taskInfo.pictureInPictureParams, rotatedDestinationBounds); } return sourceHintRect; } private void startExitToSplitAnimation(TransitionInfo info, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, Loading Loading @@ -595,6 +694,13 @@ public class PipTransition extends PipTransitionController { startTransaction.setCornerRadius(leash, 0); startTransaction.setPosition(leash, bounds.left, bounds.top); if (mHasFadeOut && prevPipChange.getTaskInfo().isVisible()) { if (mPipAnimationController.getCurrentAnimator() != null) { mPipAnimationController.getCurrentAnimator().cancel(); } startTransaction.setAlpha(leash, 1); } mHasFadeOut = false; mCurrentPipTaskToken = null; mPipOrganizer.onExitPipFinished(prevPipChange.getTaskInfo()); } Loading @@ -615,6 +721,25 @@ public class PipTransition extends PipTransitionController { .round(finishTransaction, leash, isInPip); } /** Hides and shows the existing PIP during fixed rotation transition of other activities. */ private void fadeExistingPip(boolean show) { final SurfaceControl leash = mPipOrganizer.getSurfaceControl(); final TaskInfo taskInfo = mPipOrganizer.getTaskInfo(); if (leash == null || !leash.isValid() || taskInfo == null) { Log.w(TAG, "Invalid leash on fadeExistingPip: " + leash); return; } final float alphaStart = show ? 0 : 1; final float alphaEnd = show ? 1 : 0; mPipAnimationController .getAnimator(taskInfo, leash, mPipBoundsState.getBounds(), alphaStart, alphaEnd) .setTransitionDirection(TRANSITION_DIRECTION_SAME) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration) .start(); mHasFadeOut = !show; } private void finishResizeForMenu(Rect destinationBounds) { mPipMenuController.movePipMenu(null, null, destinationBounds); mPipMenuController.updateMenuBounds(destinationBounds); Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +4 −0 Original line number Diff line number Diff line Loading @@ -123,6 +123,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH public void forceFinishTransition() { } /** Called when the fixed rotation started. */ public void onFixedRotationStarted() { } public PipTransitionController(PipBoundsState pipBoundsState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, PipAnimationController pipAnimationController, Transitions transitions, Loading services/core/java/com/android/server/wm/Transition.java +11 −0 Original line number Diff line number Diff line Loading @@ -1251,6 +1251,17 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final ActivityRecord topMostActivity = task.getTopMostActivity(); change.setAllowEnterPip(topMostActivity != null && topMostActivity.checkEnterPictureInPictureAppOpsState()); final ActivityRecord topRunningActivity = task.topRunningActivity(); if (topRunningActivity != null && task.mDisplayContent != null) { // If Activity is in fixed rotation, its will be applied with the next rotation, // when the Task is still in the previous rotation. final int taskRotation = task.getWindowConfiguration().getDisplayRotation(); final int activityRotation = topRunningActivity.getWindowConfiguration() .getDisplayRotation(); if (taskRotation != activityRotation) { change.setEndFixedRotation(activityRotation); } } } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) { change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS); } Loading Loading
core/java/android/window/TransitionInfo.java +23 −3 Original line number Diff line number Diff line Loading @@ -364,8 +364,13 @@ public final class TransitionInfo implements Parcelable { private final Point mEndRelOffset = new Point(); private ActivityManager.RunningTaskInfo mTaskInfo = null; private boolean mAllowEnterPip; private int mStartRotation = ROTATION_UNDEFINED; private int mEndRotation = ROTATION_UNDEFINED; private @Surface.Rotation int mStartRotation = ROTATION_UNDEFINED; private @Surface.Rotation int mEndRotation = ROTATION_UNDEFINED; /** * The end rotation of the top activity after fixed rotation is finished. If the top * activity is not in fixed rotation, it will be {@link ROTATION_UNDEFINED}. */ private @Surface.Rotation int mEndFixedRotation = ROTATION_UNDEFINED; private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED; private @ColorInt int mBackgroundColor; Loading @@ -388,6 +393,7 @@ public final class TransitionInfo implements Parcelable { mAllowEnterPip = in.readBoolean(); mStartRotation = in.readInt(); mEndRotation = in.readInt(); mEndFixedRotation = in.readInt(); mRotationAnimation = in.readInt(); mBackgroundColor = in.readInt(); } Loading Loading @@ -441,6 +447,11 @@ public final class TransitionInfo implements Parcelable { mEndRotation = end; } /** Sets end rotation that top activity will be launched to after fixed rotation. */ public void setEndFixedRotation(@Surface.Rotation int endFixedRotation) { mEndFixedRotation = endFixedRotation; } /** * Sets the app-requested animation type for rotation. Will be one of the * ROTATION_ANIMATION_ values in {@link android.view.WindowManager.LayoutParams}; Loading Loading @@ -521,14 +532,21 @@ public final class TransitionInfo implements Parcelable { return mAllowEnterPip; } @Surface.Rotation public int getStartRotation() { return mStartRotation; } @Surface.Rotation public int getEndRotation() { return mEndRotation; } @Surface.Rotation public int getEndFixedRotation() { return mEndFixedRotation; } /** @return the rotation animation. */ public int getRotationAnimation() { return mRotationAnimation; Loading @@ -555,6 +573,7 @@ public final class TransitionInfo implements Parcelable { dest.writeBoolean(mAllowEnterPip); dest.writeInt(mStartRotation); dest.writeInt(mEndRotation); dest.writeInt(mEndFixedRotation); dest.writeInt(mRotationAnimation); dest.writeInt(mBackgroundColor); } Loading Loading @@ -584,7 +603,8 @@ public final class TransitionInfo implements Parcelable { return "{" + mContainer + "(" + mParent + ") leash=" + mLeash + " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb=" + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r=" + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + "}"; + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + " endFixedRotation=" + mEndFixedRotation + "}"; } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +21 −9 Original line number Diff line number Diff line Loading @@ -579,6 +579,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Log.d(TAG, "Alpha animation is expired. Use bounds animation."); mOneShotAnimationType = ANIM_TYPE_BOUNDS; } if (Transitions.ENABLE_SHELL_TRANSITIONS) { // For Shell transition, we will animate the window in PipTransition#startAnimation // instead of #onTaskAppeared. return; } if (mWaitForFixedRotation) { onTaskAppearedWithFixedRotation(); return; Loading @@ -588,15 +595,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Objects.requireNonNull(destinationBounds, "Missing destination bounds"); final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); if (Transitions.ENABLE_SHELL_TRANSITIONS) { if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { mPipMenuController.attach(mLeash); } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { mOneShotAnimationType = ANIM_TYPE_BOUNDS; } return; } if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { mPipMenuController.attach(mLeash); final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( Loading Loading @@ -829,6 +827,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mNextRotation = newRotation; mWaitForFixedRotation = true; if (Transitions.ENABLE_SHELL_TRANSITIONS) { // The fixed rotation will also be included in the transition info. However, if it is // not a PIP transition (such as open another app to different orientation), // PIP transition handler may not be aware of the fixed rotation start. // Notify the PIP transition handler so that it can fade out the PIP window early for // fixed transition of other windows. mPipTransitionController.onFixedRotationStarted(); return; } if (mPipTransitionState.isInPip()) { // Fade out the existing PiP to avoid jump cut during seamless rotation. fadeExistingPip(false /* show */); Loading @@ -840,6 +848,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (!mWaitForFixedRotation) { return; } if (Transitions.ENABLE_SHELL_TRANSITIONS) { clearWaitForFixedRotation(); return; } if (mPipTransitionState.getTransitionState() == PipTransitionState.TASK_APPEARED) { if (mPipTransitionState.getInSwipePipToHomeTransition()) { onEndOfSwipePipToHomeTransition(); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +150 −25 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.wm.shell.pip; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.util.RotationUtils.deltaRotation; Loading @@ -32,9 +33,11 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT; import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; Loading @@ -47,6 +50,7 @@ import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.util.Log; import android.view.Surface; import android.view.SurfaceControl; import android.window.TransitionInfo; Loading Loading @@ -86,6 +90,16 @@ public class PipTransition extends PipTransitionController { /** The Task window that is currently in PIP windowing mode. */ @Nullable private WindowContainerToken mCurrentPipTaskToken; /** Whether display is in fixed rotation. */ private boolean mInFixedRotation; /** * The rotation that the display will apply after expanding PiP to fullscreen. This is only * meaningful if {@link #mInFixedRotation} is true. */ @Surface.Rotation private int mFixedRotation; /** Whether the PIP window has fade out for fixed rotation. */ private boolean mHasFadeOut; public PipTransition(Context context, PipBoundsState pipBoundsState, Loading Loading @@ -136,35 +150,41 @@ public class PipTransition extends PipTransitionController { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { final TransitionInfo.Change currentPipChange = findCurrentPipChange(info); final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); mInFixedRotation = fixedRotationChange != null; mFixedRotation = mInFixedRotation ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; // Exiting PIP. final int type = info.getType(); if (transition.equals(mExitTransition)) { mExitDestinationBounds.setEmpty(); mExitTransition = null; mHasFadeOut = false; if (mFinishCallback != null) { mFinishCallback.onTransitionFinished(null, null); mFinishCallback = null; throw new RuntimeException("Previous callback not called, aborting exit PIP."); } final TransitionInfo.Change exitPipChange = findCurrentPipChange(info); if (exitPipChange == null) { if (currentPipChange == null) { throw new RuntimeException("Cannot find the pip window for exit-pip transition."); } switch (type) { case TRANSIT_EXIT_PIP: startExitAnimation(info, startTransaction, finishTransaction, finishCallback, exitPipChange); currentPipChange); break; case TRANSIT_EXIT_PIP_TO_SPLIT: startExitToSplitAnimation(info, startTransaction, finishTransaction, finishCallback, exitPipChange); finishCallback, currentPipChange); break; case TRANSIT_REMOVE_PIP: removePipImmediately(info, startTransaction, finishTransaction, finishCallback, exitPipChange); currentPipChange); break; default: throw new IllegalStateException("mExitTransition with unexpected transit type=" Loading @@ -177,7 +197,6 @@ public class PipTransition extends PipTransitionController { // The previous PIP Task is no longer in PIP, but this is not an exit transition (This can // happen when a new activity requests enter PIP). In this case, we just show this Task in // its end state, and play other animation as normal. final TransitionInfo.Change currentPipChange = findCurrentPipChange(info); if (currentPipChange != null && currentPipChange.getTaskInfo().getWindowingMode() != WINDOWING_MODE_PINNED) { resetPrevPip(currentPipChange, startTransaction); Loading @@ -193,6 +212,12 @@ public class PipTransition extends PipTransitionController { if (currentPipChange != null) { updatePipForUnhandledTransition(currentPipChange, startTransaction, finishTransaction); } // Fade in the fadeout PIP when the fixed rotation is finished. if (mPipTransitionState.isInPip() && !mInFixedRotation && mHasFadeOut) { fadeExistingPip(true /* show */); } return false; } Loading Loading @@ -242,9 +267,8 @@ public class PipTransition extends PipTransitionController { public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, @Nullable SurfaceControl.Transaction tx) { if (isInPipDirection(direction)) { mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP); mPipTransitionState.setTransitionState(ENTERED_PIP); } // If there is an expected exit transition, then the exit will be "merged" into this // transition so don't fire the finish-callback in that case. Loading @@ -268,6 +292,16 @@ public class PipTransition extends PipTransitionController { mFinishCallback = null; } @Override public void onFixedRotationStarted() { // The transition with this fixed rotation may be handled by other handler before reaching // PipTransition, so we cannot do this in #startAnimation. if (mPipTransitionState.getTransitionState() == ENTERED_PIP && !mHasFadeOut) { // Fade out the existing PiP to avoid jump cut during seamless rotation. fadeExistingPip(false /* show */); } } @Nullable private TransitionInfo.Change findCurrentPipChange(@NonNull TransitionInfo info) { if (mCurrentPipTaskToken == null) { Loading @@ -282,6 +316,17 @@ public class PipTransition extends PipTransitionController { return null; } @Nullable private TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change.getEndFixedRotation() != ROTATION_UNDEFINED) { return change; } } return null; } private void startExitAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, Loading Loading @@ -453,6 +498,7 @@ public class PipTransition extends PipTransitionController { } // Keep track of the PIP task. mCurrentPipTaskToken = enterPip.getContainer(); mHasFadeOut = false; if (mFinishCallback != null) { mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */); Loading @@ -465,12 +511,25 @@ public class PipTransition extends PipTransitionController { startTransaction.show(wallpaper.getLeash()); startTransaction.setAlpha(wallpaper.getLeash(), 1.f); } // Make sure other open changes are visible as entering PIP. Some may be hidden in // Transitions#setupStartState because the transition type is OPEN (such as auto-enter). for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change == enterPip || change == wallpaper) { continue; } if (isOpeningType(change.getMode())) { final SurfaceControl leash = change.getLeash(); startTransaction.show(leash).setAlpha(leash, 1.f); } } mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); mFinishCallback = finishCallback; final int endRotation = mInFixedRotation ? mFixedRotation : enterPip.getEndRotation(); return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(), startTransaction, finishTransaction, enterPip.getStartRotation(), enterPip.getEndRotation()); endRotation); } private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash, Loading @@ -481,25 +540,36 @@ public class PipTransition extends PipTransitionController { taskInfo.topActivityInfo); final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds(); int rotationDelta = deltaRotation(startRotation, endRotation); Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( taskInfo.pictureInPictureParams, currentBounds); if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { // Need to get the bounds of new rotation in old rotation for fixed rotation, sourceHintRect = computeRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo, TRANSITION_DIRECTION_TO_PIP, destinationBounds, sourceHintRect); } PipAnimationController.PipTransitionAnimator animator; // Set corner radius for entering pip. mSurfaceTransactionHelper .crop(finishTransaction, leash, destinationBounds) .round(finishTransaction, leash, true /* applyCornerRadius */); mPipMenuController.attach(leash); if (taskInfo.pictureInPictureParams != null && taskInfo.pictureInPictureParams.isAutoEnterEnabled() && mPipTransitionState.getInSwipePipToHomeTransition()) { mOneShotAnimationType = ANIM_TYPE_BOUNDS; // PiP menu is attached late in the process here to avoid any artifacts on the leash // caused by addShellRoot when in gesture navigation mode. mPipMenuController.attach(leash); SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9]) .setPosition(leash, destinationBounds.left, destinationBounds.top) .setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()); startTransaction.merge(tx); startTransaction.apply(); if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { // For fixed rotation, set the destination bounds to the new rotation coordinates // at the end. destinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds()); } mPipBoundsState.setBounds(destinationBounds); onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */); sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); Loading @@ -507,17 +577,14 @@ public class PipTransition extends PipTransitionController { return true; } int rotationDelta = deltaRotation(endRotation, startRotation); if (rotationDelta != Surface.ROTATION_0) { Matrix tmpTransform = new Matrix(); tmpTransform.postRotate(rotationDelta == Surface.ROTATION_90 ? Surface.ROTATION_270 : Surface.ROTATION_90); tmpTransform.postRotate(rotationDelta); startTransaction.setMatrix(leash, tmpTransform, new float[9]); } if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( taskInfo.pictureInPictureParams, currentBounds); // Reverse the rotation for Shell transition animation. rotationDelta = deltaRotation(rotationDelta, 0); animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, rotationDelta); Loading @@ -528,9 +595,6 @@ public class PipTransition extends PipTransitionController { } } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { startTransaction.setAlpha(leash, 0f); // PiP menu is attached late in the process here to avoid any artifacts on the leash // caused by addShellRoot when in gesture navigation mode. mPipMenuController.attach(leash); animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 0f, 1f); mOneShotAnimationType = ANIM_TYPE_BOUNDS; Loading @@ -541,12 +605,47 @@ public class PipTransition extends PipTransitionController { startTransaction.apply(); animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration) .start(); .setDuration(mEnterExitAnimationDuration); if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { // For fixed rotation, the animation destination bounds is in old rotation coordinates. // Set the destination bounds to new coordinates after the animation is finished. // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation. animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds()); } animator.start(); return true; } /** Computes destination bounds in old rotation and returns source hint rect if available. */ @Nullable private Rect computeRotatedBounds(int rotationDelta, int startRotation, int endRotation, TaskInfo taskInfo, int direction, Rect outDestinationBounds, @Nullable Rect sourceHintRect) { if (direction == TRANSITION_DIRECTION_TO_PIP) { mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation); final Rect displayBounds = mPipBoundsState.getDisplayBounds(); outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds()); // Transform the destination bounds to current display coordinates. rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation); // When entering PiP (from button navigation mode), adjust the source rect hint by // display cutout if applicable. if (sourceHintRect != null && taskInfo.displayCutoutInsets != null) { if (rotationDelta == Surface.ROTATION_270) { sourceHintRect.offset(taskInfo.displayCutoutInsets.left, taskInfo.displayCutoutInsets.top); } } } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) { final Rect rotatedDestinationBounds = new Rect(outDestinationBounds); rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(), rotationDelta); return PipBoundsAlgorithm.getValidSourceHintRect(taskInfo.pictureInPictureParams, rotatedDestinationBounds); } return sourceHintRect; } private void startExitToSplitAnimation(TransitionInfo info, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, Loading Loading @@ -595,6 +694,13 @@ public class PipTransition extends PipTransitionController { startTransaction.setCornerRadius(leash, 0); startTransaction.setPosition(leash, bounds.left, bounds.top); if (mHasFadeOut && prevPipChange.getTaskInfo().isVisible()) { if (mPipAnimationController.getCurrentAnimator() != null) { mPipAnimationController.getCurrentAnimator().cancel(); } startTransaction.setAlpha(leash, 1); } mHasFadeOut = false; mCurrentPipTaskToken = null; mPipOrganizer.onExitPipFinished(prevPipChange.getTaskInfo()); } Loading @@ -615,6 +721,25 @@ public class PipTransition extends PipTransitionController { .round(finishTransaction, leash, isInPip); } /** Hides and shows the existing PIP during fixed rotation transition of other activities. */ private void fadeExistingPip(boolean show) { final SurfaceControl leash = mPipOrganizer.getSurfaceControl(); final TaskInfo taskInfo = mPipOrganizer.getTaskInfo(); if (leash == null || !leash.isValid() || taskInfo == null) { Log.w(TAG, "Invalid leash on fadeExistingPip: " + leash); return; } final float alphaStart = show ? 0 : 1; final float alphaEnd = show ? 1 : 0; mPipAnimationController .getAnimator(taskInfo, leash, mPipBoundsState.getBounds(), alphaStart, alphaEnd) .setTransitionDirection(TRANSITION_DIRECTION_SAME) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration) .start(); mHasFadeOut = !show; } private void finishResizeForMenu(Rect destinationBounds) { mPipMenuController.movePipMenu(null, null, destinationBounds); mPipMenuController.updateMenuBounds(destinationBounds); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +4 −0 Original line number Diff line number Diff line Loading @@ -123,6 +123,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH public void forceFinishTransition() { } /** Called when the fixed rotation started. */ public void onFixedRotationStarted() { } public PipTransitionController(PipBoundsState pipBoundsState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, PipAnimationController pipAnimationController, Transitions transitions, Loading
services/core/java/com/android/server/wm/Transition.java +11 −0 Original line number Diff line number Diff line Loading @@ -1251,6 +1251,17 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final ActivityRecord topMostActivity = task.getTopMostActivity(); change.setAllowEnterPip(topMostActivity != null && topMostActivity.checkEnterPictureInPictureAppOpsState()); final ActivityRecord topRunningActivity = task.topRunningActivity(); if (topRunningActivity != null && task.mDisplayContent != null) { // If Activity is in fixed rotation, its will be applied with the next rotation, // when the Task is still in the previous rotation. final int taskRotation = task.getWindowConfiguration().getDisplayRotation(); final int activityRotation = topRunningActivity.getWindowConfiguration() .getDisplayRotation(); if (taskRotation != activityRotation) { change.setEndFixedRotation(activityRotation); } } } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) { change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS); } Loading