Merge "Hook up fixed-rotation logic to shell transitions"
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user