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

Commit 240c02d4 authored by Massimo Carli's avatar Massimo Carli
Browse files

[1/n] Use fullscreen for modals dialog in desktop mode

When a transparent activity is launched as the only activity of a task
we disable desktop mode and hide the header.

Bug: 319492844
Test: atest WmTests:TaskTests
Test: atest WMShellUnitTests:DesktopTasksControllerTest
Test: atest WMShellUnitTests:DesktopModeWindowDecorViewModelTests
Test: atest WMShellUnitTests:AppCompatUtilsTest

Change-Id: I2ecf7c9d84ffc39fe6f5b28abc5ad7d50b3314a8
parent 77964d18
Loading
Loading
Loading
Loading
+24 −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.
 */

@file:JvmName("AppCompatUtils")

package com.android.wm.shell.compatui

import android.app.TaskInfo
fun isSingleTopActivityTranslucent(task: TaskInfo) =
    task.isTopActivityTransparent && task.numActivities == 1
+74 −18
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
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
@@ -46,6 +47,7 @@ import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
@@ -63,6 +65,7 @@ import com.android.wm.shell.common.annotations.ExternalThread
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.compatui.isSingleTopActivityTranslucent
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.draganddrop.DragAndDropController
@@ -80,6 +83,8 @@ import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import com.android.wm.shell.windowdecor.extension.isFreeform
import com.android.wm.shell.windowdecor.extension.isFullscreen
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -162,8 +167,11 @@ class DesktopTasksController(
    private fun onInit() {
        KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
        shellCommandHandler.addDumpCallback(this::dump, this)
        shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler,
            this)
        shellCommandHandler.addCommandCallback(
            "desktopmode",
            desktopModeShellCommandHandler,
            this
        )
        shellController.addExternalInterface(
            ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
            { createExternalInterface() },
@@ -267,9 +275,11 @@ class DesktopTasksController(
                    // Split-screen case where there are two focused tasks, then we find the child
                    // task to move to desktop.
                    val splitFocusedTask =
                        if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId)
                        if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId) {
                            allFocusedTasks[1]
                        else allFocusedTasks[0]
                        } else {
                            allFocusedTasks[0]
                        }
                    moveToDesktop(splitFocusedTask)
                }
                1 -> {
@@ -308,8 +318,18 @@ class DesktopTasksController(
    ) {
        if (!DesktopModeStatus.canEnterDesktopMode(context)) {
            KtProtoLog.w(
                WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, " +
                        "display does not meet minimum size requirements")
                WM_SHELL_DESKTOP_MODE,
                "DesktopTasksController: Cannot enter desktop, " +
                        "display does not meet minimum size requirements"
            )
            return
        }
        if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) {
            KtProtoLog.w(
                WM_SHELL_DESKTOP_MODE,
                "DesktopTasksController: Cannot enter desktop, " +
                        "translucent top activity found. This is likely a modal dialog."
            )
            return
        }
        KtProtoLog.v(
@@ -444,7 +464,11 @@ class DesktopTasksController(

        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            exitDesktopTaskTransitionHandler.startTransition(
            Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct, position, mOnAnimationFinishedCallback)
            Transitions.TRANSIT_EXIT_DESKTOP_MODE,
                wct,
                position,
                mOnAnimationFinishedCallback
            )
        } else {
            shellTaskOrganizer.applyTransaction(wct)
            releaseVisualIndicator()
@@ -490,8 +514,12 @@ class DesktopTasksController(
            KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
            return
        }
        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d",
                taskId, task.displayId)
        KtProtoLog.v(
            WM_SHELL_DESKTOP_MODE,
            "moveToNextDisplay: taskId=%d taskDisplayId=%d",
                taskId,
            task.displayId
        )

        val displayIds = rootTaskDisplayAreaOrganizer.displayIds.sorted()
        // Get the first display id that is higher than current task display id
@@ -513,8 +541,12 @@ class DesktopTasksController(
     * No-op if task is already on that display per [RunningTaskInfo.displayId].
     */
    private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d",
                task.taskId, displayId)
        KtProtoLog.v(
            WM_SHELL_DESKTOP_MODE,
            "moveToDisplay: taskId=%d displayId=%d",
                task.taskId,
            displayId
        )

        if (task.displayId == displayId) {
            KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
@@ -588,7 +620,8 @@ class DesktopTasksController(
        // Center the task in screen bounds
        outBounds.offset(
            screenBounds.centerX() - outBounds.centerX(),
            screenBounds.centerY() - outBounds.centerY())
            screenBounds.centerY() - outBounds.centerY()
        )
    }

    private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
@@ -735,11 +768,13 @@ class DesktopTasksController(
        val result = triggerTask?.let { task ->
            when {
                // If display has tasks stashed, handle as stashed launch
                desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
                task.isStashed -> handleStashedTaskLaunch(task)
                // Check if the task has a top transparent activity
                shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task)
                // Check if fullscreen task should be updated
                task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
                task.isFullscreen -> handleFullscreenTaskLaunch(task)
                // Check if freeform task should be updated
                task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
                task.isFreeform -> handleFreeformTaskLaunch(task)
                else -> {
                    null
                }
@@ -772,6 +807,13 @@ class DesktopTasksController(
                .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
    }

    private val TaskInfo.isStashed: Boolean
        get() = desktopModeTaskRepository.isStashed(displayId)

    private fun shouldLaunchAsModal(task: TaskInfo): Boolean {
        return Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
    }

    private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
        val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
@@ -819,6 +861,16 @@ class DesktopTasksController(
        return wct
    }

    // Always launch transparent tasks in fullscreen.
    private fun handleTransparentTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
        // Already fullscreen, no-op.
        if (task.isFullscreen)
            return null
        return WindowContainerTransaction().also { wct ->
            addMoveToFullscreenChanges(wct, task)
        }
    }

    private fun addMoveToDesktopChanges(
        wct: WindowContainerTransaction,
        taskInfo: RunningTaskInfo
@@ -899,9 +951,12 @@ class DesktopTasksController(
        ) {
            val wct = WindowContainerTransaction()
            addMoveToSplitChanges(wct, taskInfo)
            splitScreenController.requestEnterSplitSelect(taskInfo, wct,
            splitScreenController.requestEnterSplitSelect(
                taskInfo,
                wct,
                if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT,
                taskInfo.configuration.windowConfiguration.bounds)
                taskInfo.configuration.windowConfiguration.bounds
            )
        }
    }

@@ -1096,7 +1151,8 @@ class DesktopTasksController(
            pendingIntentLaunchFlags =
                Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
            setPendingIntentBackgroundActivityStartMode(
                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
            )
            isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
        }
        val wct = WindowContainerTransaction()
+6 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static android.view.WindowInsets.Type.statusBars;

import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.compatui.AppCompatUtils.isSingleTopActivityTranslucent;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
@@ -75,6 +76,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -1048,6 +1050,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                && taskInfo.isFocused) {
            return false;
        }
        if (Flags.enableDesktopWindowingModalsPolicy()
                && isSingleTopActivityTranslucent(taskInfo)) {
            return false;
        }
        return DesktopModeStatus.isEnabled()
                && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
                && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+8 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.wm.shell.windowdecor.extension

import android.app.TaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND

@@ -31,3 +33,9 @@ val TaskInfo.isLightCaptionBarAppearance: Boolean
        val appearance = taskDescription?.systemBarsAppearance ?: 0
        return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
    }

val TaskInfo.isFullscreen: Boolean
    get() = windowingMode == WINDOWING_MODE_FULLSCREEN

val TaskInfo.isFreeform: Boolean
    get() = windowingMode == WINDOWING_MODE_FREEFORM
+59 −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.compatui

import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith

/**
 * Tests for {@link AppCompatUtils}.
 *
 * Build/Install/Run:
 *  atest WMShellUnitTests:AppCompatUtilsTest
 */
@RunWith(AndroidTestingRunner::class)
@SmallTest
class AppCompatUtilsTest : ShellTestCase() {

    @Test
    fun testIsSingleTopActivityTranslucent() {
        assertTrue(isSingleTopActivityTranslucent(
            createFreeformTask(/* displayId */ 0)
                    .apply {
                        isTopActivityTransparent = true
                        numActivities = 1
                    }))
        assertFalse(isSingleTopActivityTranslucent(
            createFreeformTask(/* displayId */ 0)
                    .apply {
                        isTopActivityTransparent = true
                        numActivities = 0
                    }))
        assertFalse(isSingleTopActivityTranslucent(
            createFreeformTask(/* displayId */ 0)
                    .apply {
                        isTopActivityTransparent = false
                        numActivities = 1
                    }))
    }
}
 No newline at end of file
Loading