• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.desktopmode
18 
19 import android.app.ActivityManager.RunningTaskInfo
20 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
21 import android.content.Context
22 import android.hardware.input.InputManager
23 import android.hardware.input.InputManager.KeyGestureEventHandler
24 import android.hardware.input.KeyGestureEvent
25 import android.os.IBinder
26 import com.android.internal.protolog.ProtoLog
27 import com.android.wm.shell.ShellTaskOrganizer
28 import com.android.wm.shell.common.DisplayController
29 import com.android.wm.shell.common.ShellExecutor
30 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
31 import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
32 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
33 import com.android.wm.shell.shared.annotations.ShellMainThread
34 import com.android.wm.shell.transition.FocusTransitionObserver
35 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
36 import java.util.Optional
37 
38 /** Handles key gesture events (keyboard shortcuts) in Desktop Mode. */
39 class DesktopModeKeyGestureHandler(
40     private val context: Context,
41     private val desktopModeWindowDecorViewModel: Optional<DesktopModeWindowDecorViewModel>,
42     private val desktopTasksController: Optional<DesktopTasksController>,
43     inputManager: InputManager,
44     private val shellTaskOrganizer: ShellTaskOrganizer,
45     private val focusTransitionObserver: FocusTransitionObserver,
46     @ShellMainThread private val mainExecutor: ShellExecutor,
47     private val displayController: DisplayController,
48 ) : KeyGestureEventHandler {
49 
50     init {
51         if (desktopTasksController.isPresent && desktopModeWindowDecorViewModel.isPresent) {
52             val supportedGestures =
53                 listOf(
54                     KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
55                     KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
56                     KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
57                     KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
58                     KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW,
59                 )
60             inputManager.registerKeyGestureEventHandler(supportedGestures, this)
61         }
62     }
63 
64     override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?) {
65         when (event.keyGestureType) {
66             KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> {
67                 logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled")
68                 getGloballyFocusedFreeformTask()?.let {
69                     mainExecutor.execute {
70                         desktopTasksController.get().moveToNextDisplay(it.taskId)
71                     }
72                 }
73             }
74             KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW -> {
75                 logV("Key gesture SNAP_LEFT_FREEFORM_WINDOW is handled")
76                 getGloballyFocusedFreeformTask()?.let {
77                     mainExecutor.execute {
78                         desktopModeWindowDecorViewModel
79                             .get()
80                             .onSnapResize(
81                                 it.taskId,
82                                 true,
83                                 DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
84                                 /* fromMenu= */ false,
85                             )
86                     }
87                 }
88             }
89             KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW -> {
90                 logV("Key gesture SNAP_RIGHT_FREEFORM_WINDOW is handled")
91                 getGloballyFocusedFreeformTask()?.let {
92                     mainExecutor.execute {
93                         desktopModeWindowDecorViewModel
94                             .get()
95                             .onSnapResize(
96                                 it.taskId,
97                                 false,
98                                 DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
99                                 /* fromMenu= */ false,
100                             )
101                     }
102                 }
103             }
104             KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW -> {
105                 logV("Key gesture TOGGLE_MAXIMIZE_FREEFORM_WINDOW is handled")
106                 getGloballyFocusedFreeformTask()?.let { taskInfo ->
107                     mainExecutor.execute {
108                         desktopTasksController
109                             .get()
110                             .toggleDesktopTaskSize(
111                                 taskInfo,
112                                 ToggleTaskSizeInteraction(
113                                     isMaximized = isTaskMaximized(taskInfo, displayController),
114                                     source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT,
115                                     inputMethod =
116                                         DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
117                                 ),
118                             )
119                     }
120                 }
121             }
122             KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> {
123                 logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled")
124                 getGloballyFocusedFreeformTask()?.let {
125                     mainExecutor.execute {
126                         desktopTasksController.get().minimizeTask(it, MinimizeReason.KEY_GESTURE)
127                     }
128                 }
129             }
130         }
131     }
132 
133     //  TODO: b/364154795 - wait for the completion of moveToNextDisplay transition, otherwise it
134     //  will pick a wrong task when a user quickly perform other actions with keyboard shortcuts
135     //  after moveToNextDisplay, and move this to FocusTransitionObserver class.
136     private fun getGloballyFocusedFreeformTask(): RunningTaskInfo? =
137         shellTaskOrganizer.getRunningTasks().find { taskInfo ->
138             taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
139                 focusTransitionObserver.hasGlobalFocus(taskInfo)
140         }
141 
142     private fun logV(msg: String, vararg arguments: Any?) {
143         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
144     }
145 
146     companion object {
147         private const val TAG = "DesktopModeKeyGestureHandler"
148     }
149 }
150