Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt +127 −8 Original line number Original line Diff line number Diff line Loading @@ -30,11 +30,13 @@ import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.StatusBarIconView 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.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest Loading Loading @@ -72,7 +74,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { whenTime = 1000L, whenTime = 1000L, callType = CallType.Ongoing, callType = CallType.Ongoing, statusBarChipIcon = testIconView, statusBarChipIcon = testIconView, contentIntent = testIntent contentIntent = testIntent, ) ) ) ) } } Loading @@ -95,7 +97,9 @@ class OngoingCallInteractorTest : SysuiTestCase() { .apply { .apply { addIndividualNotif( addIndividualNotif( activeNotificationModel( activeNotificationModel( key = "notif1", whenTime = 1000L, callType = CallType.Ongoing key = "notif1", whenTime = 1000L, callType = CallType.Ongoing, ) ) ) ) } } Loading @@ -114,7 +118,9 @@ class OngoingCallInteractorTest : SysuiTestCase() { .apply { .apply { addIndividualNotif( addIndividualNotif( activeNotificationModel( activeNotificationModel( key = "notif1", whenTime = 1000L, callType = CallType.Ongoing key = "notif1", whenTime = 1000L, callType = CallType.Ongoing, ) ) ) ) } } Loading @@ -138,7 +144,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { key = "notif1", key = "notif1", whenTime = 1000L, whenTime = 1000L, callType = CallType.Ongoing, callType = CallType.Ongoing, uid = UID uid = UID, ) ) ) ) } } Loading @@ -161,7 +167,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { key = "notif1", key = "notif1", whenTime = 1000L, whenTime = 1000L, callType = CallType.Ongoing, callType = CallType.Ongoing, uid = UID uid = UID, ) ) ) ) } } Loading @@ -185,13 +191,12 @@ class OngoingCallInteractorTest : SysuiTestCase() { key = "notif1", key = "notif1", whenTime = 1000L, whenTime = 1000L, callType = CallType.Ongoing, callType = CallType.Ongoing, uid = UID uid = UID, ) ) ) ) } } .build() .build() assertThat(latest) assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) .isInstanceOf(OngoingCallModel.InCall::class.java) // App becomes visible // App becomes visible kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true) kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true) Loading @@ -202,6 +207,120 @@ class OngoingCallInteractorTest : SysuiTestCase() { assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) 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 { companion object { private const val UID = 885 private const val UID = 885 } } Loading packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt +34 −2 Original line number Original line Diff line number Diff line Loading @@ -37,6 +37,7 @@ import com.android.systemui.statusbar.phone.BoundsPair import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator import com.android.systemui.statusbar.phone.StatusBarBoundsProvider import com.android.systemui.statusbar.phone.StatusBarBoundsProvider import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent 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.data.repository.OngoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import dagger.assisted.Assisted import dagger.assisted.Assisted Loading Loading @@ -89,6 +90,9 @@ interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener /** The current mode of the status bar. */ /** The current mode of the status bar. */ val statusBarMode: StateFlow<StatusBarMode> 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. * Requests for the status bar to be shown transiently. * * Loading @@ -110,6 +114,12 @@ interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener * if needed. * if needed. */ */ fun stop() 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 class StatusBarModePerDisplayRepositoryImpl Loading Loading @@ -195,6 +205,16 @@ constructor( statusBarBoundsProvider.addChangeListener(listener) 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> = override val isInFullscreenMode: StateFlow<Boolean> = _originalStatusBarAttributes _originalStatusBarAttributes .map { params -> .map { params -> Loading Loading @@ -235,16 +255,28 @@ constructor( isTransientShown, isTransientShown, isInFullscreenMode, isInFullscreenMode, ongoingCallRepository.ongoingCallState, ongoingCallRepository.ongoingCallState, ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallState -> _ongoingProcessRequiresStatusBarVisible, ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallStateLegacy, ongoingProcessRequiresStatusBarVisible -> if (modifiedAttributes == null) { if (modifiedAttributes == null) { null null } else { } else { val hasOngoingCall = if (StatusBarChipsModernization.isEnabled) { ongoingProcessRequiresStatusBarVisible } else { ongoingCallStateLegacy is OngoingCallModel.InCall } val statusBarMode = val statusBarMode = toBarMode( toBarMode( modifiedAttributes.appearance, modifiedAttributes.appearance, isTransientShown, isTransientShown, isInFullscreenMode, isInFullscreenMode, hasOngoingCall = ongoingCallState is OngoingCallModel.InCall, hasOngoingCall, ) ) StatusBarAppearance( StatusBarAppearance( statusBarMode, statusBarMode, Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt +76 −37 Original line number Original line Diff line number Diff line Loading @@ -21,17 +21,22 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger 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.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.OngoingCallLog import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn /** /** Loading @@ -47,7 +52,9 @@ import kotlinx.coroutines.flow.stateIn @SysUISingleton @SysUISingleton class OngoingCallInteractor @Inject constructor( class OngoingCallInteractor @Inject constructor( @Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope, activityManagerRepository: ActivityManagerRepository, private val activityManagerRepository: ActivityManagerRepository, private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore, private val statusBarWindowControllerStore: StatusBarWindowControllerStore, activeNotificationsInteractor: ActiveNotificationsInteractor, activeNotificationsInteractor: ActiveNotificationsInteractor, @OngoingCallLog private val logBuffer: LogBuffer, @OngoingCallLog private val logBuffer: LogBuffer, ) { ) { Loading @@ -58,22 +65,45 @@ class OngoingCallInteractor @Inject constructor( */ */ val ongoingCallState: StateFlow<OngoingCallModel> = val ongoingCallState: StateFlow<OngoingCallModel> = activeNotificationsInteractor.ongoingCallNotification activeNotificationsInteractor.ongoingCallNotification .flatMapLatest { notificationModel -> .flatMapLatest { notification -> when (notificationModel) { createOngoingCallStateFlow( null -> { 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") logger.d("No active call notification - hiding chip") flowOf(OngoingCallModel.NoCall) return flowOf(OngoingCallModel.NoCall) } } else -> combine( return combine( flowOf(notificationModel), flowOf(notification), activityManagerRepository.createIsAppVisibleFlow( activityManagerRepository.createIsAppVisibleFlow( creationUid = notificationModel.uid, creationUid = notification.uid, logger = logger, logger = logger, identifyingLogTag = TAG, identifyingLogTag = TAG, ), ) ) { model, isVisible -> ) { model, isVisible -> when { deriveOngoingCallState(model, isVisible) } } private fun deriveOngoingCallState( model: ActiveNotificationModel, isVisible: Boolean ): OngoingCallModel { return when { isVisible -> { isVisible -> { logger.d({ "Call app is visible: uid=$int1" }) { logger.d({ "Call app is visible: uid=$int1" }) { int1 = model.uid int1 = model.uid Loading @@ -90,14 +120,23 @@ class OngoingCallInteractor @Inject constructor( startTimeMs = model.whenTime, startTimeMs = model.whenTime, notificationIconView = model.statusBarChipIconView, notificationIconView = model.statusBarChipIconView, intent = model.contentIntent, intent = model.contentIntent, notificationKey = model.key, notificationKey = model.key ) ) } } } } } } 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) } } } .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingCallModel.NoCall) companion object { companion object { private val TAG = "OngoingCall" private val TAG = "OngoingCall" Loading packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -47,6 +47,7 @@ class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository override val isInFullscreenMode = MutableStateFlow(false) override val isInFullscreenMode = MutableStateFlow(false) override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null) override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null) override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT) override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT) override val ongoingProcessRequiresStatusBarVisible = MutableStateFlow(false) override fun showTransient() { override fun showTransient() { isTransientShown.value = true isTransientShown.value = true Loading @@ -59,6 +60,9 @@ class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository override fun start() {} override fun start() {} override fun stop() {} override fun stop() {} override fun setOngoingProcessRequiresStatusBarVisible(requiredVisible: Boolean) { ongoingProcessRequiresStatusBarVisible.value = requiredVisible } override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {} override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {} Loading packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -20,7 +20,9 @@ import com.android.systemui.activity.data.repository.activityManagerRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.logcatLogBuffer 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.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore val Kosmos.ongoingCallInteractor: OngoingCallInteractor by val Kosmos.ongoingCallInteractor: OngoingCallInteractor by Kosmos.Fixture { Kosmos.Fixture { Loading @@ -28,6 +30,8 @@ val Kosmos.ongoingCallInteractor: OngoingCallInteractor by scope = applicationCoroutineScope, scope = applicationCoroutineScope, activeNotificationsInteractor = activeNotificationsInteractor, activeNotificationsInteractor = activeNotificationsInteractor, activityManagerRepository = activityManagerRepository, activityManagerRepository = activityManagerRepository, statusBarModeRepositoryStore = fakeStatusBarModeRepository, statusBarWindowControllerStore = fakeStatusBarWindowControllerStore, logBuffer = logcatLogBuffer("OngoingCallInteractorKosmos"), logBuffer = logcatLogBuffer("OngoingCallInteractorKosmos"), ) ) } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt +127 −8 Original line number Original line Diff line number Diff line Loading @@ -30,11 +30,13 @@ import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.StatusBarIconView 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.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest Loading Loading @@ -72,7 +74,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { whenTime = 1000L, whenTime = 1000L, callType = CallType.Ongoing, callType = CallType.Ongoing, statusBarChipIcon = testIconView, statusBarChipIcon = testIconView, contentIntent = testIntent contentIntent = testIntent, ) ) ) ) } } Loading @@ -95,7 +97,9 @@ class OngoingCallInteractorTest : SysuiTestCase() { .apply { .apply { addIndividualNotif( addIndividualNotif( activeNotificationModel( activeNotificationModel( key = "notif1", whenTime = 1000L, callType = CallType.Ongoing key = "notif1", whenTime = 1000L, callType = CallType.Ongoing, ) ) ) ) } } Loading @@ -114,7 +118,9 @@ class OngoingCallInteractorTest : SysuiTestCase() { .apply { .apply { addIndividualNotif( addIndividualNotif( activeNotificationModel( activeNotificationModel( key = "notif1", whenTime = 1000L, callType = CallType.Ongoing key = "notif1", whenTime = 1000L, callType = CallType.Ongoing, ) ) ) ) } } Loading @@ -138,7 +144,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { key = "notif1", key = "notif1", whenTime = 1000L, whenTime = 1000L, callType = CallType.Ongoing, callType = CallType.Ongoing, uid = UID uid = UID, ) ) ) ) } } Loading @@ -161,7 +167,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { key = "notif1", key = "notif1", whenTime = 1000L, whenTime = 1000L, callType = CallType.Ongoing, callType = CallType.Ongoing, uid = UID uid = UID, ) ) ) ) } } Loading @@ -185,13 +191,12 @@ class OngoingCallInteractorTest : SysuiTestCase() { key = "notif1", key = "notif1", whenTime = 1000L, whenTime = 1000L, callType = CallType.Ongoing, callType = CallType.Ongoing, uid = UID uid = UID, ) ) ) ) } } .build() .build() assertThat(latest) assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) .isInstanceOf(OngoingCallModel.InCall::class.java) // App becomes visible // App becomes visible kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true) kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true) Loading @@ -202,6 +207,120 @@ class OngoingCallInteractorTest : SysuiTestCase() { assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) 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 { companion object { private const val UID = 885 private const val UID = 885 } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt +34 −2 Original line number Original line Diff line number Diff line Loading @@ -37,6 +37,7 @@ import com.android.systemui.statusbar.phone.BoundsPair import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator import com.android.systemui.statusbar.phone.StatusBarBoundsProvider import com.android.systemui.statusbar.phone.StatusBarBoundsProvider import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent 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.data.repository.OngoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import dagger.assisted.Assisted import dagger.assisted.Assisted Loading Loading @@ -89,6 +90,9 @@ interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener /** The current mode of the status bar. */ /** The current mode of the status bar. */ val statusBarMode: StateFlow<StatusBarMode> 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. * Requests for the status bar to be shown transiently. * * Loading @@ -110,6 +114,12 @@ interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener * if needed. * if needed. */ */ fun stop() 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 class StatusBarModePerDisplayRepositoryImpl Loading Loading @@ -195,6 +205,16 @@ constructor( statusBarBoundsProvider.addChangeListener(listener) 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> = override val isInFullscreenMode: StateFlow<Boolean> = _originalStatusBarAttributes _originalStatusBarAttributes .map { params -> .map { params -> Loading Loading @@ -235,16 +255,28 @@ constructor( isTransientShown, isTransientShown, isInFullscreenMode, isInFullscreenMode, ongoingCallRepository.ongoingCallState, ongoingCallRepository.ongoingCallState, ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallState -> _ongoingProcessRequiresStatusBarVisible, ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallStateLegacy, ongoingProcessRequiresStatusBarVisible -> if (modifiedAttributes == null) { if (modifiedAttributes == null) { null null } else { } else { val hasOngoingCall = if (StatusBarChipsModernization.isEnabled) { ongoingProcessRequiresStatusBarVisible } else { ongoingCallStateLegacy is OngoingCallModel.InCall } val statusBarMode = val statusBarMode = toBarMode( toBarMode( modifiedAttributes.appearance, modifiedAttributes.appearance, isTransientShown, isTransientShown, isInFullscreenMode, isInFullscreenMode, hasOngoingCall = ongoingCallState is OngoingCallModel.InCall, hasOngoingCall, ) ) StatusBarAppearance( StatusBarAppearance( statusBarMode, statusBarMode, Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt +76 −37 Original line number Original line Diff line number Diff line Loading @@ -21,17 +21,22 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger 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.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.OngoingCallLog import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn /** /** Loading @@ -47,7 +52,9 @@ import kotlinx.coroutines.flow.stateIn @SysUISingleton @SysUISingleton class OngoingCallInteractor @Inject constructor( class OngoingCallInteractor @Inject constructor( @Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope, activityManagerRepository: ActivityManagerRepository, private val activityManagerRepository: ActivityManagerRepository, private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore, private val statusBarWindowControllerStore: StatusBarWindowControllerStore, activeNotificationsInteractor: ActiveNotificationsInteractor, activeNotificationsInteractor: ActiveNotificationsInteractor, @OngoingCallLog private val logBuffer: LogBuffer, @OngoingCallLog private val logBuffer: LogBuffer, ) { ) { Loading @@ -58,22 +65,45 @@ class OngoingCallInteractor @Inject constructor( */ */ val ongoingCallState: StateFlow<OngoingCallModel> = val ongoingCallState: StateFlow<OngoingCallModel> = activeNotificationsInteractor.ongoingCallNotification activeNotificationsInteractor.ongoingCallNotification .flatMapLatest { notificationModel -> .flatMapLatest { notification -> when (notificationModel) { createOngoingCallStateFlow( null -> { 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") logger.d("No active call notification - hiding chip") flowOf(OngoingCallModel.NoCall) return flowOf(OngoingCallModel.NoCall) } } else -> combine( return combine( flowOf(notificationModel), flowOf(notification), activityManagerRepository.createIsAppVisibleFlow( activityManagerRepository.createIsAppVisibleFlow( creationUid = notificationModel.uid, creationUid = notification.uid, logger = logger, logger = logger, identifyingLogTag = TAG, identifyingLogTag = TAG, ), ) ) { model, isVisible -> ) { model, isVisible -> when { deriveOngoingCallState(model, isVisible) } } private fun deriveOngoingCallState( model: ActiveNotificationModel, isVisible: Boolean ): OngoingCallModel { return when { isVisible -> { isVisible -> { logger.d({ "Call app is visible: uid=$int1" }) { logger.d({ "Call app is visible: uid=$int1" }) { int1 = model.uid int1 = model.uid Loading @@ -90,14 +120,23 @@ class OngoingCallInteractor @Inject constructor( startTimeMs = model.whenTime, startTimeMs = model.whenTime, notificationIconView = model.statusBarChipIconView, notificationIconView = model.statusBarChipIconView, intent = model.contentIntent, intent = model.contentIntent, notificationKey = model.key, notificationKey = model.key ) ) } } } } } } 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) } } } .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingCallModel.NoCall) companion object { companion object { private val TAG = "OngoingCall" private val TAG = "OngoingCall" Loading
packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -47,6 +47,7 @@ class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository override val isInFullscreenMode = MutableStateFlow(false) override val isInFullscreenMode = MutableStateFlow(false) override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null) override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null) override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT) override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT) override val ongoingProcessRequiresStatusBarVisible = MutableStateFlow(false) override fun showTransient() { override fun showTransient() { isTransientShown.value = true isTransientShown.value = true Loading @@ -59,6 +60,9 @@ class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository override fun start() {} override fun start() {} override fun stop() {} override fun stop() {} override fun setOngoingProcessRequiresStatusBarVisible(requiredVisible: Boolean) { ongoingProcessRequiresStatusBarVisible.value = requiredVisible } override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {} override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {} Loading
packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -20,7 +20,9 @@ import com.android.systemui.activity.data.repository.activityManagerRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.logcatLogBuffer 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.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore val Kosmos.ongoingCallInteractor: OngoingCallInteractor by val Kosmos.ongoingCallInteractor: OngoingCallInteractor by Kosmos.Fixture { Kosmos.Fixture { Loading @@ -28,6 +30,8 @@ val Kosmos.ongoingCallInteractor: OngoingCallInteractor by scope = applicationCoroutineScope, scope = applicationCoroutineScope, activeNotificationsInteractor = activeNotificationsInteractor, activeNotificationsInteractor = activeNotificationsInteractor, activityManagerRepository = activityManagerRepository, activityManagerRepository = activityManagerRepository, statusBarModeRepositoryStore = fakeStatusBarModeRepository, statusBarWindowControllerStore = fakeStatusBarWindowControllerStore, logBuffer = logcatLogBuffer("OngoingCallInteractorKosmos"), logBuffer = logcatLogBuffer("OngoingCallInteractorKosmos"), ) ) } }