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

Commit 1b24a6c2 authored by Gustav Sennton's avatar Gustav Sennton
Browse files

Remote transitions: only block display transitions with display updates

Animations for moving an app to another display are being implemented in
Launcher (QuickstepTransitionManager) through remote transitions.
This CL alters code that blocks certain remote transitions from being
passed from Shell to Launcher, so that we can implement those
transitions in Launcher:

When moving an app from the device display to the external display
through an external-display app launch, we end up with a transition that
contains a display change (FLAG_IS_DISPLAY). Before this CL,
RemoteTransitionHandler would ignore such transitions, because Launcher
doesn't have the permissions needed to make certain display changes.
With this CL, instead of ignoring all remote transitions that contain
FLAG_IS_DISPLAY changes, we now only ignore transitions with changes
that impact display attributes such a size or rotation.

Bug: 422333177
Flag: com.android.window.flags.enable_cross_displays_app_launch_transition
Test: RemoteTransitionHandlerTest
Change-Id: Ia8be5ce2614bf7e21dfb3867c1516079645ca605
parent 3dc7bbbc
Loading
Loading
Loading
Loading
+17 −1
Original line number Diff line number Diff line
@@ -105,6 +105,21 @@ public class TransitionUtil {
        return false;
    }

    /**
     * Returns {@code true} if the transition has a display change that is not just an order-change.
     */
    public static boolean hasNonOrderOnlyDisplayChange(@NonNull TransitionInfo info) {
        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            final TransitionInfo.Change change = info.getChanges().get(i);
            if (change.getMode() == TRANSIT_CHANGE
                    && change.hasFlags(FLAG_IS_DISPLAY)
                    && !isOrderOnly(change)) {
                return true;
            }
        }
        return false;
    }

    /** Returns `true` if `change` is a wallpaper. */
    public static boolean isWallpaper(TransitionInfo.Change change) {
        return (change.getTaskInfo() == null)
@@ -135,7 +150,8 @@ public class TransitionUtil {
                && (change.getFlags() & FLAG_MOVED_TO_TOP) != 0
                && change.getStartAbsBounds().equals(change.getEndAbsBounds())
                && (change.getLastParent() == null
                        || change.getLastParent().equals(change.getParent()));
                        || change.getLastParent().equals(change.getParent()))
                && (change.getStartRotation() == change.getEndRotation());
    }

    /**
+8 −1
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;

import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.TransitionUtil;
@@ -117,7 +118,13 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        if (!Transitions.SHELL_TRANSITIONS_ROTATION && TransitionUtil.hasDisplayChange(info)) {
        // Ignore the remote transition if the display changes size or rotation, since Launcher
        // doesn't have the necessary permission to deal with such changes.
        final boolean ignoreTransition = !Transitions.SHELL_TRANSITIONS_ROTATION
                && (Flags.enableCrossDisplaysAppLaunchTransition()
                        ? TransitionUtil.hasNonOrderOnlyDisplayChange(info) :
                        TransitionUtil.hasDisplayChange(info));
        if (ignoreTransition) {
            // Note that if the remote doesn't have permission ACCESS_SURFACE_FLINGER, some
            // operations of the start transaction may be ignored.
            mRequestedRemotes.remove(transition);
+55 −1
Original line number Diff line number Diff line
@@ -16,14 +16,17 @@

package com.android.wm.shell.transition

import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.Surface
import android.view.WindowManager
import android.window.RemoteTransition
import android.window.TransitionFilter
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.window.flags.Flags
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestSyncExecutor
import org.junit.Assert.assertFalse
@@ -100,6 +103,57 @@ class RemoteTransitionHandlerTest : ShellTestCase() {
        assertTrue(isHandled)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_CROSS_DISPLAYS_APP_LAUNCH_TRANSITION)
    fun startAnimation_remoteTransition_displayRotationChange_returnsFalse() {
        val request =
            TransitionRequestInfo(WindowManager.TRANSIT_CHANGE, null, testRemoteTransition)
        handler.addFiltered(TransitionFilter(), testRemoteTransition)
        handler.handleRequest(mock(), request)
        val transitionInfo = TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply {
            addChange(createDisplayChange().apply {
                setRotation(Surface.ROTATION_0, Surface.ROTATION_90)
            })
        }

        val isHandled = handler.startAnimation(
            /* transition= */ testRemoteTransition.remoteTransition.asBinder(),
            /* info= */ transitionInfo,
            /* startTransaction= */ mock(),
            /* finishTransaction= */ mock(),
            /* finishCallback= */ {},
        )

        assertFalse(isHandled)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_CROSS_DISPLAYS_APP_LAUNCH_TRANSITION)
    fun startAnimation_remoteTransition_orderOnlyDisplayChange_returnsTrue() {
        val request = TransitionRequestInfo(WindowManager.TRANSIT_OPEN, null, testRemoteTransition)
        handler.addFiltered(TransitionFilter(), testRemoteTransition)
        handler.handleRequest(mock(), request)
        val transitionInfo = TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply {
            addChange(createDisplayChange())
        }

        val isHandled = handler.startAnimation(
            /* transition= */ testRemoteTransition.remoteTransition.asBinder(),
            /* info= */ transitionInfo,
            /* startTransaction= */ mock(),
            /* finishTransaction= */ mock(),
            /* finishCallback= */ {},
        )

        assertTrue(isHandled)
    }

    private fun createDisplayChange(): TransitionInfo.Change =
        TransitionInfo.Change(/* container= */ mock(), /* leash= */ mock()).apply {
            mode = WindowManager.TRANSIT_CHANGE
            flags = TransitionInfo.FLAG_IS_DISPLAY or TransitionInfo.FLAG_MOVED_TO_TOP
        }

    private fun createTransitionInfo(
        type: Int = WindowManager.TRANSIT_OPEN,
        changeMode: Int = WindowManager.TRANSIT_CLOSE,