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.graphics.Insets
20 import android.util.Log
21 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
22 import com.android.internal.util.ScreenshotRequest
23 import com.android.systemui.dagger.SysUISingleton
24 import com.android.systemui.dagger.qualifiers.Application
25 import com.android.systemui.flags.FeatureFlags
26 import kotlinx.coroutines.CoroutineScope
27 import kotlinx.coroutines.launch
28 import java.util.function.Consumer
29 import javax.inject.Inject
30 
31 /**
32  * Processes a screenshot request sent from {@link ScreenshotHelper}.
33  */
34 @SysUISingleton
35 class RequestProcessor @Inject constructor(
36         private val capture: ImageCapture,
37         private val policy: ScreenshotPolicy,
38         private val flags: FeatureFlags,
39         /** For the Java Async version, to invoke the callback. */
40         @Application private val mainScope: CoroutineScope
41 ) {
42     /**
43      * Inspects the incoming request, returning a potentially modified request depending on policy.
44      *
45      * @param request the request to process
46      */
47     // TODO: Delete once SCREENSHOT_METADATA flag is launched
48     suspend fun process(request: ScreenshotRequest): ScreenshotRequest {
49         var result = request
50 
51         // Apply work profile screenshots policy:
52         //
53         // If the focused app belongs to a work profile, transforms a full screen
54         // (or partial) screenshot request to a task snapshot (provided image) screenshot.
55 
56         // Whenever displayContentInfo is fetched, the topComponent is also populated
57         // regardless of the managed profile status.
58 
59         if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
60 
61             val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
62             Log.d(TAG, "findPrimaryContent: $info")
63 
64             result = if (policy.isManagedProfile(info.user.identifier)) {
65                 val image = capture.captureTask(info.taskId)
66                         ?: error("Task snapshot returned a null Bitmap!")
67 
68                 // Provide the task snapshot as the screenshot
69                 ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source)
70                         .setTopComponent(info.component)
71                         .setTaskId(info.taskId)
72                         .setUserId(info.user.identifier)
73                         .setBitmap(image)
74                         .setBoundsOnScreen(info.bounds)
75                         .setInsets(Insets.NONE)
76                         .build()
77             } else {
78                 // Create a new request of the same type which includes the top component
79                 ScreenshotRequest.Builder(request.type, request.source)
80                         .setTopComponent(info.component).build()
81             }
82         }
83 
84         return result
85     }
86 
87     /**
88      * Note: This is for compatibility with existing Java. Prefer the suspending function when
89      * calling from a Coroutine context.
90      *
91      * @param request the request to process
92      * @param callback the callback to provide the processed request, invoked from the main thread
93      */
94     // TODO: Delete once SCREENSHOT_METADATA flag is launched
95     fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) {
96         mainScope.launch {
97             val result = process(request)
98             callback.accept(result)
99         }
100     }
101 
102     /**
103      * Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
104      *
105      * @param screenshot the screenshot to process
106      */
107     suspend fun process(screenshot: ScreenshotData): ScreenshotData {
108         var result = screenshot
109 
110         // Apply work profile screenshots policy:
111         //
112         // If the focused app belongs to a work profile, transforms a full screen
113         // (or partial) screenshot request to a task snapshot (provided image) screenshot.
114 
115         // Whenever displayContentInfo is fetched, the topComponent is also populated
116         // regardless of the managed profile status.
117 
118         if (screenshot.type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
119             val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
120             Log.d(TAG, "findPrimaryContent: $info")
121             result.taskId = info.taskId
122             result.topComponent = info.component
123             result.userHandle = info.user
124 
125             if (policy.isManagedProfile(info.user.identifier)) {
126                 val image = capture.captureTask(info.taskId)
127                     ?: error("Task snapshot returned a null Bitmap!")
128 
129                 // Provide the task snapshot as the screenshot
130                 result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
131                 result.bitmap = image
132                 result.screenBounds = info.bounds
133             }
134         }
135 
136         return result
137     }
138 
139     /**
140      * Note: This is for compatibility with existing Java. Prefer the suspending function when
141      * calling from a Coroutine context.
142      *
143      * @param screenshot the screenshot to process
144      * @param callback the callback to provide the processed screenshot, invoked from the main
145      *                 thread
146      */
147     fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) {
148         mainScope.launch {
149             val result = process(screenshot)
150             callback.accept(result)
151         }
152     }
153 }
154 
155 private const val TAG = "RequestProcessor"
156