summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/window/DesktopModeFlags.java4
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt219
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt63
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java6
13 files changed, 462 insertions, 22 deletions
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 289c5cf4bf85..be69d3da3874 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -86,7 +86,9 @@ public enum DesktopModeFlags {
ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, false),
ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(
- Flags::enableDesktopAppLaunchTransitionsBugfix, false);
+ Flags::enableDesktopAppLaunchTransitionsBugfix, false),
+ INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC(
+ Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true);
private static final String TAG = "DesktopModeFlagsUtil";
// Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 1707e61b28e4..3b77b1f65dac 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -16,6 +16,17 @@ flag {
}
flag {
+ name: "include_top_transparent_fullscreen_task_in_desktop_heuristic"
+ namespace: "lse_desktop_experience"
+ description: "Whether to include any top transparent fullscreen task launched in desktop /n"
+ "mode in the heuristic for if desktop windowing is showing or not."
+ bug: "379543275"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_windowing_dynamic_initial_bounds"
namespace: "lse_desktop_experience"
description: "Enables new initial bounds for desktop windowing which adjust depending on app constraints"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index bc56637b2a1e..d1dcc9b1d591 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -18,22 +18,29 @@
package com.android.wm.shell.compatui
-import android.app.TaskInfo
+import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import com.android.internal.R
// TODO(b/347289970): Consider replacing with API
/**
* If the top activity should be exempt from desktop windowing and forced back to fullscreen.
- * Currently includes all system ui activities and modal dialogs. However is the top activity is not
+ * Currently includes all system ui activities and modal dialogs. However if the top activity is not
* being displayed, regardless of its configuration, we will not exempt it as to remain in the
* desktop windowing environment.
*/
-fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
- (isSystemUiTask(context, task) || (task.numActivities > 0 && task.isActivityStackTransparent))
+fun isTopActivityExemptFromDesktopWindowing(context: Context, task: RunningTaskInfo) =
+ (isSystemUiTask(context, task) || isTransparentTask(task))
&& !task.isTopActivityNoDisplay
-private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
+/**
+ * Returns true if all activities in a tasks stack are transparent. If there are no activities will
+ * return false.
+ */
+fun isTransparentTask(task: RunningTaskInfo): Boolean = task.isActivityStackTransparent
+ && task.numActivities > 0
+
+private fun isSystemUiTask(context: Context, task: RunningTaskInfo): Boolean {
val sysUiPackageName: String =
context.resources.getString(R.string.config_systemUi)
return task.baseActivity?.packageName == sysUiPackageName
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index c4abee3bed78..c5b570dd3d57 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -54,7 +54,9 @@ class DesktopRepository(
* @property closingTasks task ids for tasks that are going to close, but are currently visible.
* @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom
* @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode.
- * (top is at index 0).
+ * @property topTransparentFullscreenTaskId the task id of any current top transparent
+ * fullscreen task launched on top of Desktop Mode. Cleared when the transparent task is
+ * closed or sent to back. (top is at index 0).
*/
private data class DesktopTaskData(
val activeTasks: ArraySet<Int> = ArraySet(),
@@ -64,6 +66,7 @@ class DesktopRepository(
val closingTasks: ArraySet<Int> = ArraySet(),
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
var fullImmersiveTaskId: Int? = null,
+ var topTransparentFullscreenTaskId: Int? = null,
) {
fun deepCopy(): DesktopTaskData =
DesktopTaskData(
@@ -73,6 +76,7 @@ class DesktopRepository(
closingTasks = ArraySet(closingTasks),
freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
fullImmersiveTaskId = fullImmersiveTaskId,
+ topTransparentFullscreenTaskId = topTransparentFullscreenTaskId,
)
fun clear() {
@@ -82,6 +86,7 @@ class DesktopRepository(
closingTasks.clear()
freeformTasksInZOrder.clear()
fullImmersiveTaskId = null
+ topTransparentFullscreenTaskId = null
}
}
@@ -322,13 +327,27 @@ class DesktopRepository(
fun getTaskInFullImmersiveState(displayId: Int): Int? =
desktopTaskDataByDisplayId.getOrCreate(displayId).fullImmersiveTaskId
+ /** Sets the top transparent fullscreen task id for a given display. */
+ fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) {
+ desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = taskId
+ }
+
+ /** Returns the top transparent fullscreen task id for a given display, or null. */
+ fun getTopTransparentFullscreenTaskId(displayId: Int): Int? =
+ desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId
+
+ /** Clears the top transparent fullscreen task id info for a given display. */
+ fun clearTopTransparentFullscreenTaskId(displayId: Int) {
+ desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = null
+ }
+
private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
visibleTasksListeners.forEach { (listener, executor) ->
executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) }
}
}
- /** Gets number of visible tasks on given [displayId] */
+ /** Gets number of visible freeform tasks on given [displayId] */
fun getVisibleTaskCount(displayId: Int): Int =
desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size
?: 0.also { logD("getVisibleTaskCount=$it") }
@@ -526,6 +545,10 @@ class DesktopRepository(
)
pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}")
pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}")
+ pw.println(
+ "${innerPrefix}topTransparentFullscreenTaskId=" +
+ "${data.topTransparentFullscreenTaskId}"
+ )
pw.println("${innerPrefix}wallpaperActivityToken=$wallpaperActivityToken")
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 609ac0aac381..a3d3a90fef3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -21,7 +21,6 @@ import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
import android.app.KeyguardManager
import android.app.PendingIntent
-import android.app.TaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -84,6 +83,7 @@ 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.compatui.isTransparentTask
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
@@ -308,11 +308,23 @@ class DesktopTasksController(
}
}
- /** Gets number of visible tasks in [displayId]. */
+ /** Gets number of visible freeform tasks in [displayId]. */
fun visibleTaskCount(displayId: Int): Int = taskRepository.getVisibleTaskCount(displayId)
- /** Returns true if any tasks are visible in Desktop Mode. */
- fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
+ /**
+ * Returns true if any freeform tasks are visible or if a transparent fullscreen task exists on
+ * top in Desktop Mode.
+ */
+ fun isDesktopModeShowing(displayId: Int): Boolean {
+ if (
+ DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
+ .isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
+ ) {
+ return visibleTaskCount(displayId) > 0 ||
+ taskRepository.getTopTransparentFullscreenTaskId(displayId) != null
+ }
+ return visibleTaskCount(displayId) > 0
+ }
/** Moves focused task to desktop mode for given [displayId]. */
fun moveFocusedTaskToDesktop(displayId: Int, transitionSource: DesktopModeTransitionSource) {
@@ -863,7 +875,11 @@ class DesktopTasksController(
}
val wct = WindowContainerTransaction()
- if (!task.isFreeform) addMoveToDesktopChanges(wct, task, displayId)
+ if (!task.isFreeform) {
+ addMoveToDesktopChanges(wct, task, displayId)
+ } else if (Flags.enableMoveToNextDisplayShortcut()) {
+ applyFreeformDisplayChange(wct, task, displayId)
+ }
wct.reparent(task.token, displayAreaInfo.token, true /* onTop */)
if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
@@ -1592,7 +1608,7 @@ class DesktopTasksController(
TransitionUtil.isOpeningType(request.type) &&
taskRepository.isActiveTask(triggerTask.taskId))
- private fun isIncompatibleTask(task: TaskInfo) =
+ private fun isIncompatibleTask(task: RunningTaskInfo) =
DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() &&
isTopActivityExemptFromDesktopWindowing(context, task)
@@ -1848,6 +1864,15 @@ class DesktopTasksController(
* fullscreen.
*/
private fun handleIncompatibleTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+ logV("handleIncompatibleTaskLaunch")
+ if (!isDesktopModeShowing(task.displayId)) return null
+ // Only update task repository for transparent task.
+ if (
+ DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
+ .isTrue() && isTransparentTask(task)
+ ) {
+ taskRepository.setTopTransparentFullscreenTaskId(task.displayId, task.taskId)
+ }
// Already fullscreen, no-op.
if (task.isFullscreen) return null
return WindowContainerTransaction().also { wct -> addMoveToFullscreenChanges(wct, task) }
@@ -1909,6 +1934,50 @@ class DesktopTasksController(
}
}
+ /**
+ * Apply changes to move a freeform task from one display to another, which includes handling
+ * density changes between displays.
+ */
+ private fun applyFreeformDisplayChange(
+ wct: WindowContainerTransaction,
+ taskInfo: RunningTaskInfo,
+ destDisplayId: Int,
+ ) {
+ val sourceLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+ val destLayout = displayController.getDisplayLayout(destDisplayId) ?: return
+ val bounds = taskInfo.configuration.windowConfiguration.bounds
+ val scaledWidth = bounds.width() * destLayout.densityDpi() / sourceLayout.densityDpi()
+ val scaledHeight = bounds.height() * destLayout.densityDpi() / sourceLayout.densityDpi()
+ val sourceWidthMargin = sourceLayout.width() - bounds.width()
+ val sourceHeightMargin = sourceLayout.height() - bounds.height()
+ val destWidthMargin = destLayout.width() - scaledWidth
+ val destHeightMargin = destLayout.height() - scaledHeight
+ val scaledLeft =
+ if (sourceWidthMargin != 0) {
+ bounds.left * destWidthMargin / sourceWidthMargin
+ } else {
+ destWidthMargin / 2
+ }
+ val scaledTop =
+ if (sourceHeightMargin != 0) {
+ bounds.top * destHeightMargin / sourceHeightMargin
+ } else {
+ destHeightMargin / 2
+ }
+ val boundsWithinDisplay =
+ if (destWidthMargin >= 0 && destHeightMargin >= 0) {
+ Rect(0, 0, scaledWidth, scaledHeight).apply {
+ offsetTo(
+ scaledLeft.coerceIn(0, destWidthMargin),
+ scaledTop.coerceIn(0, destHeightMargin),
+ )
+ }
+ } else {
+ getInitialBounds(destLayout, taskInfo, destDisplayId)
+ }
+ wct.setBounds(taskInfo.token, boundsWithinDisplay)
+ }
+
private fun getInitialBounds(
displayLayout: DisplayLayout,
taskInfo: RunningTaskInfo,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 9625b71ad3cb..5c79658b6809 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -75,6 +75,12 @@ class DesktopTasksTransitionObserver(
finishTransaction: SurfaceControl.Transaction,
) {
// TODO: b/332682201 Update repository state
+ if (
+ DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
+ .isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
+ ) {
+ updateTopTransparentFullscreenTaskId(info)
+ }
updateWallpaperToken(info)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
handleBackNavigation(transition, info)
@@ -264,4 +270,22 @@ class DesktopTasksTransitionObserver(
}
}
}
+
+ private fun updateTopTransparentFullscreenTaskId(info: TransitionInfo) {
+ info.changes.forEach { change ->
+ change.taskInfo?.let { task ->
+ val desktopRepository = desktopUserRepositories.getProfile(task.userId)
+ val displayId = task.displayId
+ // Clear `topTransparentFullscreenTask` information from repository if task
+ // is closed or sent to back.
+ if (
+ TransitionUtil.isClosingMode(change.mode) &&
+ task.taskId ==
+ desktopRepository.getTopTransparentFullscreenTaskId(displayId)
+ ) {
+ desktopRepository.clearTopTransparentFullscreenTaskId(displayId)
+ }
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index 3ab7b3418e02..7157a7f0b38f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -27,10 +27,9 @@ import org.junit.Test
import org.junit.runner.RunWith
/**
- * Tests for {@link AppCompatUtils}.
+ * Tests for [@link AppCompatUtils].
*
- * Build/Install/Run:
- * atest WMShellUnitTests:AppCompatUtilsTest
+ * Build/Install/Run: atest WMShellUnitTests:AppCompatUtilsTest
*/
@RunWith(AndroidTestingRunner::class)
@SmallTest
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index e777ec7b55f6..5629127b8c54 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -56,6 +56,11 @@ import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+/**
+ * Tests for [@link DesktopRepository].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesktopRepositoryTest
+ */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@ExperimentalCoroutinesApi
@@ -978,6 +983,22 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
+ fun setTaskIdAsTopTransparentFullscreenTaskId_savesTaskId() {
+ repo.setTopTransparentFullscreenTaskId(displayId = DEFAULT_DISPLAY, taskId = 1)
+
+ assertThat(repo.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)).isEqualTo(1)
+ }
+
+ @Test
+ fun clearTaskIdAsTopTransparentFullscreenTaskId_clearsTaskId() {
+ repo.setTopTransparentFullscreenTaskId(displayId = DEFAULT_DISPLAY, taskId = 1)
+
+ repo.clearTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)
+
+ assertThat(repo.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)).isNull()
+ }
+
+ @Test
fun setTaskInFullImmersiveState_savedAsInImmersiveState() {
assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 0eb88e368054..4f37180baa37 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -83,6 +83,7 @@ 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_FULLY_IMMERSIVE_IN_DESKTOP
+import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT
import com.android.window.flags.Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY
import com.android.wm.shell.MockToken
import com.android.wm.shell.R
@@ -297,6 +298,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
(i.arguments.first() as Rect).set(STABLE_BOUNDS)
}
+ whenever(displayLayout.densityDpi()).thenReturn(160)
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) })
.thenReturn(Desktop.getDefaultInstance())
doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) }
@@ -543,6 +545,18 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun isDesktopModeShowing_topTransparentFullscreenTask_returnsTrue() {
+ val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId)
+
+ assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
val homeTask = setUpHomeTask(SECOND_DISPLAY)
@@ -1746,6 +1760,154 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
+ fun moveToNextDisplay_sizeInDpPreserved() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ // Two displays have different density
+ whenever(displayLayout.densityDpi()).thenReturn(320)
+ whenever(displayLayout.width()).thenReturn(2400)
+ whenever(displayLayout.height()).thenReturn(1600)
+ val secondaryLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout)
+ whenever(secondaryLayout.densityDpi()).thenReturn(160)
+ whenever(secondaryLayout.width()).thenReturn(1280)
+ whenever(secondaryLayout.height()).thenReturn(720)
+
+ // Place a task with a size of 640x480 at a position where the ratio of the left margin to
+ // the right margin is 1:3 and the ratio of top margin to the bottom margin is 1:2.
+ val task =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(440, 374, 1080, 854))
+
+ controller.moveToNextDisplay(task.taskId)
+
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
+ val taskChange = changes[task.token.asBinder()]
+ assertThat(taskChange).isNotNull()
+ // To preserve DP size, pixel size is changed to 320x240. The ratio of the left margin
+ // to the right margin and the ratio of the top margin to bottom margin are also
+ // preserved.
+ assertThat(taskChange!!.configuration.windowConfiguration.bounds)
+ .isEqualTo(Rect(240, 160, 560, 400))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
+ fun moveToNextDisplay_shiftWithinDestinationDisplayBounds() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ // Two displays have different density
+ whenever(displayLayout.densityDpi()).thenReturn(320)
+ whenever(displayLayout.width()).thenReturn(2400)
+ whenever(displayLayout.height()).thenReturn(1600)
+ val secondaryLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout)
+ whenever(secondaryLayout.densityDpi()).thenReturn(160)
+ whenever(secondaryLayout.width()).thenReturn(1280)
+ whenever(secondaryLayout.height()).thenReturn(720)
+
+ // Place a task with a size of 640x480 at a position where the bottom-right corner of the
+ // window is outside the source display bounds. The destination display still has enough
+ // space to place the window within its bounds.
+ val task =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(2000, 1200, 2640, 1680))
+
+ controller.moveToNextDisplay(task.taskId)
+
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
+ val taskChange = changes[task.token.asBinder()]
+ assertThat(taskChange).isNotNull()
+ assertThat(taskChange!!.configuration.windowConfiguration.bounds)
+ .isEqualTo(Rect(960, 480, 1280, 720))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
+ fun moveToNextDisplay_maximizedTask() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ // Two displays have different density
+ whenever(displayLayout.densityDpi()).thenReturn(320)
+ whenever(displayLayout.width()).thenReturn(1280)
+ whenever(displayLayout.height()).thenReturn(960)
+ val secondaryLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout)
+ whenever(secondaryLayout.densityDpi()).thenReturn(160)
+ whenever(secondaryLayout.width()).thenReturn(1280)
+ whenever(secondaryLayout.height()).thenReturn(720)
+
+ // Place a task with a size equals to display size.
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(0, 0, 1280, 960))
+
+ controller.moveToNextDisplay(task.taskId)
+
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
+ val taskChange = changes[task.token.asBinder()]
+ assertThat(taskChange).isNotNull()
+ // DP size is preserved. The window is centered in the destination display.
+ assertThat(taskChange!!.configuration.windowConfiguration.bounds)
+ .isEqualTo(Rect(320, 120, 960, 600))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
+ fun moveToNextDisplay_defaultBoundsWhenDestinationTooSmall() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ // Two displays have different density
+ whenever(displayLayout.densityDpi()).thenReturn(320)
+ whenever(displayLayout.width()).thenReturn(2400)
+ whenever(displayLayout.height()).thenReturn(1600)
+ val secondaryLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout)
+ whenever(secondaryLayout.densityDpi()).thenReturn(160)
+ whenever(secondaryLayout.width()).thenReturn(640)
+ whenever(secondaryLayout.height()).thenReturn(480)
+ whenever(secondaryLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(0, 0, 640, 480)
+ }
+
+ // A task with a size of 1800x1200 is being placed. To preserve DP size,
+ // 900x600 pixels are needed, which does not fit in the destination display.
+ val task =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(300, 200, 2100, 1400))
+
+ controller.moveToNextDisplay(task.taskId)
+
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
+ val taskChange = changes[task.token.asBinder()]
+ assertThat(taskChange).isNotNull()
+ assertThat(taskChange!!.configuration.windowConfiguration.bounds.left).isAtLeast(0)
+ assertThat(taskChange.configuration.windowConfiguration.bounds.top).isAtLeast(0)
+ assertThat(taskChange.configuration.windowConfiguration.bounds.right).isAtMost(640)
+ assertThat(taskChange.configuration.windowConfiguration.bounds.bottom).isAtMost(480)
+ }
+ }
+
+ @Test
fun getTaskWindowingMode() {
val fullscreenTask = setUpFullscreenTask()
val freeformTask = setUpFreeformTask()
@@ -2473,6 +2635,63 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun handleRequest_topActivityTransparentWithDisplay_savedToDesktopRepository() {
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ markTaskVisible(freeformTask)
+
+ val transparentTask =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY).apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = false
+ numActivities = 1
+ }
+
+ controller.handleRequest(Binder(), createTransition(transparentTask))
+ assertThat(taskRepository.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY))
+ .isEqualTo(transparentTask.taskId)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun handleRequest_desktopNotShowing_topTransparentFullscreenTask_notSavedToDesktopRepository() {
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ controller.handleRequest(Binder(), createTransition(task))
+ assertThat(taskRepository.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)).isNull()
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun handleRequest_onlyTopTransparentFullscreenTask_returnSwitchToFreeformWCT() {
+ val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId)
+
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_desktopNotShowing_topTransparentFullscreenTask_returnNull() {
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() {
val freeformTask = setUpFreeformTask()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index c9623bcd5c16..c66d203fd89a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -24,6 +24,7 @@ import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
@@ -63,8 +64,15 @@ import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+/**
+ * Tests for [@link DesktopTasksTransitionObserver].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesktopTasksTransitionObserverTest
+ */
class DesktopTasksTransitionObserverTest {
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
@JvmField
@Rule
val extendedMockitoRule =
@@ -245,6 +253,48 @@ class DesktopTasksTransitionObserverTest {
wct.assertRemoveAt(index = 0, wallpaperToken)
}
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun topTransparentTaskClosed_clearTaskIdFromRepository() {
+ val mockTransition = Mockito.mock(IBinder::class.java)
+ val topTransparentTask = createTaskInfo(1)
+ whenever(taskRepository.getTopTransparentFullscreenTaskId(any()))
+ .thenReturn(topTransparentTask.taskId)
+
+ transitionObserver.onTransitionReady(
+ transition = mockTransition,
+ info = createCloseTransition(topTransparentTask),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun topTransparentTaskSentToBack_clearTaskIdFromRepository() {
+ val mockTransition = Mockito.mock(IBinder::class.java)
+ val topTransparentTask = createTaskInfo(1)
+ whenever(taskRepository.getTopTransparentFullscreenTaskId(any()))
+ .thenReturn(topTransparentTask.taskId)
+
+ transitionObserver.onTransitionReady(
+ transition = mockTransition,
+ info = createToBackTransition(topTransparentTask),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId)
+ }
+
private fun createBackNavigationTransition(
task: RunningTaskInfo?,
type: Int = TRANSIT_TO_BACK,
@@ -301,6 +351,19 @@ class DesktopTasksTransitionObserverTest {
}
}
+ private fun createToBackTransition(task: RunningTaskInfo?): TransitionInfo {
+ return TransitionInfo(TRANSIT_TO_BACK, 0 /* flags */).apply {
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_TO_BACK
+ parent = null
+ taskInfo = task
+ flags = flags
+ }
+ )
+ }
+ }
+
private fun getLatestWct(
@WindowManager.TransitionType type: Int = TRANSIT_OPEN,
handlerClass: Class<out Transitions.TransitionHandler>? = null,
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 7aed33d94223..16e20297dcf3 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -23,6 +23,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCA
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
import static android.content.pm.ActivityInfo.isFixedOrientation;
import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
+import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
import static android.content.pm.ActivityInfo.screenOrientationToString;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -66,6 +67,7 @@ class AppCompatOrientationPolicy {
final boolean shouldCameraCompatControlOrientation =
AppCompatCameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
if (hasFullscreenOverride && isIgnoreOrientationRequestEnabled
+ && (isFixedOrientationLandscape(candidate) || isFixedOrientationPortrait(candidate))
// Do not override orientation to fullscreen for camera activities.
// Fixed-orientation activities are rarely tested in other orientations, and it
// often results in sideways or stretched previews. As the camera compat treatment
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index d0d3d4321a0a..f3b043bb51dd 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -60,7 +60,7 @@ class AppCompatSizeCompatModePolicy {
/**
* The precomputed display insets for resolving configuration. It will be non-null if
- * {@link #shouldCreateAppCompatDisplayInsets} returns {@code true}.
+ * {@link ActivityRecord#shouldCreateAppCompatDisplayInsets} returns {@code true}.
*/
@Nullable
private AppCompatDisplayInsets mAppCompatDisplayInsets;
@@ -84,7 +84,7 @@ class AppCompatSizeCompatModePolicy {
}
/**
- * @return The {@code true} if the current instance has {@link mAppCompatDisplayInsets} without
+ * @return The {@code true} if the current instance has {@link #mAppCompatDisplayInsets} without
* considering the inheritance implemented in {@link #getAppCompatDisplayInsets()}
*/
boolean hasAppCompatDisplayInsetsWithoutInheritance() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index 09ed9baba096..90bf5f03bb1f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -302,15 +302,15 @@ public class AppCompatOrientationPolicyTest extends WindowTestsBase {
}
@Test
- public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() {
+ public void testOverrideOrientationIfNeeded_userFullscreenOverride_notLetterboxed_unchanged() {
runTestScenarioWithActivity((robot) -> {
robot.applyOnActivity((a) -> {
a.setShouldApplyUserFullscreenOverride(true);
a.setIgnoreOrientationRequest(true);
});
- robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED,
- /* expected */ SCREEN_ORIENTATION_USER);
+ robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_LOCKED,
+ /* expected */ SCREEN_ORIENTATION_LOCKED);
});
}