diff options
17 files changed, 1508 insertions, 663 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt index 7a803867d4a4..53eccfdf70d5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt @@ -17,6 +17,8 @@ package com.android.systemui.shade import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl import dagger.Binds import dagger.Module @@ -30,4 +32,8 @@ abstract class ShadeEmptyImplModule { @Binds @SysUISingleton abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController + + @Binds + @SysUISingleton + abstract fun bindsShadeInteractor(si: ShadeInteractorEmptyImpl): ShadeInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index 89aaaafbcdf3..54467cffa401 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -17,13 +17,40 @@ package com.android.systemui.shade import com.android.systemui.dagger.SysUISingleton - +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.shade.domain.interactor.BaseShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl +import com.android.systemui.shade.domain.interactor.ShadeInteractorSceneContainerImpl import dagger.Binds import dagger.Module +import dagger.Provides +import javax.inject.Provider /** Module for classes related to the notification shade. */ @Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class]) abstract class ShadeModule { + companion object { + @Provides + @SysUISingleton + fun provideBaseShadeInteractor( + sceneContainerFlags: SceneContainerFlags, + sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>, + sceneContainerOff: Provider<ShadeInteractorLegacyImpl> + ): BaseShadeInteractor { + return if (sceneContainerFlags.isEnabled()) { + sceneContainerOn.get() + } else { + sceneContainerOff.get() + } + } + } + + @Binds + @SysUISingleton + abstract fun bindsShadeInteractor(si: ShadeInteractorImpl): ShadeInteractor + @Binds @SysUISingleton abstract fun bindsShadeViewController( diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index d687ef64aec4..5f7b07760a5b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -16,149 +16,39 @@ package com.android.systemui.shade.domain.interactor -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.data.repository.KeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.DozeStateModel -import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.StatusBarState -import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.shade.data.repository.ShadeRepository -import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository -import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor -import com.android.systemui.statusbar.phone.DozeParameters -import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository -import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository -import com.android.systemui.user.domain.interactor.UserSwitcherInteractor -import javax.inject.Inject -import javax.inject.Provider import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.isActive /** Business logic for shade interactions. */ -@OptIn(ExperimentalCoroutinesApi::class) -@SysUISingleton -class ShadeInteractor -@Inject -constructor( - @Application scope: CoroutineScope, - deviceProvisioningRepository: DeviceProvisioningRepository, - disableFlagsRepository: DisableFlagsRepository, - dozeParams: DozeParameters, - sceneContainerFlags: SceneContainerFlags, - // TODO(b/300258424) convert to direct reference instead of provider - sceneInteractorProvider: Provider<SceneInteractor>, - keyguardRepository: KeyguardRepository, - keyguardTransitionInteractor: KeyguardTransitionInteractor, - powerInteractor: PowerInteractor, - userSetupRepository: UserSetupRepository, - userSwitcherInteractor: UserSwitcherInteractor, - sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, - private val repository: ShadeRepository, -) { +interface ShadeInteractor : BaseShadeInteractor { /** Emits true if the shade is currently allowed and false otherwise. */ - val isShadeEnabled: StateFlow<Boolean> = - disableFlagsRepository.disableFlags - .map { it.isShadeEnabled() } - .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + val isShadeEnabled: StateFlow<Boolean> - /** - * Whether split shade, the combined notifications and quick settings shade used for large - * screens, is enabled. - */ - val isSplitShadeEnabled: Flow<Boolean> = - sharedNotificationContainerInteractor.configurationBasedDimensions - .map { dimens -> dimens.useSplitShade } - .distinctUntilChanged() - - /** The amount [0-1] that the shade has been opened */ - val shadeExpansion: Flow<Float> = - if (sceneContainerFlags.isEnabled()) { - sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.Shade) - } else { - combine( - repository.lockscreenShadeExpansion, - keyguardRepository.statusBarState, - repository.legacyShadeExpansion, - repository.qsExpansion, - isSplitShadeEnabled - ) { - lockscreenShadeExpansion, - statusBarState, - legacyShadeExpansion, - qsExpansion, - splitShadeEnabled -> - when (statusBarState) { - // legacyShadeExpansion is 1 instead of 0 when QS is expanded - StatusBarState.SHADE -> - if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion - StatusBarState.KEYGUARD -> lockscreenShadeExpansion - // dragDownAmount, which drives lockscreenShadeExpansion resets to 0f when - // the pointer is lifted and the lockscreen shade is fully expanded - StatusBarState.SHADE_LOCKED -> 1f - } - } - .distinctUntilChanged() - } + /** Whether either the shade or QS is fully expanded. */ + val isAnyFullyExpanded: Flow<Boolean> /** - * The amount [0-1] QS has been opened. Normal shade with notifications (QQS) visible will - * report 0f. If split shade is enabled, value matches shadeExpansion. + * Whether the user is expanding or collapsing either the shade or quick settings with user + * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended + * but a transition they initiated is still animating. */ - val qsExpansion: StateFlow<Float> = - if (sceneContainerFlags.isEnabled()) { - val qsExp = sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.QuickSettings) - combine(isSplitShadeEnabled, shadeExpansion, qsExp) { - isSplitShadeEnabled, - shadeExp, - qsExp -> - if (isSplitShadeEnabled) { - shadeExp - } else { - qsExp - } - } - .stateIn(scope, SharingStarted.Eagerly, 0f) - } else { - repository.qsExpansion - } + val isUserInteracting: Flow<Boolean> - /** Whether Quick Settings is expanded a non-zero amount. */ - val isQsExpanded: StateFlow<Boolean> = - if (sceneContainerFlags.isEnabled()) { - qsExpansion - .map { it > 0 } - .distinctUntilChanged() - .stateIn(scope, SharingStarted.Eagerly, false) - } else { - repository.legacyIsQsExpanded - } + /** Are touches allowed on the notification panel? */ + val isShadeTouchable: Flow<Boolean> - /** The amount [0-1] either QS or the shade has been opened. */ - val anyExpansion: StateFlow<Float> = - combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) } - .stateIn(scope, SharingStarted.Eagerly, 0f) + /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ + val isExpandToQsEnabled: Flow<Boolean> +} - /** Whether either the shade or QS is fully expanded. */ - val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged() +/** ShadeInteractor methods with implementations that differ between non-empty impls. */ +interface BaseShadeInteractor { + /** The amount [0-1] either QS or the shade has been opened. */ + val anyExpansion: StateFlow<Float> /** * Whether either the shade or QS is partially or fully expanded, i.e. not fully collapsed. At @@ -169,149 +59,39 @@ constructor( * * TODO(b/300258424) remove all but the first sentence of this comment */ - val isAnyExpanded: StateFlow<Boolean> = - if (sceneContainerFlags.isEnabled()) { - anyExpansion.map { it > 0f }.distinctUntilChanged() - } else { - repository.legacyExpandedOrAwaitingInputTransfer - } - .stateIn(scope, SharingStarted.Eagerly, false) + val isAnyExpanded: StateFlow<Boolean> + + /** The amount [0-1] that the shade has been opened */ + val shadeExpansion: Flow<Float> + + /** + * The amount [0-1] QS has been opened. Normal shade with notifications (QQS) visible will + * report 0f. If split shade is enabled, value matches shadeExpansion. + */ + val qsExpansion: StateFlow<Float> + + /** Whether Quick Settings is expanded a non-zero amount. */ + val isQsExpanded: StateFlow<Boolean> /** * Whether the user is expanding or collapsing the shade with user input. This will be true even * if the user's input gesture has ended but a transition they initiated is animating. */ - val isUserInteractingWithShade: Flow<Boolean> = - if (sceneContainerFlags.isEnabled()) { - sceneBasedInteracting(sceneInteractorProvider.get(), SceneKey.Shade) - } else { - combine( - userInteractingFlow( - repository.legacyShadeTracking, - repository.legacyShadeExpansion - ), - repository.legacyLockscreenShadeTracking - ) { legacyShadeTracking, legacyLockscreenShadeTracking -> - legacyShadeTracking || legacyLockscreenShadeTracking - } - } + val isUserInteractingWithShade: Flow<Boolean> /** * Whether the user is expanding or collapsing quick settings with user input. This will be true * even if the user's input gesture has ended but a transition they initiated is still * animating. */ - val isUserInteractingWithQs: Flow<Boolean> = - if (sceneContainerFlags.isEnabled()) { - sceneBasedInteracting(sceneInteractorProvider.get(), SceneKey.QuickSettings) - } else { - userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion) - } - - /** - * Whether the user is expanding or collapsing either the shade or quick settings with user - * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended - * but a transition they initiated is still animating. - */ - val isUserInteracting: Flow<Boolean> = - combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs } - .distinctUntilChanged() - - /** Are touches allowed on the notification panel? */ - val isShadeTouchable: Flow<Boolean> = - combine( - powerInteractor.isAsleep, - keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD }, - keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING }, - deviceProvisioningRepository.isFactoryResetProtectionActive, - ) { isAsleep, goingToSleep, isPulsing, isFrpActive -> - when { - // Touches are disabled when Factory Reset Protection is active - isFrpActive -> false - // If the device is going to sleep, only accept touches if we're still - // animating - goingToSleep -> dozeParams.shouldControlScreenOff() - // If the device is asleep, only accept touches if there's a pulse - isAsleep -> isPulsing - else -> true - } - } - - /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ - val isExpandToQsEnabled: Flow<Boolean> = - combine( - disableFlagsRepository.disableFlags, - isShadeEnabled, - keyguardRepository.isDozing, - userSetupRepository.isUserSetupFlow, - deviceProvisioningRepository.isDeviceProvisioned, - ) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned -> - isDeviceProvisioned && - // Disallow QS during setup if it's a simple user switcher. (The user intends to - // use the lock screen user switcher, QS is not needed.) - (isUserSetup || !userSwitcherInteractor.isSimpleUserSwitcher) && - isShadeEnabled && - disableFlags.isQuickSettingsEnabled() && - !isDozing - } - - fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) = - sceneInteractor.transitionState - .flatMapLatest { state -> - when (state) { - is ObservableTransitionState.Idle -> - if (state.scene == sceneKey) { - flowOf(1f) - } else { - flowOf(0f) - } - is ObservableTransitionState.Transition -> - if (state.toScene == sceneKey) { - state.progress - } else if (state.fromScene == sceneKey) { - state.progress.map { progress -> 1 - progress } - } else { - flowOf(0f) - } - } - } - .distinctUntilChanged() - - fun sceneBasedInteracting(sceneInteractor: SceneInteractor, sceneKey: SceneKey) = - sceneInteractor.transitionState - .map { state -> - when (state) { - is ObservableTransitionState.Idle -> false - is ObservableTransitionState.Transition -> - state.isInitiatedByUserInput && - (state.toScene == sceneKey || state.fromScene == sceneKey) - } - } - .distinctUntilChanged() + val isUserInteractingWithQs: Flow<Boolean> +} - /** - * Return a flow for whether a user is interacting with an expandable shade component using - * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that - * [expansion.first] checks the current value of the flow. - */ - private fun userInteractingFlow( - tracking: Flow<Boolean>, - expansion: StateFlow<Float> - ): Flow<Boolean> { - return flow { - // initial value is false - emit(false) - while (currentCoroutineContext().isActive) { - // wait for tracking to become true - tracking.first { it } - emit(true) - // wait for tracking to become false - tracking.first { !it } - // wait for expansion to complete in either direction - expansion.first { it <= 0f || it >= 1f } - // interaction complete - emit(false) - } - } - } +fun createAnyExpansionFlow( + scope: CoroutineScope, + shadeExpansion: Flow<Float>, + qsExpansion: Flow<Float> +): StateFlow<Float> { + return combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) } + .stateIn(scope, SharingStarted.Eagerly, 0f) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt new file mode 100644 index 000000000000..e36897c94964 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 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.shade.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** Empty implementation of ShadeInteractor for System UI variants with no shade. */ +@SysUISingleton +class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor { + private val inactiveFlowBoolean = MutableStateFlow(false) + private val inactiveFlowFloat = MutableStateFlow(0f) + override val isShadeEnabled: StateFlow<Boolean> = inactiveFlowBoolean + override val shadeExpansion: Flow<Float> = inactiveFlowFloat + override val qsExpansion: StateFlow<Float> = inactiveFlowFloat + override val isQsExpanded: StateFlow<Boolean> = inactiveFlowBoolean + override val anyExpansion: StateFlow<Float> = inactiveFlowFloat + override val isAnyFullyExpanded: Flow<Boolean> = inactiveFlowBoolean + override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean + override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean + override val isUserInteractingWithQs: Flow<Boolean> = inactiveFlowBoolean + override val isUserInteracting: Flow<Boolean> = inactiveFlowBoolean + override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean + override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt new file mode 100644 index 000000000000..fb21aee3a036 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2023 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.shade.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.DozeStateModel +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository +import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository +import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** The non-empty SceneInteractor implementation. */ +@SysUISingleton +class ShadeInteractorImpl +@Inject +constructor( + @Application val scope: CoroutineScope, + deviceProvisioningRepository: DeviceProvisioningRepository, + disableFlagsRepository: DisableFlagsRepository, + dozeParams: DozeParameters, + keyguardRepository: KeyguardRepository, + keyguardTransitionInteractor: KeyguardTransitionInteractor, + powerInteractor: PowerInteractor, + userSetupRepository: UserSetupRepository, + userSwitcherInteractor: UserSwitcherInteractor, + private val baseShadeInteractor: BaseShadeInteractor, +) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor { + override val isShadeEnabled: StateFlow<Boolean> = + disableFlagsRepository.disableFlags + .map { it.isShadeEnabled() } + .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + + override val isAnyFullyExpanded: Flow<Boolean> = + anyExpansion.map { it >= 1f }.distinctUntilChanged() + + override val isUserInteracting: Flow<Boolean> = + combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs } + .distinctUntilChanged() + + override val isShadeTouchable: Flow<Boolean> = + combine( + powerInteractor.isAsleep, + keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD }, + keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING }, + deviceProvisioningRepository.isFactoryResetProtectionActive, + ) { isAsleep, goingToSleep, isPulsing, isFrpActive -> + when { + // Touches are disabled when Factory Reset Protection is active + isFrpActive -> false + // If the device is going to sleep, only accept touches if we're still + // animating + goingToSleep -> dozeParams.shouldControlScreenOff() + // If the device is asleep, only accept touches if there's a pulse + isAsleep -> isPulsing + else -> true + } + } + + override val isExpandToQsEnabled: Flow<Boolean> = + combine( + disableFlagsRepository.disableFlags, + isShadeEnabled, + keyguardRepository.isDozing, + userSetupRepository.isUserSetupFlow, + deviceProvisioningRepository.isDeviceProvisioned, + ) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned -> + isDeviceProvisioned && + // Disallow QS during setup if it's a simple user switcher. (The user intends to + // use the lock screen user switcher, QS is not needed.) + (isUserSetup || !userSwitcherInteractor.isSimpleUserSwitcher) && + isShadeEnabled && + disableFlags.isQuickSettingsEnabled() && + !isDozing + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt new file mode 100644 index 000000000000..5b3834b343bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2023 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.shade.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.isActive + +/** ShadeInteractor implementation for the legacy codebase, e.g. NPVC. */ +@SysUISingleton +class ShadeInteractorLegacyImpl +@Inject +constructor( + @Application val scope: CoroutineScope, + keyguardRepository: KeyguardRepository, + sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, + repository: ShadeRepository, +) : BaseShadeInteractor { + /** The amount [0-1] that the shade has been opened */ + override val shadeExpansion: Flow<Float> = + combine( + repository.lockscreenShadeExpansion, + keyguardRepository.statusBarState, + repository.legacyShadeExpansion, + repository.qsExpansion, + sharedNotificationContainerInteractor.isSplitShadeEnabled + ) { + lockscreenShadeExpansion, + statusBarState, + legacyShadeExpansion, + qsExpansion, + splitShadeEnabled -> + when (statusBarState) { + // legacyShadeExpansion is 1 instead of 0 when QS is expanded + StatusBarState.SHADE -> + if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion + StatusBarState.KEYGUARD -> lockscreenShadeExpansion + // dragDownAmount, which drives lockscreenShadeExpansion resets to 0f when + // the pointer is lifted and the lockscreen shade is fully expanded + StatusBarState.SHADE_LOCKED -> 1f + } + } + .distinctUntilChanged() + + override val qsExpansion: StateFlow<Float> = repository.qsExpansion + + override val isQsExpanded: StateFlow<Boolean> = repository.legacyIsQsExpanded + + override val anyExpansion: StateFlow<Float> = + createAnyExpansionFlow(scope, shadeExpansion, qsExpansion) + + override val isAnyExpanded = + repository.legacyExpandedOrAwaitingInputTransfer.stateIn( + scope, + SharingStarted.Eagerly, + false + ) + + override val isUserInteractingWithShade: Flow<Boolean> = + combine( + userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion), + repository.legacyLockscreenShadeTracking + ) { legacyShadeTracking, legacyLockscreenShadeTracking -> + legacyShadeTracking || legacyLockscreenShadeTracking + } + + override val isUserInteractingWithQs: Flow<Boolean> = + userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion) + + /** + * Return a flow for whether a user is interacting with an expandable shade component using + * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that + * [expansion.first] checks the current value of the flow. + */ + private fun userInteractingFlow( + tracking: Flow<Boolean>, + expansion: StateFlow<Float> + ): Flow<Boolean> { + return flow { + // initial value is false + emit(false) + while (currentCoroutineContext().isActive) { + // wait for tracking to become true + tracking.first { it } + emit(true) + // wait for tracking to become false + tracking.first { !it } + // wait for expansion to complete in either direction + expansion.first { it <= 0f || it >= 1f } + // interaction complete + emit(false) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt new file mode 100644 index 000000000000..58ebbc6ac8bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023 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.shade.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.ObservableTransitionState +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +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.combine +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 + +/** ShadeInteractor implementation for Scene Container. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class ShadeInteractorSceneContainerImpl +@Inject +constructor( + @Application scope: CoroutineScope, + sceneInteractor: SceneInteractor, + sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, +) : BaseShadeInteractor { + override val shadeExpansion: Flow<Float> = sceneBasedExpansion(sceneInteractor, SceneKey.Shade) + + private val sceneBasedQsExpansion = sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings) + + override val qsExpansion: StateFlow<Float> = + combine( + sharedNotificationContainerInteractor.isSplitShadeEnabled, + shadeExpansion, + sceneBasedQsExpansion, + ) { isSplitShadeEnabled, shadeExpansion, qsExpansion -> + if (isSplitShadeEnabled) { + shadeExpansion + } else { + qsExpansion + } + } + .stateIn(scope, SharingStarted.Eagerly, 0f) + + override val isQsExpanded: StateFlow<Boolean> = + qsExpansion + .map { it > 0 } + .distinctUntilChanged() + .stateIn(scope, SharingStarted.Eagerly, false) + + override val anyExpansion: StateFlow<Float> = + createAnyExpansionFlow(scope, shadeExpansion, qsExpansion) + + override val isAnyExpanded = + anyExpansion + .map { it > 0f } + .distinctUntilChanged() + .stateIn(scope, SharingStarted.Eagerly, false) + + override val isUserInteractingWithShade: Flow<Boolean> = + sceneBasedInteracting(sceneInteractor, SceneKey.Shade) + + override val isUserInteractingWithQs: Flow<Boolean> = + sceneBasedInteracting(sceneInteractor, SceneKey.QuickSettings) + + /** + * Returns a flow that uses scene transition progress to and from a scene that is pulled down + * from the top of the screen to a 0-1 expansion amount float. + */ + internal fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) = + sceneInteractor.transitionState + .flatMapLatest { state -> + when (state) { + is ObservableTransitionState.Idle -> + if (state.scene == sceneKey) { + flowOf(1f) + } else { + flowOf(0f) + } + is ObservableTransitionState.Transition -> + if (state.toScene == sceneKey) { + state.progress + } else if (state.fromScene == sceneKey) { + state.progress.map { progress -> 1 - progress } + } else { + flowOf(0f) + } + } + } + .distinctUntilChanged() + + /** + * Returns a flow that uses scene transition data to determine whether the user is interacting + * with a scene that is pulled down from the top of the screen. + */ + internal fun sceneBasedInteracting(sceneInteractor: SceneInteractor, sceneKey: SceneKey) = + sceneInteractor.transitionState + .map { state -> + when (state) { + is ObservableTransitionState.Idle -> false + is ObservableTransitionState.Transition -> + state.isInitiatedByUserInput && + (state.toScene == sceneKey || state.fromScene == sceneKey) + } + } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt index eb1c17aaca78..c2c5eed6f013 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt @@ -73,6 +73,11 @@ constructor( } .distinctUntilChanged() + val isSplitShadeEnabled: Flow<Boolean> = + configurationBasedDimensions + .map { dimens: ConfigurationBasedDimensions -> dimens.useSplitShade } + .distinctUntilChanged() + /** Top position (without translation) of the shared container. */ fun setTopPosition(top: Float) { _topPosition.value = top diff --git a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt index 97e43ad91f53..d8e7cd642ad9 100644 --- a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt +++ b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt @@ -26,11 +26,19 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.broadcast.FakeBroadcastDispatcher import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.shade.domain.interactor.BaseShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl +import com.android.systemui.shade.domain.interactor.ShadeInteractorSceneContainerImpl import dagger.Binds import dagger.Module import dagger.Provides +import javax.inject.Provider import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.CoroutineStart @@ -56,6 +64,7 @@ interface SysUITestModule { @Binds @Application fun bindAppResources(resources: Resources): Resources @Binds @Main fun bindMainResources(resources: Resources): Resources @Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher + @Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor companion object { @Provides @@ -72,6 +81,19 @@ interface SysUITestModule { @Provides fun provideFakeBroadcastDispatcher(test: SysuiTestCase): FakeBroadcastDispatcher = test.fakeBroadcastDispatcher + + @Provides + fun provideBaseShadeInteractor( + sceneContainerFlags: SceneContainerFlags, + sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>, + sceneContainerOff: Provider<ShadeInteractorLegacyImpl> + ): BaseShadeInteractor { + return if (sceneContainerFlags.isEnabled()) { + sceneContainerOn.get() + } else { + sceneContainerOff.get() + } + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index f1429b5dd7b3..36d78685edf9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -123,11 +123,12 @@ import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; import com.android.systemui.scene.SceneTestUtils; -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl; +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl; import com.android.systemui.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -389,26 +390,27 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mPowerInteractor = keyguardInteractorDeps.getPowerInteractor(); when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn( StateFlowKt.MutableStateFlow(false)); - mShadeInteractor = new ShadeInteractor( + mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), new FakeDeviceProvisioningRepository(), new FakeDisableFlagsRepository(), mDozeParameters, - new FakeSceneContainerFlags(), - mUtils::sceneInteractor, mFakeKeyguardRepository, mKeyguardTransitionInteractor, mPowerInteractor, new FakeUserSetupRepository(), mock(UserSwitcherInteractor.class), - new SharedNotificationContainerInteractor( - new FakeConfigurationRepository(), - mContext, - new ResourcesSplitShadeStateController() - ), - mShadeRepository + new ShadeInteractorLegacyImpl( + mTestScope.getBackgroundScope(), + mFakeKeyguardRepository, + new SharedNotificationContainerInteractor( + new FakeConfigurationRepository(), + mContext, + new ResourcesSplitShadeStateController() + ), + mShadeRepository + ) ); - SystemClock systemClock = new FakeSystemClock(); mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mInteractionJankMonitor, mJavaAdapter, () -> mShadeInteractor); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 6aaa0a135b66..8403ac59d2f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -78,6 +78,8 @@ import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl; +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -231,25 +233,26 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mKeyguardSecurityModel, mSelectedUserInteractor, powerInteractor); - - mShadeInteractor = - new ShadeInteractor( + mShadeInteractor = new ShadeInteractorImpl( + mTestScope.getBackgroundScope(), + new FakeDeviceProvisioningRepository(), + new FakeDisableFlagsRepository(), + mock(DozeParameters.class), + keyguardRepository, + keyguardTransitionInteractor, + powerInteractor, + new FakeUserSetupRepository(), + mock(UserSwitcherInteractor.class), + new ShadeInteractorLegacyImpl( mTestScope.getBackgroundScope(), - new FakeDeviceProvisioningRepository(), - new FakeDisableFlagsRepository(), - mock(DozeParameters.class), - sceneContainerFlags, - () -> sceneInteractor, keyguardRepository, - keyguardTransitionInteractor, - powerInteractor, - new FakeUserSetupRepository(), - mock(UserSwitcherInteractor.class), new SharedNotificationContainerInteractor( configurationRepository, mContext, new ResourcesSplitShadeStateController()), - shadeRepository); + shadeRepository + ) + ); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index ff110c5437a2..26b84e372d5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -68,6 +68,8 @@ import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl; +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl; import com.android.systemui.shade.transition.ShadeTransitionController; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -267,25 +269,26 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { ResourcesSplitShadeStateController splitShadeStateController = new ResourcesSplitShadeStateController(); - mShadeInteractor = - new ShadeInteractor( + mShadeInteractor = new ShadeInteractorImpl( + mTestScope.getBackgroundScope(), + deviceProvisioningRepository, + mDisableFlagsRepository, + mDozeParameters, + mKeyguardRepository, + keyguardTransitionInteractor, + powerInteractor, + new FakeUserSetupRepository(), + mUserSwitcherInteractor, + new ShadeInteractorLegacyImpl( mTestScope.getBackgroundScope(), - deviceProvisioningRepository, - mDisableFlagsRepository, - mDozeParameters, - sceneContainerFlags, - () -> sceneInteractor, mKeyguardRepository, - keyguardTransitionInteractor, - powerInteractor, - new FakeUserSetupRepository(), - mUserSwitcherInteractor, new SharedNotificationContainerInteractor( configurationRepository, mContext, splitShadeStateController), mShadeRepository - ); + ) + ); KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt index ff7443f10bf3..09700e186978 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.systemui.shade.data.repository +package com.android.systemui.shade.domain.interactor import android.app.StatusBarManager.DISABLE2_NONE import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE @@ -46,9 +46,7 @@ import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository import com.android.systemui.statusbar.phone.DozeParameters @@ -62,14 +60,12 @@ import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Test @SmallTest -class ShadeInteractorTest : SysuiTestCase() { +class ShadeInteractorImplTest : SysuiTestCase() { @SysUISingleton @Component( @@ -79,7 +75,7 @@ class ShadeInteractorTest : SysuiTestCase() { UserDomainLayerModule::class, ] ) - interface TestComponent : SysUITestComponent<ShadeInteractor> { + interface TestComponent : SysUITestComponent<ShadeInteractorImpl> { val configurationRepository: FakeConfigurationRepository val deviceProvisioningRepository: FakeDeviceProvisioningRepository @@ -105,7 +101,7 @@ class ShadeInteractorTest : SysuiTestCase() { private val dozeParameters: DozeParameters = mock() private val testComponent: TestComponent = - DaggerShadeInteractorTest_TestComponent.factory() + DaggerShadeInteractorImplTest_TestComponent.factory() .create( test = this, featureFlags = @@ -446,154 +442,6 @@ class ShadeInteractorTest : SysuiTestCase() { } @Test - fun lockscreenShadeExpansion_idle_onScene() = - testComponent.runTest() { - // GIVEN an expansion flow based on transitions to and from a scene - val key = SceneKey.Shade - val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) - val expansionAmount by collectLastValue(expansion) - - // WHEN transition state is idle on the scene - val transitionState = - MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) - sceneInteractor.setTransitionState(transitionState) - - // THEN expansion is 1 - assertThat(expansionAmount).isEqualTo(1f) - } - - @Test - fun lockscreenShadeExpansion_idle_onDifferentScene() = - testComponent.runTest() { - // GIVEN an expansion flow based on transitions to and from a scene - val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade) - val expansionAmount by collectLastValue(expansion) - - // WHEN transition state is idle on a different scene - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Lockscreen) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN expansion is 0 - assertThat(expansionAmount).isEqualTo(0f) - } - - @Test - fun lockscreenShadeExpansion_transitioning_toScene() = - testComponent.runTest() { - // GIVEN an expansion flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) - val expansionAmount by collectLastValue(expansion) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = key, - progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN expansion is 0 - assertThat(expansionAmount).isEqualTo(0f) - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN expansion matches the progress - assertThat(expansionAmount).isEqualTo(.4f) - - // WHEN transition completes - progress.value = 1f - - // THEN expansion is 1 - assertThat(expansionAmount).isEqualTo(1f) - } - - @Test - fun lockscreenShadeExpansion_transitioning_fromScene() = - testComponent.runTest() { - // GIVEN an expansion flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) - val expansionAmount by collectLastValue(expansion) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = key, - toScene = SceneKey.Lockscreen, - progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN expansion is 1 - assertThat(expansionAmount).isEqualTo(1f) - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN expansion reflects the progress - assertThat(expansionAmount).isEqualTo(.6f) - - // WHEN transition completes - progress.value = 1f - - // THEN expansion is 0 - assertThat(expansionAmount).isEqualTo(0f) - } - - @Test - fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() = - testComponent.runTest() { - // GIVEN an expansion flow based on transitions to and from a scene - val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings) - val expansionAmount by collectLastValue(expansion) - - // WHEN transition state is starting to between different scenes - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = SceneKey.Shade, - progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN expansion is 0 - assertThat(expansionAmount).isEqualTo(0f) - - // WHEN transition state is partially complete - progress.value = .4f - - // THEN expansion is still 0 - assertThat(expansionAmount).isEqualTo(0f) - - // WHEN transition completes - progress.value = 1f - - // THEN expansion is still 0 - assertThat(expansionAmount).isEqualTo(0f) - } - - @Test fun userInteractingWithShade_shadeDraggedUpAndDown() = testComponent.runTest() { val actual by collectLastValue(underTest.isUserInteractingWithShade) @@ -815,199 +663,6 @@ class ShadeInteractorTest : SysuiTestCase() { // THEN user is not interacting assertThat(actual).isFalse() } - @Test - fun userInteracting_idle() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.Shade - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is idle - val transitionState = - MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is false - assertThat(interacting).isFalse() - } - - @Test - fun userInteracting_transitioning_toScene_programmatic() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = key, - progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is false - assertThat(interacting).isFalse() - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN interacting is false - assertThat(interacting).isFalse() - - // WHEN transition completes - progress.value = 1f - - // THEN interacting is false - assertThat(interacting).isFalse() - } - - @Test - fun userInteracting_transitioning_toScene_userInputDriven() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = key, - progress = progress, - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is true - assertThat(interacting).isTrue() - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN interacting is true - assertThat(interacting).isTrue() - - // WHEN transition completes - progress.value = 1f - - // THEN interacting is true - assertThat(interacting).isTrue() - } - - @Test - fun userInteracting_transitioning_fromScene_programmatic() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = key, - toScene = SceneKey.Lockscreen, - progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is false - assertThat(interacting).isFalse() - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN interacting is false - assertThat(interacting).isFalse() - - // WHEN transition completes - progress.value = 1f - - // THEN interacting is false - assertThat(interacting).isFalse() - } - - @Test - fun userInteracting_transitioning_fromScene_userInputDriven() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = key, - toScene = SceneKey.Lockscreen, - progress = progress, - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is true - assertThat(interacting).isTrue() - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN interacting is true - assertThat(interacting).isTrue() - - // WHEN transition completes - progress.value = 1f - - // THEN interacting is true - assertThat(interacting).isTrue() - } - - @Test - fun userInteracting_transitioning_toAndFromDifferentScenes() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, SceneKey.Shade) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is starting to between different scenes - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = SceneKey.QuickSettings, - progress = MutableStateFlow(0f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is false - assertThat(interacting).isFalse() - } @Test fun isShadeTouchable_isFalse_whenFrpIsActive() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt new file mode 100644 index 000000000000..f3c875e671c4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2023 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.shade.domain.interactor + +import android.content.pm.UserInfo +import android.os.UserManager +import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.runCurrent +import com.android.runTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.res.R +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test + +@SmallTest +class ShadeInteractorLegacyImplTest : SysuiTestCase() { + + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<ShadeInteractorLegacyImpl> { + + val configurationRepository: FakeConfigurationRepository + val keyguardRepository: FakeKeyguardRepository + val keyguardTransitionRepository: FakeKeyguardTransitionRepository + val powerRepository: FakePowerRepository + val sceneInteractor: SceneInteractor + val shadeRepository: FakeShadeRepository + val userRepository: FakeUserRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private val dozeParameters: DozeParameters = mock() + + private val testComponent: TestComponent = + DaggerShadeInteractorLegacyImplTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, false) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = + TestMocksModule( + dozeParameters = dozeParameters, + ), + ) + + @Before + fun setUp() { + runBlocking { + val userInfos = + listOf( + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or + UserInfo.FLAG_ADMIN or + UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + ) + testComponent.apply { + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + } + } + } + + @Test + fun fullShadeExpansionWhenShadeLocked() = + testComponent.runTest() { + val actual by collectLastValue(underTest.shadeExpansion) + + keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) + shadeRepository.setLockscreenShadeExpansion(0.5f) + + assertThat(actual).isEqualTo(1f) + } + + @Test + fun fullShadeExpansionWhenStatusBarStateIsNotShadeLocked() = + testComponent.runTest() { + val actual by collectLastValue(underTest.shadeExpansion) + + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + + shadeRepository.setLockscreenShadeExpansion(0.5f) + assertThat(actual).isEqualTo(0.5f) + + shadeRepository.setLockscreenShadeExpansion(0.8f) + assertThat(actual).isEqualTo(0.8f) + } + + @Test + fun shadeExpansionWhenInSplitShadeAndQsExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, true) + configurationRepository.onAnyConfigurationChange() + shadeRepository.setQsExpansion(.5f) + shadeRepository.setLegacyShadeExpansion(.7f) + runCurrent() + + // THEN legacy shade expansion is passed through + assertThat(actual).isEqualTo(.7f) + } + + @Test + fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is not enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, false) + shadeRepository.setQsExpansion(.5f) + shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() + + // THEN shade expansion is zero + assertThat(actual).isEqualTo(0f) + } + + @Test + fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() = + testComponent.runTest() { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is not enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLegacyShadeExpansion(.6f) + + // THEN shade expansion is zero + assertThat(actual).isEqualTo(.6f) + } + + @Test + fun userInteractingWithShade_shadeDraggedUpAndDown() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged down halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully expanded but tracking is not stopped + shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully collapsed but tracking is not stopped + shadeRepository.setLegacyShadeExpansion(0f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged halfway and tracking is stopped + shadeRepository.setLegacyShadeExpansion(.6f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade completes expansion stopped + shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadeExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged down halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully expanded and tracking is stopped + shadeRepository.setLegacyShadeExpansion(1f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadePartiallyExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade partially expanded + shadeRepository.setLegacyShadeExpansion(.4f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN tracking is stopped + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade goes back to collapsed + shadeRepository.setLegacyShadeExpansion(0f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadeCollapsed() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade expanded and not tracking input + shadeRepository.setLegacyShadeExpansion(1f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged up halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully collapsed and tracking is stopped + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithQs_qsDraggedUpAndDown() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithQs) + // GIVEN qs collapsed and not tracking input + shadeRepository.setQsExpansion(0f) + shadeRepository.setLegacyQsTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN qs tracking starts + shadeRepository.setLegacyQsTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs dragged down halfway + shadeRepository.setQsExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs fully expanded but tracking is not stopped + shadeRepository.setQsExpansion(1f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs fully collapsed but tracking is not stopped + shadeRepository.setQsExpansion(0f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs dragged halfway and tracking is stopped + shadeRepository.setQsExpansion(.6f) + shadeRepository.setLegacyQsTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs completes expansion stopped + shadeRepository.setQsExpansion(1f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt new file mode 100644 index 000000000000..3e21f15a54a9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt @@ -0,0 +1,522 @@ +/* + * Copyright (C) 2023 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.shade.domain.interactor + +import android.content.pm.UserInfo +import android.os.UserManager +import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.runCurrent +import com.android.runTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.res.R +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.ObservableTransitionState +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth +import dagger.BindsInstance +import dagger.Component +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Ignore +import org.junit.Test + +@SmallTest +class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { + + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<ShadeInteractorSceneContainerImpl> { + + val configurationRepository: FakeConfigurationRepository + val keyguardRepository: FakeKeyguardRepository + val keyguardTransitionRepository: FakeKeyguardTransitionRepository + val powerRepository: FakePowerRepository + val sceneInteractor: SceneInteractor + val shadeRepository: FakeShadeRepository + val userRepository: FakeUserRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private val dozeParameters: DozeParameters = mock() + + private val testComponent: TestComponent = + DaggerShadeInteractorSceneContainerImplTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, false) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = + TestMocksModule( + dozeParameters = dozeParameters, + ), + ) + + @Before + fun setUp() { + runBlocking { + val userInfos = + listOf( + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or + UserInfo.FLAG_ADMIN or + UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + ) + testComponent.apply { + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + } + } + } + + @Ignore("b/309825977") + @Test + fun qsExpansionWhenInSplitShadeAndQsExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.qsExpansion) + + // WHEN split shade is enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, true) + configurationRepository.onAnyConfigurationChange() + val progress = MutableStateFlow(.3f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.QuickSettings, + toScene = SceneKey.Shade, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN legacy shade expansion is passed through + Truth.assertThat(actual).isEqualTo(.3f) + } + + @Ignore("b/309825977") + @Test + fun qsExpansionWhenNotInSplitShadeAndQsExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.qsExpansion) + + // WHEN split shade is not enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, false) + val progress = MutableStateFlow(.3f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.QuickSettings, + toScene = SceneKey.Shade, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN shade expansion is zero + Truth.assertThat(actual).isEqualTo(.7f) + } + + @Test + fun lockscreenShadeExpansion_idle_onScene() = + testComponent.runTest() { + // GIVEN an expansion flow based on transitions to and from a scene + val key = SceneKey.Shade + val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) + val expansionAmount by collectLastValue(expansion) + + // WHEN transition state is idle on the scene + val transitionState = + MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) + sceneInteractor.setTransitionState(transitionState) + + // THEN expansion is 1 + Truth.assertThat(expansionAmount).isEqualTo(1f) + } + + @Test + fun lockscreenShadeExpansion_idle_onDifferentScene() = + testComponent.runTest() { + // GIVEN an expansion flow based on transitions to and from a scene + val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade) + val expansionAmount by collectLastValue(expansion) + + // WHEN transition state is idle on a different scene + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN expansion is 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + } + + @Test + fun lockscreenShadeExpansion_transitioning_toScene() = + testComponent.runTest() { + // GIVEN an expansion flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) + val expansionAmount by collectLastValue(expansion) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = key, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN expansion is 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN expansion matches the progress + Truth.assertThat(expansionAmount).isEqualTo(.4f) + + // WHEN transition completes + progress.value = 1f + + // THEN expansion is 1 + Truth.assertThat(expansionAmount).isEqualTo(1f) + } + + @Test + fun lockscreenShadeExpansion_transitioning_fromScene() = + testComponent.runTest() { + // GIVEN an expansion flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) + val expansionAmount by collectLastValue(expansion) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = key, + toScene = SceneKey.Lockscreen, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN expansion is 1 + Truth.assertThat(expansionAmount).isEqualTo(1f) + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN expansion reflects the progress + Truth.assertThat(expansionAmount).isEqualTo(.6f) + + // WHEN transition completes + progress.value = 1f + + // THEN expansion is 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + } + + @Test + fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() = + testComponent.runTest() { + // GIVEN an expansion flow based on transitions to and from a scene + val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings) + val expansionAmount by collectLastValue(expansion) + + // WHEN transition state is starting to between different scenes + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN expansion is 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + + // WHEN transition state is partially complete + progress.value = .4f + + // THEN expansion is still 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + + // WHEN transition completes + progress.value = 1f + + // THEN expansion is still 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + } + + @Test + fun userInteracting_idle() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val key = SceneKey.Shade + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is idle + val transitionState = + MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + } + + @Test + fun userInteracting_transitioning_toScene_programmatic() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = key, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + + // WHEN transition completes + progress.value = 1f + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + } + + @Test + fun userInteracting_transitioning_toScene_userInputDriven() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = key, + progress = progress, + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + + // WHEN transition completes + progress.value = 1f + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + } + + @Test + fun userInteracting_transitioning_fromScene_programmatic() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = key, + toScene = SceneKey.Lockscreen, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + + // WHEN transition completes + progress.value = 1f + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + } + + @Test + fun userInteracting_transitioning_fromScene_userInputDriven() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = key, + toScene = SceneKey.Lockscreen, + progress = progress, + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + + // WHEN transition completes + progress.value = 1f + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + } + + @Test + fun userInteracting_transitioning_toAndFromDifferentScenes() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, SceneKey.Shade) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is starting to between different scenes + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.QuickSettings, + progress = MutableStateFlow(0f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 4b79a499bf90..8fa7cd291851 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -44,6 +44,8 @@ import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository @@ -153,23 +155,25 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { mock(), mock(), powerInteractor) - shadeInteractor = ShadeInteractor( + shadeInteractor = ShadeInteractorImpl( testScope.backgroundScope, FakeDeviceProvisioningRepository(), FakeDisableFlagsRepository(), mock(), - sceneContainerFlags, - utils::sceneInteractor, keyguardRepository, keyguardTransitionInteractor, powerInteractor, FakeUserSetupRepository(), mock(), - SharedNotificationContainerInteractor( - configurationRepository, - mContext, - ResourcesSplitShadeStateController()), - shadeRepository, + ShadeInteractorLegacyImpl( + testScope.backgroundScope, + keyguardRepository, + SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ResourcesSplitShadeStateController()), + shadeRepository, + ) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 102c3fc76a61..7e83034e513e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -126,6 +126,8 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeWindowLogger; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl; +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.NotificationEntryHelper; @@ -454,23 +456,24 @@ public class BubblesTest extends SysuiTestCase { new ResourcesSplitShadeStateController(); mShadeInteractor = - new ShadeInteractor( + new ShadeInteractorImpl( mTestScope.getBackgroundScope(), deviceProvisioningRepository, new FakeDisableFlagsRepository(), mDozeParameters, - sceneContainerFlags, - () -> sceneInteractor, keyguardRepository, keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), mock(UserSwitcherInteractor.class), - new SharedNotificationContainerInteractor( - configurationRepository, - mContext, - splitShadeStateController), - new FakeShadeRepository() + new ShadeInteractorLegacyImpl( + mTestScope.getBackgroundScope(), keyguardRepository, + new SharedNotificationContainerInteractor( + configurationRepository, + mContext, + splitShadeStateController), + shadeRepository + ) ); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl( |