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