From 4c52a3c37b845921a3ffc982fce93b45daeec609 Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Thu, 6 Oct 2022 17:28:59 -0400 Subject: [PATCH] [Status bar refactor] Implement the lookup from network type to RAT icon This CL adds the ability to map from network type to icon used to display the RAT indicator (LTE, 3G, etc.). This process starts by consuming TelephonyDisplayInfo and using the SettingsLib MobileMappings.java utilities to map all of the telephony Network Types to TelephonyIcons.java. 1. Add an injectable MobileMappings proxy 2. Add support for generating the MobileMappings.Config class under the same conditions that NetworkController does. 3. Thread all of the mobile mappings to the MobileIconGroup in MobileIconViewModel 4. A bunch of tests Test: atest MobileIconInteractorTest Bug: 240492102 Change-Id: I38e917cfa1c98bd86258061defa89cc8705816e0 --- .../dagger/StatusBarPipelineModule.kt | 10 ++ .../data/model/MobileSubscriptionModel.kt | 10 +- .../mobile/data/model/ResolvedNetworkType.kt | 33 +++++++ .../MobileSubscriptionRepository.kt | 68 +++++++++++++- .../domain/interactor/MobileIconInteractor.kt | 34 ++++++- .../interactor/MobileIconsInteractor.kt | 61 ++++++++++-- .../mobile/ui/binder/MobileIconBinder.kt | 16 +++- .../ui/viewmodel/MobileIconViewModel.kt | 13 +++ .../pipeline/mobile/util/MobileMappings.kt | 52 ++++++++++ .../FakeMobileSubscriptionRepository.kt | 8 ++ .../MobileSubscriptionRepositoryTest.kt | 74 ++++++++++++++- .../interactor/FakeMobileIconInteractor.kt | 2 +- .../interactor/FakeMobileIconsInteractor.kt | 75 +++++++++++++++ .../interactor/MobileIconInteractorTest.kt | 94 ++++++++++++++++++- .../interactor/MobileIconsInteractorTest.kt | 8 +- .../mobile/util/FakeMobileMappingsProxy.kt | 46 +++++++++ 16 files changed, 580 insertions(+), 24 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 2aaa085645e4b..6c9ad6a2df7f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -22,6 +22,10 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubs import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository @@ -47,4 +51,10 @@ abstract class StatusBarPipelineModule { @Binds abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository + + @Binds + abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy + + @Binds + abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor } 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/MobileSubscriptionModel.kt index 46ccf32cc7f93..eaba0e93e7504 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/MobileSubscriptionModel.kt @@ -27,6 +27,7 @@ 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 /** * Data class containing all of the relevant information for a particular line of service, known as @@ -57,6 +58,11 @@ data class MobileSubscriptionModel( /** From [CarrierNetworkListener.onCarrierNetworkChange] */ val carrierNetworkChangeActive: Boolean? = null, - /** From [DisplayInfoListener.onDisplayInfoChanged] */ - val displayInfo: TelephonyDisplayInfo? = null + /** + * From [DisplayInfoListener.onDisplayInfoChanged]. + * + * [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), ) 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 new file mode 100644 index 0000000000000..f385806c1b223 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt @@ -0,0 +1,33 @@ +/* + * 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 + +import android.telephony.Annotation.NetworkType +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy + +/** + * A SysUI type to represent the [NetworkType] that we pull out of [TelephonyDisplayInfo]. Depending + * on whether or not the display info contains an override type, we may have to call different + * methods on [MobileMappingsProxy] to generate an icon lookup key. + */ +sealed interface ResolvedNetworkType { + @NetworkType val type: Int +} + +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/repository/MobileSubscriptionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt index 36de2a2541600..b6adc406cce99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository +import android.content.Context +import android.content.IntentFilter +import android.telephony.CarrierConfigManager import android.telephony.CellSignalStrength import android.telephony.CellSignalStrengthCdma import android.telephony.ServiceState @@ -31,13 +34,21 @@ import android.telephony.TelephonyCallback.DisplayInfoListener import android.telephony.TelephonyCallback.ServiceStateListener import android.telephony.TelephonyCallback.SignalStrengthsListener import android.telephony.TelephonyDisplayInfo +import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE import android.telephony.TelephonyManager import androidx.annotation.VisibleForTesting +import com.android.settingslib.mobile.MobileMappings +import com.android.settingslib.mobile.MobileMappings.Config +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow 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.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.util.MobileMappingsProxy +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -47,7 +58,9 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @@ -62,6 +75,9 @@ interface MobileSubscriptionRepository { /** Observable for the subscriptionId of the current mobile data connection */ val activeMobileDataSubscriptionId: Flow + /** Observable for [MobileMappings.Config] tracking the defaults */ + val defaultDataSubRatConfig: StateFlow + /** Get or create an observable for the given subscription ID */ fun getFlowForSubId(subId: Int): Flow } @@ -74,6 +90,10 @@ class MobileSubscriptionRepositoryImpl constructor( private val subscriptionManager: SubscriptionManager, private val telephonyManager: TelephonyManager, + private val logger: ConnectivityPipelineLogger, + broadcastDispatcher: BroadcastDispatcher, + private val context: Context, + private val mobileMappings: MobileMappingsProxy, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, ) : MobileSubscriptionRepository { @@ -122,6 +142,36 @@ constructor( SubscriptionManager.INVALID_SUBSCRIPTION_ID ) + private val defaultDataSubChangedEvent = + broadcastDispatcher.broadcastFlow( + IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED) + ) + + private val carrierConfigChangedEvent = + broadcastDispatcher.broadcastFlow( + IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED) + ) + + /** + * [Config] is an object that tracks relevant configuration flags for a given subscription ID. + * In the case of [MobileMappings], it's hard-coded to check the default data subscription's + * config, so this will apply to every icon that we care about. + * + * Relevant bits in the config are things like + * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL] + * + * This flow will produce whenever the default data subscription or the carrier config changes. + */ + override val defaultDataSubRatConfig: StateFlow = + combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ -> + Config.readConfig(context) + } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + initialValue = Config.readConfig(context) + ) + /** * Each mobile subscription needs its own flow, which comes from registering listeners on the * system. Use this method to create those flows and cache them for reuse @@ -151,6 +201,7 @@ constructor( state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly) trySend(state) } + override fun onSignalStrengthsChanged(signalStrength: SignalStrength) { val cdmaLevel = signalStrength @@ -173,6 +224,7 @@ constructor( ) trySend(state) } + override fun onDataConnectionStateChanged( dataState: Int, networkType: Int @@ -180,18 +232,31 @@ constructor( state = state.copy(dataConnectionState = dataState) trySend(state) } + override fun onDataActivity(direction: Int) { state = state.copy(dataActivityDirection = direction) trySend(state) } + override fun onCarrierNetworkChange(active: Boolean) { state = state.copy(carrierNetworkChangeActive = active) trySend(state) } + override fun onDisplayInfoChanged( telephonyDisplayInfo: TelephonyDisplayInfo ) { - state = state.copy(displayInfo = telephonyDisplayInfo) + val networkType = + if ( + telephonyDisplayInfo.overrideNetworkType == + OVERRIDE_NETWORK_TYPE_NONE + ) { + DefaultNetworkType(telephonyDisplayInfo.networkType) + } else { + OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType) + } + + state = state.copy(resolvedNetworkType = networkType) trySend(state) } } @@ -202,6 +267,7 @@ constructor( subIdFlowCache.remove(subId) } } + .onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) } .stateIn(scope, SharingStarted.WhileSubscribed(), state) } 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 40fe0f3e8fe09..e6d9993734d9c 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 @@ -17,32 +17,56 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CarrierConfigManager -import com.android.settingslib.SignalIcon -import com.android.settingslib.mobile.TelephonyIcons +import com.android.settingslib.SignalIcon.MobileIconGroup +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.util.MobileMappingsProxy import com.android.systemui.util.CarrierConfigTracker import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map interface MobileIconInteractor { - /** Identifier for RAT type indicator */ - val iconGroup: Flow + /** Observable for RAT type (network type) indicator */ + val networkTypeIconGroup: Flow + /** True if this line of service is emergency-only */ val isEmergencyOnly: Flow + /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */ val level: Flow + /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */ val numberOfLevels: Flow + /** True when we want to draw an icon that makes room for the exclamation mark */ val cutOut: Flow } /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */ class MobileIconInteractorImpl( + defaultMobileIconMapping: Flow>, + defaultMobileIconGroup: Flow, + mobileMappingsProxy: MobileMappingsProxy, mobileStatusInfo: Flow, ) : MobileIconInteractor { - override val iconGroup: Flow = flowOf(TelephonyIcons.THREE_G) + /** Observable for the current RAT indicator icon ([MobileIconGroup]) */ + override val networkTypeIconGroup: Flow = + combine( + mobileStatusInfo, + 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 + } + override val isEmergencyOnly: Flow = mobileStatusInfo.map { it.isEmergencyOnly } override val level: Flow = 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 8e67e19f3e355..e401a9c78cf27 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 @@ -19,29 +19,52 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CarrierConfigManager import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager +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.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository 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 import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn /** - * Business layer logic for mobile subscription icons + * Business layer logic for the set of mobile subscription icons. * - * Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s - * that the system knows about. They obey policy that depends on OEM, carrier, and locale configs + * This interactor represents known set of mobile subscriptions (represented by [SubscriptionInfo]). + * The list of subscriptions is filtered based on the opportunistic flags on the infos. + * + * It provides the default mapping between the telephony display info and the icon group that + * represents each RAT (LTE, 3G, etc.), as well as can produce an interactor for each individual + * icon */ +interface MobileIconsInteractor { + val filteredSubscriptions: Flow> + val defaultMobileIconMapping: Flow> + val defaultMobileIconGroup: Flow + val isUserSetup: Flow + fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor +} + @SysUISingleton -class MobileIconsInteractor +class MobileIconsInteractorImpl @Inject constructor( private val mobileSubscriptionRepo: MobileSubscriptionRepository, private val carrierConfigTracker: CarrierConfigTracker, + private val mobileMappingsProxy: MobileMappingsProxy, userSetupRepo: UserSetupRepository, -) { + @Application private val scope: CoroutineScope, +) : MobileIconsInteractor { private val activeMobileDataSubscriptionId = mobileSubscriptionRepo.activeMobileDataSubscriptionId @@ -61,7 +84,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. */ - val filteredSubscriptions: Flow> = + override val filteredSubscriptions: Flow> = combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId -> // Based on the old logic, @@ -92,11 +115,31 @@ constructor( } } - val isUserSetup: Flow = userSetupRepo.isUserSetupFlow + /** + * Mapping from network type to [MobileIconGroup] using the config generated for the default + * subscription Id. This mapping is the same for every subscription. + */ + override val defaultMobileIconMapping: StateFlow> = + mobileSubscriptionRepo.defaultDataSubRatConfig + .map { mobileMappingsProxy.mapIconSets(it) } + .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf()) + + /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */ + override val defaultMobileIconGroup: StateFlow = + mobileSubscriptionRepo.defaultDataSubRatConfig + .map { mobileMappingsProxy.getDefaultIcons(it) } + .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G) + + override val isUserSetup: Flow = userSetupRepo.isUserSetupFlow /** Vends out new [MobileIconInteractor] for a particular subId */ - fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = - MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId)) + override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = + MobileIconInteractorImpl( + defaultMobileIconMapping, + defaultMobileIconGroup, + mobileMappingsProxy, + mobileSubscriptionFlowForSubId(subId), + ) /** * Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index 1405b050234bb..67ea139271fcb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.binder import android.content.res.ColorStateList +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.ImageView import androidx.core.view.isVisible @@ -24,6 +26,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.settingslib.graph.SignalDrawable import com.android.systemui.R +import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel import kotlinx.coroutines.flow.collect @@ -37,6 +40,7 @@ object MobileIconBinder { view: ViewGroup, viewModel: MobileIconViewModel, ) { + val networkTypeView = view.requireViewById(R.id.mobile_type) val iconView = view.requireViewById(R.id.mobile_signal) val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) } @@ -52,10 +56,20 @@ object MobileIconBinder { } } + // Set the network type icon + launch { + viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId -> + dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) } + networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE + } + } + // Set the tint launch { viewModel.tint.collect { tint -> - iconView.imageTintList = ColorStateList.valueOf(tint) + val tintList = ColorStateList.valueOf(tint) + iconView.imageTintList = tintList + networkTypeView.imageTintList = tintList } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index cfabeba8432ce..cc8f6dd085853 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import android.graphics.Color import com.android.settingslib.graph.SignalDrawable +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger @@ -26,6 +28,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map /** * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over @@ -54,5 +57,15 @@ constructor( .distinctUntilChanged() .logOutputChange(logger, "iconId($subscriptionId)") + /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ + var networkTypeIcon: Flow = + iconInteractor.networkTypeIconGroup.map { + val desc = + if (it.dataContentDescription != 0) + ContentDescription.Resource(it.dataContentDescription) + else null + Icon.Resource(it.dataType, desc) + } + var tint: Flow = flowOf(Color.CYAN) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt new file mode 100644 index 0000000000000..60bd0383f8c72 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt @@ -0,0 +1,52 @@ +/* + * 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.util + +import android.telephony.Annotation.NetworkType +import android.telephony.TelephonyDisplayInfo +import com.android.settingslib.SignalIcon.MobileIconGroup +import com.android.settingslib.mobile.MobileMappings +import com.android.settingslib.mobile.MobileMappings.Config +import javax.inject.Inject + +/** + * [MobileMappings] owns the logic on creating the map from [TelephonyDisplayInfo] to + * [MobileIconGroup]. It creates that hash map and also manages the creation of lookup keys. This + * interface allows us to proxy those calls to the static java methods in SettingsLib and also fake + * them out in tests + */ +interface MobileMappingsProxy { + fun mapIconSets(config: Config): Map + fun getDefaultIcons(config: Config): MobileIconGroup + fun toIconKey(@NetworkType networkType: Int): String + fun toIconKeyOverride(@NetworkType networkType: Int): String +} + +/** Injectable wrapper class for [MobileMappings] */ +class MobileMappingsProxyImpl @Inject constructor() : MobileMappingsProxy { + override fun mapIconSets(config: Config): Map = + MobileMappings.mapIconSets(config) + + override fun getDefaultIcons(config: Config): MobileIconGroup = + MobileMappings.getDefaultIcons(config) + + override fun toIconKey(@NetworkType networkType: Int): String = + MobileMappings.toIconKey(networkType) + + override fun toIconKeyOverride(networkType: Int): String = + MobileMappings.toDisplayIconKey(networkType) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt index 0d15268830230..d5137a0e939d0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager +import com.android.settingslib.mobile.MobileMappings.Config import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -30,6 +31,9 @@ class FakeMobileSubscriptionRepository : MobileSubscriptionRepository { MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID) override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId + private val _defaultDataSubRatConfig = MutableStateFlow(Config()) + override val defaultDataSubRatConfig = _defaultDataSubRatConfig + private val subIdFlows = mutableMapOf>() override fun getFlowForSubId(subId: Int): Flow { return subIdFlows[subId] @@ -40,6 +44,10 @@ class FakeMobileSubscriptionRepository : MobileSubscriptionRepository { _subscriptionsFlow.value = subs } + fun setDefaultDataSubRatConfig(config: Config) { + _defaultDataSubRatConfig.value = config + } + fun setActiveMobileDataSubscriptionId(subId: Int) { _activeMobileDataSubscriptionId.value = subId } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt index 316b795ac9495..31203b3147eb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt @@ -30,25 +30,36 @@ import android.telephony.TelephonyCallback.DisplayInfoListener import android.telephony.TelephonyCallback.ServiceStateListener import android.telephony.TelephonyCallback.SignalStrengthsListener import android.telephony.TelephonyDisplayInfo +import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA import android.telephony.TelephonyManager +import android.telephony.TelephonyManager.NETWORK_TYPE_LTE +import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastDispatcher +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.util.FakeMobileMappingsProxy +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before import org.junit.Test +import org.mockito.ArgumentMatchers import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -61,16 +72,33 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() { @Mock private lateinit var subscriptionManager: SubscriptionManager @Mock private lateinit var telephonyManager: TelephonyManager + @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + private val scope = CoroutineScope(IMMEDIATE) + private val mobileMappings = FakeMobileMappingsProxy() @Before fun setUp() { MockitoAnnotations.initMocks(this) + whenever( + broadcastDispatcher.broadcastFlow( + any(), + nullable(), + ArgumentMatchers.anyInt(), + nullable(), + ) + ) + .thenReturn(flowOf(Unit)) underTest = MobileSubscriptionRepositoryImpl( subscriptionManager, telephonyManager, + logger, + broadcastDispatcher, + context, + mobileMappings, IMMEDIATE, scope, ) @@ -266,7 +294,23 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() { } @Test - fun testFlowForSubId_displayInfo() = + fun testFlowForSubId_defaultNetworkType() = + runBlocking(IMMEDIATE) { + whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager) + + var latest: MobileSubscriptionModel? = null + val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this) + + val type = NETWORK_TYPE_UNKNOWN + val expected = DefaultNetworkType(type) + + assertThat(latest?.resolvedNetworkType).isEqualTo(expected) + + job.cancel() + } + + @Test + fun testFlowForSubId_networkTypeUpdates_default() = runBlocking(IMMEDIATE) { whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager) @@ -274,10 +318,34 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() { val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this) val callback = getTelephonyCallbackForType() - val ti = mock() + val type = NETWORK_TYPE_LTE + val expected = DefaultNetworkType(type) + val ti = mock().also { whenever(it.networkType).thenReturn(type) } callback.onDisplayInfoChanged(ti) - assertThat(latest?.displayInfo).isEqualTo(ti) + assertThat(latest?.resolvedNetworkType).isEqualTo(expected) + + job.cancel() + } + + @Test + fun testFlowForSubId_networkTypeUpdates_override() = + runBlocking(IMMEDIATE) { + whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager) + + var latest: MobileSubscriptionModel? = null + val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this) + + val callback = getTelephonyCallbackForType() + val type = OVERRIDE_NETWORK_TYPE_LTE_CA + val expected = OverrideNetworkType(type) + val ti = + mock().also { + whenever(it.overrideNetworkType).thenReturn(type) + } + callback.onDisplayInfoChanged(ti) + + assertThat(latest?.resolvedNetworkType).isEqualTo(expected) job.cancel() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index 8ec68f36a837e..cd4dbebcc35c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.MutableStateFlow class FakeMobileIconInteractor : MobileIconInteractor { private val _iconGroup = MutableStateFlow(TelephonyIcons.UNKNOWN) - override val iconGroup = _iconGroup + override val networkTypeIconGroup = _iconGroup private val _isEmergencyOnly = MutableStateFlow(false) override val isEmergencyOnly = _isEmergencyOnly 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 new file mode 100644 index 0000000000000..2bd228603cb01 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -0,0 +1,75 @@ +/* + * 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.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.util.MobileMappingsProxy +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) : + MobileIconsInteractor { + val THREE_G_KEY = mobileMappings.toIconKey(THREE_G) + val LTE_KEY = mobileMappings.toIconKey(LTE) + val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G) + val FIVE_G_OVERRIDE_KEY = mobileMappings.toIconKeyOverride(FIVE_G_OVERRIDE) + + /** + * 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( + THREE_G_KEY to TelephonyIcons.THREE_G, + LTE_KEY to TelephonyIcons.LTE, + FOUR_G_KEY to TelephonyIcons.FOUR_G, + FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G, + ) + + private val _filteredSubscriptions = MutableStateFlow>(listOf()) + override val filteredSubscriptions = _filteredSubscriptions + + private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING) + override val defaultMobileIconMapping = _defaultMobileIconMapping + + private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON) + override val defaultMobileIconGroup = _defaultMobileIconGroup + + private val _isUserSetup = MutableStateFlow(true) + override val isUserSetup = _isUserSetup + + /** Always returns a new fake interactor */ + override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor { + return FakeMobileIconInteractor() + } + + companion object { + val DEFAULT_ICON = TelephonyIcons.G + + // Use [MobileMappings] to define some simple definitions + const val THREE_G = NETWORK_TYPE_GSM + const val LTE = NETWORK_TYPE_LTE + const val FOUR_G = NETWORK_TYPE_UMTS + const val FIVE_G_OVERRIDE = OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO + } +} 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 2f07d9cb38312..99a7f6a2af7f1 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 @@ -18,10 +18,19 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CellSignalStrength import android.telephony.SubscriptionInfo +import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.filters.SmallTest +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.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.repository.FakeMobileSubscriptionRepository +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 +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -29,6 +38,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield import org.junit.Before import org.junit.Test @@ -36,11 +46,19 @@ import org.junit.Test class MobileIconInteractorTest : SysuiTestCase() { private lateinit var underTest: MobileIconInteractor private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository() + private val mobileMappingsProxy = FakeMobileMappingsProxy() + private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy) private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID) @Before fun setUp() { - underTest = MobileIconInteractorImpl(sub1Flow) + underTest = + MobileIconInteractorImpl( + mobileIconsInteractor.defaultMobileIconMapping, + mobileIconsInteractor.defaultMobileIconGroup, + mobileMappingsProxy, + sub1Flow, + ) } @Test @@ -114,6 +132,80 @@ class MobileIconInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun iconGroup_three_g() = + runBlocking(IMMEDIATE) { + mobileSubscriptionRepository.setMobileSubscriptionModel( + MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)), + SUB_1_ID + ) + + var latest: MobileIconGroup? = null + val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(TelephonyIcons.THREE_G) + + job.cancel() + } + + @Test + fun iconGroup_updates_on_change() = + runBlocking(IMMEDIATE) { + mobileSubscriptionRepository.setMobileSubscriptionModel( + MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)), + SUB_1_ID + ) + + var latest: MobileIconGroup? = null + val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) + + mobileSubscriptionRepository.setMobileSubscriptionModel( + MobileSubscriptionModel( + resolvedNetworkType = DefaultNetworkType(FOUR_G), + ), + SUB_1_ID + ) + yield() + + assertThat(latest).isEqualTo(TelephonyIcons.FOUR_G) + + job.cancel() + } + + @Test + fun iconGroup_5g_override_type() = + runBlocking(IMMEDIATE) { + mobileSubscriptionRepository.setMobileSubscriptionModel( + MobileSubscriptionModel(resolvedNetworkType = OverrideNetworkType(FIVE_G_OVERRIDE)), + SUB_1_ID + ) + + var latest: MobileIconGroup? = null + val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(TelephonyIcons.NR_5G) + + job.cancel() + } + + @Test + fun iconGroup_default_if_no_lookup() = + runBlocking(IMMEDIATE) { + mobileSubscriptionRepository.setMobileSubscriptionModel( + MobileSubscriptionModel( + resolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN), + ), + SUB_1_ID + ) + + var latest: MobileIconGroup? = null + val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(FakeMobileIconsInteractor.DEFAULT_ICON) + + job.cancel() + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate 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 89ad9cb9e51e6..ff8c1e2862a42 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 @@ -21,10 +21,12 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository 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 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -40,6 +42,8 @@ class MobileIconsInteractorTest : SysuiTestCase() { private lateinit var underTest: MobileIconsInteractor private val userSetupRepository = FakeUserSetupRepository() private val subscriptionsRepository = FakeMobileSubscriptionRepository() + private val mobileMappingsProxy = FakeMobileMappingsProxy() + private val scope = CoroutineScope(IMMEDIATE) @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker @@ -47,10 +51,12 @@ class MobileIconsInteractorTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) underTest = - MobileIconsInteractor( + MobileIconsInteractorImpl( subscriptionsRepository, carrierConfigTracker, + mobileMappingsProxy, userSetupRepository, + scope ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt new file mode 100644 index 0000000000000..6d8d902615de1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt @@ -0,0 +1,46 @@ +/* + * 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.util + +import com.android.settingslib.SignalIcon.MobileIconGroup +import com.android.settingslib.mobile.MobileMappings.Config +import com.android.settingslib.mobile.TelephonyIcons + +class FakeMobileMappingsProxy : MobileMappingsProxy { + private var iconMap = mapOf() + private var defaultIcons = TelephonyIcons.THREE_G + + fun setIconMap(map: Map) { + iconMap = map + } + override fun mapIconSets(config: Config): Map = iconMap + fun getIconMap() = iconMap + + fun setDefaultIcons(group: MobileIconGroup) { + defaultIcons = group + } + override fun getDefaultIcons(config: Config): MobileIconGroup = defaultIcons + fun getDefaultIcons(): MobileIconGroup = defaultIcons + + override fun toIconKey(networkType: Int): String { + return networkType.toString() + } + + override fun toIconKeyOverride(networkType: Int): String { + return toIconKey(networkType) + "_override" + } +}