Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8984924e authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Make non-main displays take headless screenshots." into main

parents c78176d9 31ab31b0
Loading
Loading
Loading
Loading
+114 −0
Original line number Diff line number Diff line
/*
 * 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"
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -101,7 +101,7 @@ import javax.inject.Provider;
/**
 * Controls the state and flow for screenshots.
 */
public class ScreenshotController {
public class ScreenshotController implements ScreenshotHandler {
    private static final String TAG = logTag(ScreenshotController.class);

    /**
@@ -351,7 +351,8 @@ public class ScreenshotController {
        mShowUIOnExternalDisplay = showUIOnExternalDisplay;
    }

    void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
    @Override
    public void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
            RequestCallback requestCallback) {
        Assert.isMainThread();

+55 −28
Original line number Diff line number Diff line
/*
 * 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
@@ -7,12 +23,12 @@ import android.view.Display
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.Flags.screenshotShelfUi2
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
import java.util.function.Consumer
import javax.inject.Inject
@@ -26,9 +42,13 @@ interface TakeScreenshotExecutor {
        onSaved: (Uri?) -> Unit,
        requestCallback: RequestCallback
    )

    fun onCloseSystemDialogsReceived()

    fun removeWindows()

    fun onDestroy()

    fun executeScreenshotsAsync(
        screenshotRequest: ScreenshotRequest,
        onSaved: Consumer<Uri?>,
@@ -36,6 +56,14 @@ interface TakeScreenshotExecutor {
    )
}

interface ScreenshotHandler {
    fun handleScreenshot(
        screenshot: ScreenshotData,
        finisher: Consumer<Uri?>,
        requestCallback: RequestCallback
    )
}

/**
 * Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the
 * result.
@@ -52,10 +80,10 @@ constructor(
    private val screenshotRequestProcessor: ScreenshotRequestProcessor,
    private val uiEventLogger: UiEventLogger,
    private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
    private val headlessScreenshotHandler: HeadlessScreenshotHandler,
) : TakeScreenshotExecutor {

    private val displays = displayRepository.displays
    private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
    private var screenshotController: ScreenshotController? = null
    private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()

    /**
@@ -73,9 +101,15 @@ constructor(
        val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
        displays.forEach { display ->
            val displayId = display.displayId
            var screenshotHandler: ScreenshotHandler =
                if (displayId == Display.DEFAULT_DISPLAY) {
                    getScreenshotController(display)
                } else {
                    headlessScreenshotHandler
                }
            Log.d(TAG, "Executing screenshot for display $displayId")
            dispatchToController(
                display = display,
                screenshotHandler,
                rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId),
                onSaved =
                    if (displayId == Display.DEFAULT_DISPLAY) {
@@ -88,7 +122,7 @@ constructor(

    /** All logging should be triggered only by this method. */
    private suspend fun dispatchToController(
        display: Display,
        screenshotHandler: ScreenshotHandler,
        rawScreenshotData: ScreenshotData,
        onSaved: (Uri?) -> Unit,
        callback: RequestCallback
@@ -102,13 +136,12 @@ constructor(
                    logScreenshotRequested(rawScreenshotData)
                    onFailedScreenshotRequest(rawScreenshotData, callback)
                }
                .getOrNull()
                ?: return
                .getOrNull() ?: return

        logScreenshotRequested(screenshotData)
        Log.d(TAG, "Screenshot request: $screenshotData")
        try {
            getScreenshotController(display).handleScreenshot(screenshotData, onSaved, callback)
            screenshotHandler.handleScreenshot(screenshotData, onSaved, callback)
        } catch (e: IllegalStateException) {
            Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e)
            onFailedScreenshotRequest(screenshotData, callback)
@@ -140,44 +173,32 @@ constructor(

    private suspend fun getDisplaysToScreenshot(requestType: Int): List<Display> {
        val allDisplays = displays.first()
        return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE || screenshotShelfUi2()) {
            // If this is a provided image or using the shelf UI, just screenshot th default display
        return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
            // If this is a provided image just screenshot th default display
            allDisplays.filter { it.displayId == Display.DEFAULT_DISPLAY }
        } else {
            allDisplays.filter { it.type in ALLOWED_DISPLAY_TYPES }
        }
    }

    /** Propagates the close system dialog signal to all controllers. */
    /** Propagates the close system dialog signal to the ScreenshotController. */
    override fun onCloseSystemDialogsReceived() {
        screenshotControllers.forEach { (_, screenshotController) ->
            if (!screenshotController.isPendingSharedTransition) {
                screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
            }
        if (screenshotController?.isPendingSharedTransition == false) {
            screenshotController?.requestDismissal(SCREENSHOT_DISMISSED_OTHER)
        }
    }

    /** Removes all screenshot related windows. */
    override fun removeWindows() {
        screenshotControllers.forEach { (_, screenshotController) ->
            screenshotController.removeWindow()
        }
        screenshotController?.removeWindow()
    }

    /**
     * Destroys the executor. Afterwards, this class is not expected to work as intended anymore.
     */
    override fun onDestroy() {
        screenshotControllers.forEach { (_, screenshotController) ->
            screenshotController.onDestroy()
        }
        screenshotControllers.clear()
    }

    private fun getScreenshotController(display: Display): ScreenshotController {
        return screenshotControllers.computeIfAbsent(display.displayId) {
            screenshotControllerFactory.create(display, /* showUIOnExternalDisplay= */ false)
        }
        screenshotController?.onDestroy()
        screenshotController = null
    }

    private fun getNotificationController(id: Int): ScreenshotNotificationsController {
@@ -197,6 +218,12 @@ constructor(
        }
    }

    private fun getScreenshotController(display: Display): ScreenshotController {
        val controller = screenshotController ?: screenshotControllerFactory.create(display, false)
        screenshotController = controller
        return controller
    }

    /**
     * Returns a [RequestCallback] that wraps [originalCallback].
     *
+37 −88

File changed.

Preview size limit exceeded, changes collapsed.