From c0e5b3d92f0d7328e84781d1f05a4f26a6d0b947 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Tue, 5 Jan 2021 20:20:38 +0800 Subject: [PATCH] Apply fixed rotation animation for pip to fullscreen The activity surface is rotated by WM core when changing the windowing mode of PiP activity to fullscreen. And then PipTaskOrganizer#onFixedRotationStarted will be called before WindowContainerTransactionCallback#onTransactionReady. So PipTaskOrganizer can use the given new rotation to run the animation in current rotation. After the animation is done, PipTaskOrganizer will set the task to fullscreen, that makes previous top activity pause and then resume the next top activity (the original PiP). Finally, the fixed rotation is finished by transition done event, and then continue to rotate the display seamlessly. Also remove mInitialState from PipTaskOrganizer because it is no longer needed for predicting the fullscreen bounds. Bug: 165794724 Test: DisplayContentTests#testFixedRotationWithPip PipAnimationControllerTest#pipTransitionAnimator_rotatedEndValue Test: Expand PiP in portrait which was in landscape Change-Id: I5c1400ece9d80226bc35b497d308215010ead027 --- core/java/android/app/ActivityThread.java | 7 ++ .../wm/shell/pip/PipAnimationController.java | 55 ++++++++- .../pip/PipSurfaceTransactionHelper.java | 26 +++++ .../wm/shell/pip/PipTaskOrganizer.java | 104 +++++++----------- .../android/wm/shell/pip/PipTransition.java | 3 +- .../shell/pip/PipAnimationControllerTest.java | 29 ++++- .../com/android/server/wm/ActivityRecord.java | 28 ++++- .../com/android/server/wm/DisplayContent.java | 1 - .../core/java/com/android/server/wm/Task.java | 34 ++++++ .../server/wm/WindowProcessController.java | 11 +- .../com/android/server/wm/WindowToken.java | 11 +- .../server/wm/DisplayContentTests.java | 19 ++-- 12 files changed, 241 insertions(+), 87 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e5a04c98b9e70..0774ac134d1b4 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -5685,6 +5685,13 @@ public final class ActivityThread extends ClientTransactionHandler { final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull( amOverrideConfig, contextThemeWrapperOverrideConfig); mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId); + final Resources res = activity.getResources(); + if (res.hasOverrideDisplayAdjustments()) { + // If fixed rotation is applied while the activity is visible (e.g. PiP), the rotated + // configuration of activity may be sent later than the adjustments. In this case, the + // adjustments need to be updated for the consistency of display info. + res.getDisplayAdjustments().getConfiguration().updateFrom(finalOverrideConfig); + } activity.mConfigChangeFlags = 0; activity.mCurrentConfig = new Configuration(newConfig); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 45aa3870ecb6d..5ffa9885a1437 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -16,6 +16,9 @@ package com.android.wm.shell.pip; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + import android.animation.AnimationHandler; import android.animation.Animator; import android.animation.RectEvaluator; @@ -24,11 +27,13 @@ import android.annotation.IntDef; import android.app.TaskInfo; import android.graphics.Rect; import android.view.Choreographer; +import android.view.Surface; import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.common.DisplayLayout; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -132,15 +137,21 @@ public class PipAnimationController { * leash bounds before transformation/any animation. This is so when we try to construct * the different transformation matrices for the animation, we are constructing this based off * the PiP original bounds, rather than the {@param startBounds}, which is post-transformed. + * + * If non-zero {@param rotationDelta} is given, it means that the display will be rotated by + * leaving PiP to fullscreen, and the {@param endBounds} is the fullscreen bounds before the + * rotation change. */ @VisibleForTesting public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash, Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect, - @PipAnimationController.TransitionDirection int direction, float startingAngle) { + @PipAnimationController.TransitionDirection int direction, float startingAngle, + @Surface.Rotation int rotationDelta) { if (mCurrentAnimator == null) { mCurrentAnimator = setupPipTransitionAnimator( PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds, - endBounds, sourceHintRect, direction, 0 /* startingAngle */)); + endBounds, sourceHintRect, direction, 0 /* startingAngle */, + rotationDelta)); } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA && mCurrentAnimator.isRunning()) { // If we are still animating the fade into pip, then just move the surface and ensure @@ -156,7 +167,7 @@ public class PipAnimationController { mCurrentAnimator.cancel(); mCurrentAnimator = setupPipTransitionAnimator( PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds, - endBounds, sourceHintRect, direction, startingAngle)); + endBounds, sourceHintRect, direction, startingAngle, rotationDelta)); } return mCurrentAnimator; } @@ -410,7 +421,8 @@ public class PipAnimationController { static PipTransitionAnimator ofBounds(TaskInfo taskInfo, SurfaceControl leash, Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect, - @PipAnimationController.TransitionDirection int direction, float startingAngle) { + @PipAnimationController.TransitionDirection int direction, float startingAngle, + @Surface.Rotation int rotationDelta) { // Just for simplicity we'll interpolate between the source rect hint insets and empty // insets to calculate the window crop final Rect initialSourceValue; @@ -431,6 +443,16 @@ public class PipAnimationController { } final Rect sourceInsets = new Rect(0, 0, 0, 0); + final Rect rotatedEndRect; + if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) { + // Rotate the end bounds according to the rotation delta because the display will + // be rotated to the same orientation. + rotatedEndRect = new Rect(endValue); + DisplayLayout.rotateBounds(rotatedEndRect, endValue, rotationDelta); + } else { + rotatedEndRect = null; + } + // construct new Rect instances in case they are recycled return new PipTransitionAnimator(taskInfo, leash, ANIM_TYPE_BOUNDS, endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue), @@ -444,6 +466,12 @@ public class PipAnimationController { final Rect base = getBaseValue(); final Rect start = getStartValue(); final Rect end = getEndValue(); + if (rotatedEndRect != null) { + // Animate the bounds in a different orientation. It only happens when + // leaving PiP to fullscreen. + applyRotation(tx, leash, fraction, start, end, rotatedEndRect); + return; + } Rect bounds = mRectEvaluator.evaluate(fraction, start, end); float angle = (1.0f - fraction) * startingAngle; setCurrentValue(bounds); @@ -469,6 +497,25 @@ public class PipAnimationController { tx.apply(); } + private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash, + float fraction, Rect start, Rect end, Rect rotatedEndRect) { + final Rect bounds = mRectEvaluator.evaluate(fraction, start, rotatedEndRect); + setCurrentValue(bounds); + final float degree, x, y; + if (rotationDelta == ROTATION_90) { + degree = 90 * fraction; + x = fraction * (end.right - start.left) + start.left; + y = fraction * (end.top - start.top) + start.top; + } else { + degree = -90 * fraction; + x = fraction * (end.left - start.left) + start.left; + y = fraction * (end.bottom - start.top) + start.top; + } + getSurfaceTransactionHelper().rotateAndScaleWithCrop(tx, leash, bounds, + rotatedEndRect, degree, x, y); + tx.apply(); + } + @Override void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { getSurfaceTransactionHelper() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java index a777a2766ee75..97aeda4b053f5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java @@ -126,6 +126,32 @@ public class PipSurfaceTransactionHelper { return this; } + /** + * Operates the rotation according to the given degrees and scale (setMatrix) according to the + * source bounds and rotated destination bounds. The crop will be the unscaled source bounds. + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + public PipSurfaceTransactionHelper rotateAndScaleWithCrop(SurfaceControl.Transaction tx, + SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degrees, + float positionX, float positionY) { + mTmpDestinationRect.set(sourceBounds); + final int dw = destinationBounds.width(); + final int dh = destinationBounds.height(); + // Scale by the short side so there won't be empty area if the aspect ratio of source and + // destination are different. + final float scale = dw <= dh + ? (float) sourceBounds.width() / dw + : (float) sourceBounds.height() / dh; + // Inverse scale for crop to fit in screen coordinates. + mTmpDestinationRect.scale(1 / scale); + mTmpTransform.setRotate(degrees); + mTmpTransform.postScale(scale, scale); + mTmpTransform.postTranslate(positionX, positionY); + tx.setMatrix(leash, mTmpTransform, mTmpFloat9) + .setWindowCrop(leash, mTmpDestinationRect.width(), mTmpDestinationRect.height()); + return this; + } + /** * Resets the scale (setMatrix) on a given transaction and leash if there's any * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index fb83006e8522a..ad6f435ea9076 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -48,13 +48,12 @@ import android.app.TaskInfo; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; -import android.content.res.Configuration; import android.graphics.Rect; -import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.util.Rational; import android.view.Display; +import android.view.Surface; import android.view.SurfaceControl; import android.window.TaskOrganizer; import android.window.WindowContainerToken; @@ -72,8 +71,6 @@ import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; @@ -134,7 +131,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private final PipUiEventLogger mPipUiEventLoggerLogger; private final int mEnterExitAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; - private final Map mInitialState = new HashMap<>(); private final Optional mSplitScreenOptional; protected final ShellTaskOrganizer mTaskOrganizer; protected final ShellExecutor mMainExecutor; @@ -191,6 +187,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, */ private boolean mWaitForFixedRotation; + /** + * The rotation that the display will apply after expanding PiP to fullscreen. This is only + * meaningful if {@link #mWaitForFixedRotation} is true. + */ + private @Surface.Rotation int mNextRotation; + /** * If set to {@code true}, no entering PiP transition would be kicked off and most likely * it's due to the fact that Launcher is handling the transition directly when swiping @@ -313,61 +315,40 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - final Configuration initialConfig = mInitialState.remove(mToken.asBinder()); - if (initialConfig == null) { - Log.wtf(TAG, "Token not in record, this should not happen mToken=" + mToken); - return; - } mPipUiEventLoggerLogger.log( PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN); - final boolean orientationDiffers = initialConfig.windowConfiguration.getRotation() - != mPipBoundsState.getDisplayLayout().rotation(); final WindowContainerTransaction wct = new WindowContainerTransaction(); - final Rect destinationBounds = initialConfig.windowConfiguration.getBounds(); + final Rect destinationBounds = mPipBoundsState.getDisplayBounds(); final int direction = syncWithSplitScreenBounds(destinationBounds) ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN : TRANSITION_DIRECTION_LEAVE_PIP; - if (orientationDiffers) { - mState = State.EXITING_PIP; - // Send started callback though animation is ignored. - sendOnPipTransitionStarted(direction); - // Don't bother doing an animation if the display rotation differs or if it's in - // a non-supported windowing mode - applyWindowingModeChangeOnExit(wct, direction); - mTaskOrganizer.applyTransaction(wct); - // Send finished callback though animation is ignored. - sendOnPipTransitionFinished(direction); - } else { - final SurfaceControl.Transaction tx = - mSurfaceControlTransactionFactory.getTransaction(); - mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds, - mPipBoundsState.getBounds()); - tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height()); - // We set to fullscreen here for now, but later it will be set to UNDEFINED for - // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit. - wct.setActivityWindowingMode(mToken, - direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN - ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY - : WINDOWING_MODE_FULLSCREEN); - wct.setBounds(mToken, destinationBounds); - wct.setBoundsChangeTransaction(mToken, tx); - mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() { - @Override - public void onTransactionReady(int id, SurfaceControl.Transaction t) { - mMainExecutor.execute(() -> { - t.apply(); - // Make sure to grab the latest source hint rect as it could have been - // updated right after applying the windowing mode change. - final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( - mPictureInPictureParams, destinationBounds); - scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds, - 0 /* startingAngle */, sourceHintRect, direction, - animationDurationMs, null /* updateBoundsCallback */); - mState = State.EXITING_PIP; - }); - } - }); - } + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); + mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds, mPipBoundsState.getBounds()); + tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height()); + // We set to fullscreen here for now, but later it will be set to UNDEFINED for + // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit. + wct.setActivityWindowingMode(mToken, + direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN + ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + : WINDOWING_MODE_FULLSCREEN); + wct.setBounds(mToken, destinationBounds); + wct.setBoundsChangeTransaction(mToken, tx); + mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() { + @Override + public void onTransactionReady(int id, SurfaceControl.Transaction t) { + mMainExecutor.execute(() -> { + t.apply(); + // Make sure to grab the latest source hint rect as it could have been + // updated right after applying the windowing mode change. + final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( + mPictureInPictureParams, destinationBounds); + scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds, + 0 /* startingAngle */, sourceHintRect, direction, + animationDurationMs, null /* updateBoundsCallback */); + mState = State.EXITING_PIP; + }); + } + }); } private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) { @@ -399,7 +380,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration) .start(); - mInitialState.remove(mToken.asBinder()); mState = State.EXITING_PIP; } @@ -424,7 +404,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mToken = mTaskInfo.token; mState = State.TASK_APPEARED; mLeash = leash; - mInitialState.put(mToken.asBinder(), new Configuration(mTaskInfo.configuration)); mPictureInPictureParams = mTaskInfo.pictureInPictureParams; setBoundsStateForEntry(mTaskInfo.topActivity, mPictureInPictureParams, mTaskInfo.topActivityInfo); @@ -606,6 +585,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @Override public void onFixedRotationStarted(int displayId, int newRotation) { + mNextRotation = newRotation; mWaitForFixedRotation = true; } @@ -645,7 +625,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipAnimationController.getCurrentAnimator(); if (animator == null || !animator.isRunning() || animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) { - if (mState.isInPip() && fromRotation) { + if (mState.isInPip() && fromRotation && !mWaitForFixedRotation) { // Update bounds state to final destination first. It's important to do this // before finishing & cancelling the transition animation so that the MotionHelper // bounds are synchronized to the destination bounds when the animation ends. @@ -1052,11 +1032,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Log.w(TAG, "Abort animation, invalid leash"); return; } + final int rotationDelta = mWaitForFixedRotation + ? ((mNextRotation - mPipBoundsState.getDisplayLayout().rotation()) + 4) % 4 + : Surface.ROTATION_0; Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE ? mPipBoundsState.getBounds() : currentBounds; mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds, - sourceHintRect, direction, startingAngle) + sourceHintRect, direction, startingAngle, rotationDelta) .setTransitionDirection(direction) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(durationMs) @@ -1101,11 +1084,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, pw.println(innerPrefix + "mState=" + mState); pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType); pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams); - pw.println(innerPrefix + "mInitialState:"); - for (Map.Entry e : mInitialState.entrySet()) { - pw.println(innerPrefix + " binder=" + e.getKey() - + " winConfig=" + e.getValue().windowConfiguration); - } } @Override 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 91e8c99392446..9b6909b2bd512 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 @@ -30,6 +30,7 @@ import android.app.TaskInfo; import android.content.Context; import android.graphics.Rect; import android.os.IBinder; +import android.view.Surface; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -112,7 +113,7 @@ public class PipTransition extends PipTransitionController { taskInfo.pictureInPictureParams, currentBounds); animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, - TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */); + TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, Surface.ROTATION_0); } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { t.setAlpha(leash, 0f); t.apply(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java index 0087d917f007c..3147dab1a0f82 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java @@ -16,6 +16,9 @@ package com.android.wm.shell.pip; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_90; + import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; @@ -34,6 +37,7 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayLayout; import org.junit.Before; import org.junit.Test; @@ -84,7 +88,7 @@ public class PipAnimationControllerTest extends ShellTestCase { public void getAnimator_withBounds_returnBoundsAnimator() { final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, new Rect(), new Rect(), new Rect(), null, - TRANSITION_DIRECTION_TO_PIP, 0); + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); assertEquals("Expect ANIM_TYPE_BOUNDS animation", animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS); @@ -98,13 +102,13 @@ public class PipAnimationControllerTest extends ShellTestCase { final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null, - TRANSITION_DIRECTION_TO_PIP, 0); + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); oldAnimator.start(); final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue2, null, - TRANSITION_DIRECTION_TO_PIP, 0); + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); assertEquals("getAnimator with same type returns same animator", oldAnimator, newAnimator); @@ -127,6 +131,21 @@ public class PipAnimationControllerTest extends ShellTestCase { animator.getTransitionDirection(), TRANSITION_DIRECTION_LEAVE_PIP); } + @Test + public void pipTransitionAnimator_rotatedEndValue() { + final Rect startBounds = new Rect(200, 700, 400, 800); + final Rect endBounds = new Rect(0, 0, 500, 1000); + final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController + .getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null, + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_90); + // Apply fraction 1 to compute the end value. + animator.applySurfaceControlTransaction(mLeash, new DummySurfaceControlTx(), 1); + final Rect rotatedEndBounds = new Rect(endBounds); + DisplayLayout.rotateBounds(rotatedEndBounds, endBounds, ROTATION_90); + + assertEquals("Expect 90 degree rotated bounds", rotatedEndBounds, animator.mCurrentValue); + } + @Test @SuppressWarnings("unchecked") public void pipTransitionAnimator_updateEndValue() { @@ -136,7 +155,7 @@ public class PipAnimationControllerTest extends ShellTestCase { final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null, - TRANSITION_DIRECTION_TO_PIP, 0); + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); animator.updateEndValue(endValue2); @@ -150,7 +169,7 @@ public class PipAnimationControllerTest extends ShellTestCase { final Rect endValue = new Rect(100, 100, 200, 200); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null, - TRANSITION_DIRECTION_TO_PIP, 0); + TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); animator.setPipAnimationCallback(mPipAnimationCallback); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index f29b57ff9305c..6bec5b2747e71 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7005,7 +7005,32 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } } - super.onConfigurationChanged(newParentConfig); + + final DisplayContent display = mDisplayContent; + if (inPinnedWindowingMode() && attachedToProcess() && display != null) { + // If the PIP activity is changing to fullscreen with display orientation change, the + // fixed rotation will take effect that requires to send fixed rotation adjustments + // before the process configuration (if the process is a configuration listener of the + // activity). So when performing process configuration on client side, it can apply + // the adjustments (see WindowToken#onFixedRotationStatePrepared). + try { + app.pauseConfigurationDispatch(); + super.onConfigurationChanged(newParentConfig); + if (mVisibleRequested && !inMultiWindowMode()) { + final int rotation = display.rotationForActivityInDifferentOrientation(this); + if (rotation != ROTATION_UNDEFINED) { + app.resumeConfigurationDispatch(); + display.setFixedRotationLaunchingApp(this, rotation); + } + } + } finally { + if (app.resumeConfigurationDispatch()) { + app.dispatchConfiguration(app.getConfiguration()); + } + } + } else { + super.onConfigurationChanged(newParentConfig); + } // Configuration's equality doesn't consider seq so if only seq number changes in resolved // override configuration. Therefore ConfigurationContainer doesn't change merged override @@ -7014,7 +7039,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A onMergedOverrideConfigurationChanged(); } - final DisplayContent display = mDisplayContent; if (display == null) { return; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index d8720aba3f550..0dd7fe02d8ab6 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1597,7 +1597,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp && mFixedRotationLaunchingApp != mFixedRotationTransitionListener.mAnimatingRecents; } - @VisibleForTesting boolean isFixedRotationLaunchingApp(ActivityRecord r) { return mFixedRotationLaunchingApp == r; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index e44a028c897f7..9bbbbe0a85356 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -809,6 +809,16 @@ class Task extends WindowContainer { // false. private boolean mDeferTaskAppear; + /** + * Forces this task to be unorganized. Currently it is used for deferring the control of + * organizer when windowing mode is changing from PiP to fullscreen with orientation change. + * It is true only during Task#setWindowingMode ~ DisplayRotation#continueRotation. + * + * TODO(b/179235349): Remove this field by making surface operations from task organizer sync + * with display rotation. + */ + private boolean mForceNotOrganized; + /** * This task was created by the task organizer which has the following implementations. *
    @@ -2246,6 +2256,21 @@ class Task extends WindowContainer { if (pipChanging) { mDisplayContent.getPinnedStackController().setPipWindowingModeChanging(true); + // If the top activity is using fixed rotation, it should be changing from PiP to + // fullscreen with display orientation change. Do not notify fullscreen task organizer + // because the restoration of task surface and the transformation of activity surface + // need to be done synchronously. + final ActivityRecord r = topRunningActivity(); + if (r != null && mDisplayContent.isFixedRotationLaunchingApp(r)) { + mForceNotOrganized = true; + } + } else if (mForceNotOrganized) { + // If the display orientation change is done, let the corresponding task organizer take + // back the control of this task. + final ActivityRecord r = topRunningActivity(); + if (r == null || !mDisplayContent.isFixedRotationLaunchingApp(r)) { + mForceNotOrganized = false; + } } try { // We have 2 reasons why we need to report orientation change here. @@ -2835,6 +2860,9 @@ class Task extends WindowContainer { if (windowingMode == WINDOWING_MODE_UNDEFINED) { windowingMode = newParentConfig.windowConfiguration.getWindowingMode(); } + // Commit the resolved windowing mode so the canSpecifyOrientation won't get the old + // mode that may cause the bounds to be miscalculated, e.g. letterboxed. + getConfiguration().windowConfiguration.setWindowingMode(windowingMode); Rect outOverrideBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds(); @@ -4533,6 +4561,9 @@ class Task extends WindowContainer { pw.print(" mSupportsPictureInPicture="); pw.print(mSupportsPictureInPicture); pw.print(" isResizeable="); pw.println(isResizeable()); pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime); + if (mForceNotOrganized) { + pw.print(prefix); pw.println("mForceNotOrganized=true"); + } pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)"); } @@ -4977,6 +5008,9 @@ class Task extends WindowContainer { } private boolean canBeOrganized() { + if (mForceNotOrganized) { + return false; + } // All root tasks can be organized if (isRootTask()) { return true; diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 8b4d415efcb5b..264a3b4edfa6d 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -1329,6 +1329,10 @@ public class WindowProcessController extends ConfigurationContainer { void updateSurfacePosition(SurfaceControl.Transaction t) { super.updateSurfacePosition(t); if (isFixedRotationTransforming()) { - // The window is layouted in a simulated rotated display but the real display hasn't - // rotated, so here transforms its surface to fit in the real display. - mFixedRotationTransformState.transform(this); + final ActivityRecord r = asActivityRecord(); + final Task rootTask = r != null ? r.getRootTask() : null; + // Don't transform the activity in PiP because the PiP task organizer will handle it. + if (rootTask == null || !rootTask.inPinnedWindowingMode()) { + // The window is laid out in a simulated rotated display but the real display hasn't + // rotated, so here transforms its surface to fit in the real display. + mFixedRotationTransformState.transform(this); + } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 4bea9a2eea45d..f15fed8bb95f1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1379,7 +1379,7 @@ public class DisplayContentTests extends WindowTestsBase { } @Test - public void testNoFixedRotationWithPip() { + public void testFixedRotationWithPip() { final DisplayContent displayContent = mDefaultDisplay; unblockDisplayRotation(displayContent); // Make resume-top really update the activity state. @@ -1406,15 +1406,20 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(homeConfigOrientation, displayConfig.orientation); clearInvocations(mWm); - // Leave PiP to fullscreen. The orientation can be updated from - // ActivityRecord#reportDescendantOrientationChangeIfNeeded. - pinnedTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + // Leave PiP to fullscreen. Simulate the step of PipTaskOrganizer that sets the activity + // to fullscreen, so fixed rotation will apply on it. + pinnedActivity.setWindowingMode(WINDOWING_MODE_FULLSCREEN); homeActivity.setState(Task.ActivityState.STOPPED, "test"); - assertFalse(displayContent.hasTopFixedRotationLaunchingApp()); - verify(mWm, atLeastOnce()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt()); - assertEquals(pinnedConfigOrientation, displayConfig.orientation); + assertTrue(displayContent.hasTopFixedRotationLaunchingApp()); + verify(mWm, never()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt()); + assertNotEquals(pinnedConfigOrientation, displayConfig.orientation); + + // Assume the animation of PipTaskOrganizer is done and then commit fullscreen to task. + pinnedTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + displayContent.continueUpdateOrientationForDiffOrienLaunchingApp(); assertFalse(displayContent.getPinnedStackController().isPipActiveOrWindowingModeChanging()); + assertEquals(pinnedConfigOrientation, displayConfig.orientation); clearInvocations(mWm); // Enter PiP from fullscreen. The orientation can be updated from