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

Commit d4124d33 authored by Massimo Carli's avatar Massimo Carli Committed by Android (Google) Code Review
Browse files

Merge "[85/n] Implement TaskIdResolver" into main

parents 41ba048b a4fd03bf
Loading
Loading
Loading
Loading
+63 −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.compatui.letterbox.lifecycle

import android.app.TaskInfo
import com.android.wm.shell.compatui.letterbox.state.LetterboxTaskInfoRepository
import com.android.wm.shell.dagger.WMSingleton
import com.android.wm.shell.windowdecor.extension.isMultiWindow
import javax.inject.Inject

/**
 * The id for the Task to use in the Letterbox Lifecycle is not always the one received in
 * [TaskInfo]. Sometimes (e.g. Split Screen) the [Change] received are related to a parent [Task].
 * This class encapsulate the logic to find the right id for the [Task] used in the [Change].
 */
@WMSingleton
class TaskIdResolver @Inject constructor(
    private val letterboxTaskInfoRepository: LetterboxTaskInfoRepository
) {

    /**
     * @return The id for the Task to consider for the Letterbox bounds update.
     */
    fun getLetterboxTaskId(taskInfo: TaskInfo): Int {
        // We use the taskId itself if in the repository.
        val candidateId = taskInfo.taskId
        letterboxTaskInfoRepository.find(candidateId)?.let { item ->
            // In this case there's an item for the candidateId which means that it's an
            // eligible Task.
            return candidateId
        }
        // In this case the candidateId is not present. In case of split screen this happens when
        // the Change contains the parent of the Task with letterbox surfaces and not the Task
        // itself.
        if (taskInfo.isMultiWindow) {
            letterboxTaskInfoRepository.find { key, item -> item.parentTaskId == candidateId }
                .let { items ->
                    if (items.isNotEmpty()) {
                        // In the repository there's a Task that is eligible for letterbox surfaces
                        // whose parent has id equals to candidateId. In this case the item id
                        // will be the one. This should be exactly 1.
                        return items.first().taskId
                    }
                }
        }
        // Here the id is not present and there's no task whose parent is eligible for letterbox.
        return candidateId
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

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

import android.app.ActivityTaskManager
import android.view.SurfaceControl
import android.window.WindowContainerToken
import com.android.internal.protolog.ProtoLog
@@ -30,7 +31,9 @@ import javax.inject.Inject
 */
data class LetterboxTaskInfoState(
    val containerToken: WindowContainerToken,
    val containerLeash: SurfaceControl
    val containerLeash: SurfaceControl,
    val taskId: Int = ActivityTaskManager.INVALID_TASK_ID,
    val parentTaskId: Int = ActivityTaskManager.INVALID_TASK_ID
)

/**
+151 −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.compatui.letterbox.lifecycle

import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.compatui.letterbox.state.LetterboxTaskInfoRepository
import com.android.wm.shell.compatui.letterbox.state.LetterboxTaskInfoState
import com.android.wm.shell.util.testTaskIdResolver
import java.util.function.Consumer
import kotlin.test.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock

/**
 * Tests for [TaskIdResolver].
 *
 * Build/Install/Run:
 *  atest WMShellUnitTests:TaskIdResolverTest
 */
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskIdResolverTest : ShellTestCase() {

    @Test
    fun `Returns the same id when the task is present in the repository`() {
        runTestScenario { r ->
            testTaskIdResolver(r::factory) {
                r.prepareRepository { repo ->
                    repo.insert(
                        key = 10,
                        r.createItem(
                            taskId = 10,
                        )
                    )
                }
                runningTaskInfo { ti ->
                    ti.taskId = 10
                }
                verifyLetterboxTaskId { letterboxTaskId ->
                    assertEquals(10, letterboxTaskId)
                }
            }
        }
    }

    @Test
    fun `InMultiWindow returns id of the task with given task as parent task`() {
        runTestScenario { r ->
            testTaskIdResolver(r::factory) {
                r.prepareRepository { repo ->
                    repo.insert(
                        key = 20,
                        r.createItem(
                            taskId = 20,
                            parentTaskId = 10
                        )
                    )
                }
                runningTaskInfo { ti ->
                    ti.taskId = 10
                    ti.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_MULTI_WINDOW
                }
                verifyLetterboxTaskId { letterboxTaskId ->
                    assertEquals(20, letterboxTaskId)
                }
            }
        }
    }

    @Test
    fun `InMultiwindow returns same id if there is no task with task id as parent id`() {
        runTestScenario { r ->
            testTaskIdResolver(r::factory) {
                r.prepareRepository { repo ->
                    repo.insert(
                        key = 20,
                        r.createItem(
                            taskId = 20,
                            parentTaskId = 30
                        )
                    )
                }
                runningTaskInfo { ti ->
                    ti.taskId = 10
                    ti.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_MULTI_WINDOW
                }
                verifyLetterboxTaskId { letterboxTaskId ->
                    assertEquals(10, letterboxTaskId)
                }
            }
        }
    }

    /**
     * Runs a test scenario providing a Robot.
     */
    fun runTestScenario(consumer: Consumer<TaskIdResolverRobotTest>) {
        consumer.accept(TaskIdResolverRobotTest())
    }

    class TaskIdResolverRobotTest {

        companion object {
            @JvmStatic
            val TEST_TOKEN = mock<WindowContainerToken>()

            @JvmStatic
            val TEST_LEASH = mock<SurfaceControl>()
        }

        private val letterboxTaskInfoRepository: LetterboxTaskInfoRepository =
            LetterboxTaskInfoRepository()

        fun factory(): TaskIdResolver = TaskIdResolver(letterboxTaskInfoRepository)

        fun prepareRepository(consumer: (LetterboxTaskInfoRepository) -> Unit) {
            consumer(letterboxTaskInfoRepository)
        }

        fun createItem(
            taskId: Int = -1,
            parentTaskId: Int = -1
        ) =
            LetterboxTaskInfoState(
                containerToken = TEST_TOKEN,
                containerLeash = TEST_LEASH,
                taskId = taskId,
                parentTaskId = parentTaskId
            )
    }
}
+20 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.wm.shell.util

import android.app.ActivityManager.RunningTaskInfo
import android.app.TaskInfo
import android.content.ComponentName
import android.graphics.Point
import android.graphics.Rect
@@ -24,7 +25,6 @@ import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TransitionFlags
import android.view.WindowManager.TransitionType

import android.window.ActivityTransitionInfo
import android.window.AppCompatTransitionInfo
import android.window.TransitionInfo
@@ -57,6 +57,25 @@ open class BaseChangeTestContext {
    }
}

/**
 * Base class for Test Contexts requiring a [TaskInfo] object.
 */
open class BaseRunningTaskInfoTestContext {

    protected lateinit var taskInfo: RunningTaskInfo

    fun runningTaskInfo(
        builder: RunningTaskInfoTestInputBuilder.(RunningTaskInfo) -> Unit
    ): RunningTaskInfo {
        val runningTaskInfoObj = RunningTaskInfoTestInputBuilder()
        return RunningTaskInfo().also {
            runningTaskInfoObj.builder(it)
        }.apply {
            taskInfo = this
        }
    }
}

/**
 * [InputBuilder] that helps in the creation of a [Change] object for testing.
 */
+44 −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.util

import com.android.wm.shell.compatui.letterbox.lifecycle.TaskIdResolver

@DslMarker
annotation class TaskIdResolverTagMarker

@TaskIdResolverTagMarker
class TaskIdResolverTestContext(
    private val taskIdResolver: TaskIdResolver
) : BaseRunningTaskInfoTestContext() {

    fun verifyLetterboxTaskId(consumer: (Int) -> Unit) {
        consumer(taskIdResolver.getLetterboxTaskId(taskInfo))
    }
}

/**
 * Function to run tests for the different [TaskIdResolver] implementations.
 */
fun testTaskIdResolver(
    factory: () -> TaskIdResolver,
    init: TaskIdResolverTestContext.() -> Unit
): TaskIdResolverTestContext {
    val testContext = TaskIdResolverTestContext(factory())
    testContext.init()
    return testContext
}