Loading core/java/android/util/RotationUtils.java +54 −1 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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. Loading core/tests/coretests/src/android/util/RotationUtilsTest.java +21 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } } libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +5 −3 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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) { Loading @@ -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, Loading Loading @@ -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; } Loading libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java +20 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +7 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading
core/java/android/util/RotationUtils.java +54 −1 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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. Loading
core/tests/coretests/src/android/util/RotationUtilsTest.java +21 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } }
libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +5 −3 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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) { Loading @@ -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, Loading Loading @@ -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; } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java +20 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +7 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading