Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7e761980 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

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
parent 9de781b8
Loading
Loading
Loading
Loading
+65 −80
Original line number Diff line number Diff line
@@ -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;
                            taskInfo.taskId);
                    allTasksSeamless = false;
                } else if (isTopTask) {
                    allTasksSeamless = true;
                }
            }
        }

        if (!allTasksSeamless || rejectSeamless) {
            return animationHint;
        }

                // 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;
        // This is the only way to get display-id currently, so check display capabilities here.
        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) {
                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 false;
            return animationHint;
        }

                    // 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 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()
                                    && (change.getStartAbsBounds().width()
                                            != change.getStartAbsBounds().height())))) {
                        && (displayChange.getStartAbsBounds().width()
                                != displayChange.getStartAbsBounds().height())))) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                    "  nav bar changes sides, so not seamless.");
                        return false;
                    }
            return animationHint;
        }
            }
        }

        // 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;
        }
        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;
            }
        }
        return ROTATION_ANIMATION_ROTATE;
        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);
+39 −25
Original line number Diff line number Diff line
@@ -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));
        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(displayChange)
                .build();
        assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
                displayChange, noTask, displays));

        // 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(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
                        .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build())
                .addChange(displayChange)
                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
                        .setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build())
                .build();
        assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessUpsideDown, displays));
        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(new ChangeBuilder(TRANSIT_CHANGE).setFlags(
                        FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build())
                .addChange(displayChange)
                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
                        .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
                .build();
        assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessButAlert, 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())
                .build();
        assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, 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(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
                        .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
                .addChange(displayChange)
                // The animation hint of task will be ignored.
                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
                        .setRotate(ROTATION_ANIMATION_ROTATE).build())
                .build();
        assertTrue(DefaultTransitionHandler.isRotationSeamless(seamlessDisplay, displays));
        assertEquals(ROTATION_ANIMATION_SEAMLESS, DefaultTransitionHandler.getRotationAnimationHint(
                displayChange, seamlessDisplay, displays));
    }

    @Test