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

Commit e74a9f18 authored by Massimo Carli's avatar Massimo Carli
Browse files

[87/n] Handle Letterbox Surface Lifecycle in SplitScreen

Before this code, Changes for a no leaf Task were skipped
at the TransitionObserver level to avoid the creation of
letterbox surfaces using, as parent surface, the surface of
Tasks without any activity.

Now leaf tasks, the only allowed to provide the parent
surface for the creation of the letterbox surfaces, are
referenced in the LetterboxTaskInfoRepository and cannot
be completely ignored.

In case of split screen, for instance, the id of the
Task received in the Change cannot be used directly
but it will be used to identify the specific leaf
Task from its parent using the logic in TaskIdResolver.

After this change the requirement to not create letterbox
surfaces for not leaf task will be accomplished because
a LetterboxLifecycleEvent will be only created for those.
LetterboxLifecycleEvent for not leaf tasks will be null
and so skipped in DelegateLetterboxTransitionObserver.

Flag: com.android.window.flags.app_compat_refactoring_fix_multiwindow_task_hierarchy
Bug: 421103832
Test: atest WMShellUnitTests:DelegateLetterboxTransitionObserverTest
Test: atest WMShellUnitTests:TaskInfoLetterboxLifecycleEventFactoryTest
Test: atest WMShellUnitTests:MixedLetterboxControllerTest
Test: atest WMShellUnitTests:LetterboxTaskListenerAdapterTest

Change-Id: I522a2261c26468bb70535025d4edfd4a8aae3d17
parent 2e92c4e6
Loading
Loading
Loading
Loading
+12 −3
Original line number Diff line number Diff line
@@ -19,8 +19,9 @@ package com.android.wm.shell.compatui.letterbox
import android.os.IBinder
import android.view.SurfaceControl
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags.appCompatRefactoring
import com.android.window.flags.Flags
import com.android.wm.shell.compatui.letterbox.lifecycle.LetterboxLifecycleController
import com.android.wm.shell.compatui.letterbox.lifecycle.LetterboxLifecycleEventFactory
import com.android.wm.shell.compatui.letterbox.lifecycle.isChangeForALeafTask
@@ -45,7 +46,7 @@ class DelegateLetterboxTransitionObserver(
    }

    init {
        if (appCompatRefactoring()) {
        if (Flags.appCompatRefactoring()) {
            logV("Initializing LetterboxTransitionObserver")
            shellInit.addInitCallback({ transitions.registerObserver(this) }, this)
        }
@@ -62,7 +63,7 @@ class DelegateLetterboxTransitionObserver(
            return
        }
        info.changes.forEach { change ->
            if (change.isChangeForALeafTask() && letterboxLifecycleEventFactory.canHandle(change)) {
            if (taskAllowed(change) && letterboxLifecycleEventFactory.canHandle(change)) {
                letterboxLifecycleEventFactory.createLifecycleEvent(change)?.let { event ->
                    letterboxLifecycleController.onLetterboxLifecycleEvent(
                        event,
@@ -77,4 +78,12 @@ class DelegateLetterboxTransitionObserver(
    private fun logV(msg: String) {
        ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, msg)
    }

    // When the flag is disabled all the changes related to leaf Tasks are skipped. This is because
    // a leaf task surfaces should not be the parent of letterbox surfaces.
    // When the flag is enabled, leaf Tasks are handled to cover the case of split screen when
    // Task in the Change is not a leaf Task but it's still useful to find the actual leaf Task used
    // to identify the right letterbox surfaces. Check [TaskIdResolver] for additional information.
    private fun taskAllowed(change: Change): Boolean =
        Flags.appCompatRefactoringFixMultiwindowTaskHierarchy() || change.isChangeForALeafTask()
}
+7 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.wm.shell.compatui.letterbox.lifecycle

import android.app.TaskInfo
import android.graphics.Rect
import android.view.SurfaceControl
import android.window.TransitionInfo.Change
@@ -74,9 +75,13 @@ fun Change.isActivityChange(): Boolean = activityTransitionInfo != null
/** Returns [true] if the [Change] is related to a translucent container. */
fun Change.isTranslucent() = hasFlags(FLAG_TRANSLUCENT)

/** Returns [true] if the related [Task] is a leaf task. */
val TaskInfo.isALeafTask: Boolean
    get() = appCompatTaskInfo?.isLeafTask ?: false

/**
 * Returns [true] if the Task hosts Activities. This is true if the Change has Activity as target or
 * if task is a leaf task.
 * Returns [true] if the [Task] hosts Activities. This is true if the Change has [Activity] as
 * target or if task is a leaf task.
 */
fun Change.isChangeForALeafTask(): Boolean =
    taskInfo?.appCompatTaskInfo?.isLeafTask ?: isActivityChange()
+53 −13
Original line number Diff line number Diff line
@@ -18,14 +18,19 @@ package com.android.wm.shell.compatui.letterbox.lifecycle

import android.graphics.Rect
import android.window.TransitionInfo.Change
import com.android.window.flags.Flags
import com.android.wm.shell.compatui.letterbox.config.LetterboxDependenciesHelper
import com.android.wm.shell.compatui.letterbox.state.LetterboxTaskInfoRepository
import com.android.wm.shell.compatui.letterbox.state.updateTaskLeafState

/**
 * [LetterboxLifecycleEventFactory] implementation which creates a [LetterboxLifecycleEvent] from a
 * [TransitionInfo.Change] using a [TaskInfo] when present.
 */
class TaskInfoLetterboxLifecycleEventFactory(
    private val letterboxDependenciesHelper: LetterboxDependenciesHelper
    private val letterboxDependenciesHelper: LetterboxDependenciesHelper,
    private val letterboxTaskInfoRepository: LetterboxTaskInfoRepository,
    private val taskIdResolver: TaskIdResolver,
) : LetterboxLifecycleEventFactory {
    override fun canHandle(change: Change): Boolean = change.taskInfo != null

@@ -43,6 +48,40 @@ class TaskInfoLetterboxLifecycleEventFactory(
                letterboxBoundsAbs?.let { absBounds ->
                    Rect(absBounds).apply { offset(-taskBoundsAbs.left, -taskBoundsAbs.top) }
                }
            val shouldSupportInput = letterboxDependenciesHelper.shouldSupportInputSurface(change)
            if (Flags.appCompatRefactoringFixMultiwindowTaskHierarchy()) {
                // Because the [TransitionObserver] is invoked before the [OnTaskAppearedListener]s
                // it's important to store the information about the Task to be reused below for the
                // actual Task resolution given its id and parentId. Only Leaf tasks are stored
                // because they are the only ones with the capability of containing letterbox
                // surfaces.
                letterboxTaskInfoRepository.updateTaskLeafState(ti, change.leash)
                // If the task is not a leaf task the related entry is not present in the
                // Repository. The taskIdResolver will then search for a task which is a direct
                // child. If no Task is found the same id will be used later and the event
                // will be null resulting in a skipped event.
                // If the task is a leaf task the related entry will be present in the Repository
                // and the effectiveTaskId will be the correct taskId to use for the event.
                val effectiveTaskId = taskIdResolver.getLetterboxTaskId(ti)
                // The effectiveTaskId will then be the taskId of a leaf task (using parentId or
                // not) or the id of a missing task (no leaf). In the former case we need to use the
                // related token and leash. In the latter case the method returns null as mentioned
                // above.
                letterboxTaskInfoRepository.find(effectiveTaskId)?.let { item ->
                    return LetterboxLifecycleEvent(
                        type = change.asLetterboxLifecycleEventType(),
                        displayId = ti.displayId,
                        taskId = effectiveTaskId,
                        taskBounds = taskBounds,
                        letterboxBounds = letterboxBounds,
                        containerToken = item.containerToken,
                        taskLeash = item.containerLeash,
                        isBubble = ti.isAppBubble,
                        isTranslucent = change.isTranslucent(),
                        supportsInput = shouldSupportInput,
                    )
                }
            } else {
                return LetterboxLifecycleEvent(
                    type = change.asLetterboxLifecycleEventType(),
                    displayId = ti.displayId,
@@ -53,9 +92,10 @@ class TaskInfoLetterboxLifecycleEventFactory(
                    taskLeash = change.leash,
                    isBubble = ti.isAppBubble,
                    isTranslucent = change.isTranslucent(),
                supportsInput = letterboxDependenciesHelper.shouldSupportInputSurface(change),
                    supportsInput = shouldSupportInput,
                )
            }
        }
        return null
    }
}
+27 −0
Original line number Diff line number Diff line
@@ -16,10 +16,12 @@

package com.android.wm.shell.compatui.letterbox.state

import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityTaskManager
import android.view.SurfaceControl
import android.window.WindowContainerToken
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.compatui.letterbox.lifecycle.isALeafTask
import com.android.wm.shell.dagger.WMSingleton
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
import com.android.wm.shell.repository.GenericRepository
@@ -45,3 +47,28 @@ class LetterboxTaskInfoRepository @Inject constructor() :
            ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", "TaskInfoMemoryRepository", msg)
        }
    )

/**
 * We assume that only leaf Tasks will be present in the [LetterboxTaskInfoRepository]. This method
 * is responsible for keeping this invariant always true.
 */
fun LetterboxTaskInfoRepository.updateTaskLeafState(
    taskInfo: RunningTaskInfo,
    leash: SurfaceControl,
) {
    if (taskInfo.isALeafTask) {
        insert(
            key = taskInfo.taskId,
            item =
                LetterboxTaskInfoState(
                    containerToken = taskInfo.token,
                    containerLeash = leash,
                    parentTaskId = taskInfo.parentTaskId,
                    taskId = taskInfo.taskId,
                ),
            overrideIfPresent = true,
        )
    } else {
        delete(taskInfo.taskId)
    }
}
+13 −14
Original line number Diff line number Diff line
@@ -21,8 +21,9 @@ import android.view.SurfaceControl
import com.android.window.flags.Flags
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTaskOrganizer.TaskAppearedListener
import com.android.wm.shell.ShellTaskOrganizer.TaskInfoChangedListener
import com.android.wm.shell.ShellTaskOrganizer.TaskVanishedListener
import com.android.wm.shell.compatui.letterbox.lifecycle.TaskIdResolver
import com.android.wm.shell.compatui.letterbox.lifecycle.isALeafTask
import com.android.wm.shell.dagger.WMSingleton
import com.android.wm.shell.sysui.ShellInit
import javax.inject.Inject
@@ -38,8 +39,7 @@ constructor(
    shellInit: ShellInit,
    shellTaskOrganizer: ShellTaskOrganizer,
    private val letterboxTaskInfoRepository: LetterboxTaskInfoRepository,
    private val taskIdResolver: TaskIdResolver,
) : TaskVanishedListener, TaskAppearedListener {
) : TaskVanishedListener, TaskAppearedListener, TaskInfoChangedListener {

    init {
        if (Flags.appCompatRefactoring()) {
@@ -47,6 +47,9 @@ constructor(
                {
                    shellTaskOrganizer.addTaskAppearedListener(this)
                    shellTaskOrganizer.addTaskVanishedListener(this)
                    if (Flags.appCompatRefactoringFixMultiwindowTaskHierarchy()) {
                        shellTaskOrganizer.addTaskInfoChangedListener(this)
                    }
                },
                this,
            )
@@ -55,17 +58,7 @@ constructor(

    override fun onTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) {
        if (Flags.appCompatRefactoringFixMultiwindowTaskHierarchy()) {
            letterboxTaskInfoRepository.insert(
                key = taskInfo.taskId,
                item =
                    LetterboxTaskInfoState(
                        containerToken = taskInfo.token,
                        containerLeash = leash,
                        parentTaskId = taskInfo.parentTaskId,
                        taskId = taskIdResolver.getLetterboxTaskId(taskInfo),
                    ),
                overrideIfPresent = true,
            )
            letterboxTaskInfoRepository.updateTaskLeafState(taskInfo, leash)
        } else {
            letterboxTaskInfoRepository.insert(
                key = taskInfo.taskId,
@@ -76,6 +69,12 @@ constructor(
        }
    }

    override fun onTaskInfoChanged(taskInfo: RunningTaskInfo) {
        if (!taskInfo.isALeafTask) {
            letterboxTaskInfoRepository.delete(taskInfo.taskId)
        }
    }

    override fun onTaskVanished(taskInfo: RunningTaskInfo) {
        letterboxTaskInfoRepository.delete(taskInfo.taskId)
    }
Loading