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

Commit 3e6f466b authored by Eghosa Ewansiha-Vlachavas's avatar Eghosa Ewansiha-Vlachavas
Browse files

[1/n] Remain in desktop mode if a fullscreen transparent task is launched

Task trampolines are normally tasks which are launched to launch another
task and close themselves. These middle tasks are commonly transparent
as they are only responsible for launching the subsequent task.

Desktop mode is defined by the presence of visible freeform tasks. In
desktop mode transparent tasks (a task with all transparent activities)
are forced to be fullscreen and launch on top of desktop mode (desktop
mode still active behind).

If the last visible freeform task triggers a task trampoline, a
fullscreen transparent task can be launched and the original freeform
task will be closed. At this point there are not visible freeform tasks
so with the existing heuristic, we are not seen as in desktop mode. When
the sequence continues, the fullscreen transparent task will launch
another task and close itself. However, this final task will launch
as fullscreen instead of freeform as we no longer believe we are in desktop
mode.

This change modifies the desktop mode heuristic to be the presence of
visible freeform tasks or the presence of a top transparent task.

Flag: com.android.window.flags.enable_desktop_windowing_modals_policy
Flag: com.android.window.flags.include_top_transparent_fullscreen_task_in_desktop_heuristic
Test: atest WMShellUnitTests:DesktopTasksTransitionObserverTest
Test: atest WMShellUnitTests:AppCompatUtilsTest
Test: atest WMShellUnitTests:DesktopTasksControllerTest
Test: atest WMShellUnitTests:DesktopRepositoryTest
Fixes: 379543275

Change-Id: Idb9371d511928be12ff3da160c98316469f13f0a
parent 7cdd993b
Loading
Loading
Loading
Loading
+12 −5
Original line number Diff line number Diff line
@@ -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
+25 −2
Original line number Diff line number Diff line
@@ -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")
        }
    }
+26 −5
Original line number Diff line number Diff line
@@ -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) {
@@ -1592,7 +1604,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 +1860,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) }
+24 −0
Original line number Diff line number Diff line
@@ -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)
                }
            }
        }
    }
}
+2 −3
Original line number Diff line number Diff line
@@ -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
Loading