diff options
| author | 2024-05-28 13:00:47 +0000 | |
|---|---|---|
| committer | 2024-05-28 13:00:47 +0000 | |
| commit | 96368b548fb4bd3a5f16c71cd9234db5529a2942 (patch) | |
| tree | 246362574b7715589caef1c72c2ef2486d132cb6 | |
| parent | 633f45097f5f465d3d552aa2a6c98b92b2619395 (diff) | |
| parent | 0f4134713cc63566414c867d20f6d0cddf5a9409 (diff) | |
Merge "Refactor communal scene logic to standalone interactor" into main
19 files changed, 286 insertions, 130 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt index 2d78a9b9d808..45e7d8a43c2a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt @@ -39,7 +39,7 @@ class CommunalRepositoryImplTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val underTest by lazy { - CommunalRepositoryImpl( + CommunalSceneRepositoryImpl( kosmos.applicationCoroutineScope, kosmos.sceneDataSource, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt index 5a7cbf6e02ca..6b896c68857f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt @@ -21,7 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase -import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository @@ -48,7 +48,7 @@ class CommunalInteractorCommunalDisabledTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private lateinit var communalRepository: FakeCommunalRepository + private lateinit var communalRepository: FakeCommunalSceneRepository private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var keyguardRepository: FakeKeyguardRepository diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 83227e1fc597..12389a3861bb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -39,7 +39,7 @@ import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository -import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository @@ -48,6 +48,7 @@ import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalWidgetContentModel @@ -111,7 +112,7 @@ class CommunalInteractorTest : SysuiTestCase() { private val testScope = kosmos.testScope private lateinit var tutorialRepository: FakeCommunalTutorialRepository - private lateinit var communalRepository: FakeCommunalRepository + private lateinit var communalRepository: FakeCommunalSceneRepository private lateinit var mediaRepository: FakeCommunalMediaRepository private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository @@ -508,30 +509,6 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun desiredScene_communalNotAvailable_returnsBlank() = - testScope.runTest { - kosmos.setCommunalAvailable(true) - runCurrent() - - val desiredScene by collectLastValue(underTest.desiredScene) - - underTest.changeScene(CommunalScenes.Communal) - assertThat(desiredScene).isEqualTo(CommunalScenes.Communal) - - kosmos.setCommunalAvailable(false) - runCurrent() - - // Scene returns blank when communal is not available. - assertThat(desiredScene).isEqualTo(CommunalScenes.Blank) - - kosmos.setCommunalAvailable(true) - runCurrent() - - // After re-enabling, scene goes back to Communal. - assertThat(desiredScene).isEqualTo(CommunalScenes.Communal) - } - - @Test fun transitionProgress_onTargetScene_fullProgress() = testScope.runTest { val targetScene = CommunalScenes.Blank @@ -545,7 +522,8 @@ class CommunalInteractorTest : SysuiTestCase() { underTest.setTransitionState(transitionState) // We're on the target scene. - assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene)) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Idle(targetScene)) } @Test @@ -563,7 +541,8 @@ class CommunalInteractorTest : SysuiTestCase() { underTest.setTransitionState(transitionState) // Transition progress is still idle, but we're not on the target scene. - assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene)) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Idle(currentScene)) } @Test @@ -581,7 +560,8 @@ class CommunalInteractorTest : SysuiTestCase() { underTest.setTransitionState(transitionState) // Progress starts at 0. - assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene)) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Idle(currentScene)) val progress = MutableStateFlow(0f) transitionState = @@ -599,16 +579,18 @@ class CommunalInteractorTest : SysuiTestCase() { // Partially transition. progress.value = .4f - assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(.4f)) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Transition(.4f)) // Transition is at full progress. progress.value = 1f - assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Transition(1f)) + assertThat(transitionProgress).isEqualTo(CommunalTransitionProgressModel.Transition(1f)) // Transition finishes. transitionState = MutableStateFlow(ObservableTransitionState.Idle(targetScene)) underTest.setTransitionState(transitionState) - assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene)) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Idle(targetScene)) } @Test @@ -626,7 +608,8 @@ class CommunalInteractorTest : SysuiTestCase() { underTest.setTransitionState(transitionState) // Progress starts at 0. - assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(currentScene)) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Idle(currentScene)) val progress = MutableStateFlow(0f) transitionState = @@ -646,16 +629,19 @@ class CommunalInteractorTest : SysuiTestCase() { progress.value = .4f // This is a transition we don't care about the progress of. - assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.OtherTransition) // Transition is at full progress. progress.value = 1f - assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.OtherTransition) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.OtherTransition) // Transition finishes. transitionState = MutableStateFlow(ObservableTransitionState.Idle(targetScene)) underTest.setTransitionState(transitionState) - assertThat(transitionProgress).isEqualTo(CommunalTransitionProgress.Idle(targetScene)) + assertThat(transitionProgress) + .isEqualTo(CommunalTransitionProgressModel.Idle(targetScene)) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index be44339bab8d..6b807754dead 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -28,7 +28,7 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository -import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository @@ -106,7 +106,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private lateinit var userRepository: FakeUserRepository private lateinit var shadeTestUtil: ShadeTestUtil private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository - private lateinit var communalRepository: FakeCommunalRepository + private lateinit var communalRepository: FakeCommunalSceneRepository private lateinit var underTest: CommunalViewModel diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt index eef23372cad2..933f0952dd09 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -45,7 +45,7 @@ import com.android.systemui.ambient.touch.scrim.ScrimController import com.android.systemui.ambient.touch.scrim.ScrimManager import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository -import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.communalInteractor @@ -79,7 +79,6 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.isNull -import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -156,7 +155,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { @Mock lateinit var mDreamOverlayCallbackController: DreamOverlayCallbackController private lateinit var bouncerRepository: FakeKeyguardBouncerRepository - private lateinit var communalRepository: FakeCommunalRepository + private lateinit var communalRepository: FakeCommunalSceneRepository @Captor var mViewCaptor: ArgumentCaptor<View>? = null private lateinit var mService: DreamOverlayService diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 20ffa3312fa6..33e2cac948e2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -26,7 +26,7 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags as AConfigFlags import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION import com.android.systemui.SysuiTestCase -import com.android.systemui.communal.data.repository.communalRepository +import com.android.systemui.communal.data.repository.communalSceneRepository import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository @@ -71,7 +71,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() private val testScope = kosmos.testScope private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } - private val communalRepository by lazy { kosmos.communalRepository } + private val communalRepository by lazy { kosmos.communalSceneRepository } private val screenOffAnimationController by lazy { kosmos.screenOffAnimationController } private val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository } private val notificationsKeyguardInteractor by lazy { kosmos.notificationsKeyguardInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt index 1de3459d8f7d..7f137f3b976b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt @@ -21,5 +21,5 @@ import dagger.Module @Module interface CommunalRepositoryModule { - @Binds fun communalRepository(impl: CommunalRepositoryImpl): CommunalRepository + @Binds fun communalRepository(impl: CommunalSceneRepositoryImpl): CommunalSceneRepository } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt index 8bfd8d91dfca..d6d08b4f1208 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt @@ -36,7 +36,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn /** Encapsulates the state of communal mode. */ -interface CommunalRepository { +interface CommunalSceneRepository { /** * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. */ @@ -48,6 +48,9 @@ interface CommunalRepository { /** Updates the requested scene. */ fun changeScene(toScene: SceneKey, transitionKey: TransitionKey? = null) + /** Immediately snaps to the desired scene. */ + fun snapToScene(toScene: SceneKey) + /** * Updates the transition state of the hub [SceneTransitionLayout]. * @@ -58,12 +61,12 @@ interface CommunalRepository { @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton -class CommunalRepositoryImpl +class CommunalSceneRepositoryImpl @Inject constructor( @Background backgroundScope: CoroutineScope, @Communal private val sceneDataSource: SceneDataSource, -) : CommunalRepository { +) : CommunalSceneRepository { override val currentScene: StateFlow<SceneKey> = sceneDataSource.currentScene @@ -82,6 +85,10 @@ constructor( sceneDataSource.changeScene(toScene, transitionKey) } + override fun snapToScene(toScene: SceneKey) { + sceneDataSource.snapToScene(toScene) + } + /** * Updates the transition state of the hub [SceneTransitionLayout]. * diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 9599a8864bcc..2be28caa71a9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -29,7 +29,6 @@ import com.android.compose.animation.scene.TransitionKey import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.communal.data.repository.CommunalMediaRepository import com.android.systemui.communal.data.repository.CommunalPrefsRepository -import com.android.systemui.communal.data.repository.CommunalRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent @@ -97,7 +96,6 @@ constructor( @Application val applicationScope: CoroutineScope, @Background val bgDispatcher: CoroutineDispatcher, broadcastDispatcher: BroadcastDispatcher, - private val communalRepository: CommunalRepository, private val widgetRepository: CommunalWidgetRepository, private val communalPrefsRepository: CommunalPrefsRepository, mediaRepository: CommunalMediaRepository, @@ -110,6 +108,7 @@ constructor( private val userTracker: UserTracker, private val activityStarter: ActivityStarter, private val userManager: UserManager, + private val communalSceneInteractor: CommunalSceneInteractor, sceneInteractor: SceneInteractor, @CommunalLog logBuffer: LogBuffer, @CommunalTableLog tableLogBuffer: TableLogBuffer, @@ -174,15 +173,19 @@ constructor( * * If [isCommunalAvailable] is false, will return [CommunalScenes.Blank] */ - val desiredScene: Flow<SceneKey> = - communalRepository.currentScene.combine(isCommunalAvailable) { scene, available -> - if (available) scene else CommunalScenes.Blank - } + @Deprecated( + "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead" + ) + val desiredScene: Flow<SceneKey> = communalSceneInteractor.currentScene /** Transition state of the hub mode. */ - val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState + @Deprecated( + "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead" + ) + val transitionState: StateFlow<ObservableTransitionState> = + communalSceneInteractor.transitionState - val _userActivity: MutableSharedFlow<Unit> = + private val _userActivity: MutableSharedFlow<Unit> = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) val userActivity: Flow<Unit> = _userActivity.asSharedFlow() @@ -212,32 +215,18 @@ constructor( * * Note that you must call is with `null` when the UI is done or risk a memory leak. */ - fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { - communalRepository.setTransitionState(transitionState) - } + @Deprecated( + "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead" + ) + fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) = + communalSceneInteractor.setTransitionState(transitionState) /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */ + @Deprecated( + "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead" + ) fun transitionProgressToScene(targetScene: SceneKey) = - transitionState - .flatMapLatest { state -> - when (state) { - is ObservableTransitionState.Idle -> - flowOf(CommunalTransitionProgress.Idle(state.currentScene)) - is ObservableTransitionState.Transition -> - if (state.toScene == targetScene) { - state.progress.map { - CommunalTransitionProgress.Transition( - // Clamp the progress values between 0 and 1 as actual progress - // values can be higher than 0 or lower than 1 due to a fling. - progress = it.coerceIn(0.0f, 1.0f) - ) - } - } else { - flowOf(CommunalTransitionProgress.OtherTransition) - } - } - } - .distinctUntilChanged() + communalSceneInteractor.transitionProgressToScene(targetScene) /** * Flow that emits a boolean if the communal UI is the target scene, ie. the [desiredScene] is @@ -283,34 +272,30 @@ constructor( * This will not be true while transitioning to the hub and will turn false immediately when a * swipe to exit the hub starts. */ - val isIdleOnCommunal: StateFlow<Boolean> = - communalRepository.transitionState - .map { - it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Communal - } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = false, - ) + @Deprecated( + "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead" + ) + val isIdleOnCommunal: StateFlow<Boolean> = communalSceneInteractor.isIdleOnCommunal /** * Flow that emits a boolean if any portion of the communal UI is visible at all. * * This flow will be true during any transition and when idle on the communal scene. */ - val isCommunalVisible: Flow<Boolean> = - communalRepository.transitionState.map { - !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank) - } + @Deprecated( + "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead" + ) + val isCommunalVisible: Flow<Boolean> = communalSceneInteractor.isCommunalVisible /** * Asks for an asynchronous scene witch to [newScene], which will use the corresponding * installed transition or the one specified by [transitionKey], if provided. */ - fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) { - communalRepository.changeScene(newScene, transitionKey) - } + @Deprecated( + "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead" + ) + fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) = + communalSceneInteractor.changeScene(newScene, transitionKey) fun setEditModeOpen(isOpen: Boolean) { _editModeOpen.value = isOpen @@ -579,17 +564,3 @@ constructor( } } } - -/** Simplified transition progress data class for tracking a single transition between scenes. */ -sealed class CommunalTransitionProgress { - /** No transition/animation is currently running. */ - data class Idle(val scene: SceneKey) : CommunalTransitionProgress() - - /** There is a transition animating to the expected scene. */ - data class Transition( - val progress: Float, - ) : CommunalTransitionProgress() - - /** There is a transition animating to a scene other than the expected scene. */ - data object OtherTransition : CommunalTransitionProgress() -} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt new file mode 100644 index 000000000000..5cfe9798420d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -0,0 +1,127 @@ +/* + * 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.communal.domain.interactor + +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey +import com.android.systemui.communal.data.repository.CommunalSceneRepository +import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class CommunalSceneInteractor +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val communalSceneRepository: CommunalSceneRepository, +) { + /** + * Asks for an asynchronous scene witch to [newScene], which will use the corresponding + * installed transition or the one specified by [transitionKey], if provided. + */ + fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) { + communalSceneRepository.changeScene(newScene, transitionKey) + } + + /** Immediately snaps to the new scene. */ + fun snapToScene(newScene: SceneKey) { + communalSceneRepository.snapToScene(newScene) + } + + /** + * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. + */ + val currentScene: Flow<SceneKey> = communalSceneRepository.currentScene + + /** Transition state of the hub mode. */ + val transitionState: StateFlow<ObservableTransitionState> = + communalSceneRepository.transitionState + + /** + * Updates the transition state of the hub [SceneTransitionLayout]. + * + * Note that you must call is with `null` when the UI is done or risk a memory leak. + */ + fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { + communalSceneRepository.setTransitionState(transitionState) + } + + /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */ + fun transitionProgressToScene(targetScene: SceneKey) = + transitionState + .flatMapLatest { state -> + when (state) { + is ObservableTransitionState.Idle -> + flowOf(CommunalTransitionProgressModel.Idle(state.currentScene)) + is ObservableTransitionState.Transition -> + if (state.toScene == targetScene) { + state.progress.map { + CommunalTransitionProgressModel.Transition( + // Clamp the progress values between 0 and 1 as actual progress + // values can be higher than 0 or lower than 1 due to a fling. + progress = it.coerceIn(0.0f, 1.0f) + ) + } + } else { + flowOf(CommunalTransitionProgressModel.OtherTransition) + } + } + } + .distinctUntilChanged() + + /** + * Flow that emits a boolean if the communal UI is fully visible and not in transition. + * + * This will not be true while transitioning to the hub and will turn false immediately when a + * swipe to exit the hub starts. + */ + val isIdleOnCommunal: StateFlow<Boolean> = + transitionState + .map { + it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Communal + } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = false, + ) + + /** + * Flow that emits a boolean if any portion of the communal UI is visible at all. + * + * This flow will be true during any transition and when idle on the communal scene. + */ + val isCommunalVisible: Flow<Boolean> = + transitionState.map { + !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalTransitionProgressModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalTransitionProgressModel.kt new file mode 100644 index 000000000000..e3187c2d7e27 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalTransitionProgressModel.kt @@ -0,0 +1,33 @@ +/* + * 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.communal.domain.model + +import com.android.compose.animation.scene.SceneKey + +/** Simplified transition progress data class for tracking a single transition between scenes. */ +sealed interface CommunalTransitionProgressModel { + /** No transition/animation is currently running. */ + data class Idle(val scene: SceneKey) : CommunalTransitionProgressModel + + /** There is a transition animating to the expected scene. */ + data class Transition( + val progress: Float, + ) : CommunalTransitionProgressModel + + /** There is a transition animating to a scene other than the expected scene. */ + data object OtherTransition : CommunalTransitionProgressModel +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt index a3c61a413639..73cfb5286e1c 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt @@ -26,4 +26,6 @@ import com.android.compose.animation.scene.TransitionKey object CommunalTransitionKeys { /** Fades the glanceable hub without any translation */ val SimpleFade = TransitionKey("SimpleFade") + /** Immediately transitions without any delay */ + val Immediately = TransitionKey("Immediately") } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt index fcf67d519cae..af1ce2bfcdde 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress +import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -71,7 +71,7 @@ constructor( if (id == null) { // No transition started. if ( - transitionProgress is CommunalTransitionProgress.Transition && + transitionProgress is CommunalTransitionProgressModel.Transition && lastStartedState == fromState ) { transitionId = @@ -93,7 +93,7 @@ constructor( val nextState: TransitionState val progressFraction: Float when (transitionProgress) { - is CommunalTransitionProgress.Idle -> { + is CommunalTransitionProgressModel.Idle -> { if (transitionProgress.scene == toScene) { nextState = TransitionState.FINISHED progressFraction = 1f @@ -102,11 +102,11 @@ constructor( progressFraction = 0f } } - is CommunalTransitionProgress.Transition -> { + is CommunalTransitionProgressModel.Transition -> { nextState = TransitionState.RUNNING progressFraction = transitionProgress.progress } - is CommunalTransitionProgress.OtherTransition -> { + is CommunalTransitionProgressModel.OtherTransition -> { // Shouldn't happen but if another transition starts during the // current one, mark the current one as canceled. nextState = TransitionState.CANCELED diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 49a467e152ec..1ef44f74e8b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -35,7 +35,7 @@ import com.android.systemui.ambient.touch.TouchHandler import com.android.systemui.ambient.touch.TouchMonitor import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository -import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable @@ -92,7 +92,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { private lateinit var containerView: View private lateinit var testableLooper: TestableLooper - private lateinit var communalRepository: FakeCommunalRepository + private lateinit var communalRepository: FakeCommunalSceneRepository private lateinit var underTest: GlanceableHubContainerController @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index 0906d8eadf44..9f752a89b16d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -22,7 +22,7 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule -import com.android.systemui.communal.data.repository.communalRepository +import com.android.systemui.communal.data.repository.communalSceneRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dump.DumpManager @@ -181,7 +181,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(CommunalScenes.Communal) ) - kosmos.communalRepository.setTransitionState(transitionState) + kosmos.communalSceneRepository.setTransitionState(transitionState) runCurrent() setDozeAmount(0f) verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f) @@ -195,7 +195,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(CommunalScenes.Communal) ) - kosmos.communalRepository.setTransitionState(transitionState) + kosmos.communalSceneRepository.setTransitionState(transitionState) runCurrent() setDozeAmount(0f) verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt index 482d60ce8adf..c7955c3e244b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt @@ -21,7 +21,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope val Kosmos.fakeCommunalRepository by Fixture { - FakeCommunalRepository(applicationScope = applicationCoroutineScope) + FakeCommunalSceneRepository(applicationScope = applicationCoroutineScope) } -val Kosmos.communalRepository by Fixture<CommunalRepository> { fakeCommunalRepository } +val Kosmos.communalSceneRepository by Fixture<CommunalSceneRepository> { fakeCommunalRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt index d958bae7ae2a..a7bf87def5be 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt @@ -14,14 +14,17 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn -/** Fake implementation of [CommunalRepository]. */ +/** Fake implementation of [CommunalSceneRepository]. */ @OptIn(ExperimentalCoroutinesApi::class) -class FakeCommunalRepository( +class FakeCommunalSceneRepository( applicationScope: CoroutineScope, override val currentScene: MutableStateFlow<SceneKey> = MutableStateFlow(CommunalScenes.Default), -) : CommunalRepository { - override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) { +) : CommunalSceneRepository { + override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = + snapToScene(toScene) + + override fun snapToScene(toScene: SceneKey) { this.currentScene.value = toScene this._transitionState.value = flowOf(ObservableTransitionState.Idle(toScene)) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 3fe6973d36df..1583d1c5d83f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -20,7 +20,6 @@ import android.os.userManager import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.repository.communalMediaRepository import com.android.systemui.communal.data.repository.communalPrefsRepository -import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.flags.Flags @@ -45,7 +44,7 @@ val Kosmos.communalInteractor by Fixture { applicationScope = applicationCoroutineScope, bgDispatcher = testDispatcher, broadcastDispatcher = broadcastDispatcher, - communalRepository = communalRepository, + communalSceneInteractor = communalSceneInteractor, widgetRepository = communalWidgetRepository, communalPrefsRepository = communalPrefsRepository, mediaRepository = communalMediaRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt new file mode 100644 index 000000000000..ee48c105a987 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt @@ -0,0 +1,29 @@ +/* + * 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.communal.domain.interactor + +import com.android.systemui.communal.data.repository.communalSceneRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope + +val Kosmos.communalSceneInteractor: CommunalSceneInteractor by + Kosmos.Fixture { + CommunalSceneInteractor( + applicationScope = applicationCoroutineScope, + communalSceneRepository = communalSceneRepository, + ) + } |