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

Commit 525c4824 authored by Toshiki Kikuchi's avatar Toshiki Kikuchi
Browse files

Desktop-first based drag-to-maximize

This CL changes the drag-to-maximize criteria from the static overlay
config to the display state of “desktop-first” where the TDA’s windowing
mode is freeform.

Flag: com.android.window.flags.enable_desktop_first_based_drag_to_maximize
Bug: 409423422
Test: DesktopFirstUtilsTest
Test: DesktopDisplayModeControllerTest
Test: DesktopTasksControllerTest
Change-Id: I379e7daaf5b935e6f11ad4485e37533cf0a3799f
parent 509d1523
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ interface DesktopConfig {
    /**
     * Whether a window should be maximized when it's dragged to the top edge of the screen.
     */
    @Deprecated("Deprecated with desktop-first based drag-to-maximize")
    val shouldMaximizeWhenDragToTopEdge: Boolean

    /** Whether the override desktop density is enabled and valid. */
+10 −14
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.wm.shell.desktopmode

import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.windowingModeToString
@@ -103,7 +102,7 @@ class DesktopDisplayModeController(
        if (!desktopModeSupported) return

        // An external display should always be a freeform display when desktop mode is enabled.
        updateDisplayWindowingMode(displayId, WINDOWING_MODE_FREEFORM)
        updateDisplayWindowingMode(displayId, DESKTOP_FIRST_DISPLAY_WINDOWING_MODE)
    }

    fun updateDefaultDisplayWindowingMode() {
@@ -148,7 +147,7 @@ class DesktopDisplayModeController(
                    // mode of freeform tasks but fullscreen tasks which are the direct children
                    // of TDA.
                    if (it.windowingMode == WINDOWING_MODE_FULLSCREEN) {
                        if (targetDisplayWindowingMode == WINDOWING_MODE_FREEFORM) {
                        if (targetDisplayWindowingMode == DESKTOP_FIRST_DISPLAY_WINDOWING_MODE) {
                            wct.setWindowingMode(it.token, WINDOWING_MODE_FULLSCREEN)
                        } else {
                            wct.setWindowingMode(it.token, WINDOWING_MODE_UNDEFINED)
@@ -174,8 +173,8 @@ class DesktopDisplayModeController(
        transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
    }

    // Do not directly use this method to check the state of desktop-first mode. Check the display
    // windowing mode instead.
    // Do not directly use this method to check the state of desktop-first mode. Use
    // [isDisplayDesktopFirst] instead.
    private fun canDesktopFirstModeBeEnabledOnDefaultDisplay(): Boolean {
        if (FORCE_DESKTOP_FIRST_ON_DEFAULT_DISPLAY) {
            logW(
@@ -219,14 +218,16 @@ class DesktopDisplayModeController(
        return false
    }

    // Do not directly use this method to check the state of desktop-first mode. Use
    // [isDisplayDesktopFirst] instead.
    @VisibleForTesting
    fun getTargetWindowingModeForDefaultDisplay(): Int {
        if (canDesktopFirstModeBeEnabledOnDefaultDisplay()) {
            return WINDOWING_MODE_FREEFORM
            return DESKTOP_FIRST_DISPLAY_WINDOWING_MODE
        }

        return if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) {
            WINDOWING_MODE_FULLSCREEN
            TOUCH_FIRST_DISPLAY_WINDOWING_MODE
        } else {
            // If form factor-based desktop first switch is disabled, use the default display
            // windowing mode here to keep the freeform mode for some form factors (e.g.,
@@ -308,13 +309,8 @@ class DesktopDisplayModeController(
        pw.println("Current Desktop Display Modes:")
        pw.increaseIndent()
        rootTaskDisplayAreaOrganizer.displayIds.forEach { displayId ->
            val desktopFirstEnabled =
                rootTaskDisplayAreaOrganizer
                    .getDisplayAreaInfo(displayId)
                    ?.configuration
                    ?.windowConfiguration
                    ?.windowingMode == WINDOWING_MODE_FREEFORM ?: false
            pw.println("Display#$displayId desktopFirstEnabled=$desktopFirstEnabled")
            val isDesktopFirst = rootTaskDisplayAreaOrganizer.isDisplayDesktopFirst(displayId)
            pw.println("Display#$displayId isDesktopFirst=$isDesktopFirst")
        }
    }

+43 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE

/** The display windowing mode for desktop-first display. */
const val DESKTOP_FIRST_DISPLAY_WINDOWING_MODE = WINDOWING_MODE_FREEFORM

/** The display windowing mode for touch-first display. */
const val TOUCH_FIRST_DISPLAY_WINDOWING_MODE = WINDOWING_MODE_FULLSCREEN

/** Returns true if a display is desktop-first. */
fun RootTaskDisplayAreaOrganizer.isDisplayDesktopFirst(displayId: Int) =
    getDisplayAreaInfo(displayId)?.configuration?.windowConfiguration?.windowingMode?.let {
        it == DESKTOP_FIRST_DISPLAY_WINDOWING_MODE
    }
        ?: run {
            ProtoLog.w(
                WM_SHELL_DESKTOP_MODE,
                "isDisplayDesktopFirst display=%d not found",
                displayId,
            )
            false
        }
+7 −1
Original line number Diff line number Diff line
@@ -3710,7 +3710,13 @@ class DesktopTasksController(
            )
        when (indicatorType) {
            IndicatorType.TO_FULLSCREEN_INDICATOR -> {
                if (desktopConfig.shouldMaximizeWhenDragToTopEdge) {
                val shouldMaximizeWhenDragToTopEdge =
                    if (DesktopExperienceFlags.ENABLE_DESKTOP_FIRST_BASED_DRAG_TO_MAXIMIZE.isTrue)
                        rootTaskDisplayAreaOrganizer.isDisplayDesktopFirst(
                            motionEvent.getDisplayId()
                        )
                    else desktopConfig.shouldMaximizeWhenDragToTopEdge
                if (shouldMaximizeWhenDragToTopEdge) {
                    dragToMaximizeDesktopTask(taskInfo, taskSurface, currentDragBounds, motionEvent)
                } else {
                    desktopModeUiEventLogger.log(
+71 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.testing.AndroidTestingRunner
import android.window.DisplayAreaInfo
import androidx.test.filters.SmallTest
import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

/**
 * Test class for [DesktopFirstUtils]
 *
 * Usage: atest WMShellUnitTests:DesktopFirstUtilsTest
 */
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopFirstUtilsTest : ShellTestCase() {
    private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
    private val desktopFirstTDA = DisplayAreaInfo(MockToken().token(), DESKTOP_FIRST_DISPLAY_ID, 0)
    private val touchFirstTDA = DisplayAreaInfo(MockToken().token(), TOUCH_FIRST_DISPLAY_ID, 0)

    @Before
    fun setUp() {
        desktopFirstTDA.configuration.windowConfiguration.windowingMode =
            DESKTOP_FIRST_DISPLAY_WINDOWING_MODE
        touchFirstTDA.configuration.windowConfiguration.windowingMode =
            TOUCH_FIRST_DISPLAY_WINDOWING_MODE
        whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DESKTOP_FIRST_DISPLAY_ID))
            .thenReturn(desktopFirstTDA)
        whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(TOUCH_FIRST_DISPLAY_ID))
            .thenReturn(touchFirstTDA)
        whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(UNKNOWN_DISPLAY_ID))
            .thenReturn(null)
    }

    @Test
    fun isDisplayDesktopFirst() {
        assertTrue(rootTaskDisplayAreaOrganizer.isDisplayDesktopFirst(DESKTOP_FIRST_DISPLAY_ID))
        assertFalse(rootTaskDisplayAreaOrganizer.isDisplayDesktopFirst(TOUCH_FIRST_DISPLAY_ID))
        assertFalse(rootTaskDisplayAreaOrganizer.isDisplayDesktopFirst(UNKNOWN_DISPLAY_ID))
    }

    companion object {
        const val DESKTOP_FIRST_DISPLAY_ID = 100
        const val TOUCH_FIRST_DISPLAY_ID = 200
        const val UNKNOWN_DISPLAY_ID = 999
    }
}
Loading