Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +44 −24 Original line number Diff line number Diff line Loading @@ -85,6 +85,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.pointer.motionEventSpy import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.boundsInWindow Loading Loading @@ -183,27 +185,37 @@ fun CommunalHub( gridCoordinates ) { detectLongPressGesture { offset -> // Deduct both grid offset relative to its container and content offset. // Deduct both grid offset relative to its container and content // offset. val adjustedOffset = gridCoordinates?.let { offset - it.positionInWindow() - contentOffset } val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) } val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) } // Display the button only when the gesture initiates from widgets, // the CTA tile, or an empty area on the screen. UMO/smartspace have // their own long-press handlers. To prevent user confusion, we should // their own long-press handlers. To prevent user confusion, we // should // not display this button. if ( index == null || communalContent[index].isWidgetContent() || communalContent[index] is CommunalContentModel.CtaTileInViewMode communalContent[index] is CommunalContentModel.CtaTileInViewMode ) { isButtonToEditWidgetsShowing = true } val key = index?.let { keyAtIndexIfEditable(communalContent, index) } val key = index?.let { keyAtIndexIfEditable(communalContent, index) } viewModel.setSelectedKey(key) } } .onPreviewKeyEvent { onKeyEvent(viewModel) false } .motionEventSpy { onMotionEvent(viewModel) } }, ) { CommunalHubLazyGrid( Loading Loading @@ -311,6 +323,14 @@ fun CommunalHub( } } private fun onKeyEvent(viewModel: BaseCommunalViewModel) { viewModel.signalUserInteraction() } private fun onMotionEvent(viewModel: BaseCommunalViewModel) { viewModel.signalUserInteraction() } @Composable private fun ScrollOnNewSmartspaceEffect( viewModel: BaseCommunalViewModel, Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +102 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.communal import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase Loading @@ -25,13 +26,17 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dock.dockManager import com.android.systemui.dock.fakeDockManager import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope Loading @@ -54,11 +59,15 @@ class CommunalSceneStartableTest : SysuiTestCase() { @Before fun setUp() { with(kosmos) { fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT) underTest = CommunalSceneStartable( dockManager = dockManager, communalInteractor = communalInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, keyguardInteractor = keyguardInteractor, systemSettings = fakeSettings, applicationScope = applicationCoroutineScope, bgScope = applicationCoroutineScope, ) Loading Loading @@ -246,6 +255,95 @@ class CommunalSceneStartableTest : SysuiTestCase() { } } @Test fun hubTimeout_whenDreaming() = with(kosmos) { testScope.runTest { // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) communalInteractor.onSceneChanged(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Scene times out back to blank after the screen timeout. advanceTimeBy(SCREEN_TIMEOUT.milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Blank) } } @Test fun hubTimeout_dreamStopped() = with(kosmos) { testScope.runTest { // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) communalInteractor.onSceneChanged(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Wait a bit, but not long enough to timeout. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Dream stops, timeout is cancelled and device stays on hub, because the regular // screen timeout will take effect at this point. fakeKeyguardRepository.setDreaming(false) advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) } } @Test fun hubTimeout_userActivityTriggered_resetsTimeout() = with(kosmos) { testScope.runTest { // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) communalInteractor.onSceneChanged(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Wait a bit, but not long enough to timeout. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) // Send user interaction to reset timeout. communalInteractor.signalUserInteraction() // If user activity didn't reset timeout, we would have gone back to Blank by now. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Timeout happens one interval after the user interaction. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Blank) } } @Test fun hubTimeout_screenTimeoutChanged() = with(kosmos) { testScope.runTest { fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) communalInteractor.onSceneChanged(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Scene times out back to blank after the screen timeout. advanceTimeBy(SCREEN_TIMEOUT.milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) advanceTimeBy(SCREEN_TIMEOUT.milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Blank) } } private fun TestScope.updateDocked(docked: Boolean) = with(kosmos) { runCurrent() Loading @@ -260,4 +358,8 @@ class CommunalSceneStartableTest : SysuiTestCase() { setCommunalAvailable(true) runCurrent() } companion object { private const val SCREEN_TIMEOUT = 1000 } } packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +52 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.communal import android.provider.Settings import com.android.compose.animation.scene.SceneKey import com.android.systemui.CoreStartable import com.android.systemui.communal.domain.interactor.CommunalInteractor Loading @@ -24,25 +25,32 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dock.DockManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.util.kotlin.emitOnStart import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import com.android.systemui.util.settings.SystemSettings import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch /** * A [CoreStartable] responsible for automatically navigating between communal scenes when certain * conditions are met. */ @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class CommunalSceneStartable @Inject Loading @@ -50,9 +58,13 @@ constructor( private val dockManager: DockManager, private val communalInteractor: CommunalInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardInteractor: KeyguardInteractor, private val systemSettings: SystemSettings, @Application private val applicationScope: CoroutineScope, @Background private val bgScope: CoroutineScope, ) : CoreStartable { private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT override fun start() { // Handle automatically switching based on keyguard state. keyguardTransitionInteractor.startedKeyguardTransitionStep Loading @@ -78,6 +90,43 @@ constructor( // } // } // .launchIn(bgScope) systemSettings .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT) // Read the setting value on start. .emitOnStart() .onEach { screenTimeout = systemSettings.getInt( Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_TIMEOUT ) } .launchIn(bgScope) // Handle timing out back to the dream. bgScope.launch { combine( communalInteractor.desiredScene, keyguardInteractor.isDreaming, // Emit a value on start so the combine starts. communalInteractor.userActivity.emitOnStart() ) { scene, isDreaming, _ -> // Time out should run whenever we're dreaming and the hub is open, even if not // docked. scene == CommunalScenes.Communal && isDreaming } // collectLatest cancels the previous action block when new values arrive, so any // already running timeout gets cancelled when conditions change or user interaction // is detected. .collectLatest { shouldTimeout -> if (!shouldTimeout) { return@collectLatest } delay(screenTimeout.milliseconds) communalInteractor.onSceneChanged(CommunalScenes.Blank) } } } private suspend fun determineSceneAfterTransition( Loading Loading @@ -105,5 +154,6 @@ constructor( companion object { val AWAKE_DEBOUNCE_DELAY = 5.seconds val DOCK_DEBOUNCE_DELAY = 1.seconds val DEFAULT_SCREEN_TIMEOUT = 15000 } } packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +12 −1 Original line number Diff line number Diff line Loading @@ -63,10 +63,13 @@ import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged Loading @@ -84,7 +87,7 @@ import kotlinx.coroutines.flow.shareIn class CommunalInteractor @Inject constructor( @Application applicationScope: CoroutineScope, @Application val applicationScope: CoroutineScope, broadcastDispatcher: BroadcastDispatcher, private val communalRepository: CommunalRepository, private val widgetRepository: CommunalWidgetRepository, Loading Loading @@ -152,6 +155,14 @@ constructor( /** Transition state of the hub mode. */ val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState val _userActivity: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) val userActivity: Flow<Unit> = _userActivity.asSharedFlow() fun signalUserInteraction() { _userActivity.tryEmit(Unit) } /** * Updates the transition state of the hub [SceneTransitionLayout]. * Loading packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +4 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,10 @@ abstract class BaseCommunalViewModel( val selectedKey: StateFlow<String?> get() = _selectedKey fun signalUserInteraction() { communalInteractor.signalUserInteraction() } fun onSceneChanged(scene: SceneKey) { communalInteractor.onSceneChanged(scene) } Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +44 −24 Original line number Diff line number Diff line Loading @@ -85,6 +85,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.pointer.motionEventSpy import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.boundsInWindow Loading Loading @@ -183,27 +185,37 @@ fun CommunalHub( gridCoordinates ) { detectLongPressGesture { offset -> // Deduct both grid offset relative to its container and content offset. // Deduct both grid offset relative to its container and content // offset. val adjustedOffset = gridCoordinates?.let { offset - it.positionInWindow() - contentOffset } val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) } val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) } // Display the button only when the gesture initiates from widgets, // the CTA tile, or an empty area on the screen. UMO/smartspace have // their own long-press handlers. To prevent user confusion, we should // their own long-press handlers. To prevent user confusion, we // should // not display this button. if ( index == null || communalContent[index].isWidgetContent() || communalContent[index] is CommunalContentModel.CtaTileInViewMode communalContent[index] is CommunalContentModel.CtaTileInViewMode ) { isButtonToEditWidgetsShowing = true } val key = index?.let { keyAtIndexIfEditable(communalContent, index) } val key = index?.let { keyAtIndexIfEditable(communalContent, index) } viewModel.setSelectedKey(key) } } .onPreviewKeyEvent { onKeyEvent(viewModel) false } .motionEventSpy { onMotionEvent(viewModel) } }, ) { CommunalHubLazyGrid( Loading Loading @@ -311,6 +323,14 @@ fun CommunalHub( } } private fun onKeyEvent(viewModel: BaseCommunalViewModel) { viewModel.signalUserInteraction() } private fun onMotionEvent(viewModel: BaseCommunalViewModel) { viewModel.signalUserInteraction() } @Composable private fun ScrollOnNewSmartspaceEffect( viewModel: BaseCommunalViewModel, Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +102 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.communal import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase Loading @@ -25,13 +26,17 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dock.dockManager import com.android.systemui.dock.fakeDockManager import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope Loading @@ -54,11 +59,15 @@ class CommunalSceneStartableTest : SysuiTestCase() { @Before fun setUp() { with(kosmos) { fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT) underTest = CommunalSceneStartable( dockManager = dockManager, communalInteractor = communalInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, keyguardInteractor = keyguardInteractor, systemSettings = fakeSettings, applicationScope = applicationCoroutineScope, bgScope = applicationCoroutineScope, ) Loading Loading @@ -246,6 +255,95 @@ class CommunalSceneStartableTest : SysuiTestCase() { } } @Test fun hubTimeout_whenDreaming() = with(kosmos) { testScope.runTest { // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) communalInteractor.onSceneChanged(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Scene times out back to blank after the screen timeout. advanceTimeBy(SCREEN_TIMEOUT.milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Blank) } } @Test fun hubTimeout_dreamStopped() = with(kosmos) { testScope.runTest { // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) communalInteractor.onSceneChanged(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Wait a bit, but not long enough to timeout. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Dream stops, timeout is cancelled and device stays on hub, because the regular // screen timeout will take effect at this point. fakeKeyguardRepository.setDreaming(false) advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) } } @Test fun hubTimeout_userActivityTriggered_resetsTimeout() = with(kosmos) { testScope.runTest { // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) communalInteractor.onSceneChanged(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Wait a bit, but not long enough to timeout. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) // Send user interaction to reset timeout. communalInteractor.signalUserInteraction() // If user activity didn't reset timeout, we would have gone back to Blank by now. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Timeout happens one interval after the user interaction. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Blank) } } @Test fun hubTimeout_screenTimeoutChanged() = with(kosmos) { testScope.runTest { fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) communalInteractor.onSceneChanged(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Scene times out back to blank after the screen timeout. advanceTimeBy(SCREEN_TIMEOUT.milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) advanceTimeBy(SCREEN_TIMEOUT.milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Blank) } } private fun TestScope.updateDocked(docked: Boolean) = with(kosmos) { runCurrent() Loading @@ -260,4 +358,8 @@ class CommunalSceneStartableTest : SysuiTestCase() { setCommunalAvailable(true) runCurrent() } companion object { private const val SCREEN_TIMEOUT = 1000 } }
packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +52 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.communal import android.provider.Settings import com.android.compose.animation.scene.SceneKey import com.android.systemui.CoreStartable import com.android.systemui.communal.domain.interactor.CommunalInteractor Loading @@ -24,25 +25,32 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dock.DockManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.util.kotlin.emitOnStart import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import com.android.systemui.util.settings.SystemSettings import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch /** * A [CoreStartable] responsible for automatically navigating between communal scenes when certain * conditions are met. */ @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class CommunalSceneStartable @Inject Loading @@ -50,9 +58,13 @@ constructor( private val dockManager: DockManager, private val communalInteractor: CommunalInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardInteractor: KeyguardInteractor, private val systemSettings: SystemSettings, @Application private val applicationScope: CoroutineScope, @Background private val bgScope: CoroutineScope, ) : CoreStartable { private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT override fun start() { // Handle automatically switching based on keyguard state. keyguardTransitionInteractor.startedKeyguardTransitionStep Loading @@ -78,6 +90,43 @@ constructor( // } // } // .launchIn(bgScope) systemSettings .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT) // Read the setting value on start. .emitOnStart() .onEach { screenTimeout = systemSettings.getInt( Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_TIMEOUT ) } .launchIn(bgScope) // Handle timing out back to the dream. bgScope.launch { combine( communalInteractor.desiredScene, keyguardInteractor.isDreaming, // Emit a value on start so the combine starts. communalInteractor.userActivity.emitOnStart() ) { scene, isDreaming, _ -> // Time out should run whenever we're dreaming and the hub is open, even if not // docked. scene == CommunalScenes.Communal && isDreaming } // collectLatest cancels the previous action block when new values arrive, so any // already running timeout gets cancelled when conditions change or user interaction // is detected. .collectLatest { shouldTimeout -> if (!shouldTimeout) { return@collectLatest } delay(screenTimeout.milliseconds) communalInteractor.onSceneChanged(CommunalScenes.Blank) } } } private suspend fun determineSceneAfterTransition( Loading Loading @@ -105,5 +154,6 @@ constructor( companion object { val AWAKE_DEBOUNCE_DELAY = 5.seconds val DOCK_DEBOUNCE_DELAY = 1.seconds val DEFAULT_SCREEN_TIMEOUT = 15000 } }
packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +12 −1 Original line number Diff line number Diff line Loading @@ -63,10 +63,13 @@ import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged Loading @@ -84,7 +87,7 @@ import kotlinx.coroutines.flow.shareIn class CommunalInteractor @Inject constructor( @Application applicationScope: CoroutineScope, @Application val applicationScope: CoroutineScope, broadcastDispatcher: BroadcastDispatcher, private val communalRepository: CommunalRepository, private val widgetRepository: CommunalWidgetRepository, Loading Loading @@ -152,6 +155,14 @@ constructor( /** Transition state of the hub mode. */ val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState val _userActivity: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) val userActivity: Flow<Unit> = _userActivity.asSharedFlow() fun signalUserInteraction() { _userActivity.tryEmit(Unit) } /** * Updates the transition state of the hub [SceneTransitionLayout]. * Loading
packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +4 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,10 @@ abstract class BaseCommunalViewModel( val selectedKey: StateFlow<String?> get() = _selectedKey fun signalUserInteraction() { communalInteractor.signalUserInteraction() } fun onSceneChanged(scene: SceneKey) { communalInteractor.onSceneChanged(scene) } Loading