Refactor KeyGestureEventHandler out of DesktopTasksController into its
own class DesktopModeKeyGestureHandler.
Test: atest DesktopModeKeyGestureHandlerTest, DesktopTasksControllerTest
Flag: EXEMPT refactor only
Fixes: 375356887
Change-Id: I0b166080878c3b79d8b551080386cdf08010adcf
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 307e12e..00c1578 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
@@ -20,6 +20,8 @@
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS;
+import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.KeyguardManager;
@@ -76,6 +78,7 @@
import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeKeyGestureHandler;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
@@ -758,8 +761,6 @@
dragToDesktopTransitionHandler,
desktopImmersiveController.get(),
desktopRepository,
- desktopModeLoggerTransitionObserver,
- launchAdjacentController,
recentsTransitionHandler,
multiInstanceHelper,
mainExecutor,
@@ -767,8 +768,6 @@
recentTasksController.orElse(null),
interactionJankMonitor,
mainHandler,
- inputManager,
- focusTransitionObserver,
desktopModeEventLogger,
desktopTilingDecorViewModel);
}
@@ -883,6 +882,67 @@
@WMSingleton
@Provides
+ static Optional<DesktopModeKeyGestureHandler> provideDesktopModeKeyGestureHandler(
+ Context context,
+ DesktopModeWindowDecorViewModel desktopModeWindowDecorViewModel,
+ Optional<DesktopTasksController> desktopTasksController,
+ InputManager inputManager,
+ ShellTaskOrganizer shellTaskOrganizer,
+ FocusTransitionObserver focusTransitionObserver) {
+ if (DesktopModeStatus.canEnterDesktopMode(context) && useKeyGestureEventHandler()
+ && Flags.enableMoveToNextDisplayShortcut()) {
+ return Optional.of(new DesktopModeKeyGestureHandler(context,
+ desktopModeWindowDecorViewModel, desktopTasksController,
+ inputManager, shellTaskOrganizer, focusTransitionObserver));
+ }
+ return Optional.empty();
+ }
+
+ @WMSingleton
+ @Provides
+ static DesktopModeWindowDecorViewModel provideDesktopModeWindowDecorViewModel(
+ Context context,
+ @ShellMainThread ShellExecutor shellExecutor,
+ @ShellMainThread Handler mainHandler,
+ Choreographer mainChoreographer,
+ @ShellBackgroundThread ShellExecutor bgExecutor,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ IWindowManager windowManager,
+ ShellTaskOrganizer taskOrganizer,
+ @DynamicOverride DesktopRepository desktopRepository,
+ DisplayController displayController,
+ ShellController shellController,
+ DisplayInsetsController displayInsetsController,
+ SyncTransactionQueue syncQueue,
+ Transitions transitions,
+ Optional<DesktopTasksController> desktopTasksController,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ InteractionJankMonitor interactionJankMonitor,
+ AppToWebGenericLinksParser genericLinksParser,
+ AssistContentRequester assistContentRequester,
+ MultiInstanceHelper multiInstanceHelper,
+ Optional<DesktopTasksLimiter> desktopTasksLimiter,
+ AppHandleEducationController appHandleEducationController,
+ AppToWebEducationController appToWebEducationController,
+ WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
+ Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
+ FocusTransitionObserver focusTransitionObserver,
+ DesktopModeEventLogger desktopModeEventLogger
+ ) {
+ return new DesktopModeWindowDecorViewModel(context, shellExecutor, mainHandler,
+ mainChoreographer, bgExecutor, shellInit, shellCommandHandler, windowManager,
+ taskOrganizer, desktopRepository, displayController, shellController,
+ displayInsetsController, syncQueue, transitions, desktopTasksController,
+ rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser,
+ assistContentRequester, multiInstanceHelper, desktopTasksLimiter,
+ appHandleEducationController, appToWebEducationController,
+ windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
+ focusTransitionObserver, desktopModeEventLogger);
+ }
+
+ @WMSingleton
+ @Provides
static EnterDesktopTaskTransitionHandler provideEnterDesktopModeTaskTransitionHandler(
Transitions transitions,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
@@ -1234,7 +1294,8 @@
DragAndDropController dragAndDropController,
@NonNull LetterboxTransitionObserver letterboxTransitionObserver,
Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
- Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler) {
+ Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler,
+ Optional<DesktopModeKeyGestureHandler> desktopModeKeyGestureHandler) {
return new Object();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
new file mode 100644
index 0000000..db46f3c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.hardware.input.KeyGestureEvent
+import android.view.KeyEvent
+
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.KeyGestureEventHandler
+import android.os.IBinder
+import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
+import com.android.wm.shell.ShellTaskOrganizer
+import android.app.ActivityManager.RunningTaskInfo
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
+import com.android.internal.protolog.ProtoLog
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.Context
+import com.android.wm.shell.transition.FocusTransitionObserver
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import java.util.Optional
+
+/**
+ * Handles key gesture events (keyboard shortcuts) in Desktop Mode.
+ */
+class DesktopModeKeyGestureHandler(
+ private val context: Context,
+ private val desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel,
+ private val desktopTasksController: Optional<DesktopTasksController>,
+ inputManager: InputManager,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val focusTransitionObserver: FocusTransitionObserver,
+ ) : KeyGestureEventHandler {
+
+ init {
+ inputManager.registerKeyGestureEventHandler(this)
+ }
+
+ override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?): Boolean {
+ if (!isKeyGestureSupported(event.keyGestureType) || !desktopTasksController.isPresent) {
+ return false
+ }
+ when (event.keyGestureType) {
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> {
+ if (event.keycodes.contains(KeyEvent.KEYCODE_D) &&
+ event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)
+ ) {
+ logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled")
+ getGloballyFocusedFreeformTask()?.let {
+ desktopTasksController.get().moveToNextDisplay(
+ it.taskId
+ )
+ }
+ return true
+ }
+ return false
+ }
+ else -> return false
+ }
+ }
+
+ override fun isKeyGestureSupported(gestureType: Int): Boolean = when (gestureType) {
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
+ -> enableMoveToNextDisplayShortcut()
+ else -> false
+ }
+
+ // TODO: b/364154795 - wait for the completion of moveToNextDisplay transition, otherwise it
+ // will pick a wrong task when a user quickly perform other actions with keyboard shortcuts
+ // after moveToNextDisplay, and move this to FocusTransitionObserver class.
+ private fun getGloballyFocusedFreeformTask(): RunningTaskInfo? =
+ shellTaskOrganizer.getRunningTasks().find { taskInfo ->
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
+ focusTransitionObserver.hasGlobalFocus(taskInfo)
+ }
+
+ private fun logV(msg: String, vararg arguments: Any?) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ companion object {
+ private const val TAG = "DesktopModeKeyGestureHandler"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 4db0be5..cef5a30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -35,9 +35,6 @@
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.Region
-import android.hardware.input.InputManager
-import android.hardware.input.InputManager.KeyGestureEventHandler
-import android.hardware.input.KeyGestureEvent
import android.os.Binder
import android.os.Handler
import android.os.IBinder
@@ -46,7 +43,6 @@
import android.util.Size
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
-import android.view.KeyEvent
import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
@@ -66,7 +62,6 @@
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
-import com.android.hardware.input.Flags.useKeyGestureEventHandler
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
@@ -75,13 +70,11 @@
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
-import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.ExternalInterfaceBinder
-import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.MultiInstanceHelper.Companion.getComponent
import com.android.wm.shell.common.RemoteCallable
@@ -116,7 +109,6 @@
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.UserChangeListener
-import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
@@ -156,8 +148,6 @@
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
private val desktopImmersiveController: DesktopImmersiveController,
private val taskRepository: DesktopRepository,
- private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
- private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
private val multiInstanceHelper: MultiInstanceHelper,
@ShellMainThread private val mainExecutor: ShellExecutor,
@@ -165,16 +155,13 @@
private val recentTasksController: RecentTasksController?,
private val interactionJankMonitor: InteractionJankMonitor,
@ShellMainThread private val handler: Handler,
- private val inputManager: InputManager,
- private val focusTransitionObserver: FocusTransitionObserver,
private val desktopModeEventLogger: DesktopModeEventLogger,
private val desktopTilingDecorViewModel: DesktopTilingDecorViewModel,
) :
RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
DragAndDropController.DragAndDropListener,
- UserChangeListener,
- KeyGestureEventHandler {
+ UserChangeListener {
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
@@ -248,9 +235,6 @@
}
)
dragAndDropController.addListener(this)
- if (useKeyGestureEventHandler() && enableMoveToNextDisplayShortcut()) {
- inputManager.registerKeyGestureEventHandler(this)
- }
}
@VisibleForTesting
@@ -1835,26 +1819,12 @@
getFocusedFreeformTask(displayId)?.let { requestSplit(it, leftOrTop) }
}
- /** Move the focused desktop task in given `displayId` to next display. */
- fun moveFocusedTaskToNextDisplay(displayId: Int) {
- getFocusedFreeformTask(displayId)?.let { moveToNextDisplay(it.taskId) }
- }
-
private fun getFocusedFreeformTask(displayId: Int): RunningTaskInfo? {
return shellTaskOrganizer.getRunningTasks(displayId).find { taskInfo ->
taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
}
}
- // TODO(b/364154795): wait for the completion of moveToNextDisplay transition, otherwise it will
- // pick a wrong task when a user quickly perform other actions with keyboard shortcuts after
- // moveToNextDisplay.
- private fun getGloballyFocusedFreeformTask(): RunningTaskInfo? =
- shellTaskOrganizer.getRunningTasks().find { taskInfo ->
- taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
- focusTransitionObserver.hasGlobalFocus(taskInfo)
- }
-
/**
* Requests a task be transitioned from desktop to split select. Applies needed windowing
* changes if this transition is enabled.
@@ -2256,31 +2226,6 @@
taskRepository.dump(pw, innerPrefix)
}
- override fun handleKeyGestureEvent(
- event: KeyGestureEvent,
- focusedToken: IBinder?
- ): Boolean {
- if (!isKeyGestureSupported(event.keyGestureType)) return false
- when (event.keyGestureType) {
- KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> {
- if (event.keycodes.contains(KeyEvent.KEYCODE_D) &&
- event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)) {
- logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled")
- getGloballyFocusedFreeformTask()?.let { moveToNextDisplay(it.taskId) }
- return true
- }
- return false
- }
- else -> return false
- }
- }
-
- override fun isKeyGestureSupported(gestureType: Int): Boolean = when (gestureType) {
- KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
- -> enableMoveToNextDisplayShortcut()
- else -> false
- }
-
/** The interface for calls from outside the shell, within the host process. */
@ExternalThread
private inner class DesktopModeImpl : DesktopMode {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
new file mode 100644
index 0000000..d18bf1f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -0,0 +1,198 @@
+/*
+ * 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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.content.pm.ActivityInfo
+import android.graphics.Rect
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.KeyGestureEventHandler
+import android.hardware.input.KeyGestureEvent
+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.view.KeyEvent
+import android.window.DisplayAreaInfo
+import androidx.test.filters.SmallTest
+import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS
+import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.transition.FocusTransitionObserver
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
+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.StaticMockitoSession
+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.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
+import java.util.Optional
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.quality.Strictness
+
+/**
+ * Test class for [DesktopModeKeyGestureHandler]
+ *
+ * Usage: atest WMShellUnitTests:DesktopModeKeyGestureHandlerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
+ private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+ private val focusTransitionObserver = mock<FocusTransitionObserver>()
+ private val testExecutor = mock<ShellExecutor>()
+ private val inputManager = mock<InputManager>()
+ private val displayController = mock<DisplayController>()
+ private val displayLayout = mock<DisplayLayout>()
+ private val desktopModeWindowDecorViewModel = mock<DesktopModeWindowDecorViewModel>()
+ private val desktopTasksController = mock<DesktopTasksController>()
+
+ private lateinit var desktopModeKeyGestureHandler: DesktopModeKeyGestureHandler
+ private lateinit var keyGestureEventHandler: KeyGestureEventHandler
+ private lateinit var mockitoSession: StaticMockitoSession
+ private lateinit var testScope: CoroutineScope
+ private lateinit var shellInit: ShellInit
+
+ // 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() {
+ Dispatchers.setMain(StandardTestDispatcher())
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+ testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+ shellInit = spy(ShellInit(testExecutor))
+
+ whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+
+ doAnswer {
+ keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler)
+ null
+ }.whenever(inputManager).registerKeyGestureEventHandler(any())
+ shellInit.init()
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+
+ runningTasks.clear()
+ testScope.cancel()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS,
+ FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+ FLAG_USE_KEY_GESTURE_EVENT_HANDLER
+ )
+ fun keyGestureMoveToNextDisplay_shouldMoveToNextDisplay() {
+ desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
+ context,
+ desktopModeWindowDecorViewModel, Optional.of(desktopTasksController),
+ inputManager, shellTaskOrganizer, focusTransitionObserver
+ )
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: default display
+ val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .thenReturn(defaultDisplayArea)
+ // Setup a focused task on secondary display, which is expected to move to default display
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ task.isFocused = true
+ whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
+ whenever(focusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true)
+
+ val event = KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY)
+ .setDisplayId(SECOND_DISPLAY)
+ .setKeycodes(intArrayOf(KeyEvent.KEYCODE_D))
+ .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
+ .build()
+ val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+
+ assertThat(result).isTrue()
+ verify(desktopTasksController).moveToNextDisplay(task.taskId)
+ }
+
+ private fun setUpFreeformTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ bounds: Rect? = null,
+ ): RunningTaskInfo {
+ val task = createFreeformTask(displayId, bounds)
+ val activityInfo = ActivityInfo()
+ task.topActivityInfo = activityInfo
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
+ private companion object {
+ const val SECOND_DISPLAY = 2
+ val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 2319716..1d7136b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -39,9 +39,6 @@
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
-import android.hardware.input.InputManager
-import android.hardware.input.InputManager.KeyGestureEventHandler
-import android.hardware.input.KeyGestureEvent
import android.os.Binder
import android.os.Bundle
import android.os.Handler
@@ -53,7 +50,6 @@
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.Gravity
-import android.view.KeyEvent
import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.WindowInsets
@@ -75,18 +71,14 @@
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import android.window.WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
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.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
-import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS
import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP
-import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT
import com.android.wm.shell.MockToken
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -96,7 +88,6 @@
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
-import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
@@ -124,7 +115,6 @@
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
-import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.TestRemoteTransition
import com.android.wm.shell.transition.Transitions
@@ -208,12 +198,10 @@
@Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
@Mock
lateinit var mMockDesktopImmersiveController: DesktopImmersiveController
- @Mock lateinit var launchAdjacentController: LaunchAdjacentController
@Mock lateinit var splitScreenController: SplitScreenController
@Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
@Mock lateinit var dragAndDropController: DragAndDropController
@Mock lateinit var multiInstanceHelper: MultiInstanceHelper
- @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
@Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
@Mock lateinit var recentTasksController: RecentTasksController
@Mock
@@ -224,8 +212,6 @@
@Mock private lateinit var mockHandler: Handler
@Mock private lateinit var desktopModeEventLogger: DesktopModeEventLogger
@Mock lateinit var persistentRepository: DesktopPersistentRepository
- @Mock private lateinit var mockInputManager: InputManager
- @Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver
@Mock lateinit var motionEvent: MotionEvent
@Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
@@ -240,7 +226,6 @@
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
private lateinit var testScope: CoroutineScope
- private lateinit var keyGestureEventHandler: KeyGestureEventHandler
private val shellExecutor = TestShellExecutor()
@@ -305,11 +290,6 @@
controller.setSplitScreenController(splitScreenController)
controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter
- doAnswer {
- keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler)
- null
- }.whenever(mockInputManager).registerKeyGestureEventHandler(any())
-
shellInit.init()
val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
@@ -343,8 +323,6 @@
dragToDesktopTransitionHandler,
mMockDesktopImmersiveController,
taskRepository,
- desktopModeLoggerTransitionObserver,
- launchAdjacentController,
recentsTransitionHandler,
multiInstanceHelper,
shellExecutor,
@@ -352,8 +330,6 @@
recentTasksController,
mockInteractionJankMonitor,
mockHandler,
- mockInputManager,
- mockFocusTransitionObserver,
desktopModeEventLogger,
desktopTilingDecorViewModel,
)
@@ -1558,44 +1534,6 @@
}
@Test
- @EnableFlags(
- FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS,
- FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
- FLAG_USE_KEY_GESTURE_EVENT_HANDLER
- )
- fun moveToNextDisplay_withKeyGesture() {
- // Set up two display ids
- whenever(rootTaskDisplayAreaOrganizer.displayIds)
- .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
- // Create a mock for the target display area: default display
- val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
- whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
- .thenReturn(defaultDisplayArea)
- // Setup a focused task on secondary display, which is expected to move to default display
- val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
- task.isFocused = true
- whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
- whenever(mockFocusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true)
-
- val event = KeyGestureEvent.Builder()
- .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY)
- .setDisplayId(SECOND_DISPLAY)
- .setKeycodes(intArrayOf(KeyEvent.KEYCODE_D))
- .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
- .build()
- val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
-
- assertThat(result).isTrue()
- with(getLatestWct(type = TRANSIT_CHANGE)) {
- assertThat(hierarchyOps).hasSize(1)
- assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
- assertThat(hierarchyOps[0].isReparent).isTrue()
- assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder())
- assertThat(hierarchyOps[0].toTop).isTrue()
- }
- }
-
- @Test
fun getTaskWindowingMode() {
val fullscreenTask = setUpFullscreenTask()
val freeformTask = setUpFreeformTask()