From 7e7619803d50d9e48b0c305909f67096ee3a5670 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Thu, 11 Aug 2022 23:30:55 -0600 Subject: [PATCH] Use seamless rotation if it is requested by display Apart from making the display change has the highest priority to decide whether to seamless rotation. Also Merge isRotationSeamless and getRotationAnimation to one method so the animation hint can be resolved in a single loop. Fixes: 242179536 Test: ShellTransitionTests#testShouldRotateSeamlessly Change-Id: I8ccf22e230fd4b6fab1e7aceb130fd876e9dc16c --- .../transition/DefaultTransitionHandler.java | 147 ++++++++---------- .../transition/ShellTransitionTests.java | 82 ++++++---- 2 files changed, 114 insertions(+), 115 deletions(-) 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 08eb2c9b6bbed..6c659667a4a79 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 @@ -64,6 +64,7 @@ import android.animation.ValueAnimator; import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.ActivityThread; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -203,14 +204,24 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } @VisibleForTesting - static boolean isRotationSeamless(@NonNull TransitionInfo info, - DisplayController displayController) { + static int getRotationAnimationHint(@NonNull TransitionInfo.Change displayChange, + @NonNull TransitionInfo info, @NonNull DisplayController displayController) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, - "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) { + "Display is changing, resolve the animation hint."); + // The explicit request of display has the highest priority. + if (displayChange.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + " display requests explicit seamless"); + return ROTATION_ANIMATION_SEAMLESS; + } + + boolean allTasksSeamless = false; + boolean rejectSeamless = false; + ActivityManager.RunningTaskInfo topTaskInfo = null; + int animationHint = ROTATION_ANIMATION_ROTATE; + // Traverse in top-to-bottom order so that the first task is top-most. + final int size = info.getChanges().size(); + for (int i = 0; i < size; ++i) { final TransitionInfo.Change change = info.getChanges().get(i); // Only look at changing things. showing/hiding don't need to rotate. @@ -223,95 +234,69 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " display has system alert windows, so not seamless."); - return false; + rejectSeamless = true; } - 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, " wallpaper is participating but isn't seamless."); - return false; + rejectSeamless = true; } } else if (change.getTaskInfo() != null) { - hasTask = true; + final int anim = change.getRotationAnimation(); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + final boolean isTopTask = topTaskInfo == null; + if (isTopTask) { + topTaskInfo = taskInfo; + if (anim != ROTATION_ANIMATION_UNSPECIFIED + && anim != ROTATION_ANIMATION_SEAMLESS) { + animationHint = anim; + } + } // We only enable seamless rotation if all the visible task windows requested it. - if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) { + if (anim != ROTATION_ANIMATION_SEAMLESS) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " task %s isn't requesting seamless, so not seamless.", - change.getTaskInfo().taskId); - return false; - } - - // This is the only way to get display-id currently, so we will check display - // capabilities here - if (!checkedDisplayLayout) { - // only need to check display once. - checkedDisplayLayout = true; - final DisplayLayout displayLayout = displayController.getDisplayLayout( - change.getTaskInfo().displayId); - // For the upside down rotation we don't rotate seamlessly as the navigation - // bar moves position. Note most apps (using orientation:sensor or user as - // opposed to fullSensor) will not enter the reverse portrait orientation, so - // actually the orientation won't change at all. - int upsideDownRotation = displayLayout.getUpsideDownRotation(); - if (change.getStartRotation() == upsideDownRotation - || change.getEndRotation() == upsideDownRotation) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, - " rotation involves upside-down portrait, so not seamless."); - return false; - } - - // If the navigation bar can't change sides, then it will jump when we change - // orientations and we don't rotate seamlessly - unless that is allowed, eg. - // with gesture navigation where the navbar is low-profile enough that this - // isn't very noticeable. - if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving() - && (!(displayLayout.navigationBarCanMove() - && (change.getStartAbsBounds().width() - != change.getStartAbsBounds().height())))) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, - " nav bar changes sides, so not seamless."); - return false; - } + taskInfo.taskId); + allTasksSeamless = false; + } else if (isTopTask) { + allTasksSeamless = true; } } } - // 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; + if (!allTasksSeamless || rejectSeamless) { + return animationHint; } - return false; - } - /** - * Gets the rotation animation for the topmost task. Assumes that seamless is checked - * elsewhere, so it will default SEAMLESS to ROTATE. - */ - private int getRotationAnimation(@NonNull TransitionInfo info) { - // Traverse in top-to-bottom order so that the first task is top-most - for (int i = 0; i < info.getChanges().size(); ++i) { - final TransitionInfo.Change change = info.getChanges().get(i); - - // Only look at changing things. showing/hiding don't need to rotate. - if (change.getMode() != TRANSIT_CHANGE) continue; - - // This container isn't rotating, so we can ignore it. - if (change.getEndRotation() == change.getStartRotation()) continue; - - if (change.getTaskInfo() != null) { - final int anim = change.getRotationAnimation(); - if (anim == ROTATION_ANIMATION_UNSPECIFIED - // Fallback animation for seamless should also be default. - || anim == ROTATION_ANIMATION_SEAMLESS) { - return ROTATION_ANIMATION_ROTATE; - } - return anim; - } + // This is the only way to get display-id currently, so check display capabilities here. + final DisplayLayout displayLayout = displayController.getDisplayLayout( + topTaskInfo.displayId); + // For the upside down rotation we don't rotate seamlessly as the navigation bar moves + // position. Note most apps (using orientation:sensor or user as opposed to fullSensor) + // will not enter the reverse portrait orientation, so actually the orientation won't + // change at all. + final int upsideDownRotation = displayLayout.getUpsideDownRotation(); + if (displayChange.getStartRotation() == upsideDownRotation + || displayChange.getEndRotation() == upsideDownRotation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + " rotation involves upside-down portrait, so not seamless."); + return animationHint; } - return ROTATION_ANIMATION_ROTATE; + + // If the navigation bar can't change sides, then it will jump when we change orientations + // and we don't rotate seamlessly - unless that is allowed, e.g. with gesture navigation + // where the navbar is low-profile enough that this isn't very noticeable. + if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving() + && (!(displayLayout.navigationBarCanMove() + && (displayChange.getStartAbsBounds().width() + != displayChange.getStartAbsBounds().height())))) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + " nav bar changes sides, so not seamless."); + return animationHint; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless."); + return ROTATION_ANIMATION_SEAMLESS; } @Override @@ -354,8 +339,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) { if (info.getType() == TRANSIT_CHANGE) { - isSeamlessDisplayChange = isRotationSeamless(info, mDisplayController); - final int anim = getRotationAnimation(info); + final int anim = getRotationAnimationHint(change, info, mDisplayController); + isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS; if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) { startRotationAnimation(startTransaction, change, info, anim, animations, onAnimFinish); 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 388792b63db37..09b018e6ecccc 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 @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; @@ -551,64 +552,77 @@ public class ShellTransitionTests extends ShellTestCase { final @Surface.Rotation int upsideDown = displays .getDisplayLayout(DEFAULT_DISPLAY).getUpsideDownRotation(); + TransitionInfo.Change displayChange = new ChangeBuilder(TRANSIT_CHANGE) + .setFlags(FLAG_IS_DISPLAY).setRotate().build(); + // Set non-square display so nav bar won't be allowed to move. + displayChange.getStartAbsBounds().set(0, 0, 1000, 2000); final TransitionInfo normalDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE) - .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate() - .build()) + .addChange(displayChange) .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo).setRotate().build()) .build(); - assertFalse(DefaultTransitionHandler.isRotationSeamless(normalDispRotate, displays)); + assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint( + displayChange, normalDispRotate, displays)); // Seamless if all tasks are seamless final TransitionInfo rotateSeamless = new TransitionInfoBuilder(TRANSIT_CHANGE) - .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate() - .build()) + .addChange(displayChange) .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo) .setRotate(ROTATION_ANIMATION_SEAMLESS).build()) .build(); - assertTrue(DefaultTransitionHandler.isRotationSeamless(rotateSeamless, displays)); + assertEquals(ROTATION_ANIMATION_SEAMLESS, DefaultTransitionHandler.getRotationAnimationHint( + displayChange, rotateSeamless, displays)); // Not seamless if there is PiP (or any other non-seamless task) final TransitionInfo pipDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE) - .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate() - .build()) + .addChange(displayChange) .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo) .setRotate(ROTATION_ANIMATION_SEAMLESS).build()) .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfoPip) .setRotate().build()) .build(); - assertFalse(DefaultTransitionHandler.isRotationSeamless(pipDispRotate, displays)); - - // Not seamless if one of rotations is upside-down - final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE) - .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY) - .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build()) - .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo) - .setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build()) - .build(); - assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessUpsideDown, displays)); - - // Not seamless if system alert windows - final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE) - .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags( - FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build()) - .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo) - .setRotate(ROTATION_ANIMATION_SEAMLESS).build()) - .build(); - assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessButAlert, displays)); + assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint( + displayChange, pipDispRotate, displays)); // Not seamless if there is no changed task. final TransitionInfo noTask = new TransitionInfoBuilder(TRANSIT_CHANGE) - .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY) - .setRotate().build()) + .addChange(displayChange) .build(); - assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, displays)); + assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint( + displayChange, noTask, displays)); - // Seamless if display is explicitly seamless. - final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE) - .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY) + // Not seamless if one of rotations is upside-down + displayChange = new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY) + .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build(); + final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE) + .addChange(displayChange) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo) + .setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build()) + .build(); + assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint( + displayChange, seamlessUpsideDown, displays)); + + // Not seamless if system alert windows + displayChange = new ChangeBuilder(TRANSIT_CHANGE) + .setFlags(FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build(); + final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE) + .addChange(displayChange) + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo) .setRotate(ROTATION_ANIMATION_SEAMLESS).build()) .build(); - assertTrue(DefaultTransitionHandler.isRotationSeamless(seamlessDisplay, displays)); + assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint( + displayChange, seamlessButAlert, displays)); + + // Seamless if display is explicitly seamless. + displayChange = new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY) + .setRotate(ROTATION_ANIMATION_SEAMLESS).build(); + final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE) + .addChange(displayChange) + // The animation hint of task will be ignored. + .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo) + .setRotate(ROTATION_ANIMATION_ROTATE).build()) + .build(); + assertEquals(ROTATION_ANIMATION_SEAMLESS, DefaultTransitionHandler.getRotationAnimationHint( + displayChange, seamlessDisplay, displays)); } @Test