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

Commit 0c742dfd authored by Maryam Dehaini's avatar Maryam Dehaini
Browse files

Add animation for fullscreen to desktop transition from handle menu

Adds a resize veil on top of the task and then animates the task down to
the correct size when a user transitions a task to freeform using the
handle menu.

Bug: 291759344
Test: Transition to freeform using handle menu
Change-Id: I695646f534ce1ddd4ee72c7b590b7068970e5ae4
parent 2e4f6656
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -172,9 +172,13 @@ class DesktopTasksController(
    }

    /** Move a task with given `taskId` to desktop */
    fun moveToDesktop(taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction()) {
    fun moveToDesktop(
            decor: DesktopModeWindowDecoration,
            taskId: Int,
            wct: WindowContainerTransaction = WindowContainerTransaction()
    ) {
        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
            task -> moveToDesktop(task, wct)
            task -> moveToDesktop(decor, task, wct)
        }
    }

@@ -182,6 +186,7 @@ class DesktopTasksController(
     * Move a task to desktop
     */
    fun moveToDesktop(
            decor: DesktopModeWindowDecoration,
            task: RunningTaskInfo,
            wct: WindowContainerTransaction = WindowContainerTransaction()
    ) {
@@ -195,7 +200,7 @@ class DesktopTasksController(
        addMoveToDesktopChanges(wct, task)

        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
            enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor)
        } else {
            shellTaskOrganizer.applyTransaction(wct)
        }
+197 −111
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.graphics.PointF;
@@ -36,6 +37,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration;
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;

import java.util.ArrayList;
@@ -60,6 +62,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
    private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
    private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
    private MoveToDesktopAnimator mMoveToDesktopAnimator;
    private DesktopModeWindowDecoration mDesktopModeWindowDecoration;

    public EnterDesktopTaskTransitionHandler(
            Transitions transitions) {
@@ -128,6 +131,18 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
                onAnimationEndCallback);
    }

    /**
     * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP
     * @param wct WindowContainerTransaction for transition
     * @param decor {@link DesktopModeWindowDecoration} of task being animated
     */
    public void moveToDesktop(@NonNull WindowContainerTransaction wct,
            DesktopModeWindowDecoration decor) {
        mDesktopModeWindowDecoration = decor;
        startTransition(Transitions.TRANSIT_MOVE_TO_DESKTOP, wct,
                null /* onAnimationEndCallback */);
    }

    @Override
    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startT,
@@ -167,8 +182,77 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
        }

        final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
        if (type == Transitions.TRANSIT_MOVE_TO_DESKTOP
                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
            return animateMoveToDesktop(change, startT, finishCallback);
        }

        if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
            return animateStartDragToDesktopMode(change, startT, finishT, finishCallback);
        }

        final Rect endBounds = change.getEndAbsBounds();
        if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
                && !endBounds.isEmpty()) {
            return animateFinalizeDragToDesktopMode(change, startT, finishT, finishCallback,
                    endBounds);
        }

        if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
            return animateCancelDragToDesktopMode(change, startT, finishT, finishCallback,
                    endBounds);
        }

        return false;
    }

    private boolean animateMoveToDesktop(
            @NonNull TransitionInfo.Change change,
            @NonNull SurfaceControl.Transaction startT,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        if (mDesktopModeWindowDecoration == null) {
            Slog.e(TAG, "Window Decoration is not available for this transition");
            return false;
        }

        final SurfaceControl leash = change.getLeash();
        final Rect startBounds = change.getStartAbsBounds();
        startT.setPosition(leash, startBounds.left, startBounds.right)
                .setWindowCrop(leash, startBounds.width(), startBounds.height())
                .show(leash);
        mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds);

        final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(),
                change.getStartAbsBounds(), change.getEndAbsBounds());
        animator.setDuration(FREEFORM_ANIMATION_DURATION);
        SurfaceControl.Transaction t = mTransactionSupplier.get();
        animator.addUpdateListener(animation -> {
            final Rect animationValue = (Rect) animator.getAnimatedValue();
            t.setPosition(leash, animationValue.left, animationValue.right)
                    .setWindowCrop(leash, animationValue.width(), animationValue.height())
                    .show(leash);
            mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue);
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mDesktopModeWindowDecoration.hideResizeVeil();
                mTransitions.getMainExecutor().execute(
                        () -> finishCallback.onTransitionFinished(null, null));
            }
        });
        animator.start();
        return true;
    }

    private boolean animateStartDragToDesktopMode(
            @NonNull TransitionInfo.Change change,
            @NonNull SurfaceControl.Transaction startT,
            @NonNull SurfaceControl.Transaction finishT,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
        // to null and we don't require an animation
        final SurfaceControl sc = change.getLeash();
@@ -193,10 +277,12 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
        return true;
    }

        Rect endBounds = change.getEndAbsBounds();
        if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
                && !endBounds.isEmpty()) {
    private boolean animateFinalizeDragToDesktopMode(
            @NonNull TransitionInfo.Change change,
            @NonNull SurfaceControl.Transaction startT,
            @NonNull SurfaceControl.Transaction finishT,
            @NonNull Transitions.TransitionFinishCallback finishCallback,
            @NonNull Rect endBounds) {
        // This Transition animates a task to freeform bounds after being dragged into freeform
        // mode and brings the remaining freeform tasks to front
        final SurfaceControl sc = change.getLeash();
@@ -245,9 +331,12 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
        animator.start();
        return true;
    }

        if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
    private boolean animateCancelDragToDesktopMode(
            @NonNull TransitionInfo.Change change,
            @NonNull SurfaceControl.Transaction startT,
            @NonNull SurfaceControl.Transaction finishT,
            @NonNull Transitions.TransitionFinishCallback finishCallback,
            @NonNull Rect endBounds) {
        // This Transition animates a task to fullscreen after being dragged from the status
        // bar and then released back into the status bar area
        final SurfaceControl sc = change.getLeash();
@@ -296,9 +385,6 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
        return true;
    }

        return false;
    }

    @Nullable
    @Override
    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+3 −0
Original line number Diff line number Diff line
@@ -168,6 +168,9 @@ public class Transitions implements RemoteCallable<Transitions>,
    public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE =
            WindowManager.TRANSIT_FIRST_CUSTOM + 14;

    /** Transition to animate task to desktop. */
    public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;

    private final WindowOrganizer mOrganizer;
    private final Context mContext;
    private final ShellExecutor mMainExecutor;
+4 −2
Original line number Diff line number Diff line
@@ -222,7 +222,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                && (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
                || info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
                || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
                || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) {
                || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
                || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
            mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
                    .addTransitionPausingRelayout(transition);
        }
@@ -356,7 +357,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                    // App sometimes draws before the insets from WindowDecoration#relayout have
                    // been added, so they must be added here
                    mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
                    mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
                    decoration.incrementRelayoutBlock();
                    mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
                }
                decoration.closeHandleMenu();
            } else if (id == R.id.fullscreen_button) {
+21 −9
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.After
@@ -92,6 +93,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
    @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
            ToggleResizeDesktopTaskTransitionHandler
    @Mock lateinit var launchAdjacentController: LaunchAdjacentController
    @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration

    private lateinit var mockitoSession: StaticMockitoSession
    private lateinit var controller: DesktopTasksController
@@ -276,8 +278,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
    fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
        val task = setUpFullscreenTask()
        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
        controller.moveToDesktop(task)
        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
        controller.moveToDesktop(desktopModeWindowDecoration, task)
        val wct = getLatestMoveToDesktopWct()
        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
            .isEqualTo(WINDOWING_MODE_FREEFORM)
    }
@@ -286,15 +288,15 @@ class DesktopTasksControllerTest : ShellTestCase() {
    fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
        val task = setUpFullscreenTask()
        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
        controller.moveToDesktop(task)
        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
        controller.moveToDesktop(desktopModeWindowDecoration, task)
        val wct = getLatestMoveToDesktopWct()
        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
                .isEqualTo(WINDOWING_MODE_UNDEFINED)
    }

    @Test
    fun moveToDesktop_nonExistentTask_doesNothing() {
        controller.moveToDesktop(999)
        controller.moveToDesktop(desktopModeWindowDecoration, 999)
        verifyWCTNotExecuted()
    }

@@ -305,9 +307,9 @@ class DesktopTasksControllerTest : ShellTestCase() {
        val fullscreenTask = setUpFullscreenTask()
        markTaskHidden(freeformTask)

        controller.moveToDesktop(fullscreenTask)
        controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTask)

        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
        with(getLatestMoveToDesktopWct()) {
            // Operations should include home task, freeform task
            assertThat(hierarchyOps).hasSize(3)
            assertReorderSequence(homeTask, freeformTask, fullscreenTask)
@@ -327,9 +329,9 @@ class DesktopTasksControllerTest : ShellTestCase() {
        val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
        markTaskHidden(freeformTaskSecond)

        controller.moveToDesktop(fullscreenTaskDefault)
        controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTaskDefault)

        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
        with(getLatestMoveToDesktopWct()) {
            // Check that hierarchy operations do not include tasks from second display
            assertThat(hierarchyOps.map { it.container })
                .doesNotContain(homeTaskSecond.token.asBinder())
@@ -715,6 +717,16 @@ class DesktopTasksControllerTest : ShellTestCase() {
        return arg.value
    }

    private fun getLatestMoveToDesktopWct(): WindowContainerTransaction {
        val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
        if (ENABLE_SHELL_TRANSITIONS) {
            verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
        } else {
            verify(shellTaskOrganizer).applyTransaction(arg.capture())
        }
        return arg.value
    }

    private fun verifyWCTNotExecuted() {
        if (ENABLE_SHELL_TRANSITIONS) {
            verify(transitions, never()).startTransition(anyInt(), any(), isNull())