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

Commit bc8e52b6 authored by Graciela Wissen Putri's avatar Graciela Wissen Putri
Browse files

[2/2] Respect non resizeable activity's requested orientation

1. Listen to runtime requested orientation with
   onActivityRequestedOrientationChanged
2. Respect new activity launched from same task with different
   orientation in onTaskInfoChanged
3. Resize task to previous activity's orientation if different from
   closing top activity in onTaskInfoChanged

Flag: com.android.window.flags.respect_orientation_change_for_unresizeable
Fix: 353338503
Fix: 353335964
Test: atest DesktopActivityOrientationChangeHandlerTest
Change-Id: I580f4f51f017bec81d062244bc2d0325d34a17c5
parent c18e7827
Loading
Loading
Loading
Loading
+23 −2
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
@@ -237,7 +238,8 @@ public abstract class WMShellModule {
            InteractionJankMonitor interactionJankMonitor,
            AppToWebGenericLinksParser genericLinksParser,
            MultiInstanceHelper multiInstanceHelper,
            Optional<DesktopTasksLimiter> desktopTasksLimiter) {
            Optional<DesktopTasksLimiter> desktopTasksLimiter,
            Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler) {
        if (DesktopModeStatus.canEnterDesktopMode(context)) {
            return new DesktopModeWindowDecorViewModel(
                    context,
@@ -259,7 +261,8 @@ public abstract class WMShellModule {
                    interactionJankMonitor,
                    genericLinksParser,
                    multiInstanceHelper,
                    desktopTasksLimiter);
                    desktopTasksLimiter,
                    desktopActivityOrientationHandler);
        }
        return new CaptionWindowDecorViewModel(
                context,
@@ -675,6 +678,24 @@ public abstract class WMShellModule {
        return new DesktopModeTaskRepository();
    }

    @WMSingleton
    @Provides
    static Optional<DesktopActivityOrientationChangeHandler> provideActivityOrientationHandler(
            Context context,
            ShellInit shellInit,
            ShellTaskOrganizer shellTaskOrganizer,
            TaskStackListenerImpl taskStackListener,
            ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository
    ) {
        if (DesktopModeStatus.canEnterDesktopMode(context)) {
            return Optional.of(new DesktopActivityOrientationChangeHandler(
                    context, shellInit, shellTaskOrganizer, taskStackListener,
                    toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository));
        }
        return Optional.empty();
    }

    @WMSingleton
    @Provides
    static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver(
+112 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.desktopmode

import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo.ScreenOrientation
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.graphics.Rect
import android.util.Size
import android.window.WindowContainerTransaction
import com.android.window.flags.Flags
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.TaskStackListenerCallback
import com.android.wm.shell.common.TaskStackListenerImpl
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit

/** Handles task resizing to respect orientation change of non-resizeable activities in desktop. */
class DesktopActivityOrientationChangeHandler(
    context: Context,
    shellInit: ShellInit,
    private val shellTaskOrganizer: ShellTaskOrganizer,
    private val taskStackListener: TaskStackListenerImpl,
    private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler,
    private val taskRepository: DesktopModeTaskRepository,
) {

    init {
        if (DesktopModeStatus.canEnterDesktopMode(context)) {
            shellInit.addInitCallback({ onInit() }, this)
        }
    }

    private fun onInit() {
        taskStackListener.addListener(object : TaskStackListenerCallback {
            override fun onActivityRequestedOrientationChanged(
                taskId: Int,
                @ScreenOrientation requestedOrientation: Int
            ) {
                // Handle requested screen orientation changes at runtime.
                handleActivityOrientationChange(taskId, requestedOrientation)
            }
        })
    }

    /**
     * Triggered with onTaskInfoChanged to handle:
     * * New activity launching from same task with different orientation
     * * Top activity closing in same task with different orientation to previous activity
     */
    fun handleActivityOrientationChange(oldTask: RunningTaskInfo, newTask: RunningTaskInfo) {
        val newTopActivityInfo = newTask.topActivityInfo ?: return
        val oldTopActivityInfo = oldTask.topActivityInfo ?: return
        // Check if screen orientation is different from old task info so there is no duplicated
        // calls to handle runtime requested orientation changes.
        if (oldTopActivityInfo.screenOrientation != newTopActivityInfo.screenOrientation) {
            handleActivityOrientationChange(newTask.taskId, newTopActivityInfo.screenOrientation)
        }
    }

    private fun handleActivityOrientationChange(
        taskId: Int,
        @ScreenOrientation requestedOrientation: Int
    ) {
        if (!Flags.respectOrientationChangeForUnresizeable()) return
        val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return
        if (!isDesktopModeShowing(task.displayId) || !task.isFreeform || task.isResizeable) return

        val taskBounds = task.configuration.windowConfiguration.bounds
        val taskHeight = taskBounds.height()
        val taskWidth = taskBounds.width()
        if (taskWidth == taskHeight) return
        val orientation =
            if (taskWidth > taskHeight) ORIENTATION_LANDSCAPE else ORIENTATION_PORTRAIT

        // Non-resizeable activity requested opposite orientation.
        if (orientation == ORIENTATION_PORTRAIT
                && ActivityInfo.isFixedOrientationLandscape(requestedOrientation)
            || orientation == ORIENTATION_LANDSCAPE
                && ActivityInfo.isFixedOrientationPortrait(requestedOrientation)) {

            val finalSize = Size(taskHeight, taskWidth)
            // Use the center x as the resizing anchor point.
            val left = taskBounds.centerX() - finalSize.width / 2
            val right = left + finalSize.width
            val finalBounds = Rect(left, taskBounds.top, right, taskBounds.top + finalSize.height)

            val wct = WindowContainerTransaction().setBounds(task.token, finalBounds)
            resizeHandler.startTransition(wct)
        }
    }

    private fun isDesktopModeShowing(displayId: Int): Boolean =
        taskRepository.getVisibleTaskCount(displayId) > 0
}
 No newline at end of file
+12 −3
Original line number Diff line number Diff line
@@ -97,6 +97,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -166,6 +167,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
    private TaskOperations mTaskOperations;
    private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
    private final Transitions mTransitions;
    private final Optional<DesktopActivityOrientationChangeHandler>
            mActivityOrientationChangeHandler;

    private SplitScreenController mSplitScreenController;

@@ -215,7 +218,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            InteractionJankMonitor interactionJankMonitor,
            AppToWebGenericLinksParser genericLinksParser,
            MultiInstanceHelper multiInstanceHelper,
            Optional<DesktopTasksLimiter> desktopTasksLimiter
            Optional<DesktopTasksLimiter> desktopTasksLimiter,
            Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler
    ) {
        this(
                context,
@@ -241,7 +245,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                rootTaskDisplayAreaOrganizer,
                new SparseArray<>(),
                interactionJankMonitor,
                desktopTasksLimiter);
                desktopTasksLimiter,
                activityOrientationChangeHandler);
    }

    @VisibleForTesting
@@ -269,7 +274,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId,
            InteractionJankMonitor interactionJankMonitor,
            Optional<DesktopTasksLimiter> desktopTasksLimiter) {
            Optional<DesktopTasksLimiter> desktopTasksLimiter,
            Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler) {
        mContext = context;
        mMainExecutor = shellExecutor;
        mMainHandler = mainHandler;
@@ -297,6 +303,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                com.android.internal.R.string.config_systemUi);
        mInteractionJankMonitor = interactionJankMonitor;
        mDesktopTasksLimiter = desktopTasksLimiter;
        mActivityOrientationChangeHandler = activityOrientationChangeHandler;
        mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
            DesktopModeWindowDecoration decoration;
            RunningTaskInfo taskInfo;
@@ -388,6 +395,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            incrementEventReceiverTasks(taskInfo.displayId);
        }
        decoration.relayout(taskInfo);
        mActivityOrientationChangeHandler.ifPresent(handler ->
                handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
    }

    @Override
+263 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.desktopmode

import android.app.ActivityManager.RunningTaskInfo
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
import android.graphics.Rect
import android.os.Binder
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.window.flags.Flags.FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.TaskStackListenerImpl
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import kotlin.test.assertNotNull
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness

/**
 * Test class for {@link DesktopActivityOrientationChangeHandler}
 *
 * Usage: atest WMShellUnitTests:DesktopActivityOrientationChangeHandlerTest
 */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE)
class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
    @JvmField @Rule val setFlagsRule = SetFlagsRule()

    @Mock lateinit var testExecutor: ShellExecutor
    @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
    @Mock lateinit var transitions: Transitions
    @Mock lateinit var resizeTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
    @Mock lateinit var taskStackListener: TaskStackListenerImpl

    private lateinit var mockitoSession: StaticMockitoSession
    private lateinit var handler: DesktopActivityOrientationChangeHandler
    private lateinit var shellInit: ShellInit
    private lateinit var taskRepository: DesktopModeTaskRepository
    // Mock running tasks are registered here so we can get the list from mock shell task organizer.
    private val runningTasks = mutableListOf<RunningTaskInfo>()

    @Before
    fun setUp() {
        mockitoSession =
            mockitoSession()
                .strictness(Strictness.LENIENT)
                .spyStatic(DesktopModeStatus::class.java)
                .startMocking()
        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }

        shellInit = spy(ShellInit(testExecutor))
        taskRepository = DesktopModeTaskRepository()
        whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
        whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }

        handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
            taskStackListener, resizeTransitionHandler, taskRepository)

        shellInit.init()
    }

    @After
    fun tearDown() {
        mockitoSession.finishMocking()

        runningTasks.clear()
    }

    @Test
    fun instantiate_addInitCallback() {
        verify(shellInit).addInitCallback(any(), any<DesktopActivityOrientationChangeHandler>())
    }

    @Test
    fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() {
        whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
        clearInvocations(shellInit)

        handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
            taskStackListener, resizeTransitionHandler, taskRepository)

        verify(shellInit, never()).addInitCallback(any(),
            any<DesktopActivityOrientationChangeHandler>())
    }

    @Test
    fun handleActivityOrientationChange_resizeable_doNothing() {
        val task = setUpFreeformTask()

        taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
            SCREEN_ORIENTATION_LANDSCAPE)

        verify(resizeTransitionHandler, never()).startTransition(any(), any())
    }

    @Test
    fun handleActivityOrientationChange_nonResizeableFullscreen_doNothing() {
        val task = createFullscreenTask()
        task.isResizeable = false
        val activityInfo = ActivityInfo()
        activityInfo.screenOrientation = SCREEN_ORIENTATION_PORTRAIT
        task.topActivityInfo = activityInfo
        whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
        taskRepository.addActiveTask(DEFAULT_DISPLAY, task.taskId)
        taskRepository.updateTaskVisibility(DEFAULT_DISPLAY, task.taskId, visible = true)
        runningTasks.add(task)

        taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
            SCREEN_ORIENTATION_LANDSCAPE)

        verify(resizeTransitionHandler, never()).startTransition(any(), any())
    }

    @Test
    fun handleActivityOrientationChange_nonResizeablePortrait_requestSameOrientation_doNothing() {
        val task = setUpFreeformTask(isResizeable = false)
        val newTask = setUpFreeformTask(isResizeable = false,
            orientation = SCREEN_ORIENTATION_SENSOR_PORTRAIT)

        handler.handleActivityOrientationChange(task, newTask)

        verify(resizeTransitionHandler, never()).startTransition(any(), any())
    }

    @Test
    fun handleActivityOrientationChange_notInDesktopMode_doNothing() {
        val task = setUpFreeformTask(isResizeable = false)
        taskRepository.updateTaskVisibility(task.displayId, task.taskId, visible = false)

        taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
            SCREEN_ORIENTATION_LANDSCAPE)

        verify(resizeTransitionHandler, never()).startTransition(any(), any())
    }

    @Test
    fun handleActivityOrientationChange_nonResizeablePortrait_respectLandscapeRequest() {
        val task = setUpFreeformTask(isResizeable = false)
        val oldBounds = task.configuration.windowConfiguration.bounds
        val newTask = setUpFreeformTask(isResizeable = false,
            orientation = SCREEN_ORIENTATION_LANDSCAPE)

        handler.handleActivityOrientationChange(task, newTask)

        val wct = getLatestResizeDesktopTaskWct()
        val finalBounds = findBoundsChange(wct, newTask)
        assertNotNull(finalBounds)
        val finalWidth = finalBounds.width()
        val finalHeight = finalBounds.height()
        // Bounds is landscape.
        assertTrue(finalWidth > finalHeight)
        // Aspect ratio remains the same.
        assertEquals(oldBounds.height() / oldBounds.width(), finalWidth / finalHeight)
        // Anchor point for resizing is at the center.
        assertEquals(oldBounds.centerX(), finalBounds.centerX())
    }

    @Test
    fun handleActivityOrientationChange_nonResizeableLandscape_respectPortraitRequest() {
        val oldBounds = Rect(0, 0, 500, 200)
        val task = setUpFreeformTask(
            isResizeable = false, orientation = SCREEN_ORIENTATION_LANDSCAPE, bounds = oldBounds
        )
        val newTask = setUpFreeformTask(isResizeable = false, bounds = oldBounds)

        handler.handleActivityOrientationChange(task, newTask)

        val wct = getLatestResizeDesktopTaskWct()
        val finalBounds = findBoundsChange(wct, newTask)
        assertNotNull(finalBounds)
        val finalWidth = finalBounds.width()
        val finalHeight = finalBounds.height()
        // Bounds is portrait.
        assertTrue(finalHeight > finalWidth)
        // Aspect ratio remains the same.
        assertEquals(oldBounds.width() / oldBounds.height(), finalHeight / finalWidth)
        // Anchor point for resizing is at the center.
        assertEquals(oldBounds.centerX(), finalBounds.centerX())
    }

    private fun setUpFreeformTask(
        displayId: Int = DEFAULT_DISPLAY,
        isResizeable: Boolean = true,
        orientation: Int = SCREEN_ORIENTATION_PORTRAIT,
        bounds: Rect? = Rect(0, 0, 200, 500)
    ): RunningTaskInfo {
        val task = createFreeformTask(displayId, bounds)
        val activityInfo = ActivityInfo()
        activityInfo.screenOrientation = orientation
        task.topActivityInfo = activityInfo
        task.isResizeable = isResizeable
        whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
        taskRepository.addActiveTask(displayId, task.taskId)
        taskRepository.updateTaskVisibility(displayId, task.taskId, visible = true)
        taskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId)
        runningTasks.add(task)
        return task
    }

    private fun getLatestResizeDesktopTaskWct(
        currentBounds: Rect? = null
    ): WindowContainerTransaction {
        val arg: ArgumentCaptor<WindowContainerTransaction> =
            ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
        verify(resizeTransitionHandler, atLeastOnce())
            .startTransition(capture(arg), eq(currentBounds))
        return arg.value
    }

    private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
        wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
}
 No newline at end of file
+5 −1
Original line number Diff line number Diff line
@@ -79,6 +79,7 @@ import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksLimiter
@@ -167,6 +168,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
    @Mock private lateinit var mockMultiInstanceHelper: MultiInstanceHelper
    @Mock private lateinit var mockTasksLimiter: DesktopTasksLimiter
    @Mock private lateinit var mockFreeformTaskTransitionStarter: FreeformTaskTransitionStarter
    @Mock private lateinit var mockActivityOrientationChangeHandler:
            DesktopActivityOrientationChangeHandler
    private lateinit var spyContext: TestableContext

    private val transactionFactory = Supplier<SurfaceControl.Transaction> {
@@ -220,7 +223,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
                mockRootTaskDisplayAreaOrganizer,
                windowDecorByTaskIdSpy,
                mockInteractionJankMonitor,
                Optional.of(mockTasksLimiter)
                Optional.of(mockTasksLimiter),
                Optional.of(mockActivityOrientationChangeHandler)
        )
        desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
        whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)