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

Commit 42e5e083 authored by Riddle Hsu's avatar Riddle Hsu Committed by Android (Google) Code Review
Browse files

Merge "Use seamless rotation if it is requested by display" into tm-qpr-dev

parents d5d92765 7e761980
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;
@@ -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));
        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