Loading packages/SystemUI/aconfig/systemui.aconfig +11 −0 Original line number Original line Diff line number Diff line Loading @@ -413,6 +413,17 @@ flag { } } } } flag { name: "status_bar_auto_start_screen_record_chip" namespace: "systemui" description: "When screen recording, use the specified start time to update the screen record " "chip state instead of waiting for an official 'recording started' signal" bug: "366448907" metadata { purpose: PURPOSE_BUGFIX } } flag { flag { name: "status_bar_use_repos_for_call_chip" name: "status_bar_use_repos_for_call_chip" namespace: "systemui" namespace: "systemui" Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt +140 −3 Original line number Original line Diff line number Diff line Loading @@ -16,29 +16,35 @@ package com.android.systemui.statusbar.chips.screenrecord.domain.interactor package com.android.systemui.statusbar.chips.screenrecord.domain.interactor import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest import org.junit.runner.RunWith import org.junit.runner.RunWith @SmallTest @SmallTest @RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) class ScreenRecordChipInteractorTest : SysuiTestCase() { class ScreenRecordChipInteractorTest : SysuiTestCase() { private val kosmos = Kosmos().also { it.testCase = this } private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val testScope = kosmos.testScope private val screenRecordRepo = kosmos.screenRecordRepository private val screenRecordRepo = kosmos.screenRecordRepository private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository Loading Loading @@ -115,6 +121,137 @@ class ScreenRecordChipInteractorTest : SysuiTestCase() { assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task)) assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task)) } } @Test @DisableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_flagOff_doesNotAutomaticallySwitchToRecordingBasedOnTime() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) // WHEN screen record should start in 900ms screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) // WHEN 900ms has elapsed advanceTimeBy(901) // THEN we don't automatically update to the recording state if the flag is off assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) } @Test @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_flagOn_automaticallySwitchesToRecordingBasedOnTime() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) // WHEN screen record should start in 900ms screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) // WHEN 900ms has elapsed advanceTimeBy(901) // THEN we automatically update to the recording state assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = null)) } @Test @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_recordingBeginsEarly_switchesToRecording() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) // WHEN screen record should start in 900ms screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) // WHEN we update to the Recording state earlier than 900ms advanceTimeBy(800) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording val task = createTask(taskId = 1) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.SingleTask( "host.package", hostDeviceName = null, task, ) // THEN we immediately switch to Recording, and we have the task assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task)) // WHEN more than 900ms has elapsed advanceTimeBy(200) // THEN we still stay in the Recording state and we have the task assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task)) } @Test @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_secondRecording_doesNotAutomaticallyStart() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) // First recording starts, records, and stops screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) advanceTimeBy(900) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording advanceTimeBy(5000) screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing advanceTimeBy(10000) assertThat(latest).isEqualTo(ScreenRecordChipModel.DoingNothing) // WHEN a second recording is starting screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2900) // THEN we stay as starting and do not switch to Recording (verifying the auto-start // timer is reset) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(2900)) } @Test @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_startingButThenDoingNothing_doesNotAutomaticallyStart() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) // WHEN a screen recording is starting in 500ms screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(500) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(500)) // But it's cancelled after 300ms advanceTimeBy(300) screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing // THEN we don't automatically start the recording 200ms later advanceTimeBy(201) assertThat(latest).isEqualTo(ScreenRecordChipModel.DoingNothing) } @Test @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_multipleStartingValues_autoStartResets() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2900) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(2900)) advanceTimeBy(2800) // WHEN there's 100ms left to go before auto-start, but then we get a new start time // that's in 500ms screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(500) // THEN we don't auto-start in 100ms advanceTimeBy(101) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(500)) // THEN we *do* auto-start 400ms later advanceTimeBy(401) assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = null)) } @Test @Test fun stopRecording_sendsToRepo() = fun stopRecording_sendsToRepo() = testScope.runTest { testScope.runTest { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt +9 −29 Original line number Original line Diff line number Diff line Loading @@ -26,9 +26,8 @@ import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager Loading @@ -44,6 +43,7 @@ import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlin.test.Test Loading @@ -61,7 +61,7 @@ import org.mockito.kotlin.whenever @SmallTest @SmallTest @RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class) class ScreenRecordChipViewModelTest : SysuiTestCase() { class ScreenRecordChipViewModelTest : SysuiTestCase() { private val kosmos = Kosmos().also { it.testCase = this } private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val testScope = kosmos.testScope private val screenRecordRepo = kosmos.screenRecordRepository private val screenRecordRepo = kosmos.screenRecordRepository private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository Loading Loading @@ -254,7 +254,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { MediaProjectionState.Projecting.SingleTask( MediaProjectionState.Projecting.SingleTask( "host.package", "host.package", hostDeviceName = null, hostDeviceName = null, FakeActivityTaskManager.createTask(taskId = 1) FakeActivityTaskManager.createTask(taskId = 1), ) ) // THEN the start time is still the old start time // THEN the start time is still the old start time Loading @@ -275,12 +275,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { clickListener!!.onClick(chipView) clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) verify(kosmos.mockDialogTransitionAnimator) .showFromView( .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean()) eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean(), ) } } @Test @Test Loading @@ -297,12 +292,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { clickListener!!.onClick(chipView) clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) verify(kosmos.mockDialogTransitionAnimator) .showFromView( .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean()) eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean(), ) } } @Test @Test Loading @@ -314,7 +304,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { MediaProjectionState.Projecting.SingleTask( MediaProjectionState.Projecting.SingleTask( "host.package", "host.package", hostDeviceName = null, hostDeviceName = null, FakeActivityTaskManager.createTask(taskId = 1) FakeActivityTaskManager.createTask(taskId = 1), ) ) val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) Loading @@ -323,12 +313,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { clickListener!!.onClick(chipView) clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) verify(kosmos.mockDialogTransitionAnimator) .showFromView( .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean()) eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean(), ) } } @Test @Test Loading @@ -344,12 +329,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { val cujCaptor = argumentCaptor<DialogCuj>() val cujCaptor = argumentCaptor<DialogCuj>() verify(kosmos.mockDialogTransitionAnimator) verify(kosmos.mockDialogTransitionAnimator) .showFromView( .showFromView(any(), any(), cujCaptor.capture(), anyBoolean()) any(), any(), cujCaptor.capture(), anyBoolean(), ) assertThat(cujCaptor.firstValue.cujType) assertThat(cujCaptor.firstValue.cujType) .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +2 −2 Original line number Original line Diff line number Diff line Loading @@ -29,7 +29,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository Loading @@ -48,6 +47,7 @@ import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi Loading @@ -72,7 +72,7 @@ import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class) @DisableFlags(StatusBarNotifChips.FLAG_NAME) @DisableFlags(StatusBarNotifChips.FLAG_NAME) class OngoingActivityChipsViewModelTest : SysuiTestCase() { class OngoingActivityChipsViewModelTest : SysuiTestCase() { private val kosmos = Kosmos().also { it.testCase = this } private val kosmos = testKosmos() private val testScope = kosmos.testScope private val testScope = kosmos.testScope private val systemClock = kosmos.fakeSystemClock private val systemClock = kosmos.fakeSystemClock Loading packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt +82 −32 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.chips.screenrecord.domain.interactor package com.android.systemui.statusbar.chips.screenrecord.domain.interactor import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBuffer Loading @@ -28,14 +29,19 @@ import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch /** Interactor for the screen recording chip shown in the status bar. */ /** Interactor for the screen recording chip shown in the status bar. */ @SysUISingleton @SysUISingleton @OptIn(ExperimentalCoroutinesApi::class) class ScreenRecordChipInteractor class ScreenRecordChipInteractor @Inject @Inject constructor( constructor( Loading @@ -44,6 +50,32 @@ constructor( private val mediaProjectionRepository: MediaProjectionRepository, private val mediaProjectionRepository: MediaProjectionRepository, @StatusBarChipsLog private val logger: LogBuffer, @StatusBarChipsLog private val logger: LogBuffer, ) { ) { /** * Emits true if we should assume that we're currently screen recording, even if * [ScreenRecordRepository.screenRecordState] hasn't emitted [ScreenRecordModel.Recording] yet. */ private val shouldAssumeIsRecording: Flow<Boolean> = screenRecordRepository.screenRecordState .transformLatest { when (it) { is ScreenRecordModel.DoingNothing -> { emit(false) } is ScreenRecordModel.Starting -> { // If we're told that the recording will start in [it.millisUntilStarted], // optimistically assume the recording did indeed start after that time even // if [ScreenRecordRepository.screenRecordState] hasn't emitted // [ScreenRecordModel.Recording] yet. Start 50ms early so that the chip // timer will definitely be showing by the time the recording actually // starts - see b/366448907. delay(it.millisUntilStarted - 50) emit(true) } is ScreenRecordModel.Recording -> {} } } .stateIn(scope, SharingStarted.WhileSubscribed(), false) val screenRecordState: StateFlow<ScreenRecordChipModel> = val screenRecordState: StateFlow<ScreenRecordChipModel> = // ScreenRecordRepository has the main "is the screen being recorded?" state, and // ScreenRecordRepository has the main "is the screen being recorded?" state, and // MediaProjectionRepository has information about what specifically is being recorded (a // MediaProjectionRepository has information about what specifically is being recorded (a Loading @@ -51,25 +83,42 @@ constructor( combine( combine( screenRecordRepository.screenRecordState, screenRecordRepository.screenRecordState, mediaProjectionRepository.mediaProjectionState, mediaProjectionRepository.mediaProjectionState, ) { screenRecordState, mediaProjectionState -> shouldAssumeIsRecording, ) { screenRecordState, mediaProjectionState, shouldAssumeIsRecording -> if ( Flags.statusBarAutoStartScreenRecordChip() && shouldAssumeIsRecording && screenRecordState is ScreenRecordModel.Starting ) { logger.log( TAG, LogLevel.INFO, {}, { "State: Recording(taskPackage=null) due to force-start" }, ) ScreenRecordChipModel.Recording(recordedTask = null) } else { when (screenRecordState) { when (screenRecordState) { is ScreenRecordModel.DoingNothing -> { is ScreenRecordModel.DoingNothing -> { logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" }) logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" }) ScreenRecordChipModel.DoingNothing ScreenRecordChipModel.DoingNothing } } is ScreenRecordModel.Starting -> { is ScreenRecordModel.Starting -> { logger.log( logger.log( TAG, TAG, LogLevel.INFO, LogLevel.INFO, { long1 = screenRecordState.millisUntilStarted }, { long1 = screenRecordState.millisUntilStarted }, { "State: Starting($long1)" } { "State: Starting($long1)" }, ) ) ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted) ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted) } } is ScreenRecordModel.Recording -> { is ScreenRecordModel.Recording -> { val recordedTask = val recordedTask = if ( if ( mediaProjectionState is MediaProjectionState.Projecting.SingleTask mediaProjectionState is MediaProjectionState.Projecting.SingleTask ) { ) { mediaProjectionState.task mediaProjectionState.task } else { } else { Loading @@ -79,12 +128,13 @@ constructor( TAG, TAG, LogLevel.INFO, LogLevel.INFO, { str1 = recordedTask?.baseIntent?.component?.packageName }, { str1 = recordedTask?.baseIntent?.component?.packageName }, { "State: Recording(taskPackage=$str1)" } { "State: Recording(taskPackage=$str1)" }, ) ) ScreenRecordChipModel.Recording(recordedTask) ScreenRecordChipModel.Recording(recordedTask) } } } } } } } .stateIn(scope, SharingStarted.WhileSubscribed(), ScreenRecordChipModel.DoingNothing) .stateIn(scope, SharingStarted.WhileSubscribed(), ScreenRecordChipModel.DoingNothing) /** Stops the recording. */ /** Stops the recording. */ Loading Loading
packages/SystemUI/aconfig/systemui.aconfig +11 −0 Original line number Original line Diff line number Diff line Loading @@ -413,6 +413,17 @@ flag { } } } } flag { name: "status_bar_auto_start_screen_record_chip" namespace: "systemui" description: "When screen recording, use the specified start time to update the screen record " "chip state instead of waiting for an official 'recording started' signal" bug: "366448907" metadata { purpose: PURPOSE_BUGFIX } } flag { flag { name: "status_bar_use_repos_for_call_chip" name: "status_bar_use_repos_for_call_chip" namespace: "systemui" namespace: "systemui" Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt +140 −3 Original line number Original line Diff line number Diff line Loading @@ -16,29 +16,35 @@ package com.android.systemui.statusbar.chips.screenrecord.domain.interactor package com.android.systemui.statusbar.chips.screenrecord.domain.interactor import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest import org.junit.runner.RunWith import org.junit.runner.RunWith @SmallTest @SmallTest @RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) class ScreenRecordChipInteractorTest : SysuiTestCase() { class ScreenRecordChipInteractorTest : SysuiTestCase() { private val kosmos = Kosmos().also { it.testCase = this } private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val testScope = kosmos.testScope private val screenRecordRepo = kosmos.screenRecordRepository private val screenRecordRepo = kosmos.screenRecordRepository private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository Loading Loading @@ -115,6 +121,137 @@ class ScreenRecordChipInteractorTest : SysuiTestCase() { assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task)) assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task)) } } @Test @DisableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_flagOff_doesNotAutomaticallySwitchToRecordingBasedOnTime() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) // WHEN screen record should start in 900ms screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) // WHEN 900ms has elapsed advanceTimeBy(901) // THEN we don't automatically update to the recording state if the flag is off assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) } @Test @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_flagOn_automaticallySwitchesToRecordingBasedOnTime() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) // WHEN screen record should start in 900ms screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) // WHEN 900ms has elapsed advanceTimeBy(901) // THEN we automatically update to the recording state assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = null)) } @Test @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_recordingBeginsEarly_switchesToRecording() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) // WHEN screen record should start in 900ms screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) // WHEN we update to the Recording state earlier than 900ms advanceTimeBy(800) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording val task = createTask(taskId = 1) mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.Projecting.SingleTask( "host.package", hostDeviceName = null, task, ) // THEN we immediately switch to Recording, and we have the task assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task)) // WHEN more than 900ms has elapsed advanceTimeBy(200) // THEN we still stay in the Recording state and we have the task assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task)) } @Test @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_secondRecording_doesNotAutomaticallyStart() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) // First recording starts, records, and stops screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) advanceTimeBy(900) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording advanceTimeBy(5000) screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing advanceTimeBy(10000) assertThat(latest).isEqualTo(ScreenRecordChipModel.DoingNothing) // WHEN a second recording is starting screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2900) // THEN we stay as starting and do not switch to Recording (verifying the auto-start // timer is reset) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(2900)) } @Test @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_startingButThenDoingNothing_doesNotAutomaticallyStart() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) // WHEN a screen recording is starting in 500ms screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(500) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(500)) // But it's cancelled after 300ms advanceTimeBy(300) screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing // THEN we don't automatically start the recording 200ms later advanceTimeBy(201) assertThat(latest).isEqualTo(ScreenRecordChipModel.DoingNothing) } @Test @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) fun screenRecordState_multipleStartingValues_autoStartResets() = testScope.runTest { val latest by collectLastValue(underTest.screenRecordState) screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2900) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(2900)) advanceTimeBy(2800) // WHEN there's 100ms left to go before auto-start, but then we get a new start time // that's in 500ms screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(500) // THEN we don't auto-start in 100ms advanceTimeBy(101) assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(500)) // THEN we *do* auto-start 400ms later advanceTimeBy(401) assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = null)) } @Test @Test fun stopRecording_sendsToRepo() = fun stopRecording_sendsToRepo() = testScope.runTest { testScope.runTest { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt +9 −29 Original line number Original line Diff line number Diff line Loading @@ -26,9 +26,8 @@ import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager Loading @@ -44,6 +43,7 @@ import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlin.test.Test Loading @@ -61,7 +61,7 @@ import org.mockito.kotlin.whenever @SmallTest @SmallTest @RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class) class ScreenRecordChipViewModelTest : SysuiTestCase() { class ScreenRecordChipViewModelTest : SysuiTestCase() { private val kosmos = Kosmos().also { it.testCase = this } private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val testScope = kosmos.testScope private val screenRecordRepo = kosmos.screenRecordRepository private val screenRecordRepo = kosmos.screenRecordRepository private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository Loading Loading @@ -254,7 +254,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { MediaProjectionState.Projecting.SingleTask( MediaProjectionState.Projecting.SingleTask( "host.package", "host.package", hostDeviceName = null, hostDeviceName = null, FakeActivityTaskManager.createTask(taskId = 1) FakeActivityTaskManager.createTask(taskId = 1), ) ) // THEN the start time is still the old start time // THEN the start time is still the old start time Loading @@ -275,12 +275,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { clickListener!!.onClick(chipView) clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) verify(kosmos.mockDialogTransitionAnimator) .showFromView( .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean()) eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean(), ) } } @Test @Test Loading @@ -297,12 +292,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { clickListener!!.onClick(chipView) clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) verify(kosmos.mockDialogTransitionAnimator) .showFromView( .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean()) eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean(), ) } } @Test @Test Loading @@ -314,7 +304,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { MediaProjectionState.Projecting.SingleTask( MediaProjectionState.Projecting.SingleTask( "host.package", "host.package", hostDeviceName = null, hostDeviceName = null, FakeActivityTaskManager.createTask(taskId = 1) FakeActivityTaskManager.createTask(taskId = 1), ) ) val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) Loading @@ -323,12 +313,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { clickListener!!.onClick(chipView) clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) verify(kosmos.mockDialogTransitionAnimator) .showFromView( .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean()) eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean(), ) } } @Test @Test Loading @@ -344,12 +329,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { val cujCaptor = argumentCaptor<DialogCuj>() val cujCaptor = argumentCaptor<DialogCuj>() verify(kosmos.mockDialogTransitionAnimator) verify(kosmos.mockDialogTransitionAnimator) .showFromView( .showFromView(any(), any(), cujCaptor.capture(), anyBoolean()) any(), any(), cujCaptor.capture(), anyBoolean(), ) assertThat(cujCaptor.firstValue.cujType) assertThat(cujCaptor.firstValue.cujType) .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +2 −2 Original line number Original line Diff line number Diff line Loading @@ -29,7 +29,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository Loading @@ -48,6 +47,7 @@ import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi Loading @@ -72,7 +72,7 @@ import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class) @DisableFlags(StatusBarNotifChips.FLAG_NAME) @DisableFlags(StatusBarNotifChips.FLAG_NAME) class OngoingActivityChipsViewModelTest : SysuiTestCase() { class OngoingActivityChipsViewModelTest : SysuiTestCase() { private val kosmos = Kosmos().also { it.testCase = this } private val kosmos = testKosmos() private val testScope = kosmos.testScope private val testScope = kosmos.testScope private val systemClock = kosmos.fakeSystemClock private val systemClock = kosmos.fakeSystemClock Loading
packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt +82 −32 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.chips.screenrecord.domain.interactor package com.android.systemui.statusbar.chips.screenrecord.domain.interactor import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBuffer Loading @@ -28,14 +29,19 @@ import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch /** Interactor for the screen recording chip shown in the status bar. */ /** Interactor for the screen recording chip shown in the status bar. */ @SysUISingleton @SysUISingleton @OptIn(ExperimentalCoroutinesApi::class) class ScreenRecordChipInteractor class ScreenRecordChipInteractor @Inject @Inject constructor( constructor( Loading @@ -44,6 +50,32 @@ constructor( private val mediaProjectionRepository: MediaProjectionRepository, private val mediaProjectionRepository: MediaProjectionRepository, @StatusBarChipsLog private val logger: LogBuffer, @StatusBarChipsLog private val logger: LogBuffer, ) { ) { /** * Emits true if we should assume that we're currently screen recording, even if * [ScreenRecordRepository.screenRecordState] hasn't emitted [ScreenRecordModel.Recording] yet. */ private val shouldAssumeIsRecording: Flow<Boolean> = screenRecordRepository.screenRecordState .transformLatest { when (it) { is ScreenRecordModel.DoingNothing -> { emit(false) } is ScreenRecordModel.Starting -> { // If we're told that the recording will start in [it.millisUntilStarted], // optimistically assume the recording did indeed start after that time even // if [ScreenRecordRepository.screenRecordState] hasn't emitted // [ScreenRecordModel.Recording] yet. Start 50ms early so that the chip // timer will definitely be showing by the time the recording actually // starts - see b/366448907. delay(it.millisUntilStarted - 50) emit(true) } is ScreenRecordModel.Recording -> {} } } .stateIn(scope, SharingStarted.WhileSubscribed(), false) val screenRecordState: StateFlow<ScreenRecordChipModel> = val screenRecordState: StateFlow<ScreenRecordChipModel> = // ScreenRecordRepository has the main "is the screen being recorded?" state, and // ScreenRecordRepository has the main "is the screen being recorded?" state, and // MediaProjectionRepository has information about what specifically is being recorded (a // MediaProjectionRepository has information about what specifically is being recorded (a Loading @@ -51,25 +83,42 @@ constructor( combine( combine( screenRecordRepository.screenRecordState, screenRecordRepository.screenRecordState, mediaProjectionRepository.mediaProjectionState, mediaProjectionRepository.mediaProjectionState, ) { screenRecordState, mediaProjectionState -> shouldAssumeIsRecording, ) { screenRecordState, mediaProjectionState, shouldAssumeIsRecording -> if ( Flags.statusBarAutoStartScreenRecordChip() && shouldAssumeIsRecording && screenRecordState is ScreenRecordModel.Starting ) { logger.log( TAG, LogLevel.INFO, {}, { "State: Recording(taskPackage=null) due to force-start" }, ) ScreenRecordChipModel.Recording(recordedTask = null) } else { when (screenRecordState) { when (screenRecordState) { is ScreenRecordModel.DoingNothing -> { is ScreenRecordModel.DoingNothing -> { logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" }) logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" }) ScreenRecordChipModel.DoingNothing ScreenRecordChipModel.DoingNothing } } is ScreenRecordModel.Starting -> { is ScreenRecordModel.Starting -> { logger.log( logger.log( TAG, TAG, LogLevel.INFO, LogLevel.INFO, { long1 = screenRecordState.millisUntilStarted }, { long1 = screenRecordState.millisUntilStarted }, { "State: Starting($long1)" } { "State: Starting($long1)" }, ) ) ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted) ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted) } } is ScreenRecordModel.Recording -> { is ScreenRecordModel.Recording -> { val recordedTask = val recordedTask = if ( if ( mediaProjectionState is MediaProjectionState.Projecting.SingleTask mediaProjectionState is MediaProjectionState.Projecting.SingleTask ) { ) { mediaProjectionState.task mediaProjectionState.task } else { } else { Loading @@ -79,12 +128,13 @@ constructor( TAG, TAG, LogLevel.INFO, LogLevel.INFO, { str1 = recordedTask?.baseIntent?.component?.packageName }, { str1 = recordedTask?.baseIntent?.component?.packageName }, { "State: Recording(taskPackage=$str1)" } { "State: Recording(taskPackage=$str1)" }, ) ) ScreenRecordChipModel.Recording(recordedTask) ScreenRecordChipModel.Recording(recordedTask) } } } } } } } .stateIn(scope, SharingStarted.WhileSubscribed(), ScreenRecordChipModel.DoingNothing) .stateIn(scope, SharingStarted.WhileSubscribed(), ScreenRecordChipModel.DoingNothing) /** Stops the recording. */ /** Stops the recording. */ Loading