Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6480ad21 authored by Olivier St-Onge's avatar Olivier St-Onge Committed by Android (Google) Code Review
Browse files

Merge "Listen for active sub id for stacked icon" into main

parents 1cc95c9f 56c3d217
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -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.
+145 −0
Original line number Diff line number Diff line
/*
 * 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,
            )
    }
}
+11 −2
Original line number Diff line number Diff line
@@ -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 }
+2 −0
Original line number Diff line number Diff line
@@ -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 ->
+9 −3
Original line number Diff line number Diff line
@@ -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(
Loading