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

Commit aa94cf4e authored by Xiaoqian Dai's avatar Xiaoqian Dai
Browse files

screen capture: hide the PreCaptureToolbar during region move or resize

and change the PreCaptureToolbar's opacity to 0.15 if the RegionBox
overlaps with the PreCaptureToolbar after moving or resizing.

Bug: 423963580, 423964826
Test: newly added tests in PreCaptureViewModelTest.kt
Flag: com.android.systemui.large_screen_screencapture
Change-Id: Iea29da52d86e4dffe10e3cecace93cf241a4ba41
parent 15039cee
Loading
Loading
Loading
Loading
+27 −1
Original line number Original line Diff line number Diff line
@@ -27,6 +27,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.compose.ui.zIndex
@@ -37,6 +40,7 @@ import com.android.systemui.screencapture.common.ui.compose.loadIcon
import com.android.systemui.screencapture.record.largescreen.shared.model.ScreenCaptureRegion
import com.android.systemui.screencapture.record.largescreen.shared.model.ScreenCaptureRegion
import com.android.systemui.screencapture.record.largescreen.shared.model.ScreenCaptureType
import com.android.systemui.screencapture.record.largescreen.shared.model.ScreenCaptureType
import com.android.systemui.screencapture.record.largescreen.ui.viewmodel.PreCaptureViewModel
import com.android.systemui.screencapture.record.largescreen.ui.viewmodel.PreCaptureViewModel
import kotlin.math.roundToInt


/** Main component for the pre-capture UI. */
/** Main component for the pre-capture UI. */
@Composable
@Composable
@@ -53,6 +57,19 @@ fun PreCaptureUI(viewModel: PreCaptureViewModel) {
                viewModel = viewModel,
                viewModel = viewModel,
                expanded = true,
                expanded = true,
                onCloseClick = { viewModel.closeUi() },
                onCloseClick = { viewModel.closeUi() },
                modifier =
                    Modifier.onGloballyPositioned {
                            val boundsInWindow = it.boundsInWindow()
                            viewModel.updateToolbarBounds(
                                Rect(
                                    boundsInWindow.left.roundToInt(),
                                    boundsInWindow.top.roundToInt(),
                                    boundsInWindow.right.roundToInt(),
                                    boundsInWindow.bottom.roundToInt(),
                                )
                            )
                        }
                        .graphicsLayer { alpha = viewModel.toolbarOpacity },
            )
            )
        }
        }


@@ -115,8 +132,17 @@ fun PreCaptureUI(viewModel: PreCaptureViewModel) {
                                }
                                }
                        ),
                        ),
                    buttonIcon = icon,
                    buttonIcon = icon,
                    onRegionSelected = { rect: Rect -> viewModel.updateRegionBox(rect) },
                    onRegionSelected = { regionBoxRect ->
                        viewModel.updateRegionBoxBounds(regionBoxRect)
                        viewModel.updateToolbarOpacityForRegionBox(
                            isInteracting = false,
                            regionBoxRect = regionBoxRect,
                        )
                    },
                    onCaptureClick = viewModel::beginCapture,
                    onCaptureClick = viewModel::beginCapture,
                    onInteractionStateChanged = { isInteracting ->
                        viewModel.updateToolbarOpacityForRegionBox(isInteracting)
                    },
                )
                )
            }
            }


+6 −0
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.remember
@@ -355,6 +356,8 @@ class RegionBoxState(private val minSizePx: Float, private val touchAreaPx: Floa
 *   user finishes a drag gesture. This rectangle is used for taking a screenshot. The rectangle is
 *   user finishes a drag gesture. This rectangle is used for taking a screenshot. The rectangle is
 *   of type [android.graphics.Rect] because the screenshot API requires int values.
 *   of type [android.graphics.Rect] because the screenshot API requires int values.
 * @param onCaptureClick A callback function that is invoked when the capture button is clicked.
 * @param onCaptureClick A callback function that is invoked when the capture button is clicked.
 * @param onInteractionStateChanged A callback function that is invoked when the user starts or
 *   stops interacting with the region box.
 * @param modifier The modifier to be applied to the composable.
 * @param modifier The modifier to be applied to the composable.
 */
 */
@Composable
@Composable
@@ -363,6 +366,7 @@ fun RegionBox(
    buttonIcon: Icon?,
    buttonIcon: Icon?,
    onRegionSelected: (rect: IntRect) -> Unit,
    onRegionSelected: (rect: IntRect) -> Unit,
    onCaptureClick: () -> Unit,
    onCaptureClick: () -> Unit,
    onInteractionStateChanged: (isInteracting: Boolean) -> Unit,
    modifier: Modifier = Modifier,
    modifier: Modifier = Modifier,
) {
) {
    val density = LocalDensity.current
    val density = LocalDensity.current
@@ -379,6 +383,8 @@ fun RegionBox(
    val scrimColor = ScreenCaptureColors.scrimColor
    val scrimColor = ScreenCaptureColors.scrimColor
    val pointerIcon = rememberPointerIcon(state)
    val pointerIcon = rememberPointerIcon(state)


    LaunchedEffect(state.dragMode) { onInteractionStateChanged(state.dragMode != DragMode.NONE) }

    Box(
    Box(
        modifier =
        modifier =
            modifier
            modifier
+37 −1
Original line number Original line Diff line number Diff line
@@ -71,6 +71,8 @@ constructor(
                ?: ScreenCaptureRegion.FULLSCREEN
                ?: ScreenCaptureRegion.FULLSCREEN
        )
        )
    private val regionBoxSource = MutableStateFlow<Rect?>(null)
    private val regionBoxSource = MutableStateFlow<Rect?>(null)
    private val toolbarBoundsSource = MutableStateFlow(Rect())
    private val toolbarOpacitySource = MutableStateFlow(1f)


    val icons: ScreenCaptureIcons? by iconProvider.icons.hydratedStateOf()
    val icons: ScreenCaptureIcons? by iconProvider.icons.hydratedStateOf()


@@ -83,6 +85,7 @@ constructor(
    val captureRegion: ScreenCaptureRegion by captureRegionSource.hydratedStateOf()
    val captureRegion: ScreenCaptureRegion by captureRegionSource.hydratedStateOf()


    val regionBox: Rect? by regionBoxSource.hydratedStateOf()
    val regionBox: Rect? by regionBoxSource.hydratedStateOf()
    val toolbarOpacity: Float by toolbarOpacitySource.hydratedStateOf()


    val screenRecordingSupported = featuresInteractor.screenRecordingSupported
    val screenRecordingSupported = featuresInteractor.screenRecordingSupported


@@ -118,10 +121,43 @@ constructor(
        captureRegionSource.value = selectedRegion
        captureRegionSource.value = selectedRegion
    }
    }


    fun updateRegionBox(bounds: Rect) {
    fun updateRegionBoxBounds(bounds: Rect) {
        regionBoxSource.value = bounds
        regionBoxSource.value = bounds
    }
    }


    /**
     * Updates the toolbar opacity based on whether the region box selection intersects with the
     * toolbar, and whether the region box is resized or moved.
     *
     * @param isInteracting whether the region box is currently resized or moved.
     * @param regionBoxRect the current bounds of the region box selection. If not provided, will
     *   use the last selected region as the input to calculate the desired opacity.
     */
    fun updateToolbarOpacityForRegionBox(isInteracting: Boolean, regionBoxRect: Rect? = null) {
        if (isInteracting) {
            toolbarOpacitySource.value = 0f
            return
        }

        // When interaction stops, or a region is selected, calculate the final opacity.
        val finalRegion = regionBoxRect ?: regionBoxSource.value
        if (finalRegion == null) {
            toolbarOpacitySource.value = 1f
            return
        }

        val toolbarRect = toolbarBoundsSource.value
        if (!toolbarRect.isEmpty && Rect.intersects(finalRegion, toolbarRect)) {
            toolbarOpacitySource.value = 0.15f
        } else {
            toolbarOpacitySource.value = 1f
        }
    }

    fun updateToolbarBounds(bounds: Rect) {
        toolbarBoundsSource.value = bounds
    }

    /** Initiates capture of the screen depending on the currently chosen capture type. */
    /** Initiates capture of the screen depending on the currently chosen capture type. */
    fun beginCapture() {
    fun beginCapture() {
        when (captureTypeSource.value) {
        when (captureTypeSource.value) {
+47 −3
Original line number Original line Diff line number Diff line
@@ -213,7 +213,7 @@ class PreCaptureViewModelTest : SysuiTestCase() {
        }
        }


    @Test
    @Test
    fun updateRegionBox_updatesState() =
    fun updateRegionBoxBounds_updatesState() =
        kosmos.runTest {
        kosmos.runTest {
            setupViewModel()
            setupViewModel()


@@ -221,7 +221,7 @@ class PreCaptureViewModelTest : SysuiTestCase() {
            assertThat(viewModel.regionBox).isNull()
            assertThat(viewModel.regionBox).isNull()


            val regionBox = Rect(0, 0, 100, 100)
            val regionBox = Rect(0, 0, 100, 100)
            viewModel.updateRegionBox(regionBox)
            viewModel.updateRegionBoxBounds(regionBox)


            assertThat(viewModel.regionBox).isEqualTo(regionBox)
            assertThat(viewModel.regionBox).isEqualTo(regionBox)
        }
        }
@@ -255,7 +255,7 @@ class PreCaptureViewModelTest : SysuiTestCase() {
            viewModel.updateCaptureRegion(ScreenCaptureRegion.PARTIAL)
            viewModel.updateCaptureRegion(ScreenCaptureRegion.PARTIAL)


            val regionBox = Rect(0, 0, 100, 100)
            val regionBox = Rect(0, 0, 100, 100)
            viewModel.updateRegionBox(regionBox)
            viewModel.updateRegionBoxBounds(regionBox)


            whenever(kosmos.mockImageCapture.captureDisplay(any(), eq(regionBox)))
            whenever(kosmos.mockImageCapture.captureDisplay(any(), eq(regionBox)))
                .thenReturn(mockBitmap)
                .thenReturn(mockBitmap)
@@ -403,4 +403,48 @@ class PreCaptureViewModelTest : SysuiTestCase() {


            assertThat(uiState).isEqualTo(ScreenCaptureUiState.Invisible)
            assertThat(uiState).isEqualTo(ScreenCaptureUiState.Invisible)
        }
        }

    @Test
    fun updateToolbarOpacityForRegionBox_isInteracting_opacityIsZero() =
        kosmos.runTest {
            setupViewModel()
            viewModel.updateToolbarOpacityForRegionBox(isInteracting = true)

            assertThat(viewModel.toolbarOpacity).isEqualTo(0f)
        }

    @Test
    fun updateToolbarOpacityForRegionBox_notInteracting_noOverlap_opacityIsOne() =
        kosmos.runTest {
            setupViewModel()
            viewModel.updateToolbarBounds(Rect(0, 0, 100, 100))
            viewModel.updateRegionBoxBounds(Rect(200, 200, 300, 300))

            viewModel.updateToolbarOpacityForRegionBox(isInteracting = false)

            assertThat(viewModel.toolbarOpacity).isEqualTo(1f)
        }

    @Test
    fun updateToolbarOpacityForRegionBox_notInteracting_overlap_opacityIsPoint15() =
        kosmos.runTest {
            setupViewModel()
            viewModel.updateToolbarBounds(Rect(0, 0, 100, 100))
            viewModel.updateRegionBoxBounds(Rect(50, 50, 150, 150))

            viewModel.updateToolbarOpacityForRegionBox(isInteracting = false)

            assertThat(viewModel.toolbarOpacity).isEqualTo(0.15f)
        }

    @Test
    fun updateToolbarOpacityForRegionBox_notInteracting_noRegion_opacityIsOne() =
        kosmos.runTest {
            setupViewModel()
            viewModel.updateToolbarBounds(Rect(0, 0, 100, 100))

            viewModel.updateToolbarOpacityForRegionBox(isInteracting = false)

            assertThat(viewModel.toolbarOpacity).isEqualTo(1f)
        }
}
}