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