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

Commit f2f2d2ae authored by Mark Renouf's avatar Mark Renouf
Browse files

Dead code: the original screenshot policy code

This has been replaced by the new code in the 'policy' sub-package
(Last usage of this code was deleted in ag/29070486)

Flag: NONE dead code removal
Test: None
Change-Id: Id70d7dfb4e6769acaee448d5cef3459479986eb2
parent e773234a
Loading
Loading
Loading
Loading
+0 −40
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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

import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo

internal class FakeScreenshotPolicy : ScreenshotPolicy {

    private val userTypes = mutableMapOf<Int, Boolean>()
    private val contentInfo = mutableMapOf<Int, DisplayContentInfo?>()

    fun setManagedProfile(userId: Int, managedUser: Boolean) {
        userTypes[userId] = managedUser
    }
    override suspend fun isManagedProfile(userId: Int): Boolean {
        return userTypes[userId] ?: error("No managedProfile value set for userId $userId")
    }

    fun setDisplayContentInfo(userId: Int, contentInfo: DisplayContentInfo) {
        this.contentInfo[userId] = contentInfo
    }

    override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
        return contentInfo[displayId] ?: error("No DisplayContentInfo set for displayId $displayId")
    }
}
+0 −51
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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

import android.annotation.UserIdInt
import android.content.ComponentName
import android.graphics.Rect
import android.os.UserHandle
import android.view.Display

/**
 * Provides policy decision-making information to screenshot request handling.
 */
interface ScreenshotPolicy {

    /** @return true if the user is a managed profile (a.k.a. work profile) */
    suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean

    /**
     * Requests information about the owner of display content which occupies a majority of the
     * screenshot and/or has most recently been interacted with at the time the screenshot was
     * requested.
     *
     * @param displayId the id of the display to inspect
     * @return content info for the primary content on the display
     */
    suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo

    data class DisplayContentInfo(
        val component: ComponentName,
        val bounds: Rect,
        val user: UserHandle,
        val taskId: Int,
    )

    fun getDefaultDisplayId(): Int = Display.DEFAULT_DISPLAY
}
+0 −189
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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

import android.annotation.UserIdInt
import android.app.ActivityTaskManager
import android.app.ActivityTaskManager.RootTaskInfo
import android.app.IActivityTaskManager
import android.app.WindowConfiguration
import android.app.WindowConfiguration.activityTypeToString
import android.app.WindowConfiguration.windowingModeToString
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Rect
import android.os.Process
import android.os.RemoteException
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.infra.ServiceConnector
import com.android.systemui.SystemUIService
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.DisplayTracker
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
import java.util.Arrays
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext

@SysUISingleton
internal open class ScreenshotPolicyImpl @Inject constructor(
    context: Context,
    private val userMgr: UserManager,
    private val atmService: IActivityTaskManager,
    @Background val bgDispatcher: CoroutineDispatcher,
    private val displayTracker: DisplayTracker
) : ScreenshotPolicy {

    private val proxyConnector: ServiceConnector<IScreenshotProxy> =
        ServiceConnector.Impl(
            context,
            Intent(context, ScreenshotProxyService::class.java),
            Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
            context.userId,
            IScreenshotProxy.Stub::asInterface
        )

    override fun getDefaultDisplayId(): Int {
        return displayTracker.defaultDisplayId
    }

    override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
        val managed = withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
        Log.d(TAG, "isManagedProfile: $managed")
        return managed
    }

    private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
        if (DEBUG) {
            debugLogRootTaskInfo(info)
        }
        return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED &&
            info.isVisible &&
            info.isRunning &&
            info.numActivities > 0 &&
            info.topActivity != null &&
            info.childTaskIds.isNotEmpty()
    }

    /**
     * Uses RootTaskInfo from ActivityTaskManager to guess at the primary focused task within a
     * display. If no task is visible or the top task is covered by a system window, the info
     * reported will reference a SystemUI component instead.
     */
    override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
        // Determine if the notification shade is expanded. If so, task windows are not
        // visible behind it, so the screenshot should instead be associated with SystemUI.
        if (isNotificationShadeExpanded()) {
            return systemUiContent
        }

        val taskInfoList = getAllRootTaskInfosOnDisplay(displayId)

        // If no visible task is located, then report SystemUI as the foreground content
        val target = taskInfoList.firstOrNull(::nonPipVisibleTask) ?: return systemUiContent
        return target.toDisplayContentInfo()
    }

    private fun debugLogRootTaskInfo(info: RootTaskInfo) {
        Log.d(TAG, "RootTaskInfo={" +
                "taskId=${info.taskId} " +
                "parentTaskId=${info.parentTaskId} " +
                "position=${info.position} " +
                "positionInParent=${info.positionInParent} " +
                "isVisible=${info.isVisible()} " +
                "visible=${info.visible} " +
                "isFocused=${info.isFocused} " +
                "isSleeping=${info.isSleeping} " +
                "isRunning=${info.isRunning} " +
                "windowMode=${windowingModeToString(info.windowingMode)} " +
                "activityType=${activityTypeToString(info.activityType)} " +
                "topActivity=${info.topActivity} " +
                "topActivityInfo=${info.topActivityInfo} " +
                "numActivities=${info.numActivities} " +
                "childTaskIds=${Arrays.toString(info.childTaskIds)} " +
                "childUserIds=${Arrays.toString(info.childTaskUserIds)} " +
                "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " +
                "childTaskNames=${Arrays.toString(info.childTaskNames)}" +
                "}"
        )

        for (j in 0 until info.childTaskIds.size) {
            Log.d(TAG, "    *** [$j] ******")
            Log.d(TAG, "        ***  childTaskIds[$j]: ${info.childTaskIds[j]}")
            Log.d(TAG, "        ***  childTaskUserIds[$j]: ${info.childTaskUserIds[j]}")
            Log.d(TAG, "        ***  childTaskBounds[$j]: ${info.childTaskBounds[j]}")
            Log.d(TAG, "        ***  childTaskNames[$j]: ${info.childTaskNames[j]}")
        }
    }

    @VisibleForTesting
    open suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> =
        withContext(bgDispatcher) {
            try {
                atmService.getAllRootTaskInfosOnDisplay(displayId)
            } catch (e: RemoteException) {
                Log.e(TAG, "getAllRootTaskInfosOnDisplay", e)
                listOf()
            }
        }

    @VisibleForTesting
    open suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
        proxyConnector
            .postForResult { it.isNotificationShadeExpanded }
            .whenComplete { expanded, error ->
                if (error != null) {
                    Log.e(TAG, "isNotificationShadeExpanded", error)
                }
                k.resume(expanded ?: false)
            }
    }

    @VisibleForTesting
    internal val systemUiContent =
        DisplayContentInfo(
            ComponentName(context, SystemUIService::class.java),
            Rect(),
            Process.myUserHandle(),
            ActivityTaskManager.INVALID_TASK_ID
        )
}

private const val TAG: String = "ScreenshotPolicyImpl"
private const val DEBUG: Boolean = false

@VisibleForTesting
internal fun RootTaskInfo.toDisplayContentInfo(): DisplayContentInfo {
    val topActivity: ComponentName = topActivity ?: error("should not be null")
    val topChildTask = childTaskIds.size - 1
    val childTaskId = childTaskIds[topChildTask]
    val childTaskUserId = childTaskUserIds[topChildTask]
    val childTaskBounds = childTaskBounds[topChildTask]

    return DisplayContentInfo(
        topActivity,
        childTaskBounds,
        UserHandle.of(childTaskUserId),
        childTaskId)
}
+0 −5
Original line number Diff line number Diff line
@@ -27,8 +27,6 @@ import com.android.systemui.screenshot.ImageCaptureImpl;
import com.android.systemui.screenshot.InteractiveScreenshotHandler;
import com.android.systemui.screenshot.LegacyScreenshotController;
import com.android.systemui.screenshot.ScreenshotController;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
import com.android.systemui.screenshot.ScreenshotSoundController;
import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
import com.android.systemui.screenshot.ScreenshotSoundProvider;
@@ -65,9 +63,6 @@ public abstract class ScreenshotModule {
    abstract TakeScreenshotExecutor bindTakeScreenshotExecutor(
            TakeScreenshotExecutorImpl impl);

    @Binds
    abstract ScreenshotPolicy bindScreenshotPolicyImpl(ScreenshotPolicyImpl impl);

    @Binds
    abstract ImageCapture bindImageCaptureImpl(ImageCaptureImpl capture);

+0 −200
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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

import android.app.ActivityTaskManager.RootTaskInfo
import android.app.IActivityTaskManager
import android.content.ComponentName
import android.content.Context
import android.graphics.Rect
import android.os.UserHandle
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
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith

// The following values are chosen to be distinct from commonly seen real values
private const val DISPLAY_ID = 100
private const val PRIMARY_USER = 2000
private const val MANAGED_PROFILE_USER = 3000

@RunWith(AndroidTestingRunner::class)
class ScreenshotPolicyImplTest : SysuiTestCase() {

    @Test
    fun testToDisplayContentInfo() {
        assertThat(fullScreenWorkProfileTask.toDisplayContentInfo())
            .isEqualTo(
                DisplayContentInfo(
                    ComponentName(
                        "com.google.android.apps.nbu.files",
                        "com.google.android.apps.nbu.files.home.HomeActivity"
                    ),
                    Rect(0, 0, 1080, 2400),
                    UserHandle.of(MANAGED_PROFILE_USER),
                    65
                )
            )
    }

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

        val info = policy.findPrimaryContent(DISPLAY_ID)
        assertThat(info).isEqualTo(fullScreenWorkProfileTask.toDisplayContentInfo())
    }

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

        val info = policy.findPrimaryContent(DISPLAY_ID)
        assertThat(info).isEqualTo(policy.systemUiContent)
    }

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

        val info = policy.findPrimaryContent(DISPLAY_ID)
        assertThat(info).isEqualTo(policy.systemUiContent)
    }

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

        val info = policy.findPrimaryContent(DISPLAY_ID)
        assertThat(info).isEqualTo(launcherTask.toDisplayContentInfo())
    }

    private fun fakeTasksPolicyImpl(
        context: Context,
        shadeExpanded: Boolean,
        tasks: List<RootTaskInfo>
    ): ScreenshotPolicyImpl {
        val userManager = mock<UserManager>()
        val atmService = mock<IActivityTaskManager>()
        val dispatcher = Dispatchers.Unconfined
        val displayTracker = FakeDisplayTracker(context)

        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 =
        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)
            )
        }
    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 = ""),
            )
        }
}

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

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

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

private const val LAUNCHER_ACTIVITY =
    "com.google.android.apps.nexuslauncher/" +
        "com.google.android.apps.nexuslauncher.NexusLauncherActivity"