diff options
| author | 2025-02-13 13:26:42 -0500 | |
|---|---|---|
| committer | 2025-02-14 08:41:34 -0500 | |
| commit | 56c3d217d4ca30132980ee03a63c6c25be34689a (patch) | |
| tree | 5d447ccab0eff9de419d24600a006ff02e381241 | |
| parent | 2c3fffafb79984921f423ce1360e2fde5e4cfd83 (diff) | |
Listen for active sub id for stacked icon
Also adds unit tests for the stacked icon logic
Flag: com.android.systemui.status_bar_root_modernization
Flag: com.android.settingslib.flags.new_status_bar_icons
Fixes: 395604203
Test: MobileIconsInteractorTest.kt
Test: StackedMobileIconViewModelTest.kt
Change-Id: I7f57797ec9430b3f0148256c6f097637d5bd9a78
7 files changed, 231 insertions, 5 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index d17abd7da05c..dbfd57fd0f0b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.os.ParcelUuid +import android.platform.test.annotations.EnableFlags import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET @@ -33,6 +34,8 @@ import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.core.NewStatusBarIcons +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake @@ -897,6 +900,43 @@ class MobileIconsInteractorTest : SysuiTestCase() { assertThat(latest).isEqualTo(2) } + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun isStackable_tracksNumberOfSubscriptions() = + kosmos.runTest { + val latest by collectLastValue(underTest.isStackable) + + connectionsRepository.setSubscriptions(listOf(SUB_1)) + assertThat(latest).isFalse() + + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) + assertThat(latest).isTrue() + + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2, SUB_3_OPP)) + assertThat(latest).isFalse() + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun isStackable_checksForTerrestrialConnections() = + kosmos.runTest { + val exclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = true, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) + + val latest by collectLastValue(underTest.isStackable) + + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) + assertThat(latest).isTrue() + + connectionsRepository.setSubscriptions(listOf(SUB_1, exclusivelyNonTerrestrialSub)) + assertThat(latest).isFalse() + } + /** * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions * flow. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt new file mode 100644 index 000000000000..20bdebd069f7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelTest.kt @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2025 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.statusbar.pipeline.mobile.ui.viewmodel + +import android.platform.test.annotations.EnableFlags +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.statusbar.core.NewStatusBarIcons +import com.android.systemui.statusbar.core.StatusBarRootModernization +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class StackedMobileIconViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val testScope = kosmos.testScope + + private val Kosmos.underTest: StackedMobileIconViewModel by Fixture { + stackedMobileIconViewModel + } + + @Before + fun setUp() { + kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false) + kosmos.underTest.activateIn(testScope) + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun dualSim_filtersOutNonDualConnections() = + kosmos.runTest { + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf() + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1) + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3) + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + assertThat(underTest.dualSim).isNotNull() + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun dualSim_filtersOutNonCellularIcons() = + kosmos.runTest { + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1) + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor + .getInteractorForSubId(SUB_1.subscriptionId)!! + .signalLevelIcon + .value = + SignalIconModel.Satellite( + level = 0, + icon = Icon.Resource(res = 0, contentDescription = null), + ) + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + assertThat(underTest.dualSim).isNull() + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun dualSim_tracksActiveSubId() = + kosmos.runTest { + // Active sub id is null, order is unchanged + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + setIconLevel(SUB_1.subscriptionId, 1) + setIconLevel(SUB_2.subscriptionId, 2) + + assertThat(underTest.dualSim!!.primary.level).isEqualTo(1) + assertThat(underTest.dualSim!!.secondary.level).isEqualTo(2) + + // Active sub is 2, order is swapped + fakeMobileIconsInteractor.activeMobileDataSubscriptionId.value = SUB_2.subscriptionId + + assertThat(underTest.dualSim!!.primary.level).isEqualTo(2) + assertThat(underTest.dualSim!!.secondary.level).isEqualTo(1) + } + + private fun setIconLevel(subId: Int, level: Int) { + with(kosmos.fakeMobileIconsInteractor.getInteractorForSubId(subId)!!) { + signalLevelIcon.value = + (signalLevelIcon.value as SignalIconModel.Cellular).copy(level = level) + } + } + + companion object { + private val SUB_1 = + SubscriptionModel( + subscriptionId = 1, + isOpportunistic = false, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, + ) + private val SUB_2 = + SubscriptionModel( + subscriptionId = 2, + isOpportunistic = false, + carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, + ) + private val SUB_3 = + SubscriptionModel( + subscriptionId = 3, + isOpportunistic = false, + carrierName = "Carrier 3", + profileClass = PROFILE_CLASS_UNSET, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index c52536d2b312..cee685a3f47a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -21,7 +21,6 @@ import android.telephony.CarrierConfigManager import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING import com.android.settingslib.SignalIcon.MobileIconGroup -import com.android.settingslib.flags.Flags import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -29,6 +28,7 @@ import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.statusbar.core.NewStatusBarIcons import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel @@ -85,6 +85,12 @@ interface MobileIconsInteractor { /** Whether the mobile icons can be stacked vertically. */ val isStackable: StateFlow<Boolean> + /** + * Observable for the subscriptionId of the current mobile data connection. Null if we don't + * have a valid subscription id + */ + val activeMobileDataSubscriptionId: StateFlow<Int?> + /** True if the active mobile data subscription has data enabled */ val activeDataConnectionHasDataEnabled: StateFlow<Boolean> @@ -168,6 +174,9 @@ constructor( ) .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val activeMobileDataSubscriptionId: StateFlow<Int?> = + mobileConnectionsRepo.activeMobileDataSubscriptionId + override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> = mobileConnectionsRepo.activeMobileDataRepository .flatMapLatest { it?.dataEnabled ?: flowOf(false) } @@ -298,7 +307,7 @@ constructor( .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList()) override val isStackable = - if (Flags.newStatusBarIcons() && StatusBarRootModernization.isEnabled) { + if (NewStatusBarIcons.isEnabled && StatusBarRootModernization.isEnabled) { icons.flatMapLatest { icons -> combine(icons.map { it.isNonTerrestrial }) { it.size == 2 && it.none { isNonTerrestrial -> isNonTerrestrial } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index 6176a3e9e281..288e49eac5a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -63,6 +63,8 @@ constructor( @VisibleForTesting val reuseCache = ConcurrentHashMap<Int, Pair<MobileIconViewModel, CoroutineScope>>() + val activeMobileDataSubscriptionId: StateFlow<Int?> = interactor.activeMobileDataSubscriptionId + val subscriptionIdsFlow: StateFlow<List<Int>> = interactor.filteredSubscriptions .mapLatest { subscriptions -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt index a2c2a3cd1507..2c85a5150575 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModel.kt @@ -25,7 +25,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconMod import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -43,8 +43,14 @@ constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable() initialValue = false, ) - private val iconViewModelFlow: StateFlow<List<MobileIconViewModelCommon>> = - mobileIconsViewModel.mobileSubViewModels + private val iconViewModelFlow: Flow<List<MobileIconViewModelCommon>> = + combine( + mobileIconsViewModel.mobileSubViewModels, + mobileIconsViewModel.activeMobileDataSubscriptionId, + ) { viewModels, activeSubId -> + // Sort to get the active subscription first, if it's set + viewModels.sortedByDescending { it.subscriptionId == activeSubId } + } val dualSim: DualSim? by hydrator.hydratedStateOf( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index 9b6f205fba72..8fa82cad5c32 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -58,6 +58,8 @@ class FakeMobileIconsInteractor( override val defaultDataSubId: MutableStateFlow<Int?> = MutableStateFlow(DEFAULT_DATA_SUB_ID) + override val activeMobileDataSubscriptionId: MutableStateFlow<Int?> = MutableStateFlow(null) + private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false) override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt new file mode 100644 index 000000000000..880ba5eee5d2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2025 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.statusbar.pipeline.mobile.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.stackedMobileIconViewModel: StackedMobileIconViewModel by + Kosmos.Fixture { StackedMobileIconViewModel(mobileIconsViewModel) } |