From 91a630b912b0c87536f7d509064c514e1c47aa51 Mon Sep 17 00:00:00 2001 From: Evan Rosky Date: Mon, 31 Jan 2022 14:40:01 -0800 Subject: [PATCH] Hook up fixed-rotation logic to shell transitions This works by separating an app launching to 2 phases (similar to legacy fixed rotation). First it launches the app into a different rotation. Once the animation finishes it creates a new seamless display-rotation transition. Because we have displayRotation in windowconfiguration, we can directly rotate a window's surface instead of checking explicitly for fixedRotationState. Since the second-phase is a transition, we don't (ab)use seamlessRotator. Once the display lines-up, the surfaces and configurations automatically update. Since we still want to explore shell-transitions style rotation (single-transition), add a system property to turn it off and on. Bug: 217560545 Test: existing tests and manual tests Change-Id: I0b34b32ff5b2650a519d3c195df452de446cf515 --- core/java/android/util/RotationUtils.java | 55 +++++++++++++++- .../src/android/util/RotationUtilsTest.java | 21 ++++++ .../transition/DefaultTransitionHandler.java | 8 ++- .../android/wm/shell/util/CounterRotator.java | 35 +++++----- .../transition/ShellTransitionTests.java | 7 ++ .../animation/RemoteTransitionAdapter.kt | 39 +++++------ .../com/android/server/wm/DisplayContent.java | 49 ++++++++++---- .../com/android/server/wm/Transition.java | 35 ++++++++++ .../server/wm/TransitionController.java | 30 +++++++++ .../server/wm/WallpaperWindowToken.java | 8 +++ .../android/server/wm/WindowContainer.java | 49 +++++++++++++- .../com/android/server/wm/WindowToken.java | 65 ++++++++++++++++++- 12 files changed, 341 insertions(+), 60 deletions(-) diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java index 0ac2c9c25ad1c..cebdbf669f3ac 100644 --- a/core/java/android/util/RotationUtils.java +++ b/core/java/android/util/RotationUtils.java @@ -24,8 +24,10 @@ import static android.view.Surface.ROTATION_90; import android.annotation.Dimension; import android.graphics.Insets; import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; import android.view.Surface.Rotation; +import android.view.SurfaceControl; /** * A class containing utility methods related to rotation. @@ -121,12 +123,63 @@ public class RotationUtils { /** @return the rotation needed to rotate from oldRotation to newRotation. */ @Rotation - public static int deltaRotation(int oldRotation, int newRotation) { + public static int deltaRotation(@Rotation int oldRotation, @Rotation int newRotation) { int delta = newRotation - oldRotation; if (delta < 0) delta += 4; return delta; } + /** + * Rotates a surface CCW around the origin (eg. a 90-degree rotation will result in the + * bottom-left being at the origin). Use {@link #rotatePoint} to transform the top-left + * corner appropriately. + */ + public static void rotateSurface(SurfaceControl.Transaction t, SurfaceControl sc, + @Rotation int rotation) { + // Note: the matrix values look inverted, but they aren't because our coordinate-space + // is actually left-handed. + // Note: setMatrix expects values in column-major order. + switch (rotation) { + case ROTATION_0: + t.setMatrix(sc, 1.f, 0.f, 0.f, 1.f); + break; + case ROTATION_90: + t.setMatrix(sc, 0.f, -1.f, 1.f, 0.f); + break; + case ROTATION_180: + t.setMatrix(sc, -1.f, 0.f, 0.f, -1.f); + break; + case ROTATION_270: + t.setMatrix(sc, 0.f, 1.f, -1.f, 0.f); + break; + } + } + + /** + * Rotates a point CCW within a rectangle of size parentW x parentH with top/left at the + * origin as if the point is stuck to the rectangle. The rectangle is transformed such that + * it's top/left remains at the origin after the rotation. + */ + public static void rotatePoint(Point inOutPoint, @Rotation int rotation, + int parentW, int parentH) { + int origX = inOutPoint.x; + switch (rotation) { + case ROTATION_0: + return; + case ROTATION_90: + inOutPoint.x = inOutPoint.y; + inOutPoint.y = parentW - origX; + return; + case ROTATION_180: + inOutPoint.x = parentW - inOutPoint.x; + inOutPoint.y = parentH - inOutPoint.y; + return; + case ROTATION_270: + inOutPoint.x = parentH - inOutPoint.y; + inOutPoint.y = origX; + } + } + /** * Sets a matrix such that given a rotation, it transforms physical display * coordinates to that rotation's logical coordinates. diff --git a/core/tests/coretests/src/android/util/RotationUtilsTest.java b/core/tests/coretests/src/android/util/RotationUtilsTest.java index 5dbe03e8b42bd..826eb3070c7e9 100644 --- a/core/tests/coretests/src/android/util/RotationUtilsTest.java +++ b/core/tests/coretests/src/android/util/RotationUtilsTest.java @@ -17,12 +17,14 @@ package android.util; import static android.util.RotationUtils.rotateBounds; +import static android.util.RotationUtils.rotatePoint; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static org.junit.Assert.assertEquals; +import android.graphics.Point; import android.graphics.Rect; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -58,4 +60,23 @@ public class RotationUtilsTest { rotateBounds(testResult, testParent, ROTATION_270); assertEquals(new Rect(520, 40, 580, 120), testResult); } + + @Test + public void testRotatePoint() { + int parentW = 1000; + int parentH = 600; + Point testPt = new Point(60, 40); + + Point testResult = new Point(testPt); + rotatePoint(testResult, ROTATION_90, parentW, parentH); + assertEquals(new Point(40, 940), testResult); + + testResult.set(testPt.x, testPt.y); + rotatePoint(testResult, ROTATION_180, parentW, parentH); + assertEquals(new Point(940, 560), testResult); + + testResult.set(testPt.x, testPt.y); + rotatePoint(testResult, ROTATION_270, parentW, parentH); + assertEquals(new Point(560, 60), testResult); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index ddf01a8c5ee95..34d98ee87dcc5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -201,6 +201,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { "Display is changing, check if it should be seamless."); boolean checkedDisplayLayout = false; boolean hasTask = false; + boolean displayExplicitSeamless = false; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); @@ -209,7 +210,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // This container isn't rotating, so we can ignore it. if (change.getEndRotation() == change.getStartRotation()) continue; - if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) { // In the presence of System Alert windows we can not seamlessly rotate. if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) { @@ -217,6 +217,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { " display has system alert windows, so not seamless."); return false; } + displayExplicitSeamless = + change.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS; } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, @@ -268,8 +270,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - // ROTATION_ANIMATION_SEAMLESS can only be requested by task. - if (hasTask) { + // ROTATION_ANIMATION_SEAMLESS can only be requested by task or display. + if (hasTask || displayExplicitSeamless) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless."); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java index 7f8eaf1a9b559..7e95814c06c2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java @@ -16,10 +16,14 @@ package com.android.wm.shell.util; +import android.graphics.Point; +import android.util.RotationUtils; import android.view.SurfaceControl; /** - * Utility class that takes care of counter-rotating surfaces during a transition animation. + * Utility class that takes care of rotating unchanging child-surfaces to match the parent rotation + * during a transition animation. This gives the illusion that the child surfaces haven't rotated + * relative to the screen. */ public class CounterRotator { private SurfaceControl mSurface = null; @@ -33,29 +37,30 @@ public class CounterRotator { * Sets up this rotator. * * @param rotateDelta is the forward rotation change (the rotation the display is making). - * @param displayW (and H) Is the size of the rotating display. + * @param parentW (and H) Is the size of the rotating parent after the rotation. */ public void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta, - float displayW, float displayH) { + float parentW, float parentH) { if (rotateDelta == 0) return; - // We want to counter-rotate, so subtract from 4 - rotateDelta = 4 - (rotateDelta + 4) % 4; mSurface = new SurfaceControl.Builder() .setName("Transition Unrotate") .setContainerLayer() .setParent(parent) .build(); - // column-major - if (rotateDelta == 1) { - t.setMatrix(mSurface, 0, 1, -1, 0); - t.setPosition(mSurface, displayW, 0); - } else if (rotateDelta == 2) { - t.setMatrix(mSurface, -1, 0, 0, -1); - t.setPosition(mSurface, displayW, displayH); - } else if (rotateDelta == 3) { - t.setMatrix(mSurface, 0, -1, 1, 0); - t.setPosition(mSurface, 0, displayH); + // Rotate forward to match the new rotation (rotateDelta is the forward rotation the parent + // already took). Child surfaces will be in the old rotation relative to the new parent + // rotation, so we need to forward-rotate the child surfaces to match. + RotationUtils.rotateSurface(t, mSurface, rotateDelta); + final Point tmpPt = new Point(0, 0); + // parentW/H are the size in the END rotation, the rotation utilities expect the starting + // size. So swap them if necessary + if ((rotateDelta % 2) != 0) { + final float w = parentW; + parentW = parentH; + parentH = w; } + RotationUtils.rotatePoint(tmpPt, rotateDelta, (int) parentW, (int) parentH); + t.setPosition(mSurface, tmpPt.x, tmpPt.y); t.show(mSurface); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 0f4a06f22986d..dbf93b4d7e0a1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -591,6 +591,13 @@ public class ShellTransitionTests { .setRotate().build()) .build(); assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, displays)); + + // Seamless if display is explicitly seamless. + final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY) + .setRotate(ROTATION_ANIMATION_SEAMLESS).build()) + .build(); + assertTrue(DefaultTransitionHandler.isRotationSeamless(seamlessDisplay, displays)); } class TransitionInfoBuilder { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt index 7ef0901a27c98..2e9a16fffe9af 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt @@ -24,6 +24,7 @@ import android.os.IBinder import android.os.RemoteException import android.util.ArrayMap import android.util.Log +import android.util.RotationUtils import android.view.IRemoteAnimationFinishedCallback import android.view.IRemoteAnimationRunner import android.view.RemoteAnimationAdapter @@ -345,39 +346,33 @@ class RemoteTransitionAdapter { * Sets up this rotator. * * @param rotateDelta is the forward rotation change (the rotation the display is making). - * @param displayW (and H) Is the size of the rotating display. + * @param parentW (and H) Is the size of the rotating parent. */ fun setup( t: SurfaceControl.Transaction, parent: SurfaceControl, rotateDelta: Int, - displayW: Float, - displayH: Float + parentW: Float, + parentH: Float ) { - var rotateDelta = rotateDelta if (rotateDelta == 0) return - // We want to counter-rotate, so subtract from 4 - rotateDelta = 4 - (rotateDelta + 4) % 4 - surface = SurfaceControl.Builder() + val surface = SurfaceControl.Builder() .setName("Transition Unrotate") .setContainerLayer() .setParent(parent) .build() - // column-major - when (rotateDelta) { - 1 -> { - t.setMatrix(surface, 0f, 1f, -1f, 0f) - t.setPosition(surface!!, displayW, 0f) - } - 2 -> { - t.setMatrix(surface, -1f, 0f, 0f, -1f) - t.setPosition(surface!!, displayW, displayH) - } - 3 -> { - t.setMatrix(surface, 0f, -1f, 1f, 0f) - t.setPosition(surface!!, 0f, displayH) - } - } + // Rotate forward to match the new rotation (rotateDelta is the forward rotation the + // parent already took). Child surfaces will be in the old rotation relative to the new + // parent rotation, so we need to forward-rotate the child surfaces to match. + RotationUtils.rotateSurface(t, surface, rotateDelta) + val tmpPt = Point(0, 0) + // parentW/H are the size in the END rotation, the rotation utilities expect the + // starting size. So swap them if necessary + val flipped = rotateDelta % 2 != 0 + val pw = if (flipped) parentH else parentW + val ph = if (flipped) parentW else parentH + RotationUtils.rotatePoint(tmpPt, rotateDelta, pw.toInt(), ph.toInt()) + t.setPosition(surface, tmpPt.x.toFloat(), tmpPt.y.toFloat()) t.show(surface) } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 092cff384a805..95f46b15eead3 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1054,6 +1054,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier); mAppTransition.registerListenerLocked(mFixedRotationTransitionListener); mAppTransitionController = new AppTransitionController(mWmService, this); + mTransitionController.registerLegacyListener(mFixedRotationTransitionListener); mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this); final InputChannel inputChannel = mWmService.mInputManager.monitorInput( @@ -1111,6 +1112,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp t.remove(mSurfaceControl); mLastSurfacePosition.set(0, 0); + mLastDeltaRotation = Surface.ROTATION_0; configureSurfaces(t); @@ -1604,7 +1606,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ @Rotation int rotationForActivityInDifferentOrientation(@NonNull ActivityRecord r) { - if (mTransitionController.isShellTransitionsEnabled()) { + if (mTransitionController.useShellTransitionsRotation()) { return ROTATION_UNDEFINED; } if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) { @@ -1645,18 +1647,30 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // It has been set and not yet finished. return true; } - if (!r.occludesParent() || r.isVisible()) { + if (!r.occludesParent()) { // While entering or leaving a translucent or floating activity (e.g. dialog style), // there is a visible activity in the background. Then it still needs rotation animation // to cover the activity configuration change. return false; } + if (mTransitionController.isShellTransitionsEnabled() + ? mTransitionController.wasVisibleAtStart(r) : r.isVisible()) { + // If activity is already visible, then it's not "launching". However, shell-transitions + // will make it visible immediately. + return false; + } if (checkOpening) { - if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) { - // Apply normal rotation animation in case of the activity set different requested - // orientation without activity switch, or the transition is unset due to starting - // window was transferred ({@link #mSkipAppTransitionAnimation}). - return false; + if (mTransitionController.isShellTransitionsEnabled()) { + if (!mTransitionController.isCollecting(r)) { + return false; + } + } else { + if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) { + // Apply normal rotation animation in case of the activity set different + // requested orientation without activity switch, or the transition is unset due + // to starting window was transferred ({@link #mSkipAppTransitionAnimation}). + return false; + } } if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) { // If the activity is executing or has done the lifecycle callback, use normal @@ -1733,15 +1747,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) { + final boolean useAsyncRotation = !mTransitionController.isShellTransitionsEnabled(); if (mFixedRotationLaunchingApp == null && r != null) { - mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation); - startAsyncRotation( - // Delay the hide animation to avoid blinking by clicking navigation bar that - // may toggle fixed rotation in a short time. - r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */); + mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, + rotation); + if (useAsyncRotation) { + startAsyncRotation( + // Delay the hide animation to avoid blinking by clicking navigation bar + // that may toggle fixed rotation in a short time. + r == mFixedRotationTransitionListener.mAnimatingRecents); + } } else if (mFixedRotationLaunchingApp != null && r == null) { mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this); - finishAsyncRotationIfPossible(); + if (useAsyncRotation) finishAsyncRotationIfPossible(); } mFixedRotationLaunchingApp = r; } @@ -1760,7 +1778,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (prevRotatedLaunchingApp != null && prevRotatedLaunchingApp.getWindowConfiguration().getRotation() == rotation // It is animating so we can expect there will have a transition callback. - && prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS)) { + && (prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS) + || mTransitionController.inTransition(prevRotatedLaunchingApp))) { // It may be the case that multiple activities launch consecutively. Because their // rotation are the same, the transformed state can be shared to avoid duplicating // the heavy operations. This also benefits that the states of multiple activities @@ -1798,6 +1817,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } // Update directly because the app which will change the orientation of display is ready. if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) { + mTransitionController.setSeamlessRotation(this); sendNewConfiguration(); return; } @@ -3129,6 +3149,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mChangingContainers.clear(); mUnknownAppVisibilityController.clear(); mAppTransition.removeAppTransitionTimeoutCallbacks(); + mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener); handleAnimatingStoppedAndTransition(); mWmService.stopFreezingDisplayLocked(); super.removeImmediately(); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index d86382d1858f4..26871c3dc76aa 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -209,6 +209,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return mTransientLaunches != null && mTransientLaunches.contains(activity); } + void setSeamlessRotation(@NonNull WindowContainer wc) { + final ChangeInfo info = mChanges.get(wc); + if (info == null) return; + info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION; + } + @VisibleForTesting int getSyncId() { return mSyncId; @@ -1122,6 +1128,15 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // hardware-screen-level surfaces. return asDC.getWindowingLayer(); } + if (!wc.mTransitionController.useShellTransitionsRotation()) { + final WindowToken asToken = wc.asWindowToken(); + if (asToken != null) { + // WindowTokens can have a fixed-rotation applied to them. In the current + // implementation this fact is hidden from the player, so we must create a leash. + final SurfaceControl leash = asToken.getOrCreateFixedRotationLeash(); + if (leash != null) return leash; + } + } return wc.getSurfaceControl(); } @@ -1224,6 +1239,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final ActivityRecord topMostActivity = task.getTopMostActivity(); change.setAllowEnterPip(topMostActivity != null && topMostActivity.checkEnterPictureInPictureAppOpsState()); + } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) { + change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS); } final ActivityRecord activityRecord = target.asActivityRecord(); if (activityRecord != null) { @@ -1337,6 +1354,21 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe @VisibleForTesting static class ChangeInfo { + private static final int FLAG_NONE = 0; + + /** + * When set, the associated WindowContainer has been explicitly requested to be a + * seamless rotation. This is currently only used by DisplayContent during fixed-rotation. + */ + private static final int FLAG_SEAMLESS_ROTATION = 1; + + @IntDef(prefix = { "FLAG_" }, value = { + FLAG_NONE, + FLAG_SEAMLESS_ROTATION + }) + @Retention(RetentionPolicy.SOURCE) + @interface Flag {} + // Usually "post" change state. WindowContainer mParent; @@ -1350,6 +1382,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe int mRotation = ROTATION_UNDEFINED; @ActivityInfo.Config int mKnownConfigChanges; + /** These are just extra info. They aren't used for change-detection. */ + @Flag int mFlags = FLAG_NONE; + ChangeInfo(@NonNull WindowContainer origState) { mVisible = origState.isVisibleRequested(); mWindowingMode = origState.getWindowingMode(); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 60307ce220007..3d9d824fe075c 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -34,6 +34,7 @@ import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.SystemClock; +import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -58,6 +59,10 @@ import java.util.function.LongConsumer; class TransitionController { private static final String TAG = "TransitionController"; + /** Whether to use shell-transitions rotation instead of fixed-rotation. */ + private static final boolean SHELL_TRANSITIONS_ROTATION = + SystemProperties.getBoolean("persist.debug.shell_transit_rotate", false); + /** The same as legacy APP_TRANSITION_TIMEOUT_MS. */ private static final int DEFAULT_TIMEOUT_MS = 5000; /** Less duration for CHANGE type because it does not involve app startup. */ @@ -203,6 +208,11 @@ class TransitionController { return mTransitionPlayer != null; } + /** @return {@code true} if using shell-transitions rotation instead of fixed-rotation. */ + boolean useShellTransitionsRotation() { + return isShellTransitionsEnabled() && SHELL_TRANSITIONS_ROTATION; + } + /** * @return {@code true} if transition is actively collecting changes. This is {@code false} * once a transition is playing @@ -260,6 +270,21 @@ class TransitionController { return false; } + /** + * Temporary work-around to deal with integration of legacy fixed-rotation. Returns whether + * the activity was visible before the collecting transition. + * TODO: at-least replace the polling mechanism. + */ + boolean wasVisibleAtStart(@NonNull ActivityRecord ar) { + if (mCollectingTransition == null) return ar.isVisible(); + final Transition.ChangeInfo ci = mCollectingTransition.mChanges.get(ar); + if (ci == null) { + // not part of transition, so use current state. + return ar.isVisible(); + } + return ci.mVisible; + } + @WindowManager.TransitionType int getCollectingTransitionType() { return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE; @@ -484,6 +509,11 @@ class TransitionController { } } + void setSeamlessRotation(@NonNull WindowContainer wc) { + if (mCollectingTransition == null) return; + mCollectingTransition.setSeamlessRotation(wc); + } + void legacyDetachNavigationBarFromApp(@NonNull IBinder token) { final Transition transition = Transition.fromBinder(token); if (transition == null || !mPlayingTransitions.contains(transition)) { diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 36bb55e5f557d..6ee30bb956f09 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -111,6 +111,14 @@ class WallpaperWindowToken extends WindowToken { changed = true; } if (mTransitionController.isShellTransitionsEnabled()) { + // Apply legacy fixed rotation to wallpaper if it is becoming visible + if (!mTransitionController.useShellTransitionsRotation() && changed && visible) { + final WindowState wallpaperTarget = + mDisplayContent.mWallpaperController.getWallpaperTarget(); + if (wallpaperTarget != null && wallpaperTarget.mToken.hasFixedRotationTransform()) { + linkFixedRotationTransform(wallpaperTarget.mToken); + } + } return changed; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 8a373bf5c09c6..1bd305ec6bd1e 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -78,13 +78,14 @@ import android.os.Trace; import android.util.ArraySet; import android.util.Pair; import android.util.Pools; +import android.util.RotationUtils; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; -import android.view.InsetsState; import android.view.MagnificationSpec; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; +import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Builder; import android.view.SurfaceControlViewHost; @@ -197,6 +198,7 @@ class WindowContainer extends ConfigurationContainer< private final Point mTmpPos = new Point(); protected final Point mLastSurfacePosition = new Point(); + protected @Surface.Rotation int mLastDeltaRotation = Surface.ROTATION_0; /** Total number of elements in this subtree, including our own hierarchy element. */ private int mTreeWeight = 1; @@ -473,6 +475,7 @@ class WindowContainer extends ConfigurationContainer< t.remove(mSurfaceControl); // Clear the last position so the new SurfaceControl will get correct position mLastSurfacePosition.set(0, 0); + mLastDeltaRotation = Surface.ROTATION_0; final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null) .setContainerLayer() @@ -644,6 +647,7 @@ class WindowContainer extends ConfigurationContainer< getSyncTransaction().remove(mSurfaceControl); setSurfaceControl(null); mLastSurfacePosition.set(0, 0); + mLastDeltaRotation = Surface.ROTATION_0; scheduleAnimation(); } if (mOverlayHost != null) { @@ -3127,12 +3131,43 @@ class WindowContainer extends ConfigurationContainer< } getRelativePosition(mTmpPos); - if (mTmpPos.equals(mLastSurfacePosition)) { + final int deltaRotation = getRelativeDisplayRotation(); + if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) { return; } t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); + // set first, since we don't want rotation included in this (for now). mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y); + + if (mTransitionController.isShellTransitionsEnabled() + && !mTransitionController.useShellTransitionsRotation()) { + if (deltaRotation != Surface.ROTATION_0) { + updateSurfaceRotation(t, deltaRotation, null /* positionLeash */); + } else if (deltaRotation != mLastDeltaRotation) { + t.setMatrix(mSurfaceControl, 1, 0, 0, 1); + } + } + mLastDeltaRotation = deltaRotation; + } + + /** + * Updates the surface transform based on a difference in displayed-rotation from its parent. + * @param positionLeash If non-null, the rotated position will be set on this surface instead + * of the window surface. {@see WindowToken#getOrCreateFixedRotationLeash}. + */ + protected void updateSurfaceRotation(Transaction t, @Surface.Rotation int deltaRotation, + @Nullable SurfaceControl positionLeash) { + // parent must be non-null otherwise deltaRotation would be 0. + RotationUtils.rotateSurface(t, mSurfaceControl, deltaRotation); + mTmpPos.set(mLastSurfacePosition.x, mLastSurfacePosition.y); + final Rect parentBounds = getParent().getBounds(); + final boolean flipped = (deltaRotation % 2) != 0; + RotationUtils.rotatePoint(mTmpPos, deltaRotation, + flipped ? parentBounds.height() : parentBounds.width(), + flipped ? parentBounds.width() : parentBounds.height()); + t.setPosition(positionLeash != null ? positionLeash : mSurfaceControl, + mTmpPos.x, mTmpPos.y); } @VisibleForTesting @@ -3170,6 +3205,16 @@ class WindowContainer extends ConfigurationContainer< } } + /** @return the difference in displayed-rotation from parent. */ + @Surface.Rotation + int getRelativeDisplayRotation() { + final WindowContainer parent = getParent(); + if (parent == null) return Surface.ROTATION_0; + final int rotation = getWindowConfiguration().getDisplayRotation(); + final int parentRotation = parent.getWindowConfiguration().getDisplayRotation(); + return RotationUtils.deltaRotation(rotation, parentRotation); + } + void waitForAllWindowsDrawn() { forAllWindows(w -> { w.requestDrawIfNeeded(mWaitingForDrawn); diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index db231f6a17473..f3980342ec2e4 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -44,6 +44,7 @@ import android.os.IBinder; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.InsetsState; +import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManager.LayoutParams.WindowType; import android.window.WindowContext; @@ -99,6 +100,7 @@ class WindowToken extends WindowContainer { final boolean mOwnerCanManageAppTokens; private FixedRotationTransformState mFixedRotationTransformState; + private SurfaceControl mFixedRotationTransformLeash; /** * When set to {@code true}, this window token is created from {@link WindowContext} @@ -521,8 +523,14 @@ class WindowToken extends WindowContainer { if (state == null) { return; } - - state.resetTransform(); + if (!mTransitionController.isShellTransitionsEnabled()) { + state.resetTransform(); + } else { + // Remove all the leashes + for (int i = state.mAssociatedTokens.size() - 1; i >= 0; --i) { + state.mAssociatedTokens.get(i).removeFixedRotationLeash(); + } + } // Clear the flag so if the display will be updated to the same orientation, the transform // won't take effect. state.mIsTransforming = false; @@ -553,6 +561,43 @@ class WindowToken extends WindowContainer { onCancelFixedRotationTransform(originalRotation); } + /** + * Gets or creates a leash which can be treated as if this window is not-rotated. This is + * used to adapt mismatched-rotation surfaces into code that expects all windows to share + * the same rotation. + */ + @Nullable + SurfaceControl getOrCreateFixedRotationLeash() { + if (!mTransitionController.isShellTransitionsEnabled()) return null; + final int rotation = getRelativeDisplayRotation(); + if (rotation == Surface.ROTATION_0) return mFixedRotationTransformLeash; + if (mFixedRotationTransformLeash != null) return mFixedRotationTransformLeash; + + final SurfaceControl.Transaction t = getSyncTransaction(); + final SurfaceControl leash = makeSurface().setContainerLayer() + .setParent(getParentSurfaceControl()) + .setName(getSurfaceControl() + " - rotation-leash") + .setHidden(false) + .setEffectLayer() + .setCallsite("WindowToken.getOrCreateFixedRotationLeash") + .build(); + t.setPosition(leash, mLastSurfacePosition.x, mLastSurfacePosition.y); + t.show(leash); + t.reparent(getSurfaceControl(), leash); + t.setAlpha(getSurfaceControl(), 1.f); + mFixedRotationTransformLeash = leash; + updateSurfaceRotation(t, rotation, mFixedRotationTransformLeash); + return mFixedRotationTransformLeash; + } + + void removeFixedRotationLeash() { + if (mFixedRotationTransformLeash == null) return; + final SurfaceControl.Transaction t = getSyncTransaction(); + t.reparent(getSurfaceControl(), getParentSurfaceControl()); + t.remove(mFixedRotationTransformLeash); + mFixedRotationTransformLeash = null; + } + /** * It is called when the window is using fixed rotation transform, and before display applies * the same rotation, the rotation change for display is canceled, e.g. the orientation from @@ -575,7 +620,7 @@ class WindowToken extends WindowContainer { @Override void updateSurfacePosition(SurfaceControl.Transaction t) { super.updateSurfacePosition(t); - if (isFixedRotationTransforming()) { + if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) { 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. @@ -587,6 +632,20 @@ class WindowToken extends WindowContainer { } } + @Override + protected void updateSurfaceRotation(SurfaceControl.Transaction t, + @Surface.Rotation int deltaRotation, SurfaceControl positionLeash) { + final ActivityRecord r = asActivityRecord(); + if (r != null) { + final Task rootTask = r.getRootTask(); + // Don't transform the activity in PiP because the PiP task organizer will handle it. + if (rootTask != null && rootTask.inPinnedWindowingMode()) { + return; + } + } + super.updateSurfaceRotation(t, deltaRotation, positionLeash); + } + @Override void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) { // Keep the transformed position to animate because the surface will show in different