1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.screenshot 18 19 import android.annotation.UserIdInt 20 import android.app.ActivityTaskManager 21 import android.app.ActivityTaskManager.RootTaskInfo 22 import android.app.IActivityTaskManager 23 import android.app.WindowConfiguration 24 import android.app.WindowConfiguration.activityTypeToString 25 import android.app.WindowConfiguration.windowingModeToString 26 import android.content.ComponentName 27 import android.content.Context 28 import android.content.Intent 29 import android.graphics.Rect 30 import android.os.Process 31 import android.os.RemoteException 32 import android.os.UserHandle 33 import android.os.UserManager 34 import android.util.Log 35 import com.android.internal.annotations.VisibleForTesting 36 import com.android.internal.infra.ServiceConnector 37 import com.android.systemui.SystemUIService 38 import com.android.systemui.dagger.SysUISingleton 39 import com.android.systemui.dagger.qualifiers.Background 40 import com.android.systemui.settings.DisplayTracker 41 import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo 42 import java.util.Arrays 43 import javax.inject.Inject 44 import kotlin.coroutines.resume 45 import kotlin.coroutines.suspendCoroutine 46 import kotlinx.coroutines.CoroutineDispatcher 47 import kotlinx.coroutines.withContext 48 49 @SysUISingleton 50 internal open class ScreenshotPolicyImpl @Inject constructor( 51 context: Context, 52 private val userMgr: UserManager, 53 private val atmService: IActivityTaskManager, 54 @Background val bgDispatcher: CoroutineDispatcher, 55 private val displayTracker: DisplayTracker 56 ) : ScreenshotPolicy { 57 58 private val proxyConnector: ServiceConnector<IScreenshotProxy> = 59 ServiceConnector.Impl( 60 context, 61 Intent(context, ScreenshotProxyService::class.java), 62 Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, 63 context.userId, 64 IScreenshotProxy.Stub::asInterface 65 ) 66 67 override fun getDefaultDisplayId(): Int { 68 return displayTracker.defaultDisplayId 69 } 70 71 override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean { 72 val managed = withContext(bgDispatcher) { userMgr.isManagedProfile(userId) } 73 Log.d(TAG, "isManagedProfile: $managed") 74 return managed 75 } 76 77 private fun nonPipVisibleTask(info: RootTaskInfo): Boolean { 78 if (DEBUG) { 79 debugLogRootTaskInfo(info) 80 } 81 return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED && 82 info.isVisible && 83 info.isRunning && 84 info.numActivities > 0 && 85 info.topActivity != null && 86 info.childTaskIds.isNotEmpty() 87 } 88 89 /** 90 * Uses RootTaskInfo from ActivityTaskManager to guess at the primary focused task within a 91 * display. If no task is visible or the top task is covered by a system window, the info 92 * reported will reference a SystemUI component instead. 93 */ 94 override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo { 95 // Determine if the notification shade is expanded. If so, task windows are not 96 // visible behind it, so the screenshot should instead be associated with SystemUI. 97 if (isNotificationShadeExpanded()) { 98 return systemUiContent 99 } 100 101 val taskInfoList = getAllRootTaskInfosOnDisplay(displayId) 102 103 // If no visible task is located, then report SystemUI as the foreground content 104 val target = taskInfoList.firstOrNull(::nonPipVisibleTask) ?: return systemUiContent 105 return target.toDisplayContentInfo() 106 } 107 108 private fun debugLogRootTaskInfo(info: RootTaskInfo) { 109 Log.d(TAG, "RootTaskInfo={" + 110 "taskId=${info.taskId} " + 111 "parentTaskId=${info.parentTaskId} " + 112 "position=${info.position} " + 113 "positionInParent=${info.positionInParent} " + 114 "isVisible=${info.isVisible()} " + 115 "visible=${info.visible} " + 116 "isFocused=${info.isFocused} " + 117 "isSleeping=${info.isSleeping} " + 118 "isRunning=${info.isRunning} " + 119 "windowMode=${windowingModeToString(info.windowingMode)} " + 120 "activityType=${activityTypeToString(info.activityType)} " + 121 "topActivity=${info.topActivity} " + 122 "topActivityInfo=${info.topActivityInfo} " + 123 "numActivities=${info.numActivities} " + 124 "childTaskIds=${Arrays.toString(info.childTaskIds)} " + 125 "childUserIds=${Arrays.toString(info.childTaskUserIds)} " + 126 "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " + 127 "childTaskNames=${Arrays.toString(info.childTaskNames)}" + 128 "}" 129 ) 130 131 for (j in 0 until info.childTaskIds.size) { 132 Log.d(TAG, " *** [$j] ******") 133 Log.d(TAG, " *** childTaskIds[$j]: ${info.childTaskIds[j]}") 134 Log.d(TAG, " *** childTaskUserIds[$j]: ${info.childTaskUserIds[j]}") 135 Log.d(TAG, " *** childTaskBounds[$j]: ${info.childTaskBounds[j]}") 136 Log.d(TAG, " *** childTaskNames[$j]: ${info.childTaskNames[j]}") 137 } 138 } 139 140 @VisibleForTesting 141 open suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> = 142 withContext(bgDispatcher) { 143 try { 144 atmService.getAllRootTaskInfosOnDisplay(displayId) 145 } catch (e: RemoteException) { 146 Log.e(TAG, "getAllRootTaskInfosOnDisplay", e) 147 listOf() 148 } 149 } 150 151 @VisibleForTesting 152 open suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k -> 153 proxyConnector 154 .postForResult { it.isNotificationShadeExpanded } 155 .whenComplete { expanded, error -> 156 if (error != null) { 157 Log.e(TAG, "isNotificationShadeExpanded", error) 158 } 159 k.resume(expanded ?: false) 160 } 161 } 162 163 @VisibleForTesting 164 internal val systemUiContent = 165 DisplayContentInfo( 166 ComponentName(context, SystemUIService::class.java), 167 Rect(), 168 Process.myUserHandle(), 169 ActivityTaskManager.INVALID_TASK_ID 170 ) 171 } 172 173 private const val TAG: String = "ScreenshotPolicyImpl" 174 private const val DEBUG: Boolean = false 175 176 @VisibleForTesting 177 internal fun RootTaskInfo.toDisplayContentInfo(): DisplayContentInfo { 178 val topActivity: ComponentName = topActivity ?: error("should not be null") 179 val topChildTask = childTaskIds.size - 1 180 val childTaskId = childTaskIds[topChildTask] 181 val childTaskUserId = childTaskUserIds[topChildTask] 182 val childTaskBounds = childTaskBounds[topChildTask] 183 184 return DisplayContentInfo( 185 topActivity, 186 childTaskBounds, 187 UserHandle.of(childTaskUserId), 188 childTaskId) 189 } 190