1 /*
<lambda>null2 * 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
toDisplayContentInfonull177 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