Merge "Hook up fixed-rotation logic to shell transitions"

This commit is contained in:
Evan Rosky
2022-02-10 19:51:45 +00:00
committed by Android (Google) Code Review
12 changed files with 341 additions and 60 deletions

View File

@@ -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.

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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)) {

View File

@@ -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;
}

View File

@@ -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<E extends 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<E extends 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<E extends 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<E extends 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<E extends 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);

View File

@@ -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<WindowState> {
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<WindowState> {
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<WindowState> {
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<WindowState> {
@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<WindowState> {
}
}
@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