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

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

Merge "Wire "Action + Ctrl + D" to moveToNextDisplay" into main

parents c61bc369 df792e1d
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -99,6 +99,7 @@ public final class KeyGestureEvent {
    public static final int KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT = 59;
    public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT = 60;
    public static final int KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS = 61;
    public static final int KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY = 62;

    public static final int FLAG_CANCELLED = 1;

@@ -175,7 +176,7 @@ public final class KeyGestureEvent {
            KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
            KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
            KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,

            KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface KeyGestureType {
@@ -415,6 +416,8 @@ public final class KeyGestureEvent {
            case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT:
            case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT:
                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS;
            case KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY:
                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MOVE_TO_NEXT_DISPLAY;
            case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT;
            case KEY_GESTURE_TYPE_LOCK_SCREEN:
@@ -530,6 +533,8 @@ public final class KeyGestureEvent {
                return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT";
            case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT:
                return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT";
            case KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY:
                return "KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY";
            case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
                return "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT";
            case KEY_GESTURE_TYPE_LOCK_SCREEN:
+1 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
    <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
    <uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
    <uses-permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION" />
    <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" />

    <application>
        <activity
+6 −2
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.annotation.Nullable;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.UserManager;
import android.view.Choreographer;
@@ -644,7 +645,9 @@ public abstract class WMShellModule {
            @ShellMainThread Handler mainHandler,
            Optional<DesktopTasksLimiter> desktopTasksLimiter,
            Optional<RecentTasksController> recentTasksController,
            InteractionJankMonitor interactionJankMonitor) {
            InteractionJankMonitor interactionJankMonitor,
            InputManager inputManager,
            FocusTransitionObserver focusTransitionObserver) {
        return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
                displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
                dragAndDropController, transitions, keyguardManager,
@@ -655,7 +658,8 @@ public abstract class WMShellModule {
                desktopRepository,
                desktopModeLoggerTransitionObserver, launchAdjacentController,
                recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
                recentTasksController.orElse(null), interactionJankMonitor, mainHandler);
                recentTasksController.orElse(null), interactionJankMonitor, mainHandler,
                inputManager, focusTransitionObserver);
    }

    @WMSingleton
+55 −3
Original line number Diff line number Diff line
@@ -35,6 +35,9 @@ import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.Region
import android.hardware.input.InputManager
import android.hardware.input.InputManager.KeyGestureEventHandler
import android.hardware.input.KeyGestureEvent
import android.os.Binder
import android.os.Handler
import android.os.IBinder
@@ -42,6 +45,7 @@ import android.os.SystemProperties
import android.util.Size
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.KeyEvent
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CLOSE
@@ -57,6 +61,7 @@ import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.hardware.input.Flags.useKeyGestureEventHandler
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
@@ -65,6 +70,7 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
@@ -78,12 +84,13 @@ import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
@@ -92,7 +99,6 @@ import com.android.wm.shell.shared.ShellSharedConstants
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
@@ -105,6 +111,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.UserChangeListener
import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
@@ -149,11 +156,14 @@ class DesktopTasksController(
    private val recentTasksController: RecentTasksController?,
    private val interactionJankMonitor: InteractionJankMonitor,
    @ShellMainThread private val handler: Handler,
    private val inputManager: InputManager,
    private val focusTransitionObserver: FocusTransitionObserver,
) :
    RemoteCallable<DesktopTasksController>,
    Transitions.TransitionHandler,
    DragAndDropController.DragAndDropListener,
    UserChangeListener {
    UserChangeListener,
    KeyGestureEventHandler {

    private val desktopMode: DesktopModeImpl
    private var visualIndicator: DesktopModeVisualIndicator? = null
@@ -226,6 +236,9 @@ class DesktopTasksController(
            }
        )
        dragAndDropController.addListener(this)
        if (useKeyGestureEventHandler() && enableMoveToNextDisplayShortcut()) {
            inputManager.registerKeyGestureEventHandler(this)
        }
    }

    @VisibleForTesting
@@ -1587,12 +1600,26 @@ class DesktopTasksController(
        getFocusedFreeformTask(displayId)?.let { requestSplit(it, leftOrTop) }
    }

    /** Move the focused desktop task in given `displayId` to next display. */
    fun moveFocusedTaskToNextDisplay(displayId: Int) {
        getFocusedFreeformTask(displayId)?.let { moveToNextDisplay(it.taskId) }
    }

    private fun getFocusedFreeformTask(displayId: Int): RunningTaskInfo? {
        return shellTaskOrganizer.getRunningTasks(displayId).find { taskInfo ->
            taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
        }
    }

    // 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.
    private fun getGloballyFocusedFreeformTask(): RunningTaskInfo? =
        shellTaskOrganizer.getRunningTasks().find { taskInfo ->
            taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
                    focusTransitionObserver.hasGlobalFocus(taskInfo)
        }

    /**
     * Requests a task be transitioned from desktop to split select. Applies needed windowing
     * changes if this transition is enabled.
@@ -1947,6 +1974,31 @@ class DesktopTasksController(
        taskRepository.dump(pw, innerPrefix)
    }

    override fun handleKeyGestureEvent(
        event: KeyGestureEvent,
        focusedToken: IBinder?
    ): Boolean {
        if (!isKeyGestureSupported(event.keyGestureType)) return false
        when (event.keyGestureType) {
            KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> {
                if (event.keycodes.contains(KeyEvent.KEYCODE_D) &&
                    event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)) {
                    logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled")
                    getGloballyFocusedFreeformTask()?.let { moveToNextDisplay(it.taskId) }
                    return true
                }
                return false
            }
            else -> return false
        }
    }

    override fun isKeyGestureSupported(gestureType: Int): Boolean = when (gestureType) {
        KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
            -> enableMoveToNextDisplayShortcut()
        else -> false
    }

    /** The interface for calls from outside the shell, within the host process. */
    @ExternalThread
    private inner class DesktopModeImpl : DesktopMode {
+57 −0
Original line number Diff line number Diff line
@@ -39,6 +39,9 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.hardware.input.InputManager
import android.hardware.input.InputManager.KeyGestureEventHandler
import android.hardware.input.KeyGestureEvent
import android.os.Binder
import android.os.Bundle
import android.os.Handler
@@ -50,6 +53,7 @@ import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.Gravity
import android.view.KeyEvent
import android.view.SurfaceControl
import android.view.WindowInsets
import android.view.WindowManager
@@ -70,14 +74,18 @@ import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_R
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import android.window.WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
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_FULLY_IMMERSIVE_IN_DESKTOP
import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT
import com.android.wm.shell.MockToken
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -112,6 +120,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.TestRemoteTransition
import com.android.wm.shell.transition.Transitions
@@ -206,6 +215,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
  @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter
  @Mock private lateinit var mockHandler: Handler
  @Mock lateinit var persistentRepository: DesktopPersistentRepository
  @Mock private lateinit var mockInputManager: InputManager
  @Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver

  private lateinit var mockitoSession: StaticMockitoSession
  private lateinit var controller: DesktopTasksController
@@ -214,6 +225,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
  private lateinit var desktopTasksLimiter: DesktopTasksLimiter
  private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
  private lateinit var testScope: CoroutineScope
  private lateinit var keyGestureEventHandler: KeyGestureEventHandler

  private val shellExecutor = TestShellExecutor()

@@ -271,6 +283,11 @@ class DesktopTasksControllerTest : ShellTestCase() {
    controller.setSplitScreenController(splitScreenController)
    controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter

    doAnswer {
      keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler)
      null
    }.whenever(mockInputManager).registerKeyGestureEventHandler(any())

    shellInit.init()

    val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
@@ -310,6 +327,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
        recentTasksController,
        mockInteractionJankMonitor,
        mockHandler,
        mockInputManager,
        mockFocusTransitionObserver,
      )
  }

@@ -1466,6 +1485,44 @@ class DesktopTasksControllerTest : ShellTestCase() {
    }
  }

  @Test
  @EnableFlags(
    FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS,
    FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
    FLAG_USE_KEY_GESTURE_EVENT_HANDLER
  )
  fun moveToNextDisplay_withKeyGesture() {
    // Set up two display ids
    whenever(rootTaskDisplayAreaOrganizer.displayIds)
      .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
    // Create a mock for the target display area: default display
    val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
    whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
      .thenReturn(defaultDisplayArea)
    // Setup a focused task on secondary display, which is expected to move to default display
    val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
    task.isFocused = true
    whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
    whenever(mockFocusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true)

    val event = KeyGestureEvent.Builder()
        .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY)
        .setDisplayId(SECOND_DISPLAY)
        .setKeycodes(intArrayOf(KeyEvent.KEYCODE_D))
        .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
        .build()
    val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)

    assertThat(result).isTrue()
    with(getLatestWct(type = TRANSIT_CHANGE)) {
      assertThat(hierarchyOps).hasSize(1)
      assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
      assertThat(hierarchyOps[0].isReparent).isTrue()
      assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder())
      assertThat(hierarchyOps[0].toTop).isTrue()
    }
  }

  @Test
  fun getTaskWindowingMode() {
    val fullscreenTask = setUpFullscreenTask()
Loading