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

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

Merge "Complete PolicyRequestProcessor implementation" into main

parents bafc8bf1 1d279790
Loading
Loading
Loading
Loading
+23 −2
Original line number Diff line number Diff line
@@ -22,7 +22,28 @@ import com.android.systemui.screenshot.data.model.DisplayContentModel
fun interface CapturePolicy {
    /**
     * Test the policy against the current display task state. If the policy applies, Returns a
     * non-null [CaptureParameters] describing how the screenshot request should be augmented.
     * [PolicyResult.Matched] containing [CaptureParameters] used to alter the request.
     */
    suspend fun apply(content: DisplayContentModel): CaptureParameters?
    suspend fun check(content: DisplayContentModel): PolicyResult

    /** The result of a screen capture policy check. */
    sealed interface PolicyResult {
        /** The policy rules matched the given display content and will be applied. */
        data class Matched(
            /** The name of the policy rule which matched. */
            val policy: String,
            /** Why the policy matched. */
            val reason: String,
            /** Details on how to modify the screen capture request. */
            val parameters: CaptureParameters,
        ) : PolicyResult

        /** The policy rules do not match the given display content and do not apply. */
        data class NotMatched(
            /** The name of the policy rule which matched. */
            val policy: String,
            /** Why the policy did not match. */
            val reason: String
        ) : PolicyResult
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -21,10 +21,10 @@ import android.graphics.Rect
/** What to capture */
sealed interface CaptureType {
    /** Capture the entire screen contents. */
    class FullScreen(val displayId: Int) : CaptureType
    data class FullScreen(val displayId: Int) : CaptureType

    /** Capture the contents of the task only. */
    class IsolatedTask(
    data class IsolatedTask(
        val taskId: Int,
        val taskBounds: Rect?,
    ) : CaptureType
+55 −14
Original line number Diff line number Diff line
@@ -16,9 +16,12 @@

package com.android.systemui.screenshot.policy

import android.app.ActivityTaskManager.RootTaskInfo
import android.app.WindowConfiguration
import android.content.ComponentName
import android.graphics.Bitmap
import android.graphics.Rect
import android.os.Process.myUserHandle
import android.os.UserHandle
import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
@@ -27,7 +30,10 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.screenshot.ImageCapture
import com.android.systemui.screenshot.ScreenshotData
import com.android.systemui.screenshot.ScreenshotRequestProcessor
import com.android.systemui.screenshot.data.model.DisplayContentModel
import com.android.systemui.screenshot.data.repository.DisplayContentRepository
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.Matched
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
import com.android.systemui.screenshot.policy.CaptureType.FullScreen
import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
import kotlinx.coroutines.CoroutineDispatcher
@@ -39,8 +45,14 @@ private const val TAG = "PolicyRequestProcessor"
class PolicyRequestProcessor(
    @Background private val background: CoroutineDispatcher,
    private val capture: ImageCapture,
    /** Provides information about the tasks on a given display */
    private val displayTasks: DisplayContentRepository,
    /** The list of policies to apply, in order of priority */
    private val policies: List<CapturePolicy>,
    /** The owner to assign for screenshot when a focused task isn't visible */
    private val defaultOwner: UserHandle = myUserHandle(),
    /** The assigned component when no application has focus, or not visible */
    private val defaultComponent: ComponentName,
) : ScreenshotRequestProcessor {
    override suspend fun process(original: ScreenshotData): ScreenshotData {
        if (original.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
@@ -48,29 +60,26 @@ class PolicyRequestProcessor(
            Log.i(TAG, "Screenshot bitmap provided. No modifications applied.")
            return original
        }

        val tasks = displayTasks.getDisplayContent(original.displayId)
        val displayContent = displayTasks.getDisplayContent(original.displayId)

        // If policies yield explicit modifications, apply them and return the result
        Log.i(TAG, "Applying policy checks....")
        policies
            .firstNotNullOfOrNull { policy -> policy.apply(tasks) }
            ?.let {
                Log.i(TAG, "Modifying screenshot: $it")
                return apply(it, original)
        policies.map { policy ->
            when (val result = policy.check(displayContent)) {
                is Matched -> {
                    Log.i(TAG, "$result")
                    return modify(original, result.parameters)
                }
                is NotMatched -> Log.i(TAG, "$result")
            }
        }

        // Otherwise capture normally, filling in additional information as needed.
        return replaceWithScreenshot(
            original = original,
            componentName = original.topComponent ?: tasks.rootTasks.firstOrNull()?.topActivity,
            owner = original.userHandle,
            displayId = original.displayId
        )
        return captureScreenshot(original, displayContent)
    }

    /** Produce a new [ScreenshotData] using [CaptureParameters] */
    suspend fun apply(updates: CaptureParameters, original: ScreenshotData): ScreenshotData {
    suspend fun modify(original: ScreenshotData, updates: CaptureParameters): ScreenshotData {
        // Update and apply bitmap capture depending on the parameters.
        val updated =
            when (val type = updates.type) {
@@ -93,6 +102,26 @@ class PolicyRequestProcessor(
        return updated
    }

    private suspend fun captureScreenshot(
        original: ScreenshotData,
        displayContent: DisplayContentModel,
    ): ScreenshotData {
        // The first root task on the display, excluding Picture-in-Picture
        val topMainRootTask =
            if (!displayContent.systemUiState.shadeExpanded) {
                displayContent.rootTasks.firstOrNull(::nonPipVisibleTask)
            } else {
                null // Otherwise attributed to SystemUI / current user
            }

        return replaceWithScreenshot(
            original = original,
            componentName = topMainRootTask?.topActivity ?: defaultComponent,
            owner = topMainRootTask?.userId?.let { UserHandle.of(it) } ?: defaultOwner,
            displayId = original.displayId
        )
    }

    suspend fun replaceWithTaskSnapshot(
        original: ScreenshotData,
        componentName: ComponentName?,
@@ -100,6 +129,7 @@ class PolicyRequestProcessor(
        taskId: Int,
        taskBounds: Rect?,
    ): ScreenshotData {
        Log.i(TAG, "Capturing task snapshot: $componentName / $owner")
        val taskSnapshot = capture.captureTask(taskId)
        return original.copy(
            type = TAKE_SCREENSHOT_PROVIDED_IMAGE,
@@ -117,6 +147,7 @@ class PolicyRequestProcessor(
        owner: UserHandle?,
        displayId: Int,
    ): ScreenshotData {
        Log.i(TAG, "Capturing screenshot: $componentName / $owner")
        val screenshot = captureDisplay(displayId)
        return original.copy(
            type = TAKE_SCREENSHOT_FULLSCREEN,
@@ -127,6 +158,16 @@ class PolicyRequestProcessor(
        )
    }

    /** Filter for the task used to attribute a full screen capture to an owner */
    private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
        return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED &&
            info.isVisible &&
            info.isRunning &&
            info.numActivities > 0 &&
            info.topActivity != null &&
            info.childTaskIds.isNotEmpty()
    }

    /** TODO: Move to ImageCapture (existing function is non-suspending) */
    private suspend fun captureDisplay(displayId: Int): Bitmap? {
        return withContext(background) { capture.captureDisplay(displayId) }
+23 −8
Original line number Diff line number Diff line
@@ -20,10 +20,13 @@ import android.os.UserHandle
import com.android.systemui.screenshot.data.model.DisplayContentModel
import com.android.systemui.screenshot.data.model.ProfileType
import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.Matched
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
import com.android.systemui.screenshot.policy.CaptureType.FullScreen
import javax.inject.Inject
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.firstOrNull

private const val POLICY_NAME = "PrivateProfile"

/**
 * Condition: When any visible task belongs to a private user.
@@ -35,7 +38,12 @@ class PrivateProfilePolicy
constructor(
    private val profileTypes: ProfileTypeRepository,
) : CapturePolicy {
    override suspend fun apply(content: DisplayContentModel): CaptureParameters? {
    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")
        }

        // Find the first visible rootTaskInfo with a child task owned by a private user
        val (rootTask, childTask) =
            content.rootTasks
@@ -48,13 +56,20 @@ constructor(
                        }
                        ?.let { root to it }
                }
                ?: return null
                ?: return NotMatched(
                    policy = POLICY_NAME,
                    reason = "No private profile tasks are visible"
                )

        // If matched, return parameters needed to modify the request.
        return CaptureParameters(
        return Matched(
            policy = POLICY_NAME,
            reason = "At least one private profile task is visible",
            CaptureParameters(
                type = FullScreen(content.displayId),
                component = childTask.componentName ?: rootTask.topActivity,
                owner = UserHandle.of(childTask.userId),
            )
        )
    }
}
+3 −18
Original line number Diff line number Diff line
@@ -18,12 +18,10 @@ package com.android.systemui.screenshot.policy

import android.app.ActivityTaskManager.RootTaskInfo
import com.android.systemui.screenshot.data.model.ChildTaskModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.map

internal fun RootTaskInfo.childTasksTopDown(): Flow<ChildTaskModel> {
    return ((numActivities - 1) downTo 0).asFlow().map { index ->
/** The child tasks of A RootTaskInfo as [ChildTaskModel] in top-down (z-index ascending) order. */
internal fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> {
    return ((childTaskIds.size - 1) downTo 0).asSequence().map { index ->
        ChildTaskModel(
            childTaskIds[index],
            childTaskNames[index],
@@ -32,16 +30,3 @@ internal fun RootTaskInfo.childTasksTopDown(): Flow<ChildTaskModel> {
        )
    }
}

internal suspend fun RootTaskInfo.firstChildTaskOrNull(
    filter: suspend (Int) -> Boolean
): Pair<RootTaskInfo, Int>? {
    // Child tasks are provided in bottom-up order
    // Filtering is done top-down, so iterate backwards here.
    for (index in numActivities - 1 downTo 0) {
        if (filter(index)) {
            return (this to index)
        }
    }
    return null
}
Loading