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 ccb349b9c5ab0..68a2c5d5233cb 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 112fe526f0a89..3e04bc5300054 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1603,7 +1603,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 4c2d124368589..b4fd3024a6348 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