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

Commit 487ba500 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov Committed by Android (Google) Code Review
Browse files

Merge "[Misc] Move screen recording flow to general repository class." into main

parents 60bd06f1 e3e21c1e
Loading
Loading
Loading
Loading
+15 −73
Original line number Diff line number Diff line
@@ -25,11 +25,8 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
import com.android.systemui.screenrecord.RecordingController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.screenrecord.data.repository.screenRecordRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
@@ -37,7 +34,6 @@ import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -46,13 +42,13 @@ import org.mockito.Mockito.verify
class ScreenRecordTileDataInteractorTest : SysuiTestCase() {
    private val kosmos = Kosmos()
    private val testScope = kosmos.testScope
    private val controller = mock<RecordingController>()
    private val screenRecordRepo = kosmos.screenRecordRepository
    private val underTest: ScreenRecordTileDataInteractor =
        ScreenRecordTileDataInteractor(testScope.testScheduler, controller)
        ScreenRecordTileDataInteractor(screenRecordRepo)

    private val isRecording = ScreenRecordTileModel.Recording
    private val isDoingNothing = ScreenRecordTileModel.DoingNothing
    private val isStarting0 = ScreenRecordTileModel.Starting(0)
    private val isRecording = ScreenRecordModel.Recording
    private val isDoingNothing = ScreenRecordModel.DoingNothing
    private val isStarting0 = ScreenRecordModel.Starting(0)

    @Test
    fun isAvailable_returnsTrue() = runTest {
@@ -62,87 +58,33 @@ class ScreenRecordTileDataInteractorTest : SysuiTestCase() {
    }

    @Test
    fun dataMatchesController() =
    fun dataMatchesRepo() =
        testScope.runTest {
            whenever(controller.isRecording).thenReturn(false)
            whenever(controller.isStarting).thenReturn(false)

            val callbackCaptor = argumentCaptor<RecordingController.RecordingStateChangeCallback>()

            val lastModel by
                collectLastValue(
                    underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
                )
            runCurrent()

            verify(controller).addCallback(callbackCaptor.capture())
            val callback = callbackCaptor.value

            assertThat(lastModel).isEqualTo(isDoingNothing)

            val expectedModelStartingIn1 = ScreenRecordTileModel.Starting(1)
            callback.onCountdown(1)
            val expectedModelStartingIn1 = ScreenRecordModel.Starting(1)
            screenRecordRepo.screenRecordState.value = expectedModelStartingIn1
            assertThat(lastModel).isEqualTo(expectedModelStartingIn1)

            val expectedModelStartingIn0 = isStarting0
            callback.onCountdown(0)
            assertThat(lastModel).isEqualTo(expectedModelStartingIn0)
            screenRecordRepo.screenRecordState.value = isStarting0
            assertThat(lastModel).isEqualTo(isStarting0)

            callback.onCountdownEnd()
            screenRecordRepo.screenRecordState.value = isDoingNothing
            assertThat(lastModel).isEqualTo(isDoingNothing)

            callback.onRecordingStart()
            screenRecordRepo.screenRecordState.value = isRecording
            assertThat(lastModel).isEqualTo(isRecording)

            callback.onRecordingEnd()
            screenRecordRepo.screenRecordState.value = isDoingNothing
            assertThat(lastModel).isEqualTo(isDoingNothing)
        }

    @Test
    fun data_whenRecording_matchesController() =
        testScope.runTest {
            whenever(controller.isRecording).thenReturn(true)
            whenever(controller.isStarting).thenReturn(false)

            val lastModel by
                collectLastValue(
                    underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
                )
            runCurrent()

            assertThat(lastModel).isEqualTo(isRecording)
        }

    @Test
    fun data_whenStarting_matchesController() =
        testScope.runTest {
            whenever(controller.isRecording).thenReturn(false)
            whenever(controller.isStarting).thenReturn(true)

            val lastModel by
                collectLastValue(
                    underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
                )
            runCurrent()

            assertThat(lastModel).isEqualTo(isStarting0)
        }

    @Test
    fun data_whenRecordingAndStarting_matchesControllerRecording() =
        testScope.runTest {
            whenever(controller.isRecording).thenReturn(true)
            whenever(controller.isStarting).thenReturn(true)

            val lastModel by
                collectLastValue(
                    underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
                )
            runCurrent()

            assertThat(lastModel).isEqualTo(isRecording)
        }

    private companion object {
        val TEST_USER = UserHandle.of(1)!!
    }
+5 −5
Original line number Diff line number Diff line
@@ -33,8 +33,8 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.activityStarter
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
import com.android.systemui.screenrecord.RecordingController
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -89,7 +89,7 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() {

    @Test
    fun handleClick_whenStarting_cancelCountdown() = runTest {
        val startingModel = ScreenRecordTileModel.Starting(0)
        val startingModel = ScreenRecordModel.Starting(0)

        underTest.handleInput(QSTileInputTestKtx.click(startingModel))

@@ -98,7 +98,7 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() {

    @Test
    fun handleClick_whenRecording_stopRecording() = runTest {
        val recordingModel = ScreenRecordTileModel.Recording
        val recordingModel = ScreenRecordModel.Recording

        underTest.handleInput(QSTileInputTestKtx.click(recordingModel))

@@ -107,7 +107,7 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() {

    @Test
    fun handleClick_whenDoingNothing_createDialogDismissPanelShowDialog() = runTest {
        val recordingModel = ScreenRecordTileModel.DoingNothing
        val recordingModel = ScreenRecordModel.DoingNothing

        underTest.handleInput(QSTileInputTestKtx.click(recordingModel))
        val onStartRecordingClickedCaptor = argumentCaptor<Runnable>()
@@ -143,7 +143,7 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() {

        kosmos.fakeKeyguardRepository.setKeyguardShowing(false)

        val recordingModel = ScreenRecordTileModel.DoingNothing
        val recordingModel = ScreenRecordModel.DoingNothing

        underTest.handleInput(
            QSTileInputTestKtx.click(recordingModel, UserHandle.CURRENT, expandable)
+4 −4
Original line number Diff line number Diff line
@@ -25,11 +25,11 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
import com.android.systemui.qs.tiles.impl.screenrecord.domain.ui.ScreenRecordTileMapper
import com.android.systemui.qs.tiles.impl.screenrecord.qsScreenRecordTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -58,7 +58,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() {

    @Test
    fun activeStateMatchesRecordingDataModel() {
        val inputModel = ScreenRecordTileModel.Recording
        val inputModel = ScreenRecordModel.Recording

        val outputState = mapper.map(config, inputModel)

@@ -74,7 +74,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() {
    @Test
    fun activeStateMatchesStartingDataModel() {
        val timeLeft = 0L
        val inputModel = ScreenRecordTileModel.Starting(timeLeft)
        val inputModel = ScreenRecordModel.Starting(timeLeft)

        val outputState = mapper.map(config, inputModel)

@@ -89,7 +89,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() {

    @Test
    fun inactiveStateMatchesDisabledDataModel() {
        val inputModel = ScreenRecordTileModel.DoingNothing
        val inputModel = ScreenRecordModel.DoingNothing

        val outputState = mapper.map(config, inputModel)

+6 −51
Original line number Diff line number Diff line
@@ -17,70 +17,25 @@
package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor

import android.os.UserHandle
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
import com.android.systemui.screenrecord.RecordingController
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.screenrecord.data.repository.ScreenRecordRepository
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart

/** Observes screen record state changes providing the [ScreenRecordTileModel]. */
/** Observes screen record state changes providing the [ScreenRecordModel]. */
class ScreenRecordTileDataInteractor
@Inject
constructor(
    @Background private val bgCoroutineContext: CoroutineContext,
    private val recordingController: RecordingController,
) : QSTileDataInteractor<ScreenRecordTileModel> {
    private val screenRecordRepository: ScreenRecordRepository,
) : QSTileDataInteractor<ScreenRecordModel> {

    override fun tileData(
        user: UserHandle,
        triggers: Flow<DataUpdateTrigger>
    ): Flow<ScreenRecordTileModel> =
        ConflatedCallbackFlow.conflatedCallbackFlow {
                val callback =
                    object : RecordingController.RecordingStateChangeCallback {
                        override fun onRecordingStart() {
                            trySend(ScreenRecordTileModel.Recording)
                        }
                        override fun onRecordingEnd() {
                            trySend(ScreenRecordTileModel.DoingNothing)
                        }
                        override fun onCountdown(millisUntilFinished: Long) {
                            trySend(ScreenRecordTileModel.Starting(millisUntilFinished))
                        }
                        override fun onCountdownEnd() {
                            if (
                                !recordingController.isRecording && !recordingController.isStarting
                            ) {
                                // The tile was in Starting state and got canceled before recording
                                trySend(ScreenRecordTileModel.DoingNothing)
                            }
                        }
                    }
                recordingController.addCallback(callback)
                awaitClose { recordingController.removeCallback(callback) }
            }
            .onStart { emit(generateModel()) }
            .distinctUntilChanged()
            .flowOn(bgCoroutineContext)
    ): Flow<ScreenRecordModel> = screenRecordRepository.screenRecordState

    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)

    private fun generateModel(): ScreenRecordTileModel {
        if (recordingController.isRecording) {
            return ScreenRecordTileModel.Recording
        } else if (recordingController.isStarting) {
            return ScreenRecordTileModel.Starting(0)
        } else {
            return ScreenRecordTileModel.DoingNothing
        }
    }
}
+6 −6
Original line number Diff line number Diff line
@@ -32,9 +32,9 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.screenrecord.domain.model.ScreenRecordTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.screenrecord.RecordingController
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
@@ -55,19 +55,19 @@ constructor(
    private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
    private val featureFlags: FeatureFlagsClassic,
    private val activityStarter: ActivityStarter,
) : QSTileUserActionInteractor<ScreenRecordTileModel> {
    override suspend fun handleInput(input: QSTileInput<ScreenRecordTileModel>): Unit =
) : QSTileUserActionInteractor<ScreenRecordModel> {
    override suspend fun handleInput(input: QSTileInput<ScreenRecordModel>): Unit =
        with(input) {
            when (action) {
                is QSTileUserAction.Click -> {
                    when (data) {
                        is ScreenRecordTileModel.Starting -> {
                        is ScreenRecordModel.Starting -> {
                            Log.d(TAG, "Cancelling countdown")
                            withContext(backgroundContext) { recordingController.cancelCountdown() }
                        }
                        is ScreenRecordTileModel.Recording ->
                        is ScreenRecordModel.Recording ->
                            withContext(backgroundContext) { recordingController.stopRecording() }
                        is ScreenRecordTileModel.DoingNothing ->
                        is ScreenRecordModel.DoingNothing ->
                            withContext(mainContext) {
                                showPrompt(action.expandable, user.identifier)
                            }
Loading