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

Commit f9fe3a00 authored by Daichi Hirono's avatar Daichi Hirono
Browse files

Extend close window shortcut to fullscreen tasks

This change extends the Action+Ctrl+W keyboard shortcut to close the
focused fullscreen task. This builds upon the existing functionality
that allows closing desktop windows.

The feature is controlled by the
`close_fullscreen_and_splitscreen_keyboard_shortcut` flag.

Bug: 441147192
Test: DesktopModeKeyGestureHandlerTest, DesktopModeWindowDecorViewModelTests
Flag: com.android.window.flags.close_fullscreen_and_splitscreen_keyboard_shortcut
Change-Id: Icc934c009a53e5b33a9b8363a456b4c5c68e66a3
parent d0d82558
Loading
Loading
Loading
Loading
+63 −1
Original line number Diff line number Diff line
@@ -177,7 +177,27 @@ class DesktopModeKeyGestureHandler(
            }
            KeyGestureEvent.KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK -> {
                logV("Key gesture KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK is handled")
                quitFocusedDesktopTask()
                val focusedTask =
                    if (
                        DesktopExperienceFlags.CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT
                            .isTrue
                    ) {
                        getGloballyFocusedTaskToClose()
                    } else {
                        getGloballyFocusedDesktopTask().also { task ->
                            if (task != null) {
                                logV("Found focused desktop task %d to close", task.taskId)
                            } else {
                                logV(
                                    "Globally focused desktop task is not found to close. focusedDisplay=%d",
                                    focusTransitionObserver.globallyFocusedDisplayId,
                                )
                            }
                        }
                    } ?: return
                mainExecutor.execute {
                    desktopModeWindowDecorViewModel.get().closeTask(focusedTask)
                }
            }
            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_FULLSCREEN -> {
                logV("Key gesture TOGGLE_FULLSCREEN is handled")
@@ -285,6 +305,48 @@ class DesktopModeKeyGestureHandler(
        return null
    }

    private fun getGloballyFocusedTaskToClose(): RunningTaskInfo? {
        getGloballyFocusedDesktopTask()?.let { desktopTask ->
            logV("getGloballyFocusedTaskToClose: Found desktop task: %d", desktopTask.taskId)
            return@getGloballyFocusedTaskToClose desktopTask
        }
        val tasks =
            desktopTasksController
                .get()
                .getFocusedNonDesktopTasks(
                    displayId = focusTransitionObserver.globallyFocusedDisplayId,
                    userId = desktopUserRepositories.current.userId,
                )
        return when (tasks.size) {
            0 -> {
                logW(
                    "getGloballyFocusedTaskToClose: Task not found to close: " +
                        "globallyFocusedTaskId=%d globallyFocusedDisplayId=%d",
                    focusTransitionObserver.globallyFocusedTaskId,
                    focusTransitionObserver.globallyFocusedDisplayId,
                )
                null
            }
            1 -> {
                val task = tasks.single()
                if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) {
                    logV("getGloballyFocusedTaskToClose: Found fullscreen task: %d", task.taskId)
                    task
                } else {
                    logW(
                        "getGloballyFocusedTaskToClose: Ignored focused single non-fullscreen " +
                            "task."
                    )
                    null
                }
            }
            else -> {
                logW("getGloballyFocusedTaskToClose: Ignored focused 2+ tasks.")
                null
            }
        }
    }

    private fun logV(msg: String, vararg arguments: Any?) {
        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
    }
+39 −24
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.WindowManager;
import android.window.DesktopExperienceFlags;
import android.window.DesktopModeFlags;
import android.window.TaskSnapshot;
@@ -1221,13 +1222,26 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,

    private void onCloseTask(int taskId) {
        if (isTaskInSplitScreen(taskId)) {
            ProtoLog.i(WM_SHELL_WINDOW_DECORATION,
                    "%s: onCloseTask(taskId=%d): closing split screen", TAG, taskId);
            mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(taskId).taskId,
                    SplitScreenController.EXIT_REASON_DESKTOP_MODE);
        } else {
            return;
        }
        final WindowDecorationWrapper decoration = mWindowDecorByTaskId.get(taskId);
        if (decoration == null) {
            ProtoLog.e(WM_SHELL_WINDOW_DECORATION,
                        "%s: handled close key gesture but decoration is null, ignoring", TAG);
                    "%s: onCloseTask(taskId=%d): decoration is null, ignoring", TAG, taskId);
            return;
        }
        if (DesktopExperienceFlags
                .CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT.isTrue()
                && decoration.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
            ProtoLog.i(WM_SHELL_WINDOW_DECORATION,
                    "%s: onCloseTask(taskId=%d): closing fullscreen task", TAG, taskId);
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            wct.removeTask(decoration.getTaskInfo().token);
            mTransitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, null);
            return;
        }
        if (DesktopExperienceFlags
@@ -1240,6 +1254,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                nextFocusedWindow.a11yAnnounceNewFocusedWindow();
            }
        }
        ProtoLog.w(WM_SHELL_WINDOW_DECORATION,
                "%s: onCloseTask(taskId=%d): closing desktop task", TAG, taskId);
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        final Function1<IBinder, Unit> runOnTransitionStart =
                mDesktopTasksController.onDesktopWindowClose(wct,
@@ -1250,7 +1266,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
            runOnTransitionStart.invoke(transition);
        }
    }
    }

    /** Listener for caption touch events. */
    public interface CaptionTouchStatusListener {
+26 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags.FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT
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
@@ -387,6 +388,31 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
        verify(desktopModeWindowDecorViewModel).closeTask(task)
    }

    @Test
    @EnableFlags(FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT)
    fun keyGestureQuitFocusedDesktopTask_shouldQuitFullscreenTask() {
        // Setup a focused fullscreen task
        val task = setUpFullscreenTask()
        whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(task.displayId)
        whenever(
                desktopTasksController.getFocusedNonDesktopTasks(task.displayId, repository.userId)
            )
            .thenReturn(listOf(task))

        // Create and handle the key gesture event
        val event =
            KeyGestureEvent.Builder()
                .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK)
                .setKeycodes(intArrayOf(KeyEvent.KEYCODE_Q))
                .setModifierState(KeyEvent.META_META_ON)
                .build()
        keyGestureEventHandler.handleKeyGestureEvent(event, null)
        testExecutor.flushAll()

        // Verify closeTask is called
        verify(desktopModeWindowDecorViewModel).closeTask(task)
    }

    @Test
    fun keyGestureSwitchToPreviousDesk_activatesDesk() {
        val displayId = 2
+26 −0
Original line number Diff line number Diff line
@@ -57,11 +57,13 @@ import android.view.SurfaceView
import android.view.View
import android.view.ViewRootImpl
import android.view.WindowInsets.Type.statusBars
import android.view.WindowManager
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT
import com.android.wm.shell.R
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.desktopmode.DesktopImmersiveController
@@ -349,6 +351,30 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
        verify(mockFreeformTaskTransitionStarter, never()).startRemoveTransition(any())
    }

    @Test
    @EnableFlags(FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT)
    fun testCloseTask_fullscreen_closesTask() {
        desktopModeWindowDecorViewModel.setFreeformTaskTransitionStarter(
            mockFreeformTaskTransitionStarter
        )
        val decor = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FULLSCREEN)

        desktopModeWindowDecorViewModel.closeTask(decor.taskInfo)

        val transactionCaptor = argumentCaptor<WindowContainerTransaction>()
        verify(mockTransitions)
            .startTransition(
                eq(WindowManager.TRANSIT_CLOSE),
                transactionCaptor.capture(),
                anyOrNull(),
            )
        val wct = transactionCaptor.firstValue
        assertThat(wct.hierarchyOps).hasSize(1)
        val hierarchyOp = wct.hierarchyOps[0]
        assertThat(hierarchyOp.type).isEqualTo(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK)
        assertThat(hierarchyOp.container).isEqualTo(decor.taskInfo.token.asBinder())
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MINIMIZE_BUTTON)
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_HEADER_STATE_CHANGE_ANNOUNCEMENTS)