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
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fd23d14..9f9854e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -205,6 +205,7 @@
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
+ RecentsTransitionHandler recentsTransitionHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
if (DesktopModeStatus.isEnabled()) {
return new DesktopModeWindowDecorViewModel(
@@ -218,6 +219,7 @@
syncQueue,
transitions,
desktopTasksController,
+ recentsTransitionHandler,
rootTaskDisplayAreaOrganizer);
}
return new CaptionWindowDecorViewModel(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 7ae0666..0e617ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -134,6 +134,9 @@
}
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);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
index 804dcc8..e8733eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
@@ -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) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index b4e1818..949ee81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -21,6 +21,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 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.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 @@
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 @@
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
+ RecentsTransitionHandler recentsTransitionHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
) {
this(
@@ -148,6 +153,7 @@
syncQueue,
transitions,
desktopTasksController,
+ recentsTransitionHandler,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
@@ -167,6 +173,7 @@
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
+ RecentsTransitionHandler recentsTransitionHandler,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
@@ -182,6 +189,7 @@
mSyncQueue = syncQueue;
mTransitions = transitions;
mDesktopTasksController = desktopTasksController;
+ mRecentsTransitionHandler = recentsTransitionHandler;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
@@ -194,6 +202,12 @@
private void onInit() {
mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
+ mRecentsTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
+ @Override
+ public void onTransitionStarted(IBinder transition) {
+ onRecentsTransitionStarted(transition);
+ }
+ });
}
@Override
@@ -319,6 +333,16 @@
}
}
+ 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{
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index d8afe68..249f23a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -22,6 +22,7 @@
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.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.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 @@
@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 @@
mSyncQueue,
mTransitions,
Optional.of(mDesktopTasksController),
+ mRecentsTransitionHandler,
mDesktopModeWindowDecorFactory,
mMockInputMonitorFactory,
mTransactionFactory,
@@ -143,6 +153,8 @@
mDesktopModeWindowDecoration.mDisplay = mDisplay;
doReturn(Display.DEFAULT_DISPLAY).when(mDisplay).getDisplayId();
+
+ mShellInit.init();
}
@Test
@@ -296,6 +308,36 @@
.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);