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

Commit 9be7dbc6 authored by Mark Renouf's avatar Mark Renouf
Browse files

Test scenarios for screenshot policy tests

A system for parameterized generating a DisplayContentModel for
various configurations, apps and window modes.

Bug: 327613051
Test: N/A
Flag: NONE
Change-Id: Ib715811672616d5bdd043189be2ec17f14ddc6f0
parent 6f9738e9
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -185,16 +185,16 @@ class ScreenshotPolicyImplTest : SysuiTestCase() {
        }
}

const val YOUTUBE_HOME_ACTIVITY =
private const val YOUTUBE_HOME_ACTIVITY =
    "com.google.android.youtube/" + "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity"

const val FILES_HOME_ACTIVITY =
private const val FILES_HOME_ACTIVITY =
    "com.google.android.apps.nbu.files/" + "com.google.android.apps.nbu.files.home.HomeActivity"

const val YOUTUBE_PIP_ACTIVITY =
private const val YOUTUBE_PIP_ACTIVITY =
    "com.google.android.youtube/" +
        "com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity"

const val LAUNCHER_ACTIVITY =
private const val LAUNCHER_ACTIVITY =
    "com.google.android.apps.nexuslauncher/" +
        "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+259 −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.systemui.screenshot.data.model

import android.content.ComponentName
import android.graphics.Rect
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.PIP
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.SPLIT_BOTTOM
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.SPLIT_TOP
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.emptyRootSplit
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.freeForm
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.fullScreen
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.launcher
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.pictureInPicture
import com.android.systemui.screenshot.policy.ActivityType
import com.android.systemui.screenshot.policy.TestUserIds
import com.android.systemui.screenshot.policy.WindowingMode
import com.android.systemui.screenshot.policy.newChildTask
import com.android.systemui.screenshot.policy.newRootTaskInfo

/** Tools for creating a [DisplayContentModel] for different usage scenarios. */
object DisplayContentScenarios {

    data class TaskSpec(val taskId: Int, val userId: Int, val name: String)

    /** Home screen, with only the launcher visible */
    fun launcherOnly(shadeExpanded: Boolean = false) =
        DisplayContentModel(
            displayId = 0,
            systemUiState = SystemUiState(shadeExpanded = shadeExpanded),
            rootTasks =
                listOf(
                    launcher(visible = true),
                    emptyRootSplit,
                )
        )

    /** A Full screen activity for the personal (primary) user, with launcher behind it */
    fun singleFullScreen(spec: TaskSpec, shadeExpanded: Boolean = false) =
        DisplayContentModel(
            displayId = 0,
            systemUiState = SystemUiState(shadeExpanded = shadeExpanded),
            rootTasks =
                listOf(
                    fullScreen(spec, visible = true),
                    launcher(visible = false),
                    emptyRootSplit,
                )
        )

    fun splitScreenApps(
        top: TaskSpec,
        bottom: TaskSpec,
        focusedTaskId: Int,
        shadeExpanded: Boolean = false,
    ): DisplayContentModel {
        val topBounds = SPLIT_TOP
        val bottomBounds = SPLIT_BOTTOM
        return DisplayContentModel(
            displayId = 0,
            systemUiState = SystemUiState(shadeExpanded = shadeExpanded),
            rootTasks =
                listOf(
                    newRootTaskInfo(
                        taskId = 2,
                        userId = TestUserIds.PERSONAL,
                        bounds = FULL_SCREEN,
                        topActivity =
                            ComponentName.unflattenFromString(
                                if (top.taskId == focusedTaskId) top.name else bottom.name
                            ),
                    ) {
                        listOf(
                                newChildTask(
                                    taskId = top.taskId,
                                    bounds = topBounds,
                                    userId = top.userId,
                                    name = top.name
                                ),
                                newChildTask(
                                    taskId = bottom.taskId,
                                    bounds = bottomBounds,
                                    userId = bottom.userId,
                                    name = bottom.name
                                )
                            )
                            // Child tasks are ordered bottom-up in RootTaskInfo.
                            // Sort 'focusedTaskId' last.
                            // Boolean natural ordering: [false, true].
                            .sortedBy { it.id == focusedTaskId }
                    },
                    launcher(visible = false),
                )
        )
    }

    fun pictureInPictureApp(
        pip: TaskSpec,
        fullScreen: TaskSpec? = null,
        shadeExpanded: Boolean = false,
    ): DisplayContentModel {
        return DisplayContentModel(
            displayId = 0,
            systemUiState = SystemUiState(shadeExpanded = shadeExpanded),
            rootTasks =
                buildList {
                    add(pictureInPicture(pip))
                    fullScreen?.also { add(fullScreen(it, visible = true)) }
                    add(launcher(visible = (fullScreen == null)))
                    add(emptyRootSplit)
                }
        )
    }

    fun freeFormApps(
        vararg tasks: TaskSpec,
        focusedTaskId: Int,
        shadeExpanded: Boolean = false,
    ): DisplayContentModel {
        val freeFormTasks =
            tasks
                .map { freeForm(it) }
                // Root tasks are ordered top-down in List<RootTaskInfo>.
                // Sort 'focusedTaskId' last (Boolean natural ordering: [false, true])
                .sortedBy { it.childTaskIds[0] != focusedTaskId }
        return DisplayContentModel(
            displayId = 0,
            systemUiState = SystemUiState(shadeExpanded = shadeExpanded),
            rootTasks = freeFormTasks + launcher(visible = true) + emptyRootSplit
        )
    }

    /**
     * All of these are arbitrary dimensions exposed for asserting equality on test data.
     *
     * They should not be updated nor compared with any real device usage, except to keep them
     * somewhat sensible in terms of logical position (Re: PIP, SPLIT, etc).
     */
    object Bounds {
        val FULL_SCREEN = Rect(0, 0, 1080, 2400)
        val PIP = Rect(440, 1458, 1038, 1794)
        val SPLIT_TOP = Rect(0, 0, 1080, 1187)
        val SPLIT_BOTTOM = Rect(0, 1213, 1080, 2400)
        val FREE_FORM = Rect(119, 332, 1000, 1367)
    }

    /** A collection of task names used in test scenarios */
    object ActivityNames {
        /** The main YouTube activity */
        const val YOUTUBE =
            "com.google.android.youtube/" +
                "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity"

        /** The main Files Activity */
        const val FILES =
            "com.google.android.apps.nbu.files/" +
                "com.google.android.apps.nbu.files.home.HomeActivity"

        /** The YouTube picture-in-picture activity */
        const val YOUTUBE_PIP =
            "com.google.android.youtube/" +
                "com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity"

        /** The NexusLauncher activity */
        const val LAUNCHER =
            "com.google.android.apps.nexuslauncher/" +
                "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
    }

    /**
     * A set of predefined RootTaskInfo used in test scenarios, matching as closely as possible
     * actual values returned by ActivityTaskManager
     */
    object RootTasks {
        /**
         * The empty RootTaskInfo that is always at the end of a list from ActivityTaskManager when
         * no other visible activities are in split mode
         */
        val emptyRootSplit =
            newRootTaskInfo(
                taskId = 2,
                visible = false,
                running = false,
                numActivities = 0,
                bounds = FULL_SCREEN,
                activityType = ActivityType.Undefined,
            ) {
                listOf(
                    newChildTask(taskId = 3, bounds = FULL_SCREEN, name = ""),
                    newChildTask(taskId = 4, bounds = Rect(0, 2400, 1080, 3600), name = ""),
                )
            }

        /** NexusLauncher on the default display. Usually below all other visible tasks */
        fun launcher(visible: Boolean) =
            newRootTaskInfo(
                taskId = 1,
                activityType = ActivityType.Home,
                visible = visible,
                bounds = FULL_SCREEN,
                topActivity = ComponentName.unflattenFromString(ActivityNames.LAUNCHER),
                topActivityType = ActivityType.Home,
            ) {
                listOf(newChildTask(taskId = 1002, name = ActivityNames.LAUNCHER))
            }

        /** A full screen Activity */
        fun fullScreen(task: TaskSpec, visible: Boolean) =
            newRootTaskInfo(
                taskId = task.taskId,
                userId = task.userId,
                visible = visible,
                bounds = FULL_SCREEN,
                topActivity = ComponentName.unflattenFromString(task.name),
            ) {
                listOf(newChildTask(taskId = task.taskId, userId = task.userId, name = task.name))
            }

        /** An activity in Picture-in-Picture mode */
        fun pictureInPicture(task: TaskSpec) =
            newRootTaskInfo(
                taskId = task.taskId,
                userId = task.userId,
                bounds = PIP,
                windowingMode = WindowingMode.PictureInPicture,
                topActivity = ComponentName.unflattenFromString(task.name),
            ) {
                listOf(newChildTask(taskId = task.taskId, userId = userId, name = task.name))
            }

        /** An activity in FreeForm mode */
        fun freeForm(task: TaskSpec) =
            newRootTaskInfo(
                taskId = task.taskId,
                userId = task.userId,
                bounds = FREE_FORM,
                windowingMode = WindowingMode.Freeform,
                topActivity = ComponentName.unflattenFromString(task.name),
            ) {
                listOf(newChildTask(taskId = task.taskId, userId = userId, name = task.name))
            }
    }
}
+33 −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.systemui.screenshot.data.repository

import com.android.systemui.kosmos.Kosmos
import com.android.systemui.screenshot.data.model.ProfileType
import com.android.systemui.screenshot.policy.TestUserIds

val Kosmos.profileTypeRepository by
    Kosmos.Fixture {
        ProfileTypeRepository { userId ->
            when (userId) {
                TestUserIds.WORK -> ProfileType.WORK
                TestUserIds.CLONE -> ProfileType.CLONE
                TestUserIds.PRIVATE -> ProfileType.PRIVATE
                else -> ProfileType.NONE
            }
        }
    }
+26 −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.systemui.screenshot.policy

import android.os.UserHandle

object TestUserIds {
    val PERSONAL: Int = UserHandle.USER_SYSTEM
    val WORK: Int = 10
    val CLONE: Int = 11
    val PRIVATE: Int = 12
}