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

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

Merge "Screenshot a single display per invocation" into main

parents b3e70199 f518e04a
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -629,6 +629,13 @@ flag {
    bug: "354711957"
}

flag {
    name: "screenshot_multidisplay_focus_change"
    namespace: "systemui"
    description: "Only capture a single display when screenshotting"
    bug: "362720389"
}

flag {
   name: "run_fingerprint_detect_on_dismissible_keyguard"
   namespace: "systemui"
+71 −33
Original line number Diff line number Diff line
@@ -20,9 +20,11 @@ import android.net.Uri
import android.os.Trace
import android.util.Log
import android.view.Display
import android.view.WindowManager.ScreenshotSource
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.screenshotMultidisplayFocusChange
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
@@ -40,7 +42,7 @@ interface TakeScreenshotExecutor {
    suspend fun executeScreenshots(
        screenshotRequest: ScreenshotRequest,
        onSaved: (Uri?) -> Unit,
        requestCallback: RequestCallback
        requestCallback: RequestCallback,
    )

    fun onCloseSystemDialogsReceived()
@@ -52,7 +54,7 @@ interface TakeScreenshotExecutor {
    fun executeScreenshotsAsync(
        screenshotRequest: ScreenshotRequest,
        onSaved: Consumer<Uri?>,
        requestCallback: RequestCallback
        requestCallback: RequestCallback,
    )
}

@@ -60,7 +62,7 @@ interface ScreenshotHandler {
    fun handleScreenshot(
        screenshot: ScreenshotData,
        finisher: Consumer<Uri?>,
        requestCallback: RequestCallback
        requestCallback: RequestCallback,
    )
}

@@ -75,7 +77,7 @@ class TakeScreenshotExecutorImpl
@Inject
constructor(
    private val interactiveScreenshotHandlerFactory: InteractiveScreenshotHandler.Factory,
    displayRepository: DisplayRepository,
    private val displayRepository: DisplayRepository,
    @Application private val mainScope: CoroutineScope,
    private val screenshotRequestProcessor: ScreenshotRequestProcessor,
    private val uiEventLogger: UiEventLogger,
@@ -95,13 +97,24 @@ constructor(
    override suspend fun executeScreenshots(
        screenshotRequest: ScreenshotRequest,
        onSaved: (Uri?) -> Unit,
        requestCallback: RequestCallback
        requestCallback: RequestCallback,
    ) {
        if (screenshotMultidisplayFocusChange()) {
            val display = getDisplayToScreenshot(screenshotRequest)
            val screenshotHandler = getScreenshotController(display)
            dispatchToController(
                screenshotHandler,
                ScreenshotData.fromRequest(screenshotRequest, display.displayId),
                onSaved,
                requestCallback,
            )
        } else {
            val displays = getDisplaysToScreenshot(screenshotRequest.type)
            val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
            if (displays.isEmpty()) {
            Log.wtf(TAG, "No displays found for screenshot.")
                Log.e(TAG, "No displays found for screenshot.")
            }

            displays.forEach { display ->
                val displayId = display.displayId
                var screenshotHandler: ScreenshotHandler =
@@ -110,6 +123,7 @@ constructor(
                    } else {
                        headlessScreenshotHandler
                    }

                Log.d(TAG, "Executing screenshot for display $displayId")
                dispatchToController(
                    screenshotHandler,
@@ -118,17 +132,18 @@ constructor(
                        if (displayId == Display.DEFAULT_DISPLAY) {
                            onSaved
                        } else { _ -> },
                callback = resultCallbackWrapper.createCallbackForId(displayId)
                    callback = resultCallbackWrapper.createCallbackForId(displayId),
                )
            }
        }
    }

    /** All logging should be triggered only by this method. */
    private suspend fun dispatchToController(
        screenshotHandler: ScreenshotHandler,
        rawScreenshotData: ScreenshotData,
        onSaved: (Uri?) -> Unit,
        callback: RequestCallback
        callback: RequestCallback,
    ) {
        // Let's wait before logging "screenshot requested", as we should log the processed
        // ScreenshotData.
@@ -160,13 +175,13 @@ constructor(
        uiEventLogger.log(
            ScreenshotEvent.getScreenshotSource(screenshotData.source),
            0,
            screenshotData.packageNameString
            screenshotData.packageNameString,
        )
    }

    private fun onFailedScreenshotRequest(
        screenshotData: ScreenshotData,
        callback: RequestCallback
        callback: RequestCallback,
    ) {
        uiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, screenshotData.packageNameString)
        getNotificationController(screenshotData.displayId)
@@ -184,6 +199,31 @@ constructor(
        }
    }

    // Return the single display to be screenshot based upon the request.
    private suspend fun getDisplayToScreenshot(screenshotRequest: ScreenshotRequest): Display {
        return when (screenshotRequest.source) {
            // TODO(b/367394043): Overview requests should use a display ID provided in
            //  ScreenshotRequest.
            ScreenshotSource.SCREENSHOT_OVERVIEW ->
                displayRepository.getDisplay(Display.DEFAULT_DISPLAY)
                    ?: error("Can't find default display")

            // Key chord and vendor gesture occur on the device itself, so screenshot the device's
            // display
            ScreenshotSource.SCREENSHOT_KEY_CHORD,
            ScreenshotSource.SCREENSHOT_VENDOR_GESTURE ->
                displayRepository.getDisplay(Display.DEFAULT_DISPLAY)
                    ?: error("Can't find default display")

            // All other invocations use the focused display
            else -> focusedDisplay()
        }
    }

    // TODO(b/367394043): Determine the focused display here.
    private suspend fun focusedDisplay() =
        displayRepository.getDisplay(Display.DEFAULT_DISPLAY) ?: error("Can't find default display")

    /** Propagates the close system dialog signal to the ScreenshotController. */
    override fun onCloseSystemDialogsReceived() {
        if (screenshotController?.isPendingSharedTransition() == false) {
@@ -214,7 +254,7 @@ constructor(
    override fun executeScreenshotsAsync(
        screenshotRequest: ScreenshotRequest,
        onSaved: Consumer<Uri?>,
        requestCallback: RequestCallback
        requestCallback: RequestCallback,
    ) {
        mainScope.launch {
            executeScreenshots(screenshotRequest, { uri -> onSaved.accept(uri) }, requestCallback)
@@ -235,9 +275,7 @@ constructor(
     * - If any finished with an error, [reportError] of [originalCallback] is called
     * - Otherwise, [onFinish] is called.
     */
    private class MultiResultCallbackWrapper(
        private val originalCallback: RequestCallback,
    ) {
    private class MultiResultCallbackWrapper(private val originalCallback: RequestCallback) {
        private val idsPending = mutableSetOf<Int>()
        private val idsWithErrors = mutableSetOf<Int>()

@@ -290,7 +328,7 @@ constructor(
                Display.TYPE_EXTERNAL,
                Display.TYPE_INTERNAL,
                Display.TYPE_OVERLAY,
                Display.TYPE_WIFI
                Display.TYPE_WIFI,
            )
    }
}
+73 −5
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@ package com.android.systemui.screenshot
import android.content.ComponentName
import android.graphics.Bitmap
import android.net.Uri
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.Display
import android.view.Display.TYPE_EXTERNAL
import android.view.Display.TYPE_INTERNAL
@@ -15,6 +17,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.display
@@ -75,6 +78,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
    }

    @Test
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_severalDisplays_callsControllerForEachOne() =
        testScope.runTest {
            val internalDisplay = display(TYPE_INTERNAL, id = 0)
@@ -106,6 +110,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() =
        testScope.runTest {
            val internalDisplay = display(TYPE_INTERNAL, id = 0)
@@ -115,7 +120,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
            screenshotExecutor.executeScreenshots(
                createScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE),
                onSaved,
                callback
                callback,
            )

            verify(controllerFactory).create(eq(internalDisplay))
@@ -137,6 +142,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_onlyVirtualDisplays_noInteractionsWithControllers() =
        testScope.runTest {
            setDisplays(display(TYPE_VIRTUAL, id = 0), display(TYPE_VIRTUAL, id = 1))
@@ -149,6 +155,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_allowedTypes_allCaptured() =
        testScope.runTest {
            whenever(controllerFactory.create(any())).thenReturn(controller)
@@ -157,7 +164,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
                display(TYPE_INTERNAL, id = 0),
                display(TYPE_EXTERNAL, id = 1),
                display(TYPE_OVERLAY, id = 2),
                display(TYPE_WIFI, id = 3)
                display(TYPE_WIFI, id = 3),
            )
            val onSaved = { _: Uri? -> }
            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -168,6 +175,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_reportsOnFinishedOnlyWhenBothFinished() =
        testScope.runTest {
            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -193,6 +201,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_oneFinishesOtherFails_reportFailsOnlyAtTheEnd() =
        testScope.runTest {
            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -220,6 +229,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_allDisplaysFail_reportsFail() =
        testScope.runTest {
            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -319,6 +329,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
        testScope.runTest {
            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -336,6 +347,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_errorFromProcessor_logsUiError() =
        testScope.runTest {
            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
@@ -379,7 +391,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
        }

    @Test
    fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_errorFromScreenshotController_multidisplay_reportsRequested() =
        testScope.runTest {
            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
            val onSaved = { _: Uri? -> }
@@ -399,7 +412,27 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
        }

    @Test
    fun executeScreenshots_errorFromScreenshotController_reportsError() =
    @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
        testScope.runTest {
            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
            val onSaved = { _: Uri? -> }
            whenever(controller.handleScreenshot(any(), any(), any()))
                .thenThrow(IllegalStateException::class.java)

            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)

            val screenshotRequested =
                eventLogger.logs.filter {
                    it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
                }
            assertThat(screenshotRequested).hasSize(1)
            screenshotExecutor.onDestroy()
        }

    @Test
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_errorFromScreenshotController_multidisplay_reportsError() =
        testScope.runTest {
            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
            val onSaved = { _: Uri? -> }
@@ -419,7 +452,27 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
        }

    @Test
    fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
    @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_errorFromScreenshotController_reportsError() =
        testScope.runTest {
            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
            val onSaved = { _: Uri? -> }
            whenever(controller.handleScreenshot(any(), any(), any()))
                .thenThrow(IllegalStateException::class.java)

            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)

            val screenshotRequested =
                eventLogger.logs.filter {
                    it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
                }
            assertThat(screenshotRequested).hasSize(1)
            screenshotExecutor.onDestroy()
        }

    @Test
    @DisableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_errorFromScreenshotController_multidisplay_showsErrorNotification() =
        testScope.runTest {
            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
            val onSaved = { _: Uri? -> }
@@ -435,6 +488,21 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
            screenshotExecutor.onDestroy()
        }

    @Test
    @EnableFlags(Flags.FLAG_SCREENSHOT_MULTIDISPLAY_FOCUS_CHANGE)
    fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
        testScope.runTest {
            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
            val onSaved = { _: Uri? -> }
            whenever(controller.handleScreenshot(any(), any(), any()))
                .thenThrow(IllegalStateException::class.java)

            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)

            verify(notificationsController0).notifyScreenshotError(any())
            screenshotExecutor.onDestroy()
        }

    @Test
    fun executeScreenshots_finisherCalledWithNullUri_succeeds() =
        testScope.runTest {