diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index 1169d3f21e280..c7be2193e9b54 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -40,6 +40,8 @@ import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; +import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView; +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel; import java.util.ArrayList; import java.util.List; @@ -50,20 +52,25 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da private final LinearLayout mStatusIcons; private final ArrayList mMobileViews = new ArrayList<>(); + private final ArrayList mModernMobileViews = new ArrayList<>(); private final int mIconSize; private StatusBarWifiView mWifiView; private boolean mDemoMode; private int mColor; + private final MobileIconsViewModel mMobileIconsViewModel; + public DemoStatusIcons( LinearLayout statusIcons, + MobileIconsViewModel mobileIconsViewModel, int iconSize ) { super(statusIcons.getContext()); mStatusIcons = statusIcons; mIconSize = iconSize; mColor = DarkIconDispatcher.DEFAULT_ICON_TINT; + mMobileIconsViewModel = mobileIconsViewModel; if (statusIcons instanceof StatusIconContainer) { setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons()); @@ -71,7 +78,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da setShouldRestrictIcons(false); } setLayoutParams(mStatusIcons.getLayoutParams()); - setPadding(mStatusIcons.getPaddingLeft(),mStatusIcons.getPaddingTop(), + setPadding(mStatusIcons.getPaddingLeft(), mStatusIcons.getPaddingTop(), mStatusIcons.getPaddingRight(), mStatusIcons.getPaddingBottom()); setOrientation(mStatusIcons.getOrientation()); setGravity(Gravity.CENTER_VERTICAL); // no LL.getGravity() @@ -115,6 +122,8 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da public void onDemoModeFinished() { mDemoMode = false; mStatusIcons.setVisibility(View.VISIBLE); + mModernMobileViews.clear(); + mMobileViews.clear(); setVisibility(View.GONE); } @@ -268,6 +277,24 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da addView(view, getChildCount(), createLayoutParams()); } + /** + * Add a {@link ModernStatusBarMobileView} + * @param mobileContext possibly mcc/mnc overridden mobile context + * @param subId the subscriptionId for this mobile view + */ + public void addModernMobileView(Context mobileContext, int subId) { + Log.d(TAG, "addModernMobileView (subId=" + subId + ")"); + ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind( + mobileContext, + "mobile", + mMobileIconsViewModel.viewModelForSub(subId) + ); + + // mobile always goes at the end + mModernMobileViews.add(view); + addView(view, getChildCount(), createLayoutParams()); + } + /** * Apply an update to a mobile icon view for the given {@link MobileIconState}. For * compatibility with {@link MobileContextProvider}, we have to recreate the view every time we @@ -292,12 +319,19 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da if (view.getSlot().equals("wifi")) { removeView(mWifiView); mWifiView = null; - } else { + } else if (view instanceof StatusBarMobileView) { StatusBarMobileView mobileView = matchingMobileView(view); if (mobileView != null) { removeView(mobileView); mMobileViews.remove(mobileView); } + } else if (view instanceof ModernStatusBarMobileView) { + ModernStatusBarMobileView mobileView = matchingModernMobileView( + (ModernStatusBarMobileView) view); + if (mobileView != null) { + removeView(mobileView); + mModernMobileViews.remove(mobileView); + } } } @@ -316,6 +350,16 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da return null; } + private ModernStatusBarMobileView matchingModernMobileView(ModernStatusBarMobileView other) { + for (ModernStatusBarMobileView v : mModernMobileViews) { + if (v.getSubId() == other.getSubId()) { + return v; + } + } + + return null; + } + private LayoutParams createLayoutParams() { return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 0a0ded24ef306..df3ab493a4dae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -536,8 +536,7 @@ public interface StatusBarIconController { mGroup.addView(view, index, onCreateLayoutParams()); if (mIsInDemoMode) { - // TODO (b/249790009): demo mode should be handled at the data layer in the - // new pipeline + mDemoStatusIcons.addModernMobileView(mContext, subId); } return view; @@ -565,11 +564,13 @@ public interface StatusBarIconController { private ModernStatusBarMobileView onCreateModernStatusBarMobileView( String slot, int subId) { + Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext); return ModernStatusBarMobileView .constructAndBind( - mContext, + mobileContext, slot, - mMobileIconsViewModel.viewModelForSub(subId)); + mMobileIconsViewModel.viewModelForSub(subId) + ); } protected LinearLayout.LayoutParams onCreateLayoutParams() { @@ -704,7 +705,7 @@ public interface StatusBarIconController { } protected DemoStatusIcons createDemoStatusIcons() { - return new DemoStatusIcons((LinearLayout) mGroup, mIconSize); + return new DemoStatusIcons((LinearLayout) mGroup, mMobileIconsViewModel, mIconSize); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index 674e5747e331b..9fbe6cbc0e326 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -276,6 +276,11 @@ public class StatusBarIconControllerImpl implements Tunable, String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile); Slot mobileSlot = mStatusBarIconList.getSlot(slotName); + // Because of the way we cache the icon holders, we need to remove everything any time + // we get a new set of subscriptions. This might change in the future, but is required + // to support demo mode for now + removeAllIconsForSlot(slotName); + Collections.reverse(subIds); for (Integer subId : subIds) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt similarity index 94% rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt index 6341a114112c5..1d00c330c420f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt @@ -27,7 +27,6 @@ import android.telephony.TelephonyCallback.ServiceStateListener import android.telephony.TelephonyCallback.SignalStrengthsListener import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyManager -import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected /** @@ -39,7 +38,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionS * any new field that needs to be tracked should be copied into this data class rather than * threading complex system objects through the pipeline. */ -data class MobileSubscriptionModel( +data class MobileConnectionModel( /** From [ServiceStateListener.onServiceStateChanged] */ val isEmergencyOnly: Boolean = false, @@ -65,5 +64,5 @@ data class MobileSubscriptionModel( * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon */ - val resolvedNetworkType: ResolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN), + val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt index f385806c1b223..dd93541d7c8f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.model import android.telephony.Annotation.NetworkType +import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy /** @@ -26,8 +27,20 @@ import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy */ sealed interface ResolvedNetworkType { @NetworkType val type: Int + val lookupKey: String + + object UnknownNetworkType : ResolvedNetworkType { + override val type: Int = NETWORK_TYPE_UNKNOWN + override val lookupKey: String = "unknown" + } + + data class DefaultNetworkType( + @NetworkType override val type: Int, + override val lookupKey: String, + ) : ResolvedNetworkType + + data class OverrideNetworkType( + @NetworkType override val type: Int, + override val lookupKey: String, + ) : ResolvedNetworkType } - -data class DefaultNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType - -data class OverrideNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt new file mode 100644 index 0000000000000..2f34516285cf2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 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.data.model + +/** + * SystemUI representation of [SubscriptionInfo]. Currently we only use two fields on the + * subscriptions themselves: subscriptionId and isOpportunistic. Any new fields that we need can be + * added below and provided in the repository classes + */ +data class SubscriptionModel( + val subscriptionId: Int, + /** + * True if the subscription that this model represents has [SubscriptionInfo.isOpportunistic]. + * Opportunistic networks are networks with limited coverage, and we use this bit to determine + * filtering in certain cases. See [MobileIconsInteractor] for the filtering logic + */ + val isOpportunistic: Boolean = false, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index f09456342f78f..2621f997d4865 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -20,7 +20,7 @@ import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.TelephonyCallback import android.telephony.TelephonyManager -import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -36,11 +36,13 @@ import kotlinx.coroutines.flow.StateFlow * eventually becomes a single icon in the status bar. */ interface MobileConnectionRepository { + /** The subscriptionId that this connection represents */ + val subId: Int /** * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single * listener + model. */ - val subscriptionModelFlow: Flow + val connectionInfo: Flow /** Observable tracking [TelephonyManager.isDataConnectionAllowed] */ val dataEnabled: StateFlow /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt index 14200f090c87c..aea85eb020bd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt @@ -17,11 +17,10 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository import android.provider.Settings -import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager -import com.android.settingslib.mobile.MobileMappings -import com.android.settingslib.mobile.MobileMappings.Config +import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -31,14 +30,11 @@ import kotlinx.coroutines.flow.StateFlow */ interface MobileConnectionsRepository { /** Observable list of current mobile subscriptions */ - val subscriptionsFlow: Flow> + val subscriptions: StateFlow> /** Observable for the subscriptionId of the current mobile data connection */ val activeMobileDataSubscriptionId: StateFlow - /** Observable for [MobileMappings.Config] tracking the defaults */ - val defaultDataSubRatConfig: StateFlow - /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */ val defaultDataSubId: StateFlow @@ -50,4 +46,10 @@ interface MobileConnectionsRepository { /** Observe changes to the [Settings.Global.MOBILE_DATA] setting */ val globalMobileDataSettingChangedEvent: Flow + + /** The icon mapping from network type to [MobileIconGroup] for the default subscription */ + val defaultMobileIconMapping: Flow> + + /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */ + val defaultMobileIconGroup: Flow } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt index e21400525f00b..d8e0e81837c10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt @@ -17,14 +17,14 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository import android.os.Bundle -import android.telephony.SubscriptionInfo import androidx.annotation.VisibleForTesting -import com.android.settingslib.mobile.MobileMappings +import com.android.settingslib.SignalIcon import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl import javax.inject.Inject @@ -109,14 +109,10 @@ constructor( } .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository) - override val subscriptionsFlow: StateFlow> = + override val subscriptions: StateFlow> = activeRepo - .flatMapLatest { it.subscriptionsFlow } - .stateIn( - scope, - SharingStarted.WhileSubscribed(), - realRepository.subscriptionsFlow.value - ) + .flatMapLatest { it.subscriptions } + .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.subscriptions.value) override val activeMobileDataSubscriptionId: StateFlow = activeRepo @@ -127,14 +123,11 @@ constructor( realRepository.activeMobileDataSubscriptionId.value ) - override val defaultDataSubRatConfig: StateFlow = - activeRepo - .flatMapLatest { it.defaultDataSubRatConfig } - .stateIn( - scope, - SharingStarted.WhileSubscribed(), - realRepository.defaultDataSubRatConfig.value - ) + override val defaultMobileIconMapping: Flow> = + activeRepo.flatMapLatest { it.defaultMobileIconMapping } + + override val defaultMobileIconGroup: Flow = + activeRepo.flatMapLatest { it.defaultMobileIconGroup } override val defaultDataSubId: StateFlow = activeRepo diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt index 5f2feb26b739a..1e7fae717a2de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt @@ -17,26 +17,18 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo import android.content.Context -import android.telephony.Annotation -import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID -import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED -import android.telephony.TelephonyManager.NETWORK_TYPE_GSM -import android.telephony.TelephonyManager.NETWORK_TYPE_LTE -import android.telephony.TelephonyManager.NETWORK_TYPE_NR -import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS -import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import android.util.Log import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState -import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel -import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel -import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel @@ -49,7 +41,9 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn @@ -67,61 +61,40 @@ constructor( private var demoCommandJob: Job? = null - private val connectionRepoCache = mutableMapOf() - private val subscriptionInfoCache = mutableMapOf() + private var connectionRepoCache = mutableMapOf() + private val subscriptionInfoCache = mutableMapOf() val demoModeFinishedEvent = MutableSharedFlow(extraBufferCapacity = 1) - private val _subscriptions = MutableStateFlow>(listOf()) - override val subscriptionsFlow = + private val _subscriptions = MutableStateFlow>(listOf()) + override val subscriptions = _subscriptions .onEach { infos -> dropUnusedReposFromCache(infos) } .stateIn(scope, SharingStarted.WhileSubscribed(), _subscriptions.value) - private fun dropUnusedReposFromCache(newInfos: List) { + private fun dropUnusedReposFromCache(newInfos: List) { // Remove any connection repository from the cache that isn't in the new set of IDs. They // will get garbage collected once their subscribers go away val currentValidSubscriptionIds = newInfos.map { it.subscriptionId } - connectionRepoCache.keys.forEach { - if (!currentValidSubscriptionIds.contains(it)) { - connectionRepoCache.remove(it) - } - } + connectionRepoCache = + connectionRepoCache + .filter { currentValidSubscriptionIds.contains(it.key) } + .toMutableMap() } private fun maybeCreateSubscription(subId: Int) { if (!subscriptionInfoCache.containsKey(subId)) { - createSubscriptionForSubId(subId, subId).also { subscriptionInfoCache[subId] = it } + SubscriptionModel(subscriptionId = subId, isOpportunistic = false).also { + subscriptionInfoCache[subId] = it + } _subscriptions.value = subscriptionInfoCache.values.toList() } } - /** Mimics the old NetworkControllerImpl for now */ - private fun createSubscriptionForSubId(subId: Int, slotIndex: Int): SubscriptionInfo { - return SubscriptionInfo( - subId, - "", - slotIndex, - "", - "", - 0, - 0, - "", - 0, - null, - null, - null, - "", - false, - null, - null, - ) - } - // TODO(b/261029387): add a command for this value override val activeMobileDataSubscriptionId = - subscriptionsFlow + subscriptions .mapLatest { infos -> // For now, active is just the first in the list infos.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID @@ -129,12 +102,35 @@ constructor( .stateIn( scope, SharingStarted.WhileSubscribed(), - subscriptionsFlow.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID + subscriptions.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID ) /** Demo mode doesn't currently support modifications to the mobile mappings */ - override val defaultDataSubRatConfig = - MutableStateFlow(MobileMappings.Config.readConfig(context)) + val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config.readConfig(context)) + + override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G) + + override val defaultMobileIconMapping = MutableStateFlow(TelephonyIcons.ICON_NAME_TO_ICON) + + /** + * In order to maintain compatibility with the old demo mode shell command API, reverse the + * [MobileMappings] lookup from (NetworkType: String -> Icon: MobileIconGroup), so that we can + * parse the string from the command line into a preferred icon group, and send _a_ valid + * network type for that icon through the pipeline. + * + * Note: collisions don't matter here, because the data source (the command line) only cares + * about the resulting icon, not the underlying network type. + */ + private val mobileMappingsReverseLookup: StateFlow> = + defaultMobileIconMapping + .mapLatest { networkToIconMap -> networkToIconMap.reverse() } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + defaultMobileIconMapping.value.reverse() + ) + + private fun Map.reverse() = entries.associateBy({ it.value }) { it.key } // TODO(b/261029387): add a command for this value override val defaultDataSubId = @@ -189,7 +185,7 @@ constructor( connection.dataEnabled.value = true connection.isDefaultDataSubscription.value = state.dataType != null - connection.subscriptionModelFlow.value = state.toMobileSubscriptionModel() + connection.connectionInfo.value = state.toMobileConnectionModel() } private fun processDisabledMobileState(state: MobileDisabled) { @@ -229,47 +225,36 @@ constructor( private fun subIdsString(): String = _subscriptions.value.joinToString(",") { it.subscriptionId.toString() } + private fun Mobile.toMobileConnectionModel(): MobileConnectionModel { + return MobileConnectionModel( + isEmergencyOnly = false, // TODO(b/261029387): not yet supported + isGsm = false, // TODO(b/261029387): not yet supported + cdmaLevel = level ?: 0, + primaryLevel = level ?: 0, + dataConnectionState = + DataConnectionState.Connected, // TODO(b/261029387): not yet supported + dataActivityDirection = activity, + carrierNetworkChangeActive = carrierNetworkChange, + resolvedNetworkType = dataType.toResolvedNetworkType() + ) + } + + private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType { + val key = mobileMappingsReverseLookup.value[this] ?: "dis" + return DefaultNetworkType(DEMO_NET_TYPE, key) + } + companion object { private const val TAG = "DemoMobileConnectionsRepo" private const val DEFAULT_SUB_ID = 1 + + private const val DEMO_NET_TYPE = 1234 } } -private fun Mobile.toMobileSubscriptionModel(): MobileSubscriptionModel { - return MobileSubscriptionModel( - isEmergencyOnly = false, // TODO(b/261029387): not yet supported - isGsm = false, // TODO(b/261029387): not yet supported - cdmaLevel = level ?: 0, - primaryLevel = level ?: 0, - dataConnectionState = DataConnectionState.Connected, // TODO(b/261029387): not yet supported - dataActivityDirection = activity, - carrierNetworkChangeActive = carrierNetworkChange, - // TODO(b/261185097): once mobile mappings can be mocked at this layer, we can build our - // own demo map - resolvedNetworkType = dataType.toResolvedNetworkType() - ) -} - -@Annotation.NetworkType -private fun SignalIcon.MobileIconGroup?.toNetworkType(): Int = - when (this) { - TelephonyIcons.THREE_G -> NETWORK_TYPE_GSM - TelephonyIcons.LTE -> NETWORK_TYPE_LTE - TelephonyIcons.FOUR_G -> NETWORK_TYPE_UMTS - TelephonyIcons.NR_5G -> NETWORK_TYPE_NR - TelephonyIcons.NR_5G_PLUS -> OVERRIDE_NETWORK_TYPE_NR_ADVANCED - else -> NETWORK_TYPE_UNKNOWN - } - -private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType = - when (this) { - TelephonyIcons.NR_5G_PLUS -> OverrideNetworkType(toNetworkType()) - else -> DefaultNetworkType(toNetworkType()) - } - -class DemoMobileConnectionRepository(val subId: Int) : MobileConnectionRepository { - override val subscriptionModelFlow = MutableStateFlow(MobileSubscriptionModel()) +class DemoMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository { + override val connectionInfo = MutableStateFlow(MobileConnectionModel()) override val dataEnabled = MutableStateFlow(true) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index 4c1cf4a3ed08b..15505fd3d9e52 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -27,18 +27,20 @@ import android.telephony.TelephonyCallback import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE import android.telephony.TelephonyManager +import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType -import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel -import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange import com.android.systemui.util.settings.GlobalSettings -import java.lang.IllegalStateException import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -58,11 +60,12 @@ import kotlinx.coroutines.flow.stateIn @OptIn(ExperimentalCoroutinesApi::class) class MobileConnectionRepositoryImpl( private val context: Context, - private val subId: Int, + override val subId: Int, private val telephonyManager: TelephonyManager, private val globalSettings: GlobalSettings, defaultDataSubId: StateFlow, globalMobileDataSettingChangedEvent: Flow, + mobileMappingsProxy: MobileMappingsProxy, bgDispatcher: CoroutineDispatcher, logger: ConnectivityPipelineLogger, scope: CoroutineScope, @@ -78,8 +81,8 @@ class MobileConnectionRepositoryImpl( private val telephonyCallbackEvent = MutableSharedFlow(extraBufferCapacity = 1) - override val subscriptionModelFlow: StateFlow = run { - var state = MobileSubscriptionModel() + override val connectionInfo: StateFlow = run { + var state = MobileConnectionModel() conflatedCallbackFlow { // TODO (b/240569788): log all of these into the connectivity logger val callback = @@ -141,14 +144,27 @@ class MobileConnectionRepositoryImpl( override fun onDisplayInfoChanged( telephonyDisplayInfo: TelephonyDisplayInfo ) { + val networkType = - if ( + if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) { + UnknownNetworkType + } else if ( telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE ) { - DefaultNetworkType(telephonyDisplayInfo.networkType) + DefaultNetworkType( + telephonyDisplayInfo.networkType, + mobileMappingsProxy.toIconKey( + telephonyDisplayInfo.networkType + ) + ) } else { - OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType) + OverrideNetworkType( + telephonyDisplayInfo.overrideNetworkType, + mobileMappingsProxy.toIconKeyOverride( + telephonyDisplayInfo.overrideNetworkType + ) + ) } state = state.copy(resolvedNetworkType = networkType) trySend(state) @@ -211,6 +227,7 @@ class MobileConnectionRepositoryImpl( private val telephonyManager: TelephonyManager, private val logger: ConnectivityPipelineLogger, private val globalSettings: GlobalSettings, + private val mobileMappingsProxy: MobileMappingsProxy, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, ) { @@ -226,6 +243,7 @@ class MobileConnectionRepositoryImpl( globalSettings, defaultDataSubId, globalMobileDataSettingChangedEvent, + mobileMappingsProxy, bgDispatcher, logger, scope, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 08d6010f0d99d..f27a9c9cca987 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -36,6 +36,7 @@ import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener import android.telephony.TelephonyManager import androidx.annotation.VisibleForTesting import com.android.internal.telephony.PhoneConstants +import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileMappings.Config import com.android.systemui.broadcast.BroadcastDispatcher @@ -44,8 +45,10 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject @@ -59,6 +62,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach @@ -75,6 +79,7 @@ constructor( private val subscriptionManager: SubscriptionManager, private val telephonyManager: TelephonyManager, private val logger: ConnectivityPipelineLogger, + mobileMappingsProxy: MobileMappingsProxy, broadcastDispatcher: BroadcastDispatcher, private val globalSettings: GlobalSettings, private val context: Context, @@ -82,14 +87,14 @@ constructor( @Application private val scope: CoroutineScope, private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory ) : MobileConnectionsRepository { - private val subIdRepositoryCache: MutableMap = mutableMapOf() + private var subIdRepositoryCache: MutableMap = mutableMapOf() /** * State flow that emits the set of mobile data subscriptions, each represented by its own * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each * info object, but for now we keep track of the infos themselves. */ - override val subscriptionsFlow: StateFlow> = + override val subscriptions: StateFlow> = conflatedCallbackFlow { val callback = object : SubscriptionManager.OnSubscriptionsChangedListener() { @@ -105,7 +110,7 @@ constructor( awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) } } - .mapLatest { fetchSubscriptionsList() } + .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } } .onEach { infos -> dropUnusedReposFromCache(infos) } .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf()) @@ -157,7 +162,7 @@ constructor( * * This flow will produce whenever the default data subscription or the carrier config changes. */ - override val defaultDataSubRatConfig: StateFlow = + private val defaultDataSubRatConfig: StateFlow = merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent) .mapLatest { Config.readConfig(context) } .stateIn( @@ -166,6 +171,12 @@ constructor( initialValue = Config.readConfig(context) ) + override val defaultMobileIconMapping: Flow> = + defaultDataSubRatConfig.map { mobileMappingsProxy.mapIconSets(it) } + + override val defaultMobileIconGroup: Flow = + defaultDataSubRatConfig.map { mobileMappingsProxy.getDefaultIcons(it) } + override fun getRepoForSubId(subId: Int): MobileConnectionRepository { if (!isValidSubId(subId)) { throw IllegalArgumentException( @@ -229,7 +240,7 @@ constructor( .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel()) private fun isValidSubId(subId: Int): Boolean { - subscriptionsFlow.value.forEach { + subscriptions.value.forEach { if (it.subscriptionId == subId) { return true } @@ -248,18 +259,23 @@ constructor( ) } - private fun dropUnusedReposFromCache(newInfos: List) { + private fun dropUnusedReposFromCache(newInfos: List) { // Remove any connection repository from the cache that isn't in the new set of IDs. They // will get garbage collected once their subscribers go away val currentValidSubscriptionIds = newInfos.map { it.subscriptionId } - subIdRepositoryCache.keys.forEach { - if (!currentValidSubscriptionIds.contains(it)) { - subIdRepositoryCache.remove(it) - } - } + subIdRepositoryCache = + subIdRepositoryCache + .filter { currentValidSubscriptionIds.contains(it.key) } + .toMutableMap() } private suspend fun fetchSubscriptionsList(): List = withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList } + + private fun SubscriptionInfo.toSubscriptionModel(): SubscriptionModel = + SubscriptionModel( + subscriptionId = subscriptionId, + isOpportunistic = isOpportunistic, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index 0da84f0bec9cd..8e1197ca7c5c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -20,10 +20,7 @@ import android.telephony.CarrierConfigManager import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected -import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType -import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository -import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.util.CarrierConfigTracker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -70,10 +67,9 @@ class MobileIconInteractorImpl( defaultMobileIconMapping: StateFlow>, defaultMobileIconGroup: StateFlow, override val isDefaultConnectionFailed: StateFlow, - mobileMappingsProxy: MobileMappingsProxy, connectionRepository: MobileConnectionRepository, ) : MobileIconInteractor { - private val mobileStatusInfo = connectionRepository.subscriptionModelFlow + private val connectionInfo = connectionRepository.connectionInfo override val isDataEnabled: StateFlow = connectionRepository.dataEnabled @@ -82,33 +78,27 @@ class MobileIconInteractorImpl( /** Observable for the current RAT indicator icon ([MobileIconGroup]) */ override val networkTypeIconGroup: StateFlow = combine( - mobileStatusInfo, + connectionInfo, defaultMobileIconMapping, defaultMobileIconGroup, ) { info, mapping, defaultGroup -> - val lookupKey = - when (val resolved = info.resolvedNetworkType) { - is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type) - is OverrideNetworkType -> - mobileMappingsProxy.toIconKeyOverride(resolved.type) - } - mapping[lookupKey] ?: defaultGroup + mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup } .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value) override val isEmergencyOnly: StateFlow = - mobileStatusInfo + connectionInfo .mapLatest { it.isEmergencyOnly } .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val level: StateFlow = - mobileStatusInfo - .mapLatest { mobileModel -> + connectionInfo + .mapLatest { connection -> // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi] - if (mobileModel.isGsm) { - mobileModel.primaryLevel + if (connection.isGsm) { + connection.primaryLevel } else { - mobileModel.cdmaLevel + connection.cdmaLevel } } .stateIn(scope, SharingStarted.WhileSubscribed(), 0) @@ -120,7 +110,7 @@ class MobileIconInteractorImpl( override val numberOfLevels: StateFlow = MutableStateFlow(4) override val isDataConnected: StateFlow = - mobileStatusInfo - .mapLatest { subscriptionModel -> subscriptionModel.dataConnectionState == Connected } + connectionInfo + .mapLatest { connection -> connection.dataConnectionState == Connected } .stateIn(scope, SharingStarted.WhileSubscribed(), false) } 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 a4175c3a6ab19..6f8fb2e2332b7 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 @@ -17,17 +17,16 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CarrierConfigManager -import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository -import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.util.CarrierConfigTracker import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -53,7 +52,7 @@ import kotlinx.coroutines.flow.stateIn */ interface MobileIconsInteractor { /** List of subscriptions, potentially filtered for CBRS */ - val filteredSubscriptions: Flow> + val filteredSubscriptions: Flow> /** True if the active mobile data subscription has data enabled */ val activeDataConnectionHasDataEnabled: StateFlow /** The icon mapping from network type to [MobileIconGroup] for the default subscription */ @@ -79,7 +78,6 @@ class MobileIconsInteractorImpl constructor( private val mobileConnectionsRepo: MobileConnectionsRepository, private val carrierConfigTracker: CarrierConfigTracker, - private val mobileMappingsProxy: MobileMappingsProxy, userSetupRepo: UserSetupRepository, @Application private val scope: CoroutineScope, ) : MobileIconsInteractor { @@ -102,8 +100,8 @@ constructor( .flatMapLatest { it?.dataEnabled ?: flowOf(false) } .stateIn(scope, SharingStarted.WhileSubscribed(), false) - private val unfilteredSubscriptions: Flow> = - mobileConnectionsRepo.subscriptionsFlow + private val unfilteredSubscriptions: Flow> = + mobileConnectionsRepo.subscriptions /** * Generally, SystemUI wants to show iconography for each subscription that is listed by @@ -118,7 +116,7 @@ constructor( * [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN], * and by checking which subscription is opportunistic, or which one is active. */ - override val filteredSubscriptions: Flow> = + override val filteredSubscriptions: Flow> = combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId -> // Based on the old logic, @@ -154,15 +152,19 @@ constructor( * subscription Id. This mapping is the same for every subscription. */ override val defaultMobileIconMapping: StateFlow> = - mobileConnectionsRepo.defaultDataSubRatConfig - .mapLatest { mobileMappingsProxy.mapIconSets(it) } - .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf()) + mobileConnectionsRepo.defaultMobileIconMapping.stateIn( + scope, + SharingStarted.WhileSubscribed(), + initialValue = mapOf() + ) /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */ override val defaultMobileIconGroup: StateFlow = - mobileConnectionsRepo.defaultDataSubRatConfig - .mapLatest { mobileMappingsProxy.getDefaultIcons(it) } - .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G) + mobileConnectionsRepo.defaultMobileIconGroup.stateIn( + scope, + SharingStarted.WhileSubscribed(), + initialValue = TelephonyIcons.G + ) /** * We want to show an error state when cellular has actually failed to validate, but not if some @@ -189,7 +191,6 @@ constructor( defaultMobileIconMapping, defaultMobileIconGroup, isDefaultConnectionFailed, - mobileMappingsProxy, mobileConnectionsRepo.getRepoForSubId(subId), ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt index d9487bf922609..62fa723dbf04e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt @@ -56,8 +56,8 @@ constructor( private val statusBarPipelineFlags: StatusBarPipelineFlags, ) : CoreStartable { private val mobileSubIds: Flow> = - interactor.filteredSubscriptions.mapLatest { infos -> - infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId } + interactor.filteredSubscriptions.mapLatest { subscriptions -> + subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt index ec4fa9ca8128f..0ab7bcd968444 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt @@ -32,6 +32,8 @@ class ModernStatusBarMobileView( attrs: AttributeSet?, ) : BaseStatusBarFrameLayout(context, attrs) { + var subId: Int = -1 + private lateinit var slot: String override fun getSlot() = slot @@ -76,6 +78,7 @@ class ModernStatusBarMobileView( as ModernStatusBarMobileView) .also { it.slot = slot + it.subId = viewModel.subscriptionId MobileIconBinder.bind(it, viewModel) } } 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 24c1db995d50c..2349cb7c5d801 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 @@ -23,7 +23,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMob import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import javax.inject.Inject import kotlinx.coroutines.InternalCoroutinesApi -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow /** * View model for describing the system's current mobile cellular connections. The result is a list @@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.Flow class MobileIconsViewModel @Inject constructor( - val subscriptionIdsFlow: Flow>, + val subscriptionIdsFlow: StateFlow>, private val interactor: MobileIconsInteractor, private val logger: ConnectivityPipelineLogger, ) { @@ -51,7 +51,7 @@ constructor( private val interactor: MobileIconsInteractor, private val logger: ConnectivityPipelineLogger, ) { - fun create(subscriptionIdsFlow: Flow>): MobileIconsViewModel { + fun create(subscriptionIdsFlow: StateFlow>): MobileIconsViewModel { return MobileIconsViewModel( subscriptionIdsFlow, interactor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index 288f54c7d03c0..5265ec66bc246 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -16,12 +16,13 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository -import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import kotlinx.coroutines.flow.MutableStateFlow -class FakeMobileConnectionRepository : MobileConnectionRepository { - private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel()) - override val subscriptionModelFlow = _subscriptionsModelFlow +// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository +class FakeMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository { + private val _connectionInfo = MutableStateFlow(MobileConnectionModel()) + override val connectionInfo = _connectionInfo private val _dataEnabled = MutableStateFlow(true) override val dataEnabled = _dataEnabled @@ -29,8 +30,8 @@ class FakeMobileConnectionRepository : MobileConnectionRepository { private val _isDefaultDataSubscription = MutableStateFlow(true) override val isDefaultDataSubscription = _isDefaultDataSubscription - fun setMobileSubscriptionModel(model: MobileSubscriptionModel) { - _subscriptionsModelFlow.value = model + fun setConnectionInfo(model: MobileConnectionModel) { + _connectionInfo.value = model } fun setDataEnabled(enabled: Boolean) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt index 533d5d9d5b4a3..d6af0e6b3a82d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt @@ -16,23 +16,43 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository -import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID -import com.android.settingslib.mobile.MobileMappings.Config +import android.telephony.TelephonyDisplayInfo +import android.telephony.TelephonyManager +import com.android.settingslib.SignalIcon +import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel -import kotlinx.coroutines.flow.Flow +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import kotlinx.coroutines.flow.MutableStateFlow -class FakeMobileConnectionsRepository : MobileConnectionsRepository { - private val _subscriptionsFlow = MutableStateFlow>(listOf()) - override val subscriptionsFlow: Flow> = _subscriptionsFlow +// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository +class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) : + MobileConnectionsRepository { + val GSM_KEY = mobileMappings.toIconKey(GSM) + val LTE_KEY = mobileMappings.toIconKey(LTE) + val UMTS_KEY = mobileMappings.toIconKey(UMTS) + val LTE_ADVANCED_KEY = mobileMappings.toIconKeyOverride(LTE_ADVANCED_PRO) + + /** + * To avoid a reliance on [MobileMappings], we'll build a simpler map from network type to + * mobile icon. See TelephonyManager.NETWORK_TYPES for a list of types and [TelephonyIcons] for + * the exhaustive set of icons + */ + val TEST_MAPPING: Map = + mapOf( + GSM_KEY to TelephonyIcons.THREE_G, + LTE_KEY to TelephonyIcons.LTE, + UMTS_KEY to TelephonyIcons.FOUR_G, + LTE_ADVANCED_KEY to TelephonyIcons.NR_5G, + ) + + private val _subscriptions = MutableStateFlow>(listOf()) + override val subscriptions = _subscriptions private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID) override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId - private val _defaultDataSubRatConfig = MutableStateFlow(Config()) - override val defaultDataSubRatConfig = _defaultDataSubRatConfig - private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID) override val defaultDataSubId = _defaultDataSubId @@ -41,18 +61,21 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository { private val subIdRepos = mutableMapOf() override fun getRepoForSubId(subId: Int): MobileConnectionRepository { - return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it } + return subIdRepos[subId] + ?: FakeMobileConnectionRepository(subId).also { subIdRepos[subId] = it } } private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit) override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent - fun setSubscriptions(subs: List) { - _subscriptionsFlow.value = subs - } + private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING) + override val defaultMobileIconMapping = _defaultMobileIconMapping - fun setDefaultDataSubRatConfig(config: Config) { - _defaultDataSubRatConfig.value = config + private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON) + override val defaultMobileIconGroup = _defaultMobileIconGroup + + fun setSubscriptions(subs: List) { + _subscriptions.value = subs } fun setDefaultDataSubId(id: Int) { @@ -74,4 +97,14 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository { fun setMobileConnectionRepositoryMap(connections: Map) { connections.forEach { entry -> subIdRepos[entry.key] = entry.value } } + + companion object { + val DEFAULT_ICON = TelephonyIcons.G + + // Use [MobileMappings] to define some simple definitions + const val GSM = TelephonyManager.NETWORK_TYPE_GSM + const val LTE = TelephonyManager.NETWORK_TYPE_LTE + const val UMTS = TelephonyManager.NETWORK_TYPE_UMTS + const val LTE_ADVANCED_PRO = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index 96a280a296edf..18ae90db881a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -24,11 +24,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.kotlinArgumentCaptor @@ -76,6 +78,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { private val globalSettings = FakeSettings() private val fakeNetworkEventsFlow = MutableStateFlow(null) + private val mobileMappings = FakeMobileMappingsProxy() private val scope = CoroutineScope(IMMEDIATE) @@ -97,6 +100,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { subscriptionManager, telephonyManager, logger, + mobileMappings, fakeBroadcastDispatcher, globalSettings, context, @@ -155,15 +159,15 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) - var latest: List? = null - val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) // The real subscriptions has 2 subs whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) getSubscriptionCallback().onSubscriptionsChanged() - assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) + assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2)) // Demo mode turns on, and we should see only the demo subscriptions startDemoMode() @@ -176,7 +180,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { finishDemoMode() - assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) + assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2)) job.cancel() } @@ -211,9 +215,11 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { private const val SUB_1_ID = 1 private val SUB_1 = mock().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) } + private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID) private const val SUB_2_ID = 2 private val SUB_2 = mock().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } + private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt index bf5ecd895c409..e943de25c15fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt @@ -23,7 +23,7 @@ import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState -import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -109,18 +109,18 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC ) { when (model) { is FakeNetworkEventModel.Mobile -> { - val subscriptionModel: MobileSubscriptionModel = conn.subscriptionModelFlow.value + val connectionInfo: MobileConnectionModel = conn.connectionInfo.value assertThat(conn.subId).isEqualTo(model.subId) - assertThat(subscriptionModel.cdmaLevel).isEqualTo(model.level) - assertThat(subscriptionModel.primaryLevel).isEqualTo(model.level) - assertThat(subscriptionModel.dataActivityDirection).isEqualTo(model.activity) - assertThat(subscriptionModel.carrierNetworkChangeActive) + assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level) + assertThat(connectionInfo.primaryLevel).isEqualTo(model.level) + assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity) + assertThat(connectionInfo.carrierNetworkChangeActive) .isEqualTo(model.carrierNetworkChange) // TODO(b/261029387): check these once we start handling them - assertThat(subscriptionModel.isEmergencyOnly).isFalse() - assertThat(subscriptionModel.isGsm).isFalse() - assertThat(subscriptionModel.dataConnectionState) + assertThat(connectionInfo.isEmergencyOnly).isFalse() + assertThat(connectionInfo.isGsm).isFalse() + assertThat(connectionInfo.dataConnectionState) .isEqualTo(DataConnectionState.Connected) } // MobileDisabled isn't combinatorial in nature, and is tested in diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt index a8f6993373d25..32d0410d589d8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo -import android.telephony.SubscriptionInfo import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID import androidx.test.filters.SmallTest @@ -24,7 +23,8 @@ import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.TelephonyIcons.THREE_G import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState -import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled import com.android.systemui.util.mockito.mock @@ -73,8 +73,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun `network event - create new subscription`() = testScope.runTest { - var latest: List? = null - val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) assertThat(latest).isEmpty() @@ -89,8 +89,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun `network event - reuses subscription when same Id`() = testScope.runTest { - var latest: List? = null - val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) assertThat(latest).isEmpty() @@ -111,8 +111,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun `multiple subscriptions`() = testScope.runTest { - var latest: List? = null - val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) fakeNetworkEventFlow.value = validMobileEvent(subId = 1) fakeNetworkEventFlow.value = validMobileEvent(subId = 2) @@ -125,8 +125,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun `mobile disabled event - disables connection - subId specified - single conn`() = testScope.runTest { - var latest: List? = null - val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1) @@ -140,8 +140,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun `mobile disabled event - disables connection - subId not specified - single conn`() = testScope.runTest { - var latest: List? = null - val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1) @@ -155,8 +155,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun `mobile disabled event - disables connection - subId specified - multiple conn`() = testScope.runTest { - var latest: List? = null - val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1) fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1) @@ -171,8 +171,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun `mobile disabled event - subId not specified - multiple conn - ignores command`() = testScope.runTest { - var latest: List? = null - val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1) fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1) @@ -184,13 +184,32 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + /** Regression test for b/261706421 */ + @Test + fun `multiple connections - remove all - does not throw`() = + testScope.runTest { + var latest: List? = null + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) + + // Two subscriptions are added + fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1) + fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1) + + // Then both are removed by turning off demo mode + underTest.stopProcessingCommands() + + assertThat(latest).isEmpty() + + job.cancel() + } + @Test fun `demo connection - single subscription`() = testScope.runTest { var currentEvent: FakeNetworkEventModel = validMobileEvent(subId = 1) var connections: List? = null val job = - underTest.subscriptionsFlow + underTest.subscriptions .onEach { infos -> connections = infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) } @@ -222,7 +241,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { var connection2: DemoMobileConnectionRepository? = null var connections: List? = null val job = - underTest.subscriptionsFlow + underTest.subscriptions .onEach { infos -> connections = infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) } @@ -266,18 +285,18 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { ) { when (model) { is FakeNetworkEventModel.Mobile -> { - val subscriptionModel: MobileSubscriptionModel = conn.subscriptionModelFlow.value + val connectionInfo: MobileConnectionModel = conn.connectionInfo.value assertThat(conn.subId).isEqualTo(model.subId) - assertThat(subscriptionModel.cdmaLevel).isEqualTo(model.level) - assertThat(subscriptionModel.primaryLevel).isEqualTo(model.level) - assertThat(subscriptionModel.dataActivityDirection).isEqualTo(model.activity) - assertThat(subscriptionModel.carrierNetworkChangeActive) + assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level) + assertThat(connectionInfo.primaryLevel).isEqualTo(model.level) + assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity) + assertThat(connectionInfo.carrierNetworkChangeActive) .isEqualTo(model.carrierNetworkChange) // TODO(b/261029387) check these once we start handling them - assertThat(subscriptionModel.isEmergencyOnly).isFalse() - assertThat(subscriptionModel.isGsm).isFalse() - assertThat(subscriptionModel.dataConnectionState) + assertThat(connectionInfo.isEmergencyOnly).isFalse() + assertThat(connectionInfo.isGsm).isFalse() + assertThat(connectionInfo.dataConnectionState) .isEqualTo(DataConnectionState.Connected) } else -> {} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index c8df5ac17dff4..1fc9c60cd9ced 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -37,10 +37,12 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState -import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType -import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel -import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -72,8 +74,9 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Mock private lateinit var logger: ConnectivityPipelineLogger private val scope = CoroutineScope(IMMEDIATE) + private val mobileMappings = FakeMobileMappingsProxy() private val globalSettings = FakeSettings() - private val connectionsRepo = FakeMobileConnectionsRepository() + private val connectionsRepo = FakeMobileConnectionsRepository(mobileMappings) @Before fun setUp() { @@ -89,6 +92,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { globalSettings, connectionsRepo.defaultDataSubId, connectionsRepo.globalMobileDataSettingChangedEvent, + mobileMappings, IMMEDIATE, logger, scope, @@ -103,10 +107,10 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun testFlowForSubId_default() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) - assertThat(latest).isEqualTo(MobileSubscriptionModel()) + assertThat(latest).isEqualTo(MobileConnectionModel()) job.cancel() } @@ -114,8 +118,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun testFlowForSubId_emergencyOnly() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val serviceState = ServiceState() serviceState.isEmergencyOnly = true @@ -130,8 +134,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun testFlowForSubId_emergencyOnly_toggles() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() val serviceState = ServiceState() @@ -148,8 +152,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun testFlowForSubId_signalStrengths_levelsUpdate() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true) @@ -165,8 +169,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun testFlowForSubId_dataConnectionState_connected() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() @@ -180,8 +184,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun testFlowForSubId_dataConnectionState_connecting() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() @@ -195,8 +199,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun testFlowForSubId_dataConnectionState_disconnected() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() @@ -210,8 +214,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun testFlowForSubId_dataConnectionState_disconnecting() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() @@ -225,8 +229,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun testFlowForSubId_dataConnectionState_unknown() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() @@ -240,8 +244,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun testFlowForSubId_dataActivity() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() callback.onDataActivity(3) @@ -254,8 +258,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun testFlowForSubId_carrierNetworkChange() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() callback.onCarrierNetworkChange(true) @@ -268,11 +272,11 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun subscriptionFlow_networkType_default() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val type = NETWORK_TYPE_UNKNOWN - val expected = DefaultNetworkType(type) + val expected = UnknownNetworkType assertThat(latest?.resolvedNetworkType).isEqualTo(expected) @@ -282,12 +286,12 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun subscriptionFlow_networkType_updatesUsingDefault() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() val type = NETWORK_TYPE_LTE - val expected = DefaultNetworkType(type) + val expected = DefaultNetworkType(type, mobileMappings.toIconKey(type)) val ti = mock().also { whenever(it.networkType).thenReturn(type) } callback.onDisplayInfoChanged(ti) @@ -299,14 +303,15 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test fun subscriptionFlow_networkType_updatesUsingOverride() = runBlocking(IMMEDIATE) { - var latest: MobileSubscriptionModel? = null - val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) + var latest: MobileConnectionModel? = null + val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() val type = OVERRIDE_NETWORK_TYPE_LTE_CA - val expected = OverrideNetworkType(type) + val expected = OverrideNetworkType(type, mobileMappings.toIconKeyOverride(type)) val ti = mock().also { + whenever(it.networkType).thenReturn(type) whenever(it.overrideNetworkType).thenReturn(type) } callback.onDisplayInfoChanged(ti) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 359ea18fcb840..4b82b398360d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -32,6 +32,8 @@ import androidx.test.filters.SmallTest import com.android.internal.telephony.PhoneConstants import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -50,6 +52,7 @@ import org.junit.After import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -65,6 +68,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: ConnectivityPipelineLogger + private val mobileMappings = FakeMobileMappingsProxy() + private val scope = CoroutineScope(IMMEDIATE) private val globalSettings = FakeSettings() @@ -72,18 +77,37 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + // Set up so the individual connection repositories + whenever(telephonyManager.createForSubscriptionId(anyInt())).thenAnswer { invocation -> + telephonyManager.also { + whenever(telephonyManager.subscriptionId).thenReturn(invocation.getArgument(0)) + } + } + + val connectionFactory: MobileConnectionRepositoryImpl.Factory = + MobileConnectionRepositoryImpl.Factory( + context = context, + telephonyManager = telephonyManager, + bgDispatcher = IMMEDIATE, + globalSettings = globalSettings, + logger = logger, + mobileMappingsProxy = mobileMappings, + scope = scope, + ) + underTest = MobileConnectionsRepositoryImpl( connectivityManager, subscriptionManager, telephonyManager, logger, + mobileMappings, fakeBroadcastDispatcher, globalSettings, context, IMMEDIATE, scope, - mock(), + connectionFactory, ) } @@ -95,21 +119,21 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun testSubscriptions_initiallyEmpty() = runBlocking(IMMEDIATE) { - assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf()) + assertThat(underTest.subscriptions.value).isEqualTo(listOf()) } @Test fun testSubscriptions_listUpdates() = runBlocking(IMMEDIATE) { - var latest: List? = null + var latest: List? = null - val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) getSubscriptionCallback().onSubscriptionsChanged() - assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) + assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2)) job.cancel() } @@ -117,9 +141,9 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun testSubscriptions_removingSub_updatesList() = runBlocking(IMMEDIATE) { - var latest: List? = null + var latest: List? = null - val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this) + val job = underTest.subscriptions.onEach { latest = it }.launchIn(this) // WHEN 2 networks show up whenever(subscriptionManager.completeActiveSubscriptionInfoList) @@ -132,7 +156,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { getSubscriptionCallback().onSubscriptionsChanged() // THEN the subscriptions list represents the newest change - assertThat(latest).isEqualTo(listOf(SUB_2)) + assertThat(latest).isEqualTo(listOf(MODEL_2)) job.cancel() } @@ -162,7 +186,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun testConnectionRepository_validSubId_isCached() = runBlocking(IMMEDIATE) { - val job = underTest.subscriptionsFlow.launchIn(this) + val job = underTest.subscriptions.launchIn(this) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1)) @@ -179,7 +203,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun testConnectionCache_clearsInvalidSubscriptions() = runBlocking(IMMEDIATE) { - val job = underTest.subscriptionsFlow.launchIn(this) + val job = underTest.subscriptions.launchIn(this) whenever(subscriptionManager.completeActiveSubscriptionInfoList) .thenReturn(listOf(SUB_1, SUB_2)) @@ -202,10 +226,36 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + /** Regression test for b/261706421 */ + @Test + fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() = + runBlocking(IMMEDIATE) { + val job = underTest.subscriptions.launchIn(this) + + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(SUB_1, SUB_2)) + getSubscriptionCallback().onSubscriptionsChanged() + + // Get repos to trigger caching + val repo1 = underTest.getRepoForSubId(SUB_1_ID) + val repo2 = underTest.getRepoForSubId(SUB_2_ID) + + assertThat(underTest.getSubIdRepoCache()) + .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2) + + // All subscriptions disappear + whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf()) + getSubscriptionCallback().onSubscriptionsChanged() + + assertThat(underTest.getSubIdRepoCache()).isEmpty() + + job.cancel() + } + @Test fun testConnectionRepository_invalidSubId_throws() = runBlocking(IMMEDIATE) { - val job = underTest.subscriptionsFlow.launchIn(this) + val job = underTest.subscriptions.launchIn(this) assertThrows(IllegalArgumentException::class.java) { underTest.getRepoForSubId(SUB_1_ID) @@ -371,10 +421,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private const val SUB_1_ID = 1 private val SUB_1 = mock().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) } + private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID) private const val SUB_2_ID = 2 private val SUB_2 = mock().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } + private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID) private const val NET_ID = 123 private val NETWORK = mock().apply { whenever(getNetId()).thenReturn(NET_ID) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index 061c3b54650e6..0d4044db71e03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -16,14 +16,15 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor -import android.telephony.SubscriptionInfo import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO import android.telephony.TelephonyManager.NETWORK_TYPE_GSM import android.telephony.TelephonyManager.NETWORK_TYPE_LTE import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.TelephonyIcons +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor { @@ -47,8 +48,8 @@ class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIco override val isDefaultConnectionFailed = MutableStateFlow(false) - private val _filteredSubscriptions = MutableStateFlow>(listOf()) - override val filteredSubscriptions = _filteredSubscriptions + private val _filteredSubscriptions = MutableStateFlow>(listOf()) + override val filteredSubscriptions: Flow> = _filteredSubscriptions private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false) override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index 7fc1c0f6272cd..fd41b5bebd390 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -24,9 +24,9 @@ import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState -import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType -import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel -import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G @@ -49,7 +49,7 @@ class MobileIconInteractorTest : SysuiTestCase() { private lateinit var underTest: MobileIconInteractor private val mobileMappingsProxy = FakeMobileMappingsProxy() private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy) - private val connectionRepository = FakeMobileConnectionRepository() + private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID) private val scope = CoroutineScope(IMMEDIATE) @@ -62,7 +62,6 @@ class MobileIconInteractorTest : SysuiTestCase() { mobileIconsInteractor.defaultMobileIconMapping, mobileIconsInteractor.defaultMobileIconGroup, mobileIconsInteractor.isDefaultConnectionFailed, - mobileMappingsProxy, connectionRepository, ) } @@ -70,8 +69,8 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun gsm_level_default_unknown() = runBlocking(IMMEDIATE) { - connectionRepository.setMobileSubscriptionModel( - MobileSubscriptionModel(isGsm = true), + connectionRepository.setConnectionInfo( + MobileConnectionModel(isGsm = true), ) var latest: Int? = null @@ -85,8 +84,8 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun gsm_usesGsmLevel() = runBlocking(IMMEDIATE) { - connectionRepository.setMobileSubscriptionModel( - MobileSubscriptionModel( + connectionRepository.setConnectionInfo( + MobileConnectionModel( isGsm = true, primaryLevel = GSM_LEVEL, cdmaLevel = CDMA_LEVEL @@ -104,8 +103,8 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun cdma_level_default_unknown() = runBlocking(IMMEDIATE) { - connectionRepository.setMobileSubscriptionModel( - MobileSubscriptionModel(isGsm = false), + connectionRepository.setConnectionInfo( + MobileConnectionModel(isGsm = false), ) var latest: Int? = null @@ -118,8 +117,8 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun cdma_usesCdmaLevel() = runBlocking(IMMEDIATE) { - connectionRepository.setMobileSubscriptionModel( - MobileSubscriptionModel( + connectionRepository.setConnectionInfo( + MobileConnectionModel( isGsm = false, primaryLevel = GSM_LEVEL, cdmaLevel = CDMA_LEVEL @@ -137,8 +136,11 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun iconGroup_three_g() = runBlocking(IMMEDIATE) { - connectionRepository.setMobileSubscriptionModel( - MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)), + connectionRepository.setConnectionInfo( + MobileConnectionModel( + resolvedNetworkType = + DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G)) + ), ) var latest: MobileIconGroup? = null @@ -152,16 +154,23 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun iconGroup_updates_on_change() = runBlocking(IMMEDIATE) { - connectionRepository.setMobileSubscriptionModel( - MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)), + connectionRepository.setConnectionInfo( + MobileConnectionModel( + resolvedNetworkType = + DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G)) + ), ) var latest: MobileIconGroup? = null val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) - connectionRepository.setMobileSubscriptionModel( - MobileSubscriptionModel( - resolvedNetworkType = DefaultNetworkType(FOUR_G), + connectionRepository.setConnectionInfo( + MobileConnectionModel( + resolvedNetworkType = + DefaultNetworkType( + FOUR_G, + mobileMappingsProxy.toIconKey(FOUR_G), + ), ), ) yield() @@ -174,8 +183,14 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun iconGroup_5g_override_type() = runBlocking(IMMEDIATE) { - connectionRepository.setMobileSubscriptionModel( - MobileSubscriptionModel(resolvedNetworkType = OverrideNetworkType(FIVE_G_OVERRIDE)), + connectionRepository.setConnectionInfo( + MobileConnectionModel( + resolvedNetworkType = + OverrideNetworkType( + FIVE_G_OVERRIDE, + mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE) + ) + ), ) var latest: MobileIconGroup? = null @@ -189,9 +204,13 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun iconGroup_default_if_no_lookup() = runBlocking(IMMEDIATE) { - connectionRepository.setMobileSubscriptionModel( - MobileSubscriptionModel( - resolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN), + connectionRepository.setConnectionInfo( + MobileConnectionModel( + resolvedNetworkType = + DefaultNetworkType( + NETWORK_TYPE_UNKNOWN, + mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN) + ), ), ) @@ -238,8 +257,8 @@ class MobileIconInteractorTest : SysuiTestCase() { var latest: Boolean? = null val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this) - connectionRepository.setMobileSubscriptionModel( - MobileSubscriptionModel(dataConnectionState = DataConnectionState.Connected) + connectionRepository.setConnectionInfo( + MobileConnectionModel(dataConnectionState = DataConnectionState.Connected) ) yield() @@ -254,8 +273,8 @@ class MobileIconInteractorTest : SysuiTestCase() { var latest: Boolean? = null val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this) - connectionRepository.setMobileSubscriptionModel( - MobileSubscriptionModel(dataConnectionState = DataConnectionState.Disconnected) + connectionRepository.setConnectionInfo( + MobileConnectionModel(dataConnectionState = DataConnectionState.Disconnected) ) assertThat(latest).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index b56dcd7525579..58e57e298e512 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -16,17 +16,16 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor -import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel +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.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.util.CarrierConfigTracker -import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope @@ -45,8 +44,8 @@ import org.mockito.MockitoAnnotations class MobileIconsInteractorTest : SysuiTestCase() { private lateinit var underTest: MobileIconsInteractor private val userSetupRepository = FakeUserSetupRepository() - private val connectionsRepository = FakeMobileConnectionsRepository() private val mobileMappingsProxy = FakeMobileMappingsProxy() + private val connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy) private val scope = CoroutineScope(IMMEDIATE) @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker @@ -69,7 +68,6 @@ class MobileIconsInteractorTest : SysuiTestCase() { MobileIconsInteractorImpl( connectionsRepository, carrierConfigTracker, - mobileMappingsProxy, userSetupRepository, scope ) @@ -80,10 +78,10 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_default() = runBlocking(IMMEDIATE) { - var latest: List? = null + var latest: List? = null val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) - assertThat(latest).isEqualTo(listOf()) + assertThat(latest).isEqualTo(listOf()) job.cancel() } @@ -93,7 +91,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { runBlocking(IMMEDIATE) { connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) - var latest: List? = null + var latest: List? = null val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) @@ -109,7 +107,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) - var latest: List? = null + var latest: List? = null val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) // Filtered subscriptions should show the active one when the config is false @@ -126,7 +124,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) - var latest: List? = null + var latest: List? = null val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) // Filtered subscriptions should show the active one when the config is false @@ -143,7 +141,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) - var latest: List? = null + var latest: List? = null val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) // Filtered subscriptions should show the primary (non-opportunistic) if the config is @@ -161,7 +159,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) - var latest: List? = null + var latest: List? = null val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) // Filtered subscriptions should show the primary (non-opportunistic) if the config is @@ -261,29 +259,19 @@ class MobileIconsInteractorTest : SysuiTestCase() { private val IMMEDIATE = Dispatchers.Main.immediate private const val SUB_1_ID = 1 - private val SUB_1 = - mock().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) } - private val CONNECTION_1 = FakeMobileConnectionRepository() + private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID) + private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID) private const val SUB_2_ID = 2 - private val SUB_2 = - mock().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } - private val CONNECTION_2 = FakeMobileConnectionRepository() + private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID) + private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID) private const val SUB_3_ID = 3 - private val SUB_3_OPP = - mock().also { - whenever(it.subscriptionId).thenReturn(SUB_3_ID) - whenever(it.isOpportunistic).thenReturn(true) - } - private val CONNECTION_3 = FakeMobileConnectionRepository() + private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true) + private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID) private const val SUB_4_ID = 4 - private val SUB_4_OPP = - mock().also { - whenever(it.subscriptionId).thenReturn(SUB_4_ID) - whenever(it.isOpportunistic).thenReturn(true) - } - private val CONNECTION_4 = FakeMobileConnectionRepository() + private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true) + private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID) } }