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

Commit 37602600 authored by Wes Okuhara's avatar Wes Okuhara
Browse files

Screen Capture: Create ScreenshotInteractor for screenshot requests

Following the SystemUI architecture recommendations an interactor will
be responsible for employing the ScreenshotHelper to handle the business
logic of taking a screenshot. The interactor methods are set up to
support multiple displays, but will default to the default display for
now.

Bug: 424206233
Test: atest ScreenshotInteractorTest
Flag: com.android.systemui.desktop_screen_capture
Change-Id: I0a03dce8c5b94d9d400cd3df99642c12ddabb958
parent bfd4d32b
Loading
Loading
Loading
Loading
+83 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.screencapture.domain.interactor

import android.graphics.Rect
import android.os.Handler
import android.os.UserHandle
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
import com.android.internal.util.ScreenshotHelper
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.screenshot.ImageCapture
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.withContext

/** Interactor responsible for employing ScreenshotHelper to take various types of screenshots. */
@SysUISingleton
class ScreenshotInteractor
@Inject
constructor(
    private val imageCapture: ImageCapture,
    private val screenshotHelper: ScreenshotHelper,
    @Background private val backgroundContext: CoroutineContext,
    @Background private val backgroundHandler: Handler,
) {
    suspend fun takeFullscreenScreenshot(displayId: Int = DEFAULT_DISPLAY) {
        val request =
            ScreenshotRequest.Builder(
                    WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
                    // TODO(b/425755530) create and use a new screenshot source for large screen
                    WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
                )
                .setDisplayId(displayId)
                .build()

        takeScreenshot(request)
    }

    suspend fun takePartialScreenshot(regionBounds: Rect, displayId: Int = DEFAULT_DISPLAY) {
        val bitmap =
            withContext(backgroundContext) {
                requireNotNull(imageCapture.captureDisplay(displayId, regionBounds))
            }
        val request =
            ScreenshotRequest.Builder(
                    WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE,
                    // TODO(b/425755530) create and use a new screenshot source for large screen
                    WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
                )
                .setBitmap(bitmap)
                .setBoundsOnScreen(regionBounds)
                .setDisplayId(displayId)
                .setUserId(UserHandle.USER_CURRENT)
                .build()

        takeScreenshot(request)
    }

    // TODO(b/422833825): Implement takeAppWindowScreenshot

    private suspend fun takeScreenshot(request: ScreenshotRequest) {
        withContext(backgroundContext) {
            screenshotHelper.takeScreenshot(request, backgroundHandler, null)
        }
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@
package com.android.systemui.screenshot.dagger;

import android.app.Service;
import android.content.Context;
import android.view.accessibility.AccessibilityManager;

import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
@@ -93,4 +95,10 @@ public abstract class ScreenshotModule {
            ScreenshotController.Factory screenshotController) {
        return screenshotController;
    }

    @Provides
    @SysUISingleton
    static ScreenshotHelper provideScreenshotHelper(Context context) {
        return new ScreenshotHelper(context);
    }
}
+104 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.screencapture.domain.interactor

import android.graphics.Bitmap
import android.graphics.Rect
import android.os.UserHandle
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.ScreenshotRequest
import com.android.internal.util.mockScreenshotHelper
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.screenshot.mockImageCapture
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
class ScreenshotInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val testScope = kosmos.testScope

    @Mock private lateinit var mockBitmap: Bitmap
    @Captor private lateinit var screenshotRequestCaptor: ArgumentCaptor<ScreenshotRequest>

    private val interactor: ScreenshotInteractor by lazy { kosmos.screenshotInteractor }

    @Before
    fun setUp() {
        MockitoAnnotations.openMocks(this)
    }

    @Test
    fun takeFullscreenScreenshot_makesCorrectRequestAndCallsScreenshotHelper() {
        testScope.runTest {
            interactor.takeFullscreenScreenshot()

            verify(kosmos.mockScreenshotHelper, times(1))
                .takeScreenshot(screenshotRequestCaptor.capture(), any(), isNull())

            val capturedRequest = screenshotRequestCaptor.value
            assertThat(capturedRequest.type).isEqualTo(WindowManager.TAKE_SCREENSHOT_FULLSCREEN)
            assertThat(capturedRequest.source)
                .isEqualTo(WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
            assertThat(capturedRequest.displayId).isEqualTo(DEFAULT_DISPLAY)
        }
    }

    @Test
    fun takePartialScreenshot_makesCorrectRequestAndCallsScreenshotHelper() {
        testScope.runTest {
            val bounds = Rect(0, 0, 100, 100)
            whenever(kosmos.mockImageCapture.captureDisplay(eq(DEFAULT_DISPLAY), eq(bounds)))
                .thenReturn(mockBitmap)

            interactor.takePartialScreenshot(bounds)

            verify(kosmos.mockImageCapture, times(1)).captureDisplay(any(), eq(bounds))
            verify(kosmos.mockScreenshotHelper, times(1))
                .takeScreenshot(screenshotRequestCaptor.capture(), any(), isNull())

            val capturedRequest = screenshotRequestCaptor.value
            assertThat(capturedRequest.type).isEqualTo(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE)
            assertThat(capturedRequest.source)
                .isEqualTo(WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
            assertThat(capturedRequest.bitmap).isEqualTo(mockBitmap)
            assertThat(capturedRequest.boundsInScreen).isEqualTo(bounds)
            assertThat(capturedRequest.displayId).isEqualTo(DEFAULT_DISPLAY)
            assertThat(capturedRequest.userId).isEqualTo(UserHandle.USER_CURRENT)
        }
    }
}
+22 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.internal.util

import com.android.systemui.kosmos.Kosmos
import org.mockito.kotlin.mock

val Kosmos.mockScreenshotHelper by Kosmos.Fixture { mock<ScreenshotHelper>() }
+33 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.screencapture.domain.interactor

import android.os.fakeHandler
import com.android.internal.util.mockScreenshotHelper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.screenshot.mockImageCapture

val Kosmos.screenshotInteractor by
    Kosmos.Fixture {
        ScreenshotInteractor(
            imageCapture = mockImageCapture,
            screenshotHelper = mockScreenshotHelper,
            backgroundContext = backgroundCoroutineContext,
            backgroundHandler = fakeHandler,
        )
    }
Loading