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

Commit f3a70712 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement key gesture to close focused desktop task" into main

parents a31b83ac ee93fc44
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ class DesktopModeKeyGestureHandler(
                    KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW,
                    KeyGestureEvent.KEY_GESTURE_TYPE_SWITCH_TO_PREVIOUS_DESK,
                    KeyGestureEvent.KEY_GESTURE_TYPE_SWITCH_TO_NEXT_DESK,
                    KeyGestureEvent.KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK,
                )
            inputManager.registerKeyGestureEventHandler(supportedGestures, this)
        }
@@ -166,6 +167,21 @@ class DesktopModeKeyGestureHandler(
                    }
                }
            }
            KeyGestureEvent.KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK -> {
                logV("Key gesture KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK is handled")
                val focusedTask = getGloballyFocusedDesktopTask()
                if (focusedTask == null) {
                    logV(
                        "Globally focused desktop task is not found to close. focusedDisplay=%d",
                        focusTransitionObserver.globallyFocusedDisplayId,
                    )
                    return
                }
                logV("Found focused desktop task %d to close", focusedTask.taskId)
                mainExecutor.execute {
                    desktopModeWindowDecorViewModel.get().closeTask(focusedTask)
                }
            }
        }
    }

+62 −42
Original line number Diff line number Diff line
@@ -1133,6 +1133,58 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        mCaptionTouchStatusListener = l;
    }

    /**
     * Closes a task.
     * This method closes a task as if the close button on the window decor is clicked.
     * It does nothing if the task does not have a window decor, thus cannot be closed through the
     * UI.
     * Must call the method on the shell main executor.
     * @param task Task to be closed.
     */
    public void closeTask(RunningTaskInfo task) {
        final WindowDecorationWrapper decoration = mWindowDecorationFinder.apply(task.taskId);
        if (decoration == null) {
            ProtoLog.e(WM_SHELL_DESKTOP_MODE,
                    "%s: handled close key gesture but decoration is null, ignoring", TAG);
            return;
        }
        if (mTaskOperations == null) {
            ProtoLog.e(WM_SHELL_DESKTOP_MODE,
                    "%s: handled close key gesture but mTaskOperations is null, ignoring", TAG);
            return;
        }
        ProtoLog.e(WM_SHELL_DESKTOP_MODE,
                "%s: close task %d vis key gesture", TAG, task.taskId);
        onCloseTask(task.taskId, decoration, task.token);
    }

    private void onCloseTask(int taskId, @NotNull WindowDecorationWrapper decoration,
            @NotNull WindowContainerToken token) {
        if (isTaskInSplitScreen(taskId)) {
            mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(taskId).taskId,
                    SplitScreenController.EXIT_REASON_DESKTOP_MODE);
        } else {
            if (DesktopExperienceFlags
                    .ENABLE_DESKTOP_APP_HEADER_STATE_CHANGE_ANNOUNCEMENTS.isTrue()) {
                final int nextFocusedTaskId = mDesktopTasksController.getNextFocusedTask(
                        decoration.getTaskInfo());
                final WindowDecorationWrapper nextFocusedWindow =
                        mWindowDecorationFinder.apply(nextFocusedTaskId);
                if (nextFocusedWindow != null) {
                    nextFocusedWindow.a11yAnnounceNewFocusedWindow();
                }
            }
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            final Function1<IBinder, Unit> runOnTransitionStart =
                    mDesktopTasksController.onDesktopWindowClose(wct,
                            decoration.getTaskInfo().displayId, decoration.getTaskInfo());
            final IBinder transition = mTaskOperations.closeTask(token, wct);
            if (transition != null) {
                runOnTransitionStart.invoke(transition);
            }
        }
    }

    /** Listener for caption touch events. */
    public interface CaptionTouchStatusListener {
        /** Called when the caption is pressed. */
@@ -1171,7 +1223,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        private final WindowContainerToken mTaskToken;
        private final @NonNull DragPositioningCallback mDragPositioningCallback;
        private final @NonNull Function<Integer, WindowDecorationWrapper> mWindowDecorationFinder;
        private final @Nullable SplitScreenController mSplitScreenController;
        private final @NonNull DesktopTasksController mDesktopTasksController;
        private final @NonNull TaskOperations mTaskOperations;
        private final @NonNull DesktopModeUiEventLogger mDesktopModeUiEventLogger;
@@ -1215,7 +1266,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                @NonNull RunningTaskInfo taskInfo,
                @NonNull DragPositioningCallback dragPositioningCallback,
                @NonNull Function<Integer, WindowDecorationWrapper> windowDecorationFinder,
                @Nullable SplitScreenController splitScreenController,
                @NonNull DesktopTasksController desktopTasksController,
                @NonNull TaskOperations taskOperations,
                @NonNull DesktopModeUiEventLogger desktopModeUiEventLogger,
@@ -1233,7 +1283,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
            mContext = context;
            mDragPositioningCallback = dragPositioningCallback;
            mWindowDecorationFinder = windowDecorationFinder;
            mSplitScreenController = splitScreenController;
            mDesktopTasksController = desktopTasksController;
            mTaskOperations = taskOperations;
            mDesktopModeUiEventLogger = desktopModeUiEventLogger;
@@ -1291,29 +1340,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
            }
            final int id = v.getId();
            if (id == R.id.close_window) {
                if (isTaskInSplitScreen(mTaskId)) {
                    mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId,
                            SplitScreenController.EXIT_REASON_DESKTOP_MODE);
                } else {
                    if (DesktopExperienceFlags
                            .ENABLE_DESKTOP_APP_HEADER_STATE_CHANGE_ANNOUNCEMENTS.isTrue()) {
                        final int nextFocusedTaskId = mDesktopTasksController.getNextFocusedTask(
                                decoration.getTaskInfo());
                        WindowDecorationWrapper nextFocusedWindow =
                                mWindowDecorationFinder.apply(nextFocusedTaskId);
                        if (nextFocusedWindow != null) {
                            nextFocusedWindow.a11yAnnounceNewFocusedWindow();
                        }
                    }
                    WindowContainerTransaction wct = new WindowContainerTransaction();
                    final Function1<IBinder, Unit> runOnTransitionStart =
                            mDesktopTasksController.onDesktopWindowClose(wct,
                                    decoration.getTaskInfo().displayId, decoration.getTaskInfo());
                    final IBinder transition = mTaskOperations.closeTask(mTaskToken, wct);
                    if (transition != null) {
                        runOnTransitionStart.invoke(transition);
                    }
                }
                mWindowDecorationActions.onClose(mTaskId, decoration, mTaskToken);
            } else if (id == R.id.back_button) {
                mTaskOperations.injectBackKey(decoration.getTaskInfo().displayId);
            } else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
@@ -1352,7 +1379,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                        .ENABLE_DESKTOP_APP_HEADER_STATE_CHANGE_ANNOUNCEMENTS.isTrue()) {
                    final int nextFocusedTaskId = mDesktopTasksController
                            .getNextFocusedTask(decoration.getTaskInfo());
                    WindowDecorationWrapper nextFocusedWindow =
                    final WindowDecorationWrapper nextFocusedWindow =
                            mWindowDecorationFinder.apply(nextFocusedTaskId);
                    if (nextFocusedWindow != null) {
                        nextFocusedWindow.a11yAnnounceNewFocusedWindow();
@@ -1887,19 +1914,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
            return true;
        }

        private boolean isTaskInSplitScreen(int taskId) {
            return mSplitScreenController != null
                    && mSplitScreenController.isTaskInSplitScreen(taskId);
        }

        @Nullable
        private RunningTaskInfo getOtherSplitTask(int taskId) {
            @SplitPosition int remainingTaskPosition = mSplitScreenController
                    .getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
                    ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
            return mSplitScreenController.getTaskInfo(remainingTaskPosition);
        }

        private InputMethod getInputMethod(MotionEvent ev) {
            return DesktopModeEventLogger.getInputMethodFromMotionEvent(ev);
        }
@@ -2390,8 +2404,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,

        final DesktopModeTouchEventListener touchEventListener =
                new DesktopModeTouchEventListener(mContext, taskInfo, taskPositioner,
                        mWindowDecorationFinder, mSplitScreenController, mDesktopTasksController,
                        mTaskOperations, mDesktopModeUiEventLogger, mWindowDecorationActions,
                        mWindowDecorationFinder, mDesktopTasksController, mTaskOperations,
                        mDesktopModeUiEventLogger, mWindowDecorationActions,
                        mDesktopUserRepositories, mGestureExclusionTracker, mInputManager,
                        mFocusTransitionObserver, mShellDesktopState,
                        mMultiDisplayDragMoveIndicatorController, mTransactionFactory,
@@ -2645,6 +2659,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
            mDesktopTasksController.minimizeTask(taskInfo, MinimizeReason.MINIMIZE_BUTTON);
        }

        @Override
        public void onClose(int taskId, @NotNull WindowDecorationWrapper decoration,
                @NotNull WindowContainerToken token) {
            mViewModel.onCloseTask(taskId, decoration, token);
        }

        @Override
        public void onImmersiveOrRestore(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
            mViewModel.onEnterOrExitImmersive(taskInfo);
+4 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor

import android.app.ActivityManager.RunningTaskInfo
import android.content.Intent
import android.window.WindowContainerToken
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.AmbiguousSource
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
@@ -33,6 +34,9 @@ interface WindowDecorationActions {
    /** Minimizes the task. */
    fun onMinimize(taskInfo: RunningTaskInfo)

    /** Close the task */
    fun onClose(taskId: Int, decoration: WindowDecorationWrapper, token: WindowContainerToken)

    /**
     * Moves task to immersive mode or exits immersive and restores task to previous size if task is
     * already in immersive.
+19 −0
Original line number Diff line number Diff line
@@ -366,6 +366,25 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
        verify(desktopTasksController).minimizeTask(task, MinimizeReason.KEY_GESTURE)
    }

    @Test
    fun keyGestureQuitFocusedDesktopTask_shouldQuitTask() {
        val task = setUpDesktopTask()
        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_QUIT_FOCUSED_DESKTOP_TASK)
                .setKeycodes(intArrayOf(KeyEvent.KEYCODE_Q))
                .setModifierState(KeyEvent.META_META_ON)
                .build()
        keyGestureEventHandler.handleKeyGestureEvent(event, null)
        testExecutor.flushAll()

        verify(desktopModeWindowDecorViewModel).closeTask(task)
    }

    @Test
    fun keyGestureSwitchToPreviousDesk_activatesDesk() {
        val displayId = 2
+30 −0
Original line number Diff line number Diff line
@@ -319,6 +319,36 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
        assertThat(hierarchyOp.container).isEqualTo(decor.taskInfo.token.asBinder())
    }

    @Test
    fun testCloseTask_notInSplitScreen_closesTask() {
        desktopModeWindowDecorViewModel.setFreeformTaskTransitionStarter(
            mockFreeformTaskTransitionStarter
        )
        val decor = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FREEFORM)
        val taskInfo = decor.taskInfo
        whenever(mockSplitScreenController.isTaskInSplitScreen(eq(taskInfo.taskId)))
            .thenReturn(false)
        whenever(mockDesktopTasksController.getNextFocusedTask(eq(taskInfo))).thenReturn(-1)
        whenever(mockDesktopTasksController.onDesktopWindowClose(any(), any(), any())).thenReturn {
            binder: IBinder ->
        }

        desktopModeWindowDecorViewModel.closeTask(decor.taskInfo)

        verify(mockFreeformTaskTransitionStarter).startRemoveTransition(any())
    }

    @Test
    fun testCloseTask_noDecoration_doesNothing() {
        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
        // No decoration is created for this task.

        desktopModeWindowDecorViewModel.closeTask(task)

        verify(mockSplitScreenController, never()).isTaskInSplitScreen(any())
        verify(mockFreeformTaskTransitionStarter, never()).startRemoveTransition(any())
    }

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