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

Commit ea404728 authored by Mark Renouf's avatar Mark Renouf Committed by Android (Google) Code Review
Browse files

Merge changes Ib7fe8982,I917b005a into main

* changes:
  Expose NAME constant from a companion property for testing
  Streamline RootTaskInfo creation for testing
parents 1103cebe f37f242c
Loading
Loading
Loading
Loading
+6 −8
Original line number Diff line number Diff line
@@ -26,8 +26,6 @@ import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatc
import com.android.systemui.screenshot.policy.CaptureType.FullScreen
import javax.inject.Inject

private const val POLICY_NAME = "PrivateProfile"

/**
 * Condition: When any visible task belongs to a private user.
 *
@@ -41,7 +39,7 @@ constructor(
    override suspend fun check(content: DisplayContentModel): PolicyResult {
        // The systemUI notification shade isn't a private profile app, skip.
        if (content.systemUiState.shadeExpanded) {
            return NotMatched(policy = POLICY_NAME, reason = "Notification shade is expanded")
            return NotMatched(policy = NAME, reason = "Notification shade is expanded")
        }

        // Find the first visible rootTaskInfo with a child task owned by a private user
@@ -56,14 +54,11 @@ constructor(
                        }
                        ?.let { root to it }
                }
                ?: return NotMatched(
                    policy = POLICY_NAME,
                    reason = "No private profile tasks are visible"
                )
                ?: return NotMatched(policy = NAME, reason = "No private profile tasks are visible")

        // If matched, return parameters needed to modify the request.
        return Matched(
            policy = POLICY_NAME,
            policy = NAME,
            reason = "At least one private profile task is visible",
            CaptureParameters(
                type = FullScreen(content.displayId),
@@ -72,4 +67,7 @@ constructor(
            )
        )
    }
    companion object {
        const val NAME = "PrivateProfile"
    }
}
+8 −5
Original line number Diff line number Diff line
@@ -27,8 +27,6 @@ import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
import javax.inject.Inject
import kotlinx.coroutines.flow.first

private const val POLICY_NAME = "WorkProfile"

/**
 * Condition: When the top visible task (excluding PIP mode) belongs to a work user.
 *
@@ -39,10 +37,11 @@ class WorkProfilePolicy
constructor(
    private val profileTypes: ProfileTypeRepository,
) : CapturePolicy {

    override suspend fun check(content: DisplayContentModel): PolicyResult {
        // The systemUI notification shade isn't a work app, skip.
        if (content.systemUiState.shadeExpanded) {
            return NotMatched(policy = POLICY_NAME, reason = "Notification shade is expanded")
            return NotMatched(policy = NAME, reason = "Notification shade is expanded")
        }

        // Find the first non PiP rootTask with a top child task owned by a work user
@@ -54,13 +53,13 @@ constructor(
                    profileTypes.getProfileType(child.userId) == ProfileType.WORK
                }
                ?: return NotMatched(
                    policy = POLICY_NAME,
                    policy = NAME,
                    reason = "The top-most non-PINNED task does not belong to a work profile user"
                )

        // If matched, return parameters needed to modify the request.
        return PolicyResult.Matched(
            policy = POLICY_NAME,
            policy = NAME,
            reason = "The top-most non-PINNED task ($childTask) belongs to a work profile user",
            CaptureParameters(
                type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
@@ -69,4 +68,8 @@ constructor(
            )
        )
    }

    companion object {
        val NAME = "WorkProfile"
    }
}
+92 −122
Original line number Diff line number Diff line
@@ -18,11 +18,6 @@ package com.android.systemui.screenshot

import android.app.ActivityTaskManager.RootTaskInfo
import android.app.IActivityTaskManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.content.ComponentName
import android.content.Context
import android.graphics.Rect
@@ -31,6 +26,12 @@ import android.os.UserManager
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
import com.android.systemui.screenshot.policy.ActivityType.Home
import com.android.systemui.screenshot.policy.ActivityType.Undefined
import com.android.systemui.screenshot.policy.WindowingMode.FullScreen
import com.android.systemui.screenshot.policy.WindowingMode.PictureInPicture
import com.android.systemui.screenshot.policy.newChildTask
import com.android.systemui.screenshot.policy.newRootTaskInfo
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -58,19 +59,18 @@ class ScreenshotPolicyImplTest : SysuiTestCase() {
                    ),
                    Rect(0, 0, 1080, 2400),
                    UserHandle.of(MANAGED_PROFILE_USER),
                    65))
                    65
                )
            )
    }

    @Test
    fun findPrimaryContent_ignoresPipTask() = runBlocking {
        val policy = fakeTasksPolicyImpl(
        val policy =
            fakeTasksPolicyImpl(
                mContext,
                shadeExpanded = false,
            tasks = listOf(
                    pipTask,
                    fullScreenWorkProfileTask,
                    launcherTask,
                    emptyTask)
                tasks = listOf(pipTask, fullScreenWorkProfileTask, launcherTask, emptyTask)
            )

        val info = policy.findPrimaryContent(DISPLAY_ID)
@@ -79,13 +79,11 @@ class ScreenshotPolicyImplTest : SysuiTestCase() {

    @Test
    fun findPrimaryContent_shadeExpanded_ignoresTopTask() = runBlocking {
        val policy = fakeTasksPolicyImpl(
        val policy =
            fakeTasksPolicyImpl(
                mContext,
                shadeExpanded = true,
            tasks = listOf(
                fullScreenWorkProfileTask,
                launcherTask,
                emptyTask)
                tasks = listOf(fullScreenWorkProfileTask, launcherTask, emptyTask)
            )

        val info = policy.findPrimaryContent(DISPLAY_ID)
@@ -94,11 +92,7 @@ class ScreenshotPolicyImplTest : SysuiTestCase() {

    @Test
    fun findPrimaryContent_emptyTaskList() = runBlocking {
        val policy = fakeTasksPolicyImpl(
            mContext,
            shadeExpanded = false,
            tasks = listOf()
        )
        val policy = fakeTasksPolicyImpl(mContext, shadeExpanded = false, tasks = listOf())

        val info = policy.findPrimaryContent(DISPLAY_ID)
        assertThat(info).isEqualTo(policy.systemUiContent)
@@ -106,13 +100,11 @@ class ScreenshotPolicyImplTest : SysuiTestCase() {

    @Test
    fun findPrimaryContent_workProfileNotOnTop() = runBlocking {
        val policy = fakeTasksPolicyImpl(
        val policy =
            fakeTasksPolicyImpl(
                mContext,
                shadeExpanded = false,
            tasks = listOf(
                launcherTask,
                fullScreenWorkProfileTask,
                emptyTask)
                tasks = listOf(launcherTask, fullScreenWorkProfileTask, emptyTask)
            )

        val info = policy.findPrimaryContent(DISPLAY_ID)
@@ -129,102 +121,80 @@ class ScreenshotPolicyImplTest : SysuiTestCase() {
        val dispatcher = Dispatchers.Unconfined
        val displayTracker = FakeDisplayTracker(context)

        return object : ScreenshotPolicyImpl(context, userManager, atmService, dispatcher,
                displayTracker) {
        return object :
            ScreenshotPolicyImpl(context, userManager, atmService, dispatcher, displayTracker) {
            override suspend fun isManagedProfile(userId: Int) = (userId == MANAGED_PROFILE_USER)
            override suspend fun getAllRootTaskInfosOnDisplay(displayId: Int) = tasks
            override suspend fun isNotificationShadeExpanded() = shadeExpanded
        }
    }

    private val pipTask = RootTaskInfo().apply {
        configuration.windowConfiguration.apply {
            windowingMode = WINDOWING_MODE_PINNED
            setBounds(Rect(628, 1885, 1038, 2295))
            activityType = ACTIVITY_TYPE_STANDARD
        }
        displayId = DISPLAY_ID
        userId = PRIMARY_USER
        taskId = 66
        visible = true
        isVisible = true
        isRunning = true
        numActivities = 1
        topActivity = ComponentName(
            "com.google.android.youtube",
            "com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity"
    private val pipTask =
        newRootTaskInfo(
            taskId = 66,
            userId = PRIMARY_USER,
            displayId = DISPLAY_ID,
            bounds = Rect(628, 1885, 1038, 2295),
            windowingMode = PictureInPicture,
            topActivity = ComponentName.unflattenFromString(YOUTUBE_PIP_ACTIVITY),
        ) {
            listOf(newChildTask(taskId = 66, userId = 0, name = YOUTUBE_HOME_ACTIVITY))
        }

    private val fullScreenWorkProfileTask =
        newRootTaskInfo(
            taskId = 65,
            userId = MANAGED_PROFILE_USER,
            displayId = DISPLAY_ID,
            bounds = Rect(0, 0, 1080, 2400),
            windowingMode = FullScreen,
            topActivity = ComponentName.unflattenFromString(FILES_HOME_ACTIVITY),
        ) {
            listOf(
                newChildTask(taskId = 65, userId = MANAGED_PROFILE_USER, name = FILES_HOME_ACTIVITY)
            )
        childTaskIds = intArrayOf(66)
        childTaskNames = arrayOf("com.google.android.youtube/" +
                "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity")
        childTaskUserIds = intArrayOf(0)
        childTaskBounds = arrayOf(Rect(628, 1885, 1038, 2295))
    }

    private val fullScreenWorkProfileTask = RootTaskInfo().apply {
        configuration.windowConfiguration.apply {
            windowingMode = WINDOWING_MODE_FULLSCREEN
            setBounds(Rect(0, 0, 1080, 2400))
            activityType = ACTIVITY_TYPE_STANDARD
        }
        displayId = DISPLAY_ID
        userId = MANAGED_PROFILE_USER
        taskId = 65
        visible = true
        isVisible = true
        isRunning = true
        numActivities = 1
        topActivity = ComponentName(
            "com.google.android.apps.nbu.files",
            "com.google.android.apps.nbu.files.home.HomeActivity"
    private val launcherTask =
        newRootTaskInfo(
            taskId = 1,
            userId = PRIMARY_USER,
            displayId = DISPLAY_ID,
            activityType = Home,
            windowingMode = FullScreen,
            bounds = Rect(0, 0, 1080, 2400),
            topActivity = ComponentName.unflattenFromString(LAUNCHER_ACTIVITY),
        ) {
            listOf(newChildTask(taskId = 1, userId = 0, name = LAUNCHER_ACTIVITY))
        }

    private val emptyTask =
        newRootTaskInfo(
            taskId = 2,
            userId = PRIMARY_USER,
            displayId = DISPLAY_ID,
            visible = false,
            running = false,
            numActivities = 0,
            activityType = Undefined,
            bounds = Rect(0, 0, 1080, 2400),
        ) {
            listOf(
                newChildTask(taskId = 3, name = ""),
                newChildTask(taskId = 4, name = ""),
            )
        childTaskIds = intArrayOf(65)
        childTaskNames = arrayOf("com.google.android.apps.nbu.files/" +
                "com.google.android.apps.nbu.files.home.HomeActivity")
        childTaskUserIds = intArrayOf(MANAGED_PROFILE_USER)
        childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400))
        }

    private val launcherTask = RootTaskInfo().apply {
        configuration.windowConfiguration.apply {
            windowingMode = WINDOWING_MODE_FULLSCREEN
            setBounds(Rect(0, 0, 1080, 2400))
            activityType = ACTIVITY_TYPE_HOME
        }
        displayId = DISPLAY_ID
        taskId = 1
        userId = PRIMARY_USER
        visible = true
        isVisible = true
        isRunning = true
        numActivities = 1
        topActivity = ComponentName(
            "com.google.android.apps.nexuslauncher",
            "com.google.android.apps.nexuslauncher.NexusLauncherActivity",
        )
        childTaskIds = intArrayOf(1)
        childTaskNames = arrayOf("com.google.android.apps.nexuslauncher/" +
                "com.google.android.apps.nexuslauncher.NexusLauncherActivity")
        childTaskUserIds = intArrayOf(0)
        childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400))
}

    private val emptyTask = RootTaskInfo().apply {
        configuration.windowConfiguration.apply {
            windowingMode = WINDOWING_MODE_FULLSCREEN
            setBounds(Rect(0, 0, 1080, 2400))
            activityType = ACTIVITY_TYPE_UNDEFINED
        }
        displayId = DISPLAY_ID
        taskId = 2
        userId = PRIMARY_USER
        visible = false
        isVisible = false
        isRunning = false
        numActivities = 0
        childTaskIds = intArrayOf(3, 4)
        childTaskNames = arrayOf("", "")
        childTaskUserIds = intArrayOf(0, 0)
        childTaskBounds = arrayOf(Rect(0, 0, 1080, 2400), Rect(0, 2400, 1080, 4800))
    }
}
const val YOUTUBE_HOME_ACTIVITY =
    "com.google.android.youtube/" + "com.google.android.youtube.app.honeycomb.Shell\$HomeActivity"

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

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

const val LAUNCHER_ACTIVITY =
    "com.google.android.apps.nexuslauncher/" +
        "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+117 −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.app.ActivityTaskManager.RootTaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
import android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.ComponentName
import android.graphics.Rect
import android.os.UserHandle
import android.view.Display
import com.android.systemui.screenshot.data.model.ChildTaskModel
import com.android.systemui.screenshot.policy.ActivityType.Standard
import com.android.systemui.screenshot.policy.WindowingMode.FullScreen

/** An enum mapping to [android.app.WindowConfiguration] constants via [toInt]. */
enum class ActivityType(private val intValue: Int) {
    Undefined(ACTIVITY_TYPE_UNDEFINED),
    Standard(ACTIVITY_TYPE_STANDARD),
    Home(ACTIVITY_TYPE_HOME),
    Recents(ACTIVITY_TYPE_RECENTS),
    Assistant(ACTIVITY_TYPE_ASSISTANT),
    Dream(ACTIVITY_TYPE_DREAM);

    /** Returns the [android.app.WindowConfiguration] int constant for the type. */
    fun toInt() = intValue
}

/** An enum mapping to [android.app.WindowConfiguration] constants via [toInt]. */
enum class WindowingMode(private val intValue: Int) {
    Undefined(WINDOWING_MODE_UNDEFINED),
    FullScreen(WINDOWING_MODE_FULLSCREEN),
    PictureInPicture(WINDOWING_MODE_PINNED),
    Freeform(WINDOWING_MODE_FREEFORM),
    MultiWindow(WINDOWING_MODE_MULTI_WINDOW);

    /** Returns the [android.app.WindowConfiguration] int constant for the mode. */
    fun toInt() = intValue
}

/**
 * Constructs a child task for a [RootTaskInfo], copying [RootTaskInfo.bounds] and
 * [RootTaskInfo.userId] from the parent by default.
 */
fun RootTaskInfo.newChildTask(
    taskId: Int,
    name: String,
    bounds: Rect? = null,
    userId: Int? = null
): ChildTaskModel {
    return ChildTaskModel(taskId, name, bounds ?: this.bounds, userId ?: this.userId)
}

/** Constructs a new [RootTaskInfo]. */
fun newRootTaskInfo(
    taskId: Int,
    userId: Int = UserHandle.USER_SYSTEM,
    displayId: Int = Display.DEFAULT_DISPLAY,
    visible: Boolean = true,
    running: Boolean = true,
    activityType: ActivityType = Standard,
    windowingMode: WindowingMode = FullScreen,
    bounds: Rect? = null,
    topActivity: ComponentName? = null,
    topActivityType: ActivityType = Standard,
    numActivities: Int? = null,
    childTaskListBuilder: RootTaskInfo.() -> List<ChildTaskModel>,
): RootTaskInfo {
    return RootTaskInfo().apply {
        configuration.windowConfiguration.apply {
            setWindowingMode(windowingMode.toInt())
            setActivityType(activityType.toInt())
            setBounds(bounds)
        }
        this.bounds = bounds
        this.displayId = displayId
        this.userId = userId
        this.taskId = taskId
        this.visible = visible
        this.isVisible = visible
        this.isRunning = running
        this.topActivity = topActivity
        this.topActivityType = topActivityType.toInt()
        // NOTE: topActivityInfo is _not_ populated by this code

        val childTasks = childTaskListBuilder(this)
        this.numActivities = numActivities ?: childTasks.size

        childTaskNames = childTasks.map { it.name }.toTypedArray()
        childTaskIds = childTasks.map { it.id }.toIntArray()
        childTaskBounds = childTasks.map { it.bounds }.toTypedArray()
        childTaskUserIds = childTasks.map { it.userId }.toIntArray()
    }
}