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 b142039e6aa94..c6492bee040e2 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; @@ -553,64 +554,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