From aff00b6b158739425708cb7494fc09b9224248ff Mon Sep 17 00:00:00 2001 From: Ikram Gabiyev Date: Tue, 8 Oct 2024 09:18:56 -0700 Subject: Implement 3-btn-nav fixed rotation Implement the 3-btn-nav bounds type fixed rotation transition with the animation. The flow looks as follows: 1. A direct TRANSIT_PIP or TRANSIT_OPEN is detected with a fixed rotation change providing us with endFixedRotation hint. 2. Shell runs the fixed rotation animation in the original display orientation. 3. Shell updates the relevant internal state to represent that in final orientation 4. Core requests a CHANGE transition with a display change that we listen to in PipController. We update the display layout state and sent through the bounds as a WCT in the final orientation to match the layer position. Note: we might get a temporary late draw flicker after the animation is over, this is a separate potentially Core-related being discussed in b/372767189. Bug: 372310588 Flag: com.android.wm.shell.enable_pip2 Test: enter PiP from landscape to portrait Change-Id: Ifff3caf5521cc60f66f8ccfbae360e45528c3a16 --- .../com/android/wm/shell/pip/PipTransition.java | 11 --- .../wm/shell/pip/PipTransitionController.java | 16 +++++ .../wm/shell/pip2/animation/PipEnterAnimator.java | 44 ++++++++++-- .../wm/shell/pip2/animation/PipExpandAnimator.java | 1 + .../android/wm/shell/pip2/phone/PipController.java | 35 ++++++---- .../wm/shell/pip2/phone/PipTouchHandler.java | 9 +-- .../android/wm/shell/pip2/phone/PipTransition.java | 78 +++++++++++++++++++++- .../wm/shell/pip2/phone/PipTransitionState.java | 23 ++++++- 8 files changed, 179 insertions(+), 38 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index cbb08b804dfe..1453886e056b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -665,17 +665,6 @@ 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, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 9b815817d4d3..94b344fb575a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.WindowManager.TRANSIT_PIP; @@ -346,6 +347,21 @@ public abstract class PipTransitionController implements Transitions.TransitionH return false; } + /** + * Gets a change amongst the transition targets that is in a different final orientation than + * the display, signalling a potential fixed rotation transition. + */ + @Nullable + public 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; + } + /** End the currently-playing PiP animation. */ public void end() { } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java index f40a87c39aef..fcd5c3baab5d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java @@ -16,10 +16,14 @@ package com.android.wm.shell.pip2.animation; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + import android.animation.Animator; import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.Rect; import android.view.Surface; @@ -60,6 +64,11 @@ public class PipEnterAnimator extends ValueAnimator private final PointF mInitScale = new PointF(); private final PointF mInitPos = new PointF(); private final Rect mInitCrop = new Rect(); + private final PointF mInitActivityScale = new PointF(); + private final PointF mInitActivityPos = new PointF(); + + Matrix mTransformTensor = new Matrix(); + final float[] mMatrixTmp = new float[9]; public PipEnterAnimator(Context context, @NonNull SurfaceControl leash, @@ -109,6 +118,10 @@ public class PipEnterAnimator extends ValueAnimator @Override public void onAnimationEnd(@NonNull Animator animation) { + if (mFinishTransaction != null) { + onEnterAnimationUpdate(mInitScale, mInitPos, mInitCrop, + 1f /* fraction */, mFinishTransaction); + } if (mAnimationEndCallback != null) { mAnimationEndCallback.run(); } @@ -126,16 +139,24 @@ public class PipEnterAnimator extends ValueAnimator float fraction, SurfaceControl.Transaction tx) { float scaleX = 1 + (initScale.x - 1) * (1 - fraction); float scaleY = 1 + (initScale.y - 1) * (1 - fraction); - tx.setScale(mLeash, scaleX, scaleY); - float posX = initPos.x + (mEndBounds.left - initPos.x) * fraction; float posY = initPos.y + (mEndBounds.top - initPos.y) * fraction; - tx.setPosition(mLeash, posX, posY); + + int normalizedRotation = mRotation; + if (normalizedRotation == ROTATION_270) { + normalizedRotation = -ROTATION_90; + } + float degrees = -normalizedRotation * 90f * fraction; Rect endCrop = new Rect(mEndBounds); endCrop.offsetTo(0, 0); mRectEvaluator.evaluate(fraction, initCrop, endCrop); tx.setCrop(mLeash, mAnimatedRect); + + mTransformTensor.setScale(scaleX, scaleY); + mTransformTensor.postTranslate(posX, posY); + mTransformTensor.postRotate(degrees); + tx.setMatrix(mLeash, mTransformTensor, mMatrixTmp); } // no-ops @@ -153,7 +174,22 @@ public class PipEnterAnimator extends ValueAnimator * calculated differently from generic transitions. * @param pipChange PiP change received as a transition target. */ - public void setEnterStartState(@NonNull TransitionInfo.Change pipChange) { + public void setEnterStartState(@NonNull TransitionInfo.Change pipChange, + @NonNull TransitionInfo.Change pipActivityChange) { + PipUtils.calcEndTransform(pipActivityChange, pipChange, mInitActivityScale, + mInitActivityPos); + if (mStartTransaction != null && pipActivityChange.getLeash() != null) { + mStartTransaction.setCrop(pipActivityChange.getLeash(), null); + mStartTransaction.setScale(pipActivityChange.getLeash(), mInitActivityScale.x, + mInitActivityScale.y); + mStartTransaction.setPosition(pipActivityChange.getLeash(), mInitActivityPos.x, + mInitActivityPos.y); + mFinishTransaction.setCrop(pipActivityChange.getLeash(), null); + mFinishTransaction.setScale(pipActivityChange.getLeash(), mInitActivityScale.x, + mInitActivityScale.y); + mFinishTransaction.setPosition(pipActivityChange.getLeash(), mInitActivityPos.x, + mInitActivityPos.y); + } PipUtils.calcStartTransform(pipChange, mInitScale, mInitPos, mInitCrop); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java index 8fa5aa933929..a93ef12cb7fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java @@ -157,6 +157,7 @@ public class PipExpandAnimator extends ValueAnimator .shadow(tx, mLeash, false /* applyCornerRadius */); tx.apply(); } + private Rect getInsets(float fraction) { final Rect startInsets = mSourceRectHintInsets; final Rect endInsets = mZeroInsets; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index 73be8db0ea8a..0427294579dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -292,23 +292,34 @@ public class PipController implements ConfigurationChangeListener, setDisplayLayout(mDisplayController.getDisplayLayout(displayId)); if (!mPipTransitionState.isInPip()) { + // Skip the PiP-relevant updates if we aren't in a valid PiP state. + if (mPipTransitionState.isInFixedRotation()) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Fixed rotation flag shouldn't be set while in an invalid PiP state"); + } return; } mPipTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio()); - // Update the caches to reflect the new display layout in the movement bounds; - // temporarily update bounds to be at the top left for the movement bounds calculation. - Rect toBounds = new Rect(0, 0, - (int) Math.ceil(mPipBoundsState.getMaxSize().x * boundsScale), - (int) Math.ceil(mPipBoundsState.getMaxSize().y * boundsScale)); - mPipBoundsState.setBounds(toBounds); - mPipTouchHandler.updateMovementBounds(); - - // The policy is to keep PiP snap fraction invariant. - mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction); - mPipBoundsState.setBounds(toBounds); - t.setBounds(mPipTransitionState.mPipTaskToken, toBounds); + if (mPipTransitionState.isInFixedRotation()) { + // Do not change the bounds when in fixed rotation, but do update the movement bounds + // based on the current bounds state and potentially new display layout. + mPipTouchHandler.updateMovementBounds(); + mPipTransitionState.setInFixedRotation(false); + } else { + Rect toBounds = new Rect(0, 0, + (int) Math.ceil(mPipBoundsState.getMaxSize().x * boundsScale), + (int) Math.ceil(mPipBoundsState.getMaxSize().y * boundsScale)); + // Update the caches to reflect the new display layout in the movement bounds; + // temporarily update bounds to be at the top left for the movement bounds calculation. + mPipBoundsState.setBounds(toBounds); + mPipTouchHandler.updateMovementBounds(); + // The policy is to keep PiP snap fraction invariant. + mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction); + mPipBoundsState.setBounds(toBounds); + } + t.setBounds(mPipTransitionState.mPipTaskToken, mPipBoundsState.getBounds()); } private void setDisplayLayout(DisplayLayout layout) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 029f001401c5..5c7645cffac9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -406,12 +406,9 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha // We need to remove the callback even if the shelf is visible, in case it the delayed // callback hasn't been executed yet to avoid the wrong final state. mMainExecutor.removeCallbacks(mMoveOnShelVisibilityChanged); - if (shelfVisible) { - mMoveOnShelVisibilityChanged.run(); - } else { - // Postpone moving in response to hide of Launcher in case there's another change - mMainExecutor.executeDelayed(mMoveOnShelVisibilityChanged, PIP_KEEP_CLEAR_AREAS_DELAY); - } + + // Postpone moving in response to hide of Launcher in case there's another change + mMainExecutor.executeDelayed(mMoveOnShelVisibilityChanged, PIP_KEEP_CLEAR_AREAS_DELAY); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index b57f51aff176..a1a84aaa1b70 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -16,7 +16,9 @@ package com.android.wm.shell.pip2.phone; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; @@ -35,6 +37,7 @@ import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.content.Context; import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -387,8 +390,15 @@ public class PipTransition extends PipTransitionController implements return false; } - Rect startBounds = pipChange.getStartAbsBounds(); + // We expect the PiP activity as a separate change in a config-at-end transition. + TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info, + pipChange.getTaskInfo().getToken()); + if (pipActivityChange == null) { + return false; + } + Rect endBounds = pipChange.getEndAbsBounds(); + Rect activityEndBounds = pipActivityChange.getEndAbsBounds(); SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition."); @@ -410,14 +420,63 @@ public class PipTransition extends PipTransitionController implements } } + final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); + int startRotation = pipChange.getStartRotation(); + int endRotation = fixedRotationChange != null + ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; + final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 + : startRotation - endRotation; + + if (delta != ROTATION_0) { + mPipTransitionState.setInFixedRotation(true); + handleBoundsTypeFixedRotation(pipChange, pipActivityChange, fixedRotationChange); + } + PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash, - startTransaction, finishTransaction, endBounds, sourceRectHint, Surface.ROTATION_0); - animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange)); + startTransaction, finishTransaction, endBounds, sourceRectHint, delta); + animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange, + pipActivityChange)); animator.setAnimationEndCallback(this::finishInner); animator.start(); return true; } + private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange, + TransitionInfo.Change pipActivityChange, + TransitionInfo.Change fixedRotationChange) { + final Rect endBounds = pipTaskChange.getEndAbsBounds(); + final Rect endActivityBounds = pipActivityChange.getEndAbsBounds(); + int startRotation = pipTaskChange.getStartRotation(); + int endRotation = fixedRotationChange.getEndFixedRotation(); + + // Cache the task to activity offset to potentially restore later. + Point activityEndOffset = new Point(endActivityBounds.left - endBounds.left, + endActivityBounds.top - endBounds.top); + + // If we are running a fixed rotation bounds enter PiP animation, + // then update the display layout rotation, and recalculate the end rotation bounds. + // Update the endBounds in place, so that the PiP change is up-to-date. + mPipDisplayLayoutState.rotateTo(endRotation); + float snapFraction = mPipBoundsAlgorithm.getSnapFraction( + mPipBoundsAlgorithm.getEntryDestinationBounds()); + mPipBoundsAlgorithm.applySnapFraction(endBounds, snapFraction); + mPipBoundsState.setBounds(endBounds); + + // Display bounds were already updated to represent the final orientation, + // so we just need to readjust the origin, and perform rotation about (0, 0). + boolean isClockwise = (endRotation - startRotation) == -ROTATION_270; + Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds(); + int originTranslateX = isClockwise ? 0 : -displayBounds.width(); + int originTranslateY = isClockwise ? -displayBounds.height() : 0; + endBounds.offset(originTranslateX, originTranslateY); + + // Update the activity end bounds in place as well, as this is used for transform + // calculation later. + endActivityBounds.offsetTo(endBounds.left + activityEndOffset.x, + endBounds.top + activityEndOffset.y); + } + + private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @@ -531,6 +590,19 @@ public class PipTransition extends PipTransitionController implements return null; } + @Nullable + private TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info, + @NonNull WindowContainerToken parent) { + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getTaskInfo() == null + && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END) + && change.getParent() != null && change.getParent().equals(parent)) { + return change; + } + } + return null; + } + @Nullable private TransitionInfo.Change getChangeByToken(TransitionInfo info, WindowContainerToken token) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java index a132796f4a84..ccdd66b5d1a8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java @@ -155,6 +155,8 @@ public class PipTransitionState { @Nullable private Runnable mOnIdlePipTransitionStateRunnable; + private boolean mInFixedRotation = false; + /** * An interface to track state updates as we progress through PiP transitions. */ @@ -256,7 +258,7 @@ public class PipTransitionState { private void maybeRunOnIdlePipTransitionStateCallback() { if (mOnIdlePipTransitionStateRunnable != null && isPipStateIdle()) { - mOnIdlePipTransitionStateRunnable.run(); + mMainHandler.post(mOnIdlePipTransitionStateRunnable); mOnIdlePipTransitionStateRunnable = null; } } @@ -302,6 +304,23 @@ public class PipTransitionState { mSwipePipToHomeAppBounds.setEmpty(); } + /** + * @return true if either in swipe or button-nav fixed rotation. + */ + public boolean isInFixedRotation() { + return mInFixedRotation; + } + + /** + * Sets the fixed rotation flag. + */ + public void setInFixedRotation(boolean inFixedRotation) { + mInFixedRotation = inFixedRotation; + if (!inFixedRotation) { + maybeRunOnIdlePipTransitionStateCallback(); + } + } + /** * @return true if in swipe PiP to home. Note that this is true until overlay fades if used too. */ @@ -351,7 +370,7 @@ public class PipTransitionState { public boolean isPipStateIdle() { // This needs to be a valid in-PiP state that isn't a transient state. - return mState == ENTERED_PIP || mState == CHANGED_PIP_BOUNDS; + return (mState == ENTERED_PIP || mState == CHANGED_PIP_BOUNDS) && !isInFixedRotation(); } @Override -- cgit v1.2.3-59-g8ed1b