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

Commit c1a30723 authored by Jorge Gil's avatar Jorge Gil
Browse files

Block relayout during Recents transition

The Recents transition takes ownership of the task leash to reposition
it and scale it if needed for the overview animation. During this time,
a focus change may trigger TaskListener#onTaskInfoChanged which causes
a window decor relayout that applies an incorrect position and crop.

Bug: 296921174
Test: swipe up from taskbar, verify freeform task doesn't jump out of
position and gets a bad crop applied.
Test: atest DesktopModeWindowDecorViewModel

Change-Id: If6af9ab76e9c7174a9bb8c9c97e4a79a9b52a775
parent 8f9a09b3
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -205,6 +205,7 @@ public abstract class WMShellModule {
            SyncTransactionQueue syncQueue,
            Transitions transitions,
            Optional<DesktopTasksController> desktopTasksController,
            RecentsTransitionHandler recentsTransitionHandler,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
        if (DesktopModeStatus.isEnabled()) {
            return new DesktopModeWindowDecorViewModel(
@@ -218,6 +219,7 @@ public abstract class WMShellModule {
                    syncQueue,
                    transitions,
                    desktopTasksController,
                    recentsTransitionHandler,
                    rootTaskDisplayAreaOrganizer);
        }
        return new CaptionWindowDecorViewModel(
+3 −0
Original line number Diff line number Diff line
@@ -134,6 +134,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
        }
        final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
                mixedHandler == null ? this : mixedHandler);
        for (int i = 0; i < mStateListeners.size(); i++) {
            mStateListeners.get(i).onTransitionStarted(transition);
        }
        if (mixer != null) {
            mixer.setRecentsTransition(transition);
        }
+5 −0
Original line number Diff line number Diff line
@@ -16,10 +16,15 @@

package com.android.wm.shell.recents;

import android.os.IBinder;

/** The listener for the events from {@link RecentsTransitionHandler}. */
public interface RecentsTransitionStateListener {

    /** Notifies whether the recents animation is running. */
    default void onAnimationStateChanged(boolean running) {
    }

    /** Notifies that a recents shell transition has started. */
    default void onTransitionStarted(IBinder transition) {}
}
+24 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
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 com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
@@ -73,6 +74,8 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.KeyguardChangeListener;
@@ -102,6 +105,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
    private final DisplayController mDisplayController;
    private final SyncTransactionQueue mSyncQueue;
    private final Optional<DesktopTasksController> mDesktopTasksController;
    private final RecentsTransitionHandler mRecentsTransitionHandler;
    private boolean mTransitionDragActive;

    private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -135,6 +139,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            SyncTransactionQueue syncQueue,
            Transitions transitions,
            Optional<DesktopTasksController> desktopTasksController,
            RecentsTransitionHandler recentsTransitionHandler,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
    ) {
        this(
@@ -148,6 +153,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                syncQueue,
                transitions,
                desktopTasksController,
                recentsTransitionHandler,
                new DesktopModeWindowDecoration.Factory(),
                new InputMonitorFactory(),
                SurfaceControl.Transaction::new,
@@ -167,6 +173,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            SyncTransactionQueue syncQueue,
            Transitions transitions,
            Optional<DesktopTasksController> desktopTasksController,
            RecentsTransitionHandler recentsTransitionHandler,
            DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
            InputMonitorFactory inputMonitorFactory,
            Supplier<SurfaceControl.Transaction> transactionFactory,
@@ -182,6 +189,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
        mSyncQueue = syncQueue;
        mTransitions = transitions;
        mDesktopTasksController = desktopTasksController;
        mRecentsTransitionHandler = recentsTransitionHandler;

        mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
        mInputMonitorFactory = inputMonitorFactory;
@@ -194,6 +202,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {

    private void onInit() {
        mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
        mRecentsTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
            @Override
            public void onTransitionStarted(IBinder transition) {
                onRecentsTransitionStarted(transition);
            }
        });
    }

    @Override
@@ -319,6 +333,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
        }
    }

    private void onRecentsTransitionStarted(IBinder transition) {
        // Block relayout on window decorations originating from #onTaskInfoChanges until the
        // animation completes to avoid interfering with the transition animation.
        for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
            final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
            decor.incrementRelayoutBlock();
            decor.addTransitionPausingRelayout(transition);
        }
    }

    private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
            implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
            DragDetector.MotionEventHandler{
+44 −2
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_UNDEFINED;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -38,6 +39,7 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.view.Choreographer;
import android.view.Display;
@@ -54,14 +56,18 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;

import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;

import java.util.ArrayList;
@@ -96,18 +102,21 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
    @Mock private SurfaceControl.Transaction mTransaction;
    @Mock private Display mDisplay;
    @Mock private ShellController mShellController;
    @Mock private ShellInit mShellInit;
    @Mock private ShellExecutor mShellExecutor;
    @Mock private DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
            mDesktopModeKeyguardChangeListener;
    @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
    @Mock private RecentsTransitionHandler mRecentsTransitionHandler;

    private final List<InputManager> mMockInputManagers = new ArrayList<>();

    private ShellInit mShellInit;
    private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel;

    @Before
    public void setUp() {
        mMockInputManagers.add(mInputManager);

        mShellInit = new ShellInit(mShellExecutor);
        mDesktopModeWindowDecorViewModel =
                new DesktopModeWindowDecorViewModel(
                        mContext,
@@ -120,6 +129,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
                        mSyncQueue,
                        mTransitions,
                        Optional.of(mDesktopTasksController),
                        mRecentsTransitionHandler,
                        mDesktopModeWindowDecorFactory,
                        mMockInputMonitorFactory,
                        mTransactionFactory,
@@ -143,6 +153,8 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {

        mDesktopModeWindowDecoration.mDisplay = mDisplay;
        doReturn(Display.DEFAULT_DISPLAY).when(mDisplay).getDisplayId();

        mShellInit.init();
    }

    @Test
@@ -296,6 +308,36 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
                .create(any(), any(), any(), any(), any(), any(), any(), any(), any());
    }

    @Test
    public void testRelayoutBlockedDuringRecentsTransition() throws Exception {
        final ArgumentCaptor<RecentsTransitionStateListener> recentsCaptor =
                ArgumentCaptor.forClass(RecentsTransitionStateListener.class);
        verify(mRecentsTransitionHandler).addTransitionStateListener(recentsCaptor.capture());

        final IBinder transition = mock(IBinder.class);
        final DesktopModeWindowDecoration decoration = mock(DesktopModeWindowDecoration.class);
        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
        final int taskId = 1;
        final SurfaceControl taskSurface = new SurfaceControl();
        final ActivityManager.RunningTaskInfo taskInfo =
                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
        doReturn(decoration).when(mDesktopModeWindowDecorFactory)
                .create(any(), any(), any(), eq(taskInfo), eq(taskSurface), any(), any(), any(),
                        any());

        runOnMainThread(() -> {
            // Make sure a window decorations exists first by launching a freeform task.
            mDesktopModeWindowDecorViewModel.onTaskOpening(
                    taskInfo, taskSurface, startT, finishT);
            // Now call back when as a Recents transition starts.
            recentsCaptor.getValue().onTransitionStarted(transition);
        });

        verify(decoration).incrementRelayoutBlock();
        verify(decoration).addTransitionPausingRelayout(transition);
    }

    private void runOnMainThread(Runnable r) throws Exception {
        final Handler mainHandler = new Handler(Looper.getMainLooper());
        final CountDownLatch latch = new CountDownLatch(1);