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

Commit 764e8540 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB][Screen Chips] Implement the screen record chip flow.

Bug: 332662551
Test: atest ScreenRecordChipInteractorTest
OngoingActivityChipsViewModelTest
Flag: com.android.systemui.status_bar_screen_sharing_chips

Change-Id: Icd1cbc6a6a01cd29fd99f682c2c86e2b69fe6bea
parent 6513c033
Loading
Loading
Loading
Loading
+38 −4
Original line number Diff line number Diff line
@@ -16,17 +16,51 @@

package com.android.systemui.statusbar.chips.screenrecord.domain.interactor

import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.screenrecord.data.repository.ScreenRecordRepository
import com.android.systemui.statusbar.chips.domain.interactor.OngoingActivityChipInteractor
import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/** Interactor for the screen recording chip shown in the status bar. */
@SysUISingleton
open class ScreenRecordChipInteractor @Inject constructor() : OngoingActivityChipInteractor {
    // TODO(b/332662551): Implement this flow.
open class ScreenRecordChipInteractor
@Inject
constructor(
    @Application scope: CoroutineScope,
    screenRecordRepository: ScreenRecordRepository,
    val systemClock: SystemClock,
) : OngoingActivityChipInteractor {
    override val chip: StateFlow<OngoingActivityChipModel> =
        MutableStateFlow(OngoingActivityChipModel.Hidden)
        screenRecordRepository.screenRecordState
            .map { state ->
                when (state) {
                    is ScreenRecordModel.DoingNothing,
                    // TODO(b/332662551): Implement the 3-2-1 countdown chip.
                    is ScreenRecordModel.Starting -> OngoingActivityChipModel.Hidden
                    is ScreenRecordModel.Recording ->
                        OngoingActivityChipModel.Shown(
                            // TODO(b/332662551): Also provide a content description.
                            icon =
                                Icon.Resource(
                                    R.drawable.stat_sys_screen_record,
                                    contentDescription = null
                                ),
                            startTimeMs = systemClock.elapsedRealtime()
                        ) {
                            // TODO(b/332662551): Implement the pause dialog.
                        }
                }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
}
+96 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.statusbar.chips.screenrecord.domain.interactor

import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.screenrecord.data.repository.screenRecordRepository
import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.screenRecordChipInteractor
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest

@SmallTest
class ScreenRecordChipInteractorTest : SysuiTestCase() {
    private val kosmos = Kosmos()
    private val testScope = kosmos.testScope
    private val screenRecordRepo = kosmos.screenRecordRepository
    private val systemClock = kosmos.fakeSystemClock

    private val underTest = kosmos.screenRecordChipInteractor

    @Test
    fun chip_doingNothingState_isHidden() =
        testScope.runTest {
            val latest by collectLastValue(underTest.chip)

            screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing

            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
        }

    @Test
    fun chip_startingState_isHidden() =
        testScope.runTest {
            val latest by collectLastValue(underTest.chip)

            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(400)

            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
        }

    @Test
    fun chip_recordingState_isShownWithIcon() =
        testScope.runTest {
            val latest by collectLastValue(underTest.chip)

            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording

            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
            val icon = (latest as OngoingActivityChipModel.Shown).icon
            assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.stat_sys_screen_record)
        }

    @Test
    fun chip_timeResetsOnEachNewRecording() =
        testScope.runTest {
            val latest by collectLastValue(underTest.chip)

            systemClock.setElapsedRealtime(1234)
            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording

            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
            assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(1234)

            screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing
            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)

            systemClock.setElapsedRealtime(5678)
            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording

            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
            assertThat((latest as OngoingActivityChipModel.Shown).startTimeMs).isEqualTo(5678)
        }
}
+20 −32
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.screenrecord.data.repository.screenRecordRepository
import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -38,7 +40,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
    @Test
    fun chip_allHidden_hidden() =
        kosmos.testScope.runTest {
            kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing
            kosmos.callChipInteractor.chip.value = OngoingActivityChipModel.Hidden

            val latest by collectLastValue(underTest.chip)
@@ -49,28 +51,18 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
    @Test
    fun chip_screenRecordShow_restHidden_screenRecordShown() =
        kosmos.testScope.runTest {
            val screenRecordChip =
                OngoingActivityChipModel.Shown(
                    Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
                    startTimeMs = 500L,
                ) {}
            kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
            kosmos.callChipInteractor.chip.value = OngoingActivityChipModel.Hidden

            val latest by collectLastValue(underTest.chip)

            assertThat(latest).isEqualTo(screenRecordChip)
            assertIsScreenRecordChip(latest)
        }

    @Test
    fun chip_screenRecordShowAndCallShow_screenRecordShown() =
        kosmos.testScope.runTest {
            val screenRecordChip =
                OngoingActivityChipModel.Shown(
                    Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
                    startTimeMs = 500L,
                ) {}
            kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording

            val callChip =
                OngoingActivityChipModel.Shown(
@@ -81,13 +73,13 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {

            val latest by collectLastValue(underTest.chip)

            assertThat(latest).isEqualTo(screenRecordChip)
            assertIsScreenRecordChip(latest)
        }

    @Test
    fun chip_screenRecordHideAndCallShown_callShown() =
        kosmos.testScope.runTest {
            kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing

            val callChip =
                OngoingActivityChipModel.Shown(
@@ -111,34 +103,24 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
                    startTimeMs = 600L,
                ) {}
            kosmos.callChipInteractor.chip.value = callChip
            kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing

            val latest by collectLastValue(underTest.chip)

            assertThat(latest).isEqualTo(callChip)

            // WHEN the higher priority screen record chip is added
            val screenRecordChip =
                OngoingActivityChipModel.Shown(
                    Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
                    startTimeMs = 500L,
                ) {}
            kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording

            // THEN the higher priority screen record chip is used
            assertThat(latest).isEqualTo(screenRecordChip)
            assertIsScreenRecordChip(latest)
        }

    @Test
    fun chip_highestPriorityChipRemoved_showsNextPriorityChip() =
        kosmos.testScope.runTest {
            // Start with both the higher priority screen record chip and lower priority call chip
            val screenRecordChip =
                OngoingActivityChipModel.Shown(
                    Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("icon")),
                    startTimeMs = 500L,
                ) {}
            kosmos.screenRecordChipInteractor.chip.value = screenRecordChip
            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording

            val callChip =
                OngoingActivityChipModel.Shown(
@@ -149,12 +131,18 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {

            val latest by collectLastValue(underTest.chip)

            assertThat(latest).isEqualTo(screenRecordChip)
            assertIsScreenRecordChip(latest)

            // WHEN the higher priority screen record is removed
            kosmos.screenRecordChipInteractor.chip.value = OngoingActivityChipModel.Hidden
            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing

            // THEN the lower priority call is used
            assertThat(latest).isEqualTo(callChip)
        }

    private fun assertIsScreenRecordChip(latest: OngoingActivityChipModel?) {
        assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
        val icon = (latest as OngoingActivityChipModel.Shown).icon
        assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.stat_sys_screen_record)
    }
}
+0 −6
Original line number Diff line number Diff line
@@ -18,14 +18,8 @@ package com.android.systemui.statusbar.chips.ui.viewmodel

import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
import com.android.systemui.statusbar.chips.domain.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
import kotlinx.coroutines.flow.MutableStateFlow

class FakeScreenRecordChipInteractor : ScreenRecordChipInteractor() {
    override val chip: MutableStateFlow<OngoingActivityChipModel> =
        MutableStateFlow(OngoingActivityChipModel.Hidden)
}

class FakeCallChipInteractor : CallChipInteractor() {
    override val chip: MutableStateFlow<OngoingActivityChipModel> =
        MutableStateFlow(OngoingActivityChipModel.Hidden)
+12 −2
Original line number Diff line number Diff line
@@ -17,10 +17,20 @@
package com.android.systemui.statusbar.chips.ui.viewmodel

import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.screenrecord.data.repository.screenRecordRepository
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
import com.android.systemui.util.time.fakeSystemClock

val Kosmos.screenRecordChipInteractor: FakeScreenRecordChipInteractor by
    Kosmos.Fixture { FakeScreenRecordChipInteractor() }
val Kosmos.screenRecordChipInteractor: ScreenRecordChipInteractor by
    Kosmos.Fixture {
        ScreenRecordChipInteractor(
            scope = applicationCoroutineScope,
            screenRecordRepository = screenRecordRepository,
            systemClock = fakeSystemClock,
        )
    }

val Kosmos.callChipInteractor: FakeCallChipInteractor by Kosmos.Fixture { FakeCallChipInteractor() }