Loading packages/SystemUI/src/com/android/systemui/screencapture/record/largescreen/ui/compose/PreCaptureUI.kt +5 −1 Original line number Diff line number Diff line Loading @@ -24,7 +24,9 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.android.systemui.res.R Loading Loading @@ -74,7 +76,9 @@ fun PreCaptureUI(viewModel: PreCaptureViewModel) { RegionBox( initialWidth = 100.dp, initialHeight = 100.dp, onDragEnd = viewModel::onPartialRegionDragEnd, onDragEnd = { _: Offset, _: Dp, _: Dp -> // TODO(b/427541309) Update the region box in the viewmodel. }, drawableLoaderViewModel = viewModel, ) } Loading packages/SystemUI/src/com/android/systemui/screencapture/record/largescreen/ui/viewmodel/PreCaptureViewModel.kt +23 −4 Original line number Diff line number Diff line Loading @@ -17,8 +17,7 @@ package com.android.systemui.screencapture.record.largescreen.ui.viewmodel import android.content.Context import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Dp import android.graphics.Rect import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.lifecycle.HydratedActivatable Loading Loading @@ -64,6 +63,7 @@ constructor( private val isShowingUIFlow = MutableStateFlow(true) private val captureTypeSource = MutableStateFlow(ScreenCaptureType.SCREENSHOT) private val captureRegionSource = MutableStateFlow(ScreenCaptureRegion.FULLSCREEN) private val regionBoxSource = MutableStateFlow<Rect?>(null) val icons: ScreenCaptureIcons? by iconProvider.icons.hydratedStateOf() Loading @@ -75,6 +75,8 @@ constructor( // TODO(b/423697394) Init default value to be user's previously selected option val captureRegion: ScreenCaptureRegion by captureRegionSource.hydratedStateOf() val regionBox: Rect? by regionBoxSource.hydratedStateOf() val screenRecordingSupported = featuresInteractor.screenRecordingSupported val captureTypeButtonViewModels: List<RadioButtonGroupItemViewModel> by Loading Loading @@ -102,6 +104,10 @@ constructor( captureRegionSource.value = selectedRegion } fun updateRegionBox(bounds: Rect) { regionBoxSource.value = bounds } fun takeFullscreenScreenshot() { require(captureTypeSource.value == ScreenCaptureType.SCREENSHOT) require(captureRegionSource.value == ScreenCaptureRegion.FULLSCREEN) Loading @@ -117,8 +123,21 @@ constructor( } } fun onPartialRegionDragEnd(offset: Offset, width: Dp, height: Dp) { // TODO(b/427541309) Update region box position and size. fun takePartialScreenshot() { require(captureTypeSource.value == ScreenCaptureType.SCREENSHOT) require(captureRegionSource.value == ScreenCaptureRegion.PARTIAL) val regionBoxRect = requireNotNull(regionBoxSource.value) // Finishing the activity is not guaranteed to complete before the screenshot is taken. // Since the pre-capture UI should not be included in the screenshot, hide the UI first. hideUI() closeUI() backgroundScope.launch { // TODO(b/430361425) Pass in current display as argument. screenshotInteractor.takePartialScreenshot(regionBoxRect) } } /** Loading packages/SystemUI/tests/src/com/android/systemui/screencapture/record/largescreen/ui/viewmodel/PreCaptureViewModelTest.kt +69 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.screencapture.record.largescreen.ui.viewmodel import android.graphics.Bitmap import android.graphics.Rect import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.WindowManager Loading @@ -29,6 +31,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.lifecycle.activateIn import com.android.systemui.screencapture.ui.mockScreenCaptureActivity import com.android.systemui.screenshot.mockImageCapture import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.assertFailsWith Loading @@ -39,10 +42,13 @@ 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) Loading @@ -50,6 +56,7 @@ class PreCaptureViewModelTest : 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 viewModel: PreCaptureViewModel by lazy { kosmos.preCaptureViewModel } Loading Loading @@ -141,6 +148,18 @@ class PreCaptureViewModelTest : SysuiTestCase() { assertThat(fullscreenButton3.isSelected).isFalse() } @Test fun updateRegionBox_updatesState() = testScope.runTest { // State is initially null. assertThat(viewModel.regionBox).isNull() val regionBox = Rect(0, 0, 100, 100) viewModel.updateRegionBox(regionBox) assertThat(viewModel.regionBox).isEqualTo(regionBox) } @Test fun takeFullscreenScreenshot_callsScreenshotInteractor() = testScope.runTest { Loading Loading @@ -177,6 +196,56 @@ class PreCaptureViewModelTest : SysuiTestCase() { } } @Test fun takePartialScreenshot_callsScreenshotInteractor() = testScope.runTest { viewModel.updateCaptureType(ScreenCaptureType.SCREENSHOT) viewModel.updateCaptureRegion(ScreenCaptureRegion.PARTIAL) val regionBox = Rect(0, 0, 100, 100) viewModel.updateRegionBox(regionBox) whenever(kosmos.mockImageCapture.captureDisplay(any(), eq(regionBox))) .thenReturn(mockBitmap) viewModel.takePartialScreenshot() 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.bitmap).isEqualTo(mockBitmap) assertThat(capturedRequest.boundsInScreen).isEqualTo(regionBox) } @Test fun takePartialScreenshot_validatesCaptureType() = testScope.runTest { viewModel.updateCaptureType(ScreenCaptureType.SCREEN_RECORD) viewModel.updateCaptureRegion(ScreenCaptureRegion.PARTIAL) assertFailsWith(IllegalArgumentException::class) { viewModel.takePartialScreenshot() } } @Test fun takePartialScreenshot_validatesCaptureRegion() = testScope.runTest { viewModel.updateCaptureType(ScreenCaptureType.SCREENSHOT) viewModel.updateCaptureRegion(ScreenCaptureRegion.FULLSCREEN) assertFailsWith(IllegalArgumentException::class) { viewModel.takePartialScreenshot() } } @Test fun takePartialScreenshot_validatesRegionBoxIsNotNull() = testScope.runTest { viewModel.updateCaptureType(ScreenCaptureType.SCREENSHOT) viewModel.updateCaptureRegion(ScreenCaptureRegion.PARTIAL) // viewModel.regionBox is null by default assertFailsWith(IllegalArgumentException::class) { viewModel.takePartialScreenshot() } } @Test @DisableFlags(Flags.FLAG_LARGE_SCREEN_SCREENSHOT_APP_WINDOW) fun captureRegionButtonViewModels_excludesAppWindowWithFeatureDisabled() = Loading Loading
packages/SystemUI/src/com/android/systemui/screencapture/record/largescreen/ui/compose/PreCaptureUI.kt +5 −1 Original line number Diff line number Diff line Loading @@ -24,7 +24,9 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.android.systemui.res.R Loading Loading @@ -74,7 +76,9 @@ fun PreCaptureUI(viewModel: PreCaptureViewModel) { RegionBox( initialWidth = 100.dp, initialHeight = 100.dp, onDragEnd = viewModel::onPartialRegionDragEnd, onDragEnd = { _: Offset, _: Dp, _: Dp -> // TODO(b/427541309) Update the region box in the viewmodel. }, drawableLoaderViewModel = viewModel, ) } Loading
packages/SystemUI/src/com/android/systemui/screencapture/record/largescreen/ui/viewmodel/PreCaptureViewModel.kt +23 −4 Original line number Diff line number Diff line Loading @@ -17,8 +17,7 @@ package com.android.systemui.screencapture.record.largescreen.ui.viewmodel import android.content.Context import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Dp import android.graphics.Rect import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.lifecycle.HydratedActivatable Loading Loading @@ -64,6 +63,7 @@ constructor( private val isShowingUIFlow = MutableStateFlow(true) private val captureTypeSource = MutableStateFlow(ScreenCaptureType.SCREENSHOT) private val captureRegionSource = MutableStateFlow(ScreenCaptureRegion.FULLSCREEN) private val regionBoxSource = MutableStateFlow<Rect?>(null) val icons: ScreenCaptureIcons? by iconProvider.icons.hydratedStateOf() Loading @@ -75,6 +75,8 @@ constructor( // TODO(b/423697394) Init default value to be user's previously selected option val captureRegion: ScreenCaptureRegion by captureRegionSource.hydratedStateOf() val regionBox: Rect? by regionBoxSource.hydratedStateOf() val screenRecordingSupported = featuresInteractor.screenRecordingSupported val captureTypeButtonViewModels: List<RadioButtonGroupItemViewModel> by Loading Loading @@ -102,6 +104,10 @@ constructor( captureRegionSource.value = selectedRegion } fun updateRegionBox(bounds: Rect) { regionBoxSource.value = bounds } fun takeFullscreenScreenshot() { require(captureTypeSource.value == ScreenCaptureType.SCREENSHOT) require(captureRegionSource.value == ScreenCaptureRegion.FULLSCREEN) Loading @@ -117,8 +123,21 @@ constructor( } } fun onPartialRegionDragEnd(offset: Offset, width: Dp, height: Dp) { // TODO(b/427541309) Update region box position and size. fun takePartialScreenshot() { require(captureTypeSource.value == ScreenCaptureType.SCREENSHOT) require(captureRegionSource.value == ScreenCaptureRegion.PARTIAL) val regionBoxRect = requireNotNull(regionBoxSource.value) // Finishing the activity is not guaranteed to complete before the screenshot is taken. // Since the pre-capture UI should not be included in the screenshot, hide the UI first. hideUI() closeUI() backgroundScope.launch { // TODO(b/430361425) Pass in current display as argument. screenshotInteractor.takePartialScreenshot(regionBoxRect) } } /** Loading
packages/SystemUI/tests/src/com/android/systemui/screencapture/record/largescreen/ui/viewmodel/PreCaptureViewModelTest.kt +69 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.screencapture.record.largescreen.ui.viewmodel import android.graphics.Bitmap import android.graphics.Rect import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.WindowManager Loading @@ -29,6 +31,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.lifecycle.activateIn import com.android.systemui.screencapture.ui.mockScreenCaptureActivity import com.android.systemui.screenshot.mockImageCapture import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.assertFailsWith Loading @@ -39,10 +42,13 @@ 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) Loading @@ -50,6 +56,7 @@ class PreCaptureViewModelTest : 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 viewModel: PreCaptureViewModel by lazy { kosmos.preCaptureViewModel } Loading Loading @@ -141,6 +148,18 @@ class PreCaptureViewModelTest : SysuiTestCase() { assertThat(fullscreenButton3.isSelected).isFalse() } @Test fun updateRegionBox_updatesState() = testScope.runTest { // State is initially null. assertThat(viewModel.regionBox).isNull() val regionBox = Rect(0, 0, 100, 100) viewModel.updateRegionBox(regionBox) assertThat(viewModel.regionBox).isEqualTo(regionBox) } @Test fun takeFullscreenScreenshot_callsScreenshotInteractor() = testScope.runTest { Loading Loading @@ -177,6 +196,56 @@ class PreCaptureViewModelTest : SysuiTestCase() { } } @Test fun takePartialScreenshot_callsScreenshotInteractor() = testScope.runTest { viewModel.updateCaptureType(ScreenCaptureType.SCREENSHOT) viewModel.updateCaptureRegion(ScreenCaptureRegion.PARTIAL) val regionBox = Rect(0, 0, 100, 100) viewModel.updateRegionBox(regionBox) whenever(kosmos.mockImageCapture.captureDisplay(any(), eq(regionBox))) .thenReturn(mockBitmap) viewModel.takePartialScreenshot() 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.bitmap).isEqualTo(mockBitmap) assertThat(capturedRequest.boundsInScreen).isEqualTo(regionBox) } @Test fun takePartialScreenshot_validatesCaptureType() = testScope.runTest { viewModel.updateCaptureType(ScreenCaptureType.SCREEN_RECORD) viewModel.updateCaptureRegion(ScreenCaptureRegion.PARTIAL) assertFailsWith(IllegalArgumentException::class) { viewModel.takePartialScreenshot() } } @Test fun takePartialScreenshot_validatesCaptureRegion() = testScope.runTest { viewModel.updateCaptureType(ScreenCaptureType.SCREENSHOT) viewModel.updateCaptureRegion(ScreenCaptureRegion.FULLSCREEN) assertFailsWith(IllegalArgumentException::class) { viewModel.takePartialScreenshot() } } @Test fun takePartialScreenshot_validatesRegionBoxIsNotNull() = testScope.runTest { viewModel.updateCaptureType(ScreenCaptureType.SCREENSHOT) viewModel.updateCaptureRegion(ScreenCaptureRegion.PARTIAL) // viewModel.regionBox is null by default assertFailsWith(IllegalArgumentException::class) { viewModel.takePartialScreenshot() } } @Test @DisableFlags(Flags.FLAG_LARGE_SCREEN_SCREENSHOT_APP_WINDOW) fun captureRegionButtonViewModels_excludesAppWindowWithFeatureDisabled() = Loading