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