diff options
6 files changed, 251 insertions, 48 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt index ca1413e48966..b19645fadbdf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt @@ -30,11 +30,13 @@ import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel +import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -72,7 +74,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { whenTime = 1000L, callType = CallType.Ongoing, statusBarChipIcon = testIconView, - contentIntent = testIntent + contentIntent = testIntent, ) ) } @@ -95,7 +97,9 @@ class OngoingCallInteractorTest : SysuiTestCase() { .apply { addIndividualNotif( activeNotificationModel( - key = "notif1", whenTime = 1000L, callType = CallType.Ongoing + key = "notif1", + whenTime = 1000L, + callType = CallType.Ongoing, ) ) } @@ -114,7 +118,9 @@ class OngoingCallInteractorTest : SysuiTestCase() { .apply { addIndividualNotif( activeNotificationModel( - key = "notif1", whenTime = 1000L, callType = CallType.Ongoing + key = "notif1", + whenTime = 1000L, + callType = CallType.Ongoing, ) ) } @@ -138,7 +144,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { key = "notif1", whenTime = 1000L, callType = CallType.Ongoing, - uid = UID + uid = UID, ) ) } @@ -161,7 +167,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { key = "notif1", whenTime = 1000L, callType = CallType.Ongoing, - uid = UID + uid = UID, ) ) } @@ -185,13 +191,12 @@ class OngoingCallInteractorTest : SysuiTestCase() { key = "notif1", whenTime = 1000L, callType = CallType.Ongoing, - uid = UID + uid = UID, ) ) } .build() - assertThat(latest) - .isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) // App becomes visible kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true) @@ -202,6 +207,120 @@ class OngoingCallInteractorTest : SysuiTestCase() { assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) } + @Test + fun ongoingCallNotification_setsRequiresStatusBarVisibleTrue() = + kosmos.runTest { + val ongoingCallState by collectLastValue(underTest.ongoingCallState) + + val requiresStatusBarVisibleInRepository by + collectLastValue( + kosmos.fakeStatusBarModeRepository.defaultDisplay + .ongoingProcessRequiresStatusBarVisible + ) + val requiresStatusBarVisibleInWindowController by + collectLastValue( + kosmos.fakeStatusBarWindowControllerStore.defaultDisplay + .ongoingProcessRequiresStatusBarVisible + ) + repository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + whenTime = 1000L, + callType = CallType.Ongoing, + uid = UID, + ) + ) + } + .build() + + assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat(requiresStatusBarVisibleInRepository).isTrue() + assertThat(requiresStatusBarVisibleInWindowController).isTrue() + } + + @Test + fun notificationRemoved_setsRequiresStatusBarVisibleFalse() = + kosmos.runTest { + val ongoingCallState by collectLastValue(underTest.ongoingCallState) + + val requiresStatusBarVisibleInRepository by + collectLastValue( + kosmos.fakeStatusBarModeRepository.defaultDisplay + .ongoingProcessRequiresStatusBarVisible + ) + val requiresStatusBarVisibleInWindowController by + collectLastValue( + kosmos.fakeStatusBarWindowControllerStore.defaultDisplay + .ongoingProcessRequiresStatusBarVisible + ) + + repository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + whenTime = 1000L, + callType = CallType.Ongoing, + uid = UID, + ) + ) + } + .build() + + repository.activeNotifications.value = ActiveNotificationsStore() + + assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.NoCall::class.java) + assertThat(requiresStatusBarVisibleInRepository).isFalse() + assertThat(requiresStatusBarVisibleInWindowController).isFalse() + } + + @Test + fun ongoingCallNotification_appBecomesVisible_setsRequiresStatusBarVisibleFalse() = + kosmos.runTest { + val ongoingCallState by collectLastValue(underTest.ongoingCallState) + + val requiresStatusBarVisibleInRepository by + collectLastValue( + kosmos.fakeStatusBarModeRepository.defaultDisplay + .ongoingProcessRequiresStatusBarVisible + ) + val requiresStatusBarVisibleInWindowController by + collectLastValue( + kosmos.fakeStatusBarWindowControllerStore.defaultDisplay + .ongoingProcessRequiresStatusBarVisible + ) + + kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false + repository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + whenTime = 1000L, + callType = CallType.Ongoing, + uid = UID, + ) + ) + } + .build() + + assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat(requiresStatusBarVisibleInRepository).isTrue() + assertThat(requiresStatusBarVisibleInWindowController).isTrue() + + kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true) + + assertThat(ongoingCallState) + .isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java) + assertThat(requiresStatusBarVisibleInRepository).isFalse() + assertThat(requiresStatusBarVisibleInWindowController).isFalse() + } + companion object { private const val UID = 885 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt index cc91e2dc3a25..22c37df7db7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt @@ -37,6 +37,7 @@ import com.android.systemui.statusbar.phone.BoundsPair import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator import com.android.systemui.statusbar.phone.StatusBarBoundsProvider import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import dagger.assisted.Assisted @@ -89,6 +90,9 @@ interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener /** The current mode of the status bar. */ val statusBarMode: StateFlow<StatusBarMode> + /** Whether the status bar is forced to be visible because of an ongoing call */ + val ongoingProcessRequiresStatusBarVisible: StateFlow<Boolean> + /** * Requests for the status bar to be shown transiently. * @@ -110,6 +114,12 @@ interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener * if needed. */ fun stop() + + /** + * Called when an ongoing process needs to prevent the status bar from being hidden in any + * state. + */ + fun setOngoingProcessRequiresStatusBarVisible(requiredVisible: Boolean) } class StatusBarModePerDisplayRepositoryImpl @@ -195,6 +205,16 @@ constructor( statusBarBoundsProvider.addChangeListener(listener) } + private val _ongoingProcessRequiresStatusBarVisible = MutableStateFlow(false) + override val ongoingProcessRequiresStatusBarVisible = + _ongoingProcessRequiresStatusBarVisible.asStateFlow() + + override fun setOngoingProcessRequiresStatusBarVisible( + requiredVisible: Boolean + ) { + _ongoingProcessRequiresStatusBarVisible.value = requiredVisible + } + override val isInFullscreenMode: StateFlow<Boolean> = _originalStatusBarAttributes .map { params -> @@ -235,16 +255,28 @@ constructor( isTransientShown, isInFullscreenMode, ongoingCallRepository.ongoingCallState, - ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallState -> + _ongoingProcessRequiresStatusBarVisible, + ) { + modifiedAttributes, + isTransientShown, + isInFullscreenMode, + ongoingCallStateLegacy, + ongoingProcessRequiresStatusBarVisible -> if (modifiedAttributes == null) { null } else { + val hasOngoingCall = + if (StatusBarChipsModernization.isEnabled) { + ongoingProcessRequiresStatusBarVisible + } else { + ongoingCallStateLegacy is OngoingCallModel.InCall + } val statusBarMode = toBarMode( modifiedAttributes.appearance, isTransientShown, isInFullscreenMode, - hasOngoingCall = ongoingCallState is OngoingCallModel.InCall, + hasOngoingCall, ) StatusBarAppearance( statusBarMode, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt index 4b71c0268f11..b7ccfa01c92c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt @@ -21,17 +21,22 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger +import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore 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.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn /** @@ -47,7 +52,9 @@ import kotlinx.coroutines.flow.stateIn @SysUISingleton class OngoingCallInteractor @Inject constructor( @Application private val scope: CoroutineScope, - activityManagerRepository: ActivityManagerRepository, + private val activityManagerRepository: ActivityManagerRepository, + private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore, + private val statusBarWindowControllerStore: StatusBarWindowControllerStore, activeNotificationsInteractor: ActiveNotificationsInteractor, @OngoingCallLog private val logBuffer: LogBuffer, ) { @@ -58,46 +65,78 @@ class OngoingCallInteractor @Inject constructor( */ val ongoingCallState: StateFlow<OngoingCallModel> = activeNotificationsInteractor.ongoingCallNotification - .flatMapLatest { notificationModel -> - when (notificationModel) { - null -> { - logger.d("No active call notification - hiding chip") - flowOf(OngoingCallModel.NoCall) - } + .flatMapLatest { notification -> + createOngoingCallStateFlow( + notification = notification + ) + } + .onEach { state -> + setStatusBarRequiredForOngoingCall(state) + } + .stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(), + initialValue = OngoingCallModel.NoCall + ) + + private fun createOngoingCallStateFlow( + notification: ActiveNotificationModel? + ): Flow<OngoingCallModel> { + if (notification == null) { + logger.d("No active call notification - hiding chip") + return flowOf(OngoingCallModel.NoCall) + } + + return combine( + flowOf(notification), + activityManagerRepository.createIsAppVisibleFlow( + creationUid = notification.uid, + logger = logger, + identifyingLogTag = TAG, + ) + ) { model, isVisible -> + deriveOngoingCallState(model, isVisible) + } + } - else -> combine( - flowOf(notificationModel), - activityManagerRepository.createIsAppVisibleFlow( - creationUid = notificationModel.uid, - logger = logger, - identifyingLogTag = TAG, - ), - ) { model, isVisible -> - when { - isVisible -> { - logger.d({ "Call app is visible: uid=$int1" }) { - int1 = model.uid - } - OngoingCallModel.InCallWithVisibleApp - } + private fun deriveOngoingCallState( + model: ActiveNotificationModel, + isVisible: Boolean + ): OngoingCallModel { + return when { + isVisible -> { + logger.d({ "Call app is visible: uid=$int1" }) { + int1 = model.uid + } + OngoingCallModel.InCallWithVisibleApp + } - else -> { - logger.d({ "Active call detected: startTime=$long1 hasIcon=$bool1" }) { - long1 = model.whenTime - bool1 = model.statusBarChipIconView != null - } - OngoingCallModel.InCall( - startTimeMs = model.whenTime, - notificationIconView = model.statusBarChipIconView, - intent = model.contentIntent, - notificationKey = model.key, - ) - } - } - } + else -> { + logger.d({ "Active call detected: startTime=$long1 hasIcon=$bool1" }) { + long1 = model.whenTime + bool1 = model.statusBarChipIconView != null } + OngoingCallModel.InCall( + startTimeMs = model.whenTime, + notificationIconView = model.statusBarChipIconView, + intent = model.contentIntent, + notificationKey = model.key + ) } - .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingCallModel.NoCall) + } + } + + private fun setStatusBarRequiredForOngoingCall(state: OngoingCallModel) { + val statusBarRequired = state is OngoingCallModel.InCall + // TODO(b/382808183): Create a single repository that can be utilized in + // `statusBarModeRepositoryStore` and `statusBarWindowControllerStore` so we do not need + // two separate calls to force the status bar to stay visible. + statusBarModeRepositoryStore.defaultDisplay.setOngoingProcessRequiresStatusBarVisible( + statusBarRequired + ) + statusBarWindowControllerStore.defaultDisplay + .setOngoingProcessRequiresStatusBarVisible(statusBarRequired) + } companion object { private val TAG = "OngoingCall" diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt index 22f8767e1d55..3c2d004fcad7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt @@ -47,6 +47,7 @@ class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository override val isInFullscreenMode = MutableStateFlow(false) override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null) override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT) + override val ongoingProcessRequiresStatusBarVisible = MutableStateFlow(false) override fun showTransient() { isTransientShown.value = true @@ -59,6 +60,9 @@ class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository override fun start() {} override fun stop() {} + override fun setOngoingProcessRequiresStatusBarVisible(requiredVisible: Boolean) { + ongoingProcessRequiresStatusBarVisible.value = requiredVisible + } override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt index 51fb36fb2ead..9090e02b22b6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt @@ -20,7 +20,9 @@ import com.android.systemui.activity.data.repository.activityManagerRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore val Kosmos.ongoingCallInteractor: OngoingCallInteractor by Kosmos.Fixture { @@ -28,6 +30,8 @@ val Kosmos.ongoingCallInteractor: OngoingCallInteractor by scope = applicationCoroutineScope, activeNotificationsInteractor = activeNotificationsInteractor, activityManagerRepository = activityManagerRepository, + statusBarModeRepositoryStore = fakeStatusBarModeRepository, + statusBarWindowControllerStore = fakeStatusBarWindowControllerStore, logBuffer = logcatLogBuffer("OngoingCallInteractorKosmos"), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt index a110a49ccba8..09e6a0eaa301 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt @@ -20,6 +20,7 @@ import android.view.View import android.view.ViewGroup import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.fragments.FragmentHostManager +import kotlinx.coroutines.flow.MutableStateFlow import java.util.Optional class FakeStatusBarWindowController : StatusBarWindowController { @@ -30,6 +31,8 @@ class FakeStatusBarWindowController : StatusBarWindowController { var isStopped = false private set + val ongoingProcessRequiresStatusBarVisible = MutableStateFlow(false) + override val statusBarHeight: Int = 0 override fun refreshStatusBarHeight() {} @@ -57,5 +60,7 @@ class FakeStatusBarWindowController : StatusBarWindowController { override fun setForceStatusBarVisible(forceStatusBarVisible: Boolean) {} - override fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean) {} + override fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean) { + ongoingProcessRequiresStatusBarVisible.value = visible + } } |