blob: c2a50609b6a5a6d14c5650af867c814c3ac5aad2 [file] [log] [blame]
/*
* 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 android.view.Display.DEFAULT_DISPLAY
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.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,
) : 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 DEFAULT_DISPLAY
}
override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
return withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
}
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)
}