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

Commit 001d7fad authored by Massimo Carli's avatar Massimo Carli
Browse files

[53/n] Define LetterboxTaskListenerAdapter

Creates LetterboxTaskListenerAdapter as adapter which
is useful to store some TaskInfo properties to use when
the TaskInfo object is not present in Change.

This also creates the test utils for testing all the
implementation of TaskAppearedListener, TaskVanishedListener
and TaskInfoChangedListener.

Flag: com.android.window.flags.app_compat_refactoring
Bug: 309593314
Test: atest WMShellUnitTests:LetterboxTaskListenerAdapterTest

Change-Id: I04d4da5a19a2c0dbc693e9e7a7ec2ab7711759ee
parent 6004383e
Loading
Loading
Loading
Loading
+66 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.state

import android.app.ActivityManager.RunningTaskInfo
import android.view.SurfaceControl
import com.android.window.flags.Flags.appCompatRefactoring
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTaskOrganizer.TaskAppearedListener
import com.android.wm.shell.ShellTaskOrganizer.TaskVanishedListener
import com.android.wm.shell.dagger.WMSingleton
import com.android.wm.shell.sysui.ShellInit
import javax.inject.Inject

/**
 * [TaskAppearedListener] and [TaskVanishedListener] implementation to store [TaskInfo] data
 * useful for letterboxing.
 */
@WMSingleton
class LetterboxTaskListenerAdapter @Inject constructor(
    shellInit: ShellInit,
    shellTaskOrganizer: ShellTaskOrganizer,
    private val letterboxTaskInfoRepository: LetterboxTaskInfoRepository
) : TaskVanishedListener, TaskAppearedListener {

    init {
        if (appCompatRefactoring()) {
            shellInit.addInitCallback({
                shellTaskOrganizer.addTaskAppearedListener(this)
                shellTaskOrganizer.addTaskVanishedListener(this)
            }, this)
        }
    }

    override fun onTaskAppeared(
        taskInfo: RunningTaskInfo,
        leash: SurfaceControl
    ) {
        letterboxTaskInfoRepository.insert(
            key = taskInfo.taskId,
            item = LetterboxTaskInfoState(
                containerToken = taskInfo.token,
                containerLeash = leash
            ),
            overrideIfPresent = true
        )
    }

    override fun onTaskVanished(taskInfo: RunningTaskInfo) {
        letterboxTaskInfoRepository.delete(taskInfo.taskId)
    }
}
+163 −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.state

import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.compatui.letterbox.asMode
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.util.testTaskAppearedListener
import com.android.wm.shell.util.testTaskVanishedListener
import java.util.function.Consumer
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify

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

    @Test
    @EnableFlags(Flags.FLAG_APP_COMPAT_REFACTORING)
    fun `When the flag is ENABLED the listener is registered`() {
        runTestScenario { r ->
            r.invokeShellInit()
            r.checkListenerIsRegistered(expected = true)
        }
    }

    @Test
    @DisableFlags(Flags.FLAG_APP_COMPAT_REFACTORING)
    fun `When the flag is DISABLED the listener is NOT registered`() {
        runTestScenario { r ->
            r.invokeShellInit()
            r.checkListenerIsRegistered(expected = false)
        }
    }

    @Test
    fun `When a Task appears the TaskInfo data are persisted`() {
        runTestScenario { r ->
            testTaskAppearedListener(r.getLetterboxTaskListenerAdapterFactory()) {
                val leashTest = SurfaceControl()
                val tokenTest = mock<WindowContainerToken>()
                runningTaskInfo { ti ->
                    ti.taskId = 10
                    ti.token = tokenTest
                }
                leash { leashTest }
                validateOnTaskAppeared {
                    r.validateItem(10) { item ->
                        assert(item?.containerLeash == leashTest)
                        assert(item?.containerToken == tokenTest)
                    }
                }
            }
        }
    }

    @Test
    fun `When a Task vanishes the TaskInfo data are removed`() {
        runTestScenario { r ->
            val leashTest = SurfaceControl()
            val tokenTest = mock<WindowContainerToken>()
            testTaskAppearedListener(r.getLetterboxTaskListenerAdapterFactory()) {
                runningTaskInfo { ti ->
                    ti.taskId = 10
                    ti.token = tokenTest
                }
                leash { leashTest }
                validateOnTaskAppeared {
                    r.validateItem(10) { item ->
                        assert(item != null)
                    }
                }
            }
            testTaskVanishedListener(r.getLetterboxTaskListenerAdapterFactory()) {
                runningTaskInfo { ti ->
                    ti.taskId = 10
                    ti.token = tokenTest
                }
                validateOnTaskVanished {
                    r.validateItem(10) { item ->
                        assert(item == null)
                    }
                }
            }
        }
    }

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

    class LetterboxTaskListenerAdapterRobotTest {

        private val executor: ShellExecutor
        private val shellInit: ShellInit
        private val shellTaskOrganizer: ShellTaskOrganizer
        private val letterboxTaskListenerAdapter: LetterboxTaskListenerAdapter
        private val letterboxTaskInfoRepository: LetterboxTaskInfoRepository

        init {
            executor = mock<ShellExecutor>()
            shellInit = ShellInit(executor)
            shellTaskOrganizer = mock<ShellTaskOrganizer>()
            letterboxTaskInfoRepository = LetterboxTaskInfoRepository()
            letterboxTaskListenerAdapter = LetterboxTaskListenerAdapter(
                shellInit,
                shellTaskOrganizer,
                letterboxTaskInfoRepository
            )
        }

        fun getLetterboxTaskListenerAdapterFactory(): () -> LetterboxTaskListenerAdapter = {
            letterboxTaskListenerAdapter
        }

        fun invokeShellInit() = shellInit.init()

        fun checkListenerIsRegistered(expected: Boolean) {
            verify(shellTaskOrganizer, expected.asMode()).addTaskAppearedListener(any())
            verify(shellTaskOrganizer, expected.asMode()).addTaskVanishedListener(any())
        }

        fun validateItem(taskId: Int, consumer: (LetterboxTaskInfoState?) -> Unit) {
            consumer(letterboxTaskInfoRepository.find(taskId))
        }
    }
}
+145 −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 android.app.ActivityManager.RunningTaskInfo
import android.view.SurfaceControl
import com.android.wm.shell.ShellTaskOrganizer.TaskAppearedListener
import com.android.wm.shell.ShellTaskOrganizer.TaskInfoChangedListener
import com.android.wm.shell.ShellTaskOrganizer.TaskVanishedListener

@DslMarker
annotation class TaskListenerTestTagMarker

@TaskListenerTestTagMarker
class TaskAppearedListenerTestContext(
    private val testSubjectFactory: () -> TaskAppearedListener
) {

    private lateinit var inputTaskInfo: RunningTaskInfo
    private var inputLeash: SurfaceControl = SurfaceControl()

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

    fun leash(builder: SurfaceControlTestInputBuilder.() -> SurfaceControl): SurfaceControl {
        val binderObj = SurfaceControlTestInputBuilder()
        return binderObj.builder().apply {
            inputLeash = this
        }
    }

    fun validateOnTaskAppeared(verifier: () -> Unit) {
        // We execute the test subject using the input
        testSubjectFactory().onTaskAppeared(inputTaskInfo, inputLeash)
        verifier()
    }
}

/**
 * Function to run tests for the different [TaskAppearedListener] implementations.
 */
fun testTaskAppearedListener(
    testSubjectFactory: () -> TaskAppearedListener,
    init: TaskAppearedListenerTestContext.() -> Unit
): TaskAppearedListenerTestContext {
    val testContext = TaskAppearedListenerTestContext(testSubjectFactory)
    testContext.init()
    return testContext
}

@TaskListenerTestTagMarker
class TaskVanishedListenerTestContext(
    private val testSubjectFactory: () -> TaskVanishedListener
) {

    private lateinit var inputTaskInfo: RunningTaskInfo

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

    fun validateOnTaskVanished(verifier: () -> Unit) {
        // We execute the test subject using the input
        testSubjectFactory().onTaskVanished(inputTaskInfo)
        verifier()
    }
}

/**
 * Function to run tests for the different [TaskVanishedListener] implementations.
 */
fun testTaskVanishedListener(
    testSubjectFactory: () -> TaskVanishedListener,
    init: TaskVanishedListenerTestContext.() -> Unit
): TaskVanishedListenerTestContext {
    val testContext = TaskVanishedListenerTestContext(testSubjectFactory)
    testContext.init()
    return testContext
}

@TaskListenerTestTagMarker
class TaskInfoChangedListenerTestContext(
    private val testSubjectFactory: () -> TaskInfoChangedListener
) {

    private lateinit var inputTaskInfo: RunningTaskInfo

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

    fun validateOnTaskInfoChanged(verifier: () -> Unit) {
        // We execute the test subject using the input
        testSubjectFactory().onTaskInfoChanged(inputTaskInfo)
        verifier()
    }
}

/**
 * Function to run tests for the different [TaskInfoChangedListener] implementations.
 */
fun testTaskInfoChangedListener(
    testSubjectFactory: () -> TaskInfoChangedListener,
    init: TaskInfoChangedListenerTestContext.() -> Unit
): TaskInfoChangedListenerTestContext {
    val testContext = TaskInfoChangedListenerTestContext(testSubjectFactory)
    testContext.init()
    return testContext
}