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

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

Merge changes from topic "dwkbd" into main

* changes:
  Add keyboard shortcuts implementation for Desktop Windowing task resizing keyboard shortcuts (snap left/right, toggle window size and minimize task)
  Refactor KeyGestureEventHandler out of DesktopTasksController into its own class DesktopModeKeyGestureHandler.
parents 18d8a5c9 787ab1cf
Loading
Loading
Loading
Loading
+13 −16
Original line number Diff line number Diff line
@@ -115,12 +115,14 @@ public final class KeyGestureEvent {
    public static final int KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS = 67;
    public static final int KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW = 68;
    public static final int KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW = 69;
    public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 70;
    public static final int KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE = 71;
    public static final int KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW = 70;
    public static final int KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW = 71;
    public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN = 72;
    public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT = 73;
    public static final int KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION = 74;
    public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 75;
    public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 76;


    public static final int FLAG_CANCELLED = 1;

@@ -205,12 +207,13 @@ public final class KeyGestureEvent {
            KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
            KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
            KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
            KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
            KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
            KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW,
            KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
            KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
            KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
            KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
            KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK,
            KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface KeyGestureType {
@@ -557,14 +560,6 @@ public final class KeyGestureEvent {
                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
            case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
            case KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW:
                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SNAP_LEFT_FREEFORM_WINDOW;
            case KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW:
                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SNAP_RIGHT_FREEFORM_WINDOW;
            case KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW:
                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MAXIMIZE_FREEFORM_WINDOW;
            case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE:
                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RESTORE_FREEFORM_WINDOW_SIZE;
            default:
                return LOG_EVENT_UNSPECIFIED;
        }
@@ -777,10 +772,10 @@ public final class KeyGestureEvent {
                return "KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW";
            case KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW:
                return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW";
            case KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW:
                return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW";
            case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE:
                return "KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE";
            case KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW:
                return "KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW";
            case KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW:
                return "KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW";
            case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN:
                return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN";
            case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT:
@@ -789,6 +784,8 @@ public final class KeyGestureEvent {
                return "KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION";
            case KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK:
                return "KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK";
            case KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW:
                return "KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW";
            default:
                return Integer.toHexString(value);
        }
+72 −5
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRA
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.manageKeyGestures;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.KeyguardManager;
@@ -77,6 +80,7 @@ import com.android.wm.shell.desktopmode.DesktopImmersiveController;
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;
@@ -759,8 +763,6 @@ public abstract class WMShellModule {
                dragToDesktopTransitionHandler,
                desktopImmersiveController.get(),
                desktopRepository,
                desktopModeLoggerTransitionObserver,
                launchAdjacentController,
                recentsTransitionHandler,
                multiInstanceHelper,
                mainExecutor,
@@ -768,8 +770,6 @@ public abstract class WMShellModule {
                recentTasksController.orElse(null),
                interactionJankMonitor,
                mainHandler,
                inputManager,
                focusTransitionObserver,
                desktopModeEventLogger,
                desktopTilingDecorViewModel);
    }
@@ -882,6 +882,72 @@ public abstract class WMShellModule {
                        context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor);
    }

    @WMSingleton
    @Provides
    static Optional<DesktopModeKeyGestureHandler> provideDesktopModeKeyGestureHandler(
            Context context,
            Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel,
            Optional<DesktopTasksController> desktopTasksController,
            InputManager inputManager,
            ShellTaskOrganizer shellTaskOrganizer,
            FocusTransitionObserver focusTransitionObserver) {
        if (DesktopModeStatus.canEnterDesktopMode(context) && useKeyGestureEventHandler()
                && manageKeyGestures()
                && (Flags.enableMoveToNextDisplayShortcut()
                || Flags.enableTaskResizingKeyboardShortcuts())) {
            return Optional.of(new DesktopModeKeyGestureHandler(context,
                    desktopModeWindowDecorViewModel, desktopTasksController,
                    inputManager, shellTaskOrganizer, focusTransitionObserver));
        }
        return Optional.empty();
    }

    @WMSingleton
    @Provides
    static Optional<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
    ) {
        if (!DesktopModeStatus.canEnterDesktopMode(context)) {
            return Optional.empty();
        }
        return Optional.of(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(
@@ -1236,7 +1302,8 @@ public abstract class WMShellModule {
            @NonNull LetterboxTransitionObserver letterboxTransitionObserver,
            @NonNull LetterboxCommandHandler letterboxCommandHandler,
            Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
            Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler) {
            Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler,
            Optional<DesktopModeKeyGestureHandler> desktopModeKeyGestureHandler) {
        return new Object();
    }

+144 −0
Original line number Diff line number Diff line
/*
 * 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.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.hardware.input.Flags.manageKeyGestures
import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
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: Optional<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
            || !desktopModeWindowDecorViewModel.isPresent) {
            return false
        }
        when (event.keyGestureType) {
            KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> {
                logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled")
                getGloballyFocusedFreeformTask()?.let {
                    desktopTasksController.get().moveToNextDisplay(
                        it.taskId
                    )
                }
                return true
            }
            // TODO(b/375356876): Modify function to pass in keyboard shortcut as the input
            // method for logging task resize
            KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW -> {
                logV("Key gesture SNAP_LEFT_FREEFORM_WINDOW is handled")
                getGloballyFocusedFreeformTask()?.let {
                    desktopModeWindowDecorViewModel.get().onSnapResize(
                        it.taskId,
                        true,
                        null
                    )
                }
                return true
            }
            KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW -> {
                logV("Key gesture SNAP_RIGHT_FREEFORM_WINDOW is handled")
                getGloballyFocusedFreeformTask()?.let {
                    desktopModeWindowDecorViewModel.get().onSnapResize(
                        it.taskId,
                        false,
                        null
                    )
                }
                return true
            }
            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW -> {
                logV("Key gesture TOGGLE_MAXIMIZE_FREEFORM_WINDOW is handled")
                getGloballyFocusedFreeformTask()?.let {
                    desktopTasksController.get().toggleDesktopTaskSize(
                        it,
                        ResizeTrigger.MAXIMIZE_MENU,
                        null,
                    )
                }
                return true
            }
            KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> {
                logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled")
                getGloballyFocusedFreeformTask()?.let {
                    desktopTasksController.get().minimizeTask(
                        it,
                    )
                }
                return true
            }
            else -> return false
        }
    }

    override fun isKeyGestureSupported(gestureType: Int): Boolean = when (gestureType) {
        KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
            -> enableMoveToNextDisplayShortcut()
        KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
        KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
        KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW
            -> enableTaskResizingKeyboardShortcuts() && manageKeyGestures()
        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"
    }
}
+51 −68

File changed.

Preview size limit exceeded, changes collapsed.

+9 −26
Original line number Diff line number Diff line
@@ -78,7 +78,6 @@ import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Toast;
import android.window.DesktopModeFlags;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
@@ -572,9 +571,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        if (decoration == null) {
            return;
        }
        mDesktopModeEventLogger.logTaskResizingStarted(resizeTrigger, motionEvent,
                decoration.mTaskInfo,
                mDisplayController, /* displayLayoutSize= */ null);
        mInteractionJankMonitor.begin(
                decoration.mTaskSurface, mContext, mMainHandler,
                Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
@@ -593,33 +589,20 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        decoration.closeMaximizeMenu();
    }

    private void onSnapResize(int taskId, boolean left, MotionEvent motionEvent) {
    public void onSnapResize(int taskId, boolean left, @Nullable MotionEvent motionEvent) {
        final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
        if (decoration == null) {
            return;
        }

        if (!decoration.mTaskInfo.isResizeable
                && DesktopModeFlags.DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE.isTrue()) {
            Toast.makeText(mContext,
                    R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show();
        } else {
            ResizeTrigger resizeTrigger =
                    left ? ResizeTrigger.SNAP_LEFT_MENU : ResizeTrigger.SNAP_RIGHT_MENU;
            mDesktopModeEventLogger.logTaskResizingStarted(resizeTrigger, motionEvent,
                    decoration.mTaskInfo,
                    mDisplayController, /* displayLayoutSize= */ null);
        mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
                Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
            mDesktopTasksController.snapToHalfScreen(
        mDesktopTasksController.handleInstantSnapResizingTask(
                decoration.mTaskInfo,
                    decoration.mTaskSurface,
                    decoration.mTaskInfo.configuration.windowConfiguration.getBounds(),
                left ? SnapPosition.LEFT : SnapPosition.RIGHT,
                    resizeTrigger,
                left ? ResizeTrigger.SNAP_LEFT_MENU : ResizeTrigger.SNAP_RIGHT_MENU,
                motionEvent,
                    mWindowDecorByTaskId.get(taskId));
        }
                decoration);

        decoration.closeHandleMenu();
        decoration.closeMaximizeMenu();
Loading