/* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.screenshot import android.net.Uri import android.os.UserManager import android.util.Log import android.view.WindowManager import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.google.common.util.concurrent.ListenableFuture import java.util.UUID import java.util.concurrent.Executor import java.util.concurrent.Executors import java.util.function.Consumer import javax.inject.Inject /** * A ScreenshotHandler that just saves the screenshot and calls back as appropriate, with no UI. * * Basically, ScreenshotController with all the UI bits ripped out. */ class HeadlessScreenshotHandler @Inject constructor( private val imageExporter: ImageExporter, @Main private val mainExecutor: Executor, private val imageCapture: ImageCapture, private val userManager: UserManager, private val uiEventLogger: UiEventLogger, private val notificationsControllerFactory: ScreenshotNotificationsController.Factory, ) : ScreenshotHandler { override fun handleScreenshot( screenshot: ScreenshotData, finisher: Consumer<Uri?>, requestCallback: TakeScreenshotService.RequestCallback ) { if (screenshot.type == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) { screenshot.bitmap = imageCapture.captureDisplay(screenshot.displayId, crop = null) } if (screenshot.bitmap == null) { Log.e(TAG, "handleScreenshot: Screenshot bitmap was null") notificationsControllerFactory .create(screenshot.displayId) .notifyScreenshotError(R.string.screenshot_failed_to_capture_text) requestCallback.reportError() return } val future: ListenableFuture<ImageExporter.Result> = imageExporter.export( Executors.newSingleThreadExecutor(), UUID.randomUUID(), screenshot.bitmap, screenshot.getUserOrDefault(), screenshot.displayId ) future.addListener( { try { val result = future.get() Log.d(TAG, "Saved screenshot: $result") logScreenshotResultStatus(result.uri, screenshot) finisher.accept(result.uri) requestCallback.onFinish() } catch (e: Exception) { Log.d(TAG, "Failed to store screenshot", e) finisher.accept(null) requestCallback.reportError() } }, mainExecutor ) } private fun logScreenshotResultStatus(uri: Uri?, screenshot: ScreenshotData) { if (uri == null) { uiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, screenshot.packageNameString) notificationsControllerFactory .create(screenshot.displayId) .notifyScreenshotError(R.string.screenshot_failed_to_save_text) } else { uiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, screenshot.packageNameString) if (userManager.isManagedProfile(screenshot.getUserOrDefault().identifier)) { uiEventLogger.log( ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0, screenshot.packageNameString ) } } } companion object { const val TAG = "HeadlessScreenshotHandler" } }