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
diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java
index 0ac2c9c..cebdbf6 100644
--- a/core/java/android/util/RotationUtils.java
+++ b/core/java/android/util/RotationUtils.java
@@ -24,8 +24,10 @@
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,13 +123,64 @@
/** @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 5dbe03e..826eb30 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 @@
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 ddf01a8..34d98ee 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 @@
"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 @@
// 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 @@
" 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 @@
}
}
- // 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 7f8eaf1..7e95814 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 @@
* 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 0f4a06f..dbf93b4 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 @@
.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 7ef0901..2e9a16f 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.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 @@
* 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 092cff3..95f46b15 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 @@
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 @@
t.remove(mSurfaceControl);
mLastSurfacePosition.set(0, 0);
+ mLastDeltaRotation = Surface.ROTATION_0;
configureSurfaces(t);
@@ -1604,7 +1606,7 @@
*/
@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 @@
// 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 @@
}
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 @@
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 @@
}
// 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 @@
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 d86382d..26871c3 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 @@
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 @@
// 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 @@
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 @@
@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 @@
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 60307ce..3d9d824 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.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 @@
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 @@
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 @@
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 @@
}
}
+ 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 36bb55e..6ee30bb 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 @@
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 8a373bf..1bd305e 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.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 @@
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 @@
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 @@
getSyncTransaction().remove(mSurfaceControl);
setSurfaceControl(null);
mLastSurfacePosition.set(0, 0);
+ mLastDeltaRotation = Surface.ROTATION_0;
scheduleAnimation();
}
if (mOverlayHost != null) {
@@ -3127,12 +3131,43 @@
}
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 @@
}
}
+ /** @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 db231f6..f398034 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.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 @@
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 @@
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;
@@ -554,6 +562,43 @@
}
/**
+ * 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
* sensor is updated to previous direction.
@@ -575,7 +620,7 @@
@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.
@@ -588,6 +633,20 @@
}
@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
// rotation than the animator of leash.