Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopConfig.kt +1 −0 Original line number Diff line number Diff line Loading @@ -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. */ Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt +10 −14 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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) Loading @@ -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( Loading Loading @@ -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., Loading Loading @@ -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") } } Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFirstUtils.kt 0 → 100644 +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 } libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +7 −1 Original line number Diff line number Diff line Loading @@ -3723,7 +3723,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( Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFirstUtilsTest.kt 0 → 100644 +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
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopConfig.kt +1 −0 Original line number Diff line number Diff line Loading @@ -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. */ Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt +10 −14 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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) Loading @@ -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( Loading Loading @@ -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., Loading Loading @@ -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") } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopFirstUtils.kt 0 → 100644 +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 }
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +7 −1 Original line number Diff line number Diff line Loading @@ -3723,7 +3723,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( Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopFirstUtilsTest.kt 0 → 100644 +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 } }