From 0fb69e956b6320a591509dc585febf55a17e19f1 Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Wed, 7 Dec 2022 18:00:25 -0500 Subject: [PATCH 1/2] [Sb refactor] show data activity in Mobile view Adds support for reading the config `config_showActivity`, and uses the new `DataActivityModel` type to show the data activity in the mobile view model / view binder Test: MobileIconViewModelTest Test: MobileConnectionRepositoryTest Test: DemoMobileConnectionParameterizedTest Test: manual demo mode Bug: 238425913 Change-Id: Id413d57d6c694ea6f324b022f6d215ae71b22074 --- .../data/model/MobileConnectionModel.kt | 8 +- .../demo/DemoMobileConnectionsRepository.kt | 4 +- .../prod/MobileConnectionRepositoryImpl.kt | 6 +- .../domain/interactor/MobileIconInteractor.kt | 7 ++ .../mobile/ui/binder/MobileIconBinder.kt | 15 +++ .../ui/viewmodel/MobileIconViewModel.kt | 16 ++++ .../ui/viewmodel/MobileIconsViewModel.kt | 7 +- .../pipeline/shared/ConnectivityConstants.kt | 18 +++- .../shared/data/model/DataActivityModel.kt | 25 +++++ .../wifi/data/repository/WifiRepository.kt | 12 +-- .../DemoMobileConnectionParameterizedTest.kt | 5 +- .../DemoMobileConnectionsRepositoryTest.kt | 5 +- .../prod/MobileConnectionRepositoryTest.kt | 53 ++++++++++- .../interactor/FakeMobileIconInteractor.kt | 9 ++ .../ui/viewmodel/MobileIconViewModelTest.kt | 92 ++++++++++++++++++- 15 files changed, 258 insertions(+), 24 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt index a6b04e4557b0b..e1e9540e37f0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.pipeline.mobile.data.model import android.annotation.IntRange -import android.telephony.Annotation.DataActivityType import android.telephony.CellSignalStrength import android.telephony.TelephonyCallback.CarrierNetworkListener import android.telephony.TelephonyCallback.DataActivityListener @@ -28,6 +27,7 @@ import android.telephony.TelephonyCallback.SignalStrengthsListener import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyManager import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel /** * Data class containing all of the relevant information for a particular line of service, known as @@ -54,7 +54,11 @@ data class MobileConnectionModel( val dataConnectionState: DataConnectionState = Disconnected, /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */ - @DataActivityType val dataActivityDirection: Int? = null, + val dataActivityDirection: DataActivityModel = + DataActivityModel( + hasActivityIn = false, + hasActivityOut = false, + ), /** From [CarrierNetworkListener.onCarrierNetworkChange] */ val carrierNetworkChangeActive: Boolean = false, 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 98b47e4c80e52..44eeb7ca24222 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 @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo import android.content.Context import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE import android.util.Log import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.MobileMappings @@ -34,6 +35,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConn import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled +import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -236,7 +238,7 @@ constructor( primaryLevel = level ?: 0, dataConnectionState = DataConnectionState.Connected, // TODO(b/261029387): not yet supported - dataActivityDirection = activity, + dataActivityDirection = (activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel(), carrierNetworkChangeActive = carrierNetworkChange, resolvedNetworkType = dataType.toResolvedNetworkType() ) 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 295e0dc04c6de..a9e50f6b22932 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 @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConn 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.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -137,7 +138,10 @@ class MobileConnectionRepositoryImpl( } override fun onDataActivity(direction: Int) { - state = state.copy(dataActivityDirection = direction) + state = + state.copy( + dataActivityDirection = direction.toMobileDataActivityModel() + ) trySend(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 15b70f9c1d32b..ae39d12ecbde4 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 @@ -21,9 +21,11 @@ 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.repository.MobileConnectionRepository +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.util.CarrierConfigTracker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -32,6 +34,9 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn interface MobileIconInteractor { + /** The current mobile data activity */ + val activity: Flow + /** Only true if mobile is the default transport but is not validated, otherwise false */ val isDefaultConnectionFailed: StateFlow @@ -82,6 +87,8 @@ class MobileIconInteractorImpl( ) : MobileIconInteractor { private val connectionInfo = connectionRepository.connectionInfo + override val activity = connectionInfo.mapLatest { it.dataActivityDirection } + override val isDataEnabled: StateFlow = connectionRepository.dataEnabled override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled 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 4455801fe8e10..545e624273f16 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,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.binder import android.content.res.ColorStateList +import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup @@ -40,6 +41,9 @@ object MobileIconBinder { view: ViewGroup, viewModel: MobileIconViewModel, ) { + val activityContainer = view.requireViewById(R.id.inout_container) + val activityIn = view.requireViewById(R.id.mobile_in) + val activityOut = view.requireViewById(R.id.mobile_out) 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) } @@ -74,6 +78,15 @@ object MobileIconBinder { } } + // Set the activity indicators + launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } } + + launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } } + + launch { + viewModel.activityContainerVisible.collect { activityContainer.isVisible = it } + } + // Set the tint launch { viewModel.tint.collect { tint -> @@ -81,6 +94,8 @@ object MobileIconBinder { iconView.imageTintList = tintList networkTypeView.imageTintList = tintList roamingView.imageTintList = tintList + activityIn.imageTintList = tintList + activityOut.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 f4d6111c775a7..961283f57def3 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 @@ -22,13 +22,16 @@ 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.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.ExperimentalCoroutinesApi 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 import kotlinx.coroutines.flow.mapLatest /** @@ -48,6 +51,7 @@ constructor( val subscriptionId: Int, iconInteractor: MobileIconInteractor, logger: ConnectivityPipelineLogger, + constants: ConnectivityConstants, ) { /** Whether or not to show the error state of [SignalDrawable] */ private val showExclamationMark: Flow = @@ -89,5 +93,17 @@ constructor( val roaming: Flow = iconInteractor.isRoaming + private val activity: Flow = + if (!constants.shouldShowActivityConfig) { + flowOf(null) + } else { + iconInteractor.activity + } + + val activityInVisible: Flow = activity.map { it?.hasActivityIn ?: false } + val activityOutVisible: Flow = activity.map { it?.hasActivityOut ?: false } + val activityContainerVisible: Flow = + activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) } + val tint: Flow = flowOf(Color.CYAN) } 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 2349cb7c5d801..0b41d319f9dc2 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 @@ -20,6 +20,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView +import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import javax.inject.Inject import kotlinx.coroutines.InternalCoroutinesApi @@ -36,13 +37,15 @@ constructor( val subscriptionIdsFlow: StateFlow>, private val interactor: MobileIconsInteractor, private val logger: ConnectivityPipelineLogger, + private val constants: ConnectivityConstants, ) { /** TODO: do we need to cache these? */ fun viewModelForSub(subId: Int): MobileIconViewModel = MobileIconViewModel( subId, interactor.createMobileConnectionInteractorForSubId(subId), - logger + logger, + constants, ) class Factory @@ -50,12 +53,14 @@ constructor( constructor( private val interactor: MobileIconsInteractor, private val logger: ConnectivityPipelineLogger, + private val constants: ConnectivityConstants, ) { fun create(subscriptionIdsFlow: StateFlow>): MobileIconsViewModel { return MobileIconsViewModel( subscriptionIdsFlow, interactor, logger, + constants, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt index 6efb10f2a6f24..0c9b86cb2b118 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt @@ -16,8 +16,10 @@ package com.android.systemui.statusbar.pipeline.shared +import android.content.Context import android.telephony.TelephonyManager import com.android.systemui.Dumpable +import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG @@ -32,15 +34,25 @@ import javax.inject.Inject @SysUISingleton class ConnectivityConstants @Inject -constructor(dumpManager: DumpManager, telephonyManager: TelephonyManager) : Dumpable { +constructor( + context: Context, + dumpManager: DumpManager, + telephonyManager: TelephonyManager, +) : Dumpable { init { - dumpManager.registerDumpable("${SB_LOGGING_TAG}Constants", this) + dumpManager.registerNormalDumpable("${SB_LOGGING_TAG}Constants", this) } /** True if this device has the capability for data connections and false otherwise. */ val hasDataCapabilities = telephonyManager.isDataCapable + /** True if we should show the activityIn/activityOut icons and false otherwise */ + val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity) + override fun dump(pw: PrintWriter, args: Array) { - pw.apply { println("hasDataCapabilities=$hasDataCapabilities") } + pw.apply { + println("hasDataCapabilities=$hasDataCapabilities") + println("shouldShowActivityConfig=$shouldShowActivityConfig") + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt index 9b415674d5231..05d07144f8afb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.pipeline.shared.data.model +import android.net.wifi.WifiManager +import android.telephony.Annotation +import android.telephony.TelephonyManager import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger @@ -44,3 +47,25 @@ data class DataActivityModel( const val ACTIVITY_PREFIX = "dataActivity" private const val COL_ACTIVITY_IN = "in" private const val COL_ACTIVITY_OUT = "out" + +fun @receiver:Annotation.DataActivityType Int.toMobileDataActivityModel(): DataActivityModel = + when (this) { + TelephonyManager.DATA_ACTIVITY_IN -> + DataActivityModel(hasActivityIn = true, hasActivityOut = false) + TelephonyManager.DATA_ACTIVITY_OUT -> + DataActivityModel(hasActivityIn = false, hasActivityOut = true) + TelephonyManager.DATA_ACTIVITY_INOUT -> + DataActivityModel(hasActivityIn = true, hasActivityOut = true) + else -> DataActivityModel(hasActivityIn = false, hasActivityOut = false) + } + +fun Int.toWifiDataActivityModel(): DataActivityModel = + when (this) { + WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> + DataActivityModel(hasActivityIn = true, hasActivityOut = false) + WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> + DataActivityModel(hasActivityIn = false, hasActivityOut = true) + WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> + DataActivityModel(hasActivityIn = true, hasActivityOut = true) + else -> DataActivityModel(hasActivityIn = false, hasActivityOut = false) + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index 81441980abf7f..5ccd6f45770d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import java.util.concurrent.Executor import javax.inject.Inject @@ -237,7 +238,7 @@ class WifiRepositoryImpl @Inject constructor( conflatedCallbackFlow { val callback = TrafficStateCallback { state -> logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state)) - trySend(trafficStateToDataActivityModel(state)) + trySend(state.toWifiDataActivityModel()) } wifiManager.registerTrafficStateCallback(mainExecutor, callback) awaitClose { wifiManager.unregisterTrafficStateCallback(callback) } @@ -266,15 +267,6 @@ class WifiRepositoryImpl @Inject constructor( // NetworkCallback inside [wifiNetwork] for our wifi network information. val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive - private fun trafficStateToDataActivityModel(state: Int): DataActivityModel { - return DataActivityModel( - hasActivityIn = state == TrafficStateCallback.DATA_ACTIVITY_IN || - state == TrafficStateCallback.DATA_ACTIVITY_INOUT, - hasActivityOut = state == TrafficStateCallback.DATA_ACTIVITY_OUT || - state == TrafficStateCallback.DATA_ACTIVITY_INOUT, - ) - } - private fun networkCapabilitiesToWifiInfo( networkCapabilities: NetworkCapabilities ): WifiInfo? { 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 b2423da00dab4..9b602cc1b34ea 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 @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo import android.telephony.Annotation import android.telephony.TelephonyManager +import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE import androidx.test.filters.SmallTest import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.TelephonyIcons @@ -25,6 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState 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.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -114,7 +116,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC assertThat(conn.subId).isEqualTo(model.subId) assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level) assertThat(connectionInfo.primaryLevel).isEqualTo(model.level) - assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity) + assertThat(connectionInfo.dataActivityDirection) + .isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel()) assertThat(connectionInfo.carrierNetworkChangeActive) .isEqualTo(model.carrierNetworkChange) assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming) 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 e4f29e2038723..8714a9e0471bf 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 @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT +import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID import androidx.test.filters.SmallTest import com.android.settingslib.SignalIcon @@ -27,6 +28,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectio 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.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -289,7 +291,8 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(conn.subId).isEqualTo(model.subId) assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level) assertThat(connectionInfo.primaryLevel).isEqualTo(model.level) - assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity) + assertThat(connectionInfo.dataActivityDirection) + .isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel()) assertThat(connectionInfo.carrierNetworkChangeActive) .isEqualTo(model.carrierNetworkChange) assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming) 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 0b3e5b5bedea2..ebfe4f03d173f 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 @@ -23,10 +23,16 @@ import android.telephony.ServiceState import android.telephony.SignalStrength import android.telephony.SubscriptionInfo import android.telephony.TelephonyCallback +import android.telephony.TelephonyCallback.DataActivityListener import android.telephony.TelephonyCallback.ServiceStateListener import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA import android.telephony.TelephonyManager +import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT +import android.telephony.TelephonyManager.DATA_ACTIVITY_IN +import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT +import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE +import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT import android.telephony.TelephonyManager.DATA_CONNECTED import android.telephony.TelephonyManager.DATA_CONNECTING import android.telephony.TelephonyManager.DATA_DISCONNECTED @@ -46,6 +52,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetwork 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.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock @@ -249,10 +257,11 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { var latest: MobileConnectionModel? = null val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this) - val callback = getTelephonyCallbackForType() - callback.onDataActivity(3) + val callback = getTelephonyCallbackForType() + callback.onDataActivity(DATA_ACTIVITY_INOUT) - assertThat(latest?.dataActivityDirection).isEqualTo(3) + assertThat(latest?.dataActivityDirection) + .isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel()) job.cancel() } @@ -459,6 +468,44 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun `activity - updates from callback`() = + runBlocking(IMMEDIATE) { + var latest: DataActivityModel? = null + val job = + underTest.connectionInfo.onEach { latest = it.dataActivityDirection }.launchIn(this) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false)) + + val cb = getTelephonyCallbackForType() + cb.onDataActivity(DATA_ACTIVITY_IN) + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false)) + + cb.onDataActivity(DATA_ACTIVITY_OUT) + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true)) + + cb.onDataActivity(DATA_ACTIVITY_INOUT) + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true)) + + cb.onDataActivity(DATA_ACTIVITY_NONE) + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false)) + + cb.onDataActivity(DATA_ACTIVITY_DORMANT) + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false)) + + cb.onDataActivity(1234) + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false)) + + job.cancel() + } + private fun getTelephonyCallbacks(): List { val callbackCaptor = argumentCaptor() Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture()) 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 0e2c38e9a0716..27a0b55155107 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 @@ -19,11 +19,20 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CellSignalStrength import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.TelephonyIcons +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.flow.MutableStateFlow class FakeMobileIconInteractor : MobileIconInteractor { override val alwaysShowDataRatIcon = MutableStateFlow(false) + override val activity = + MutableStateFlow( + DataActivityModel( + hasActivityIn = false, + hasActivityOut = false, + ) + ) + private val _iconGroup = MutableStateFlow(TelephonyIcons.THREE_G) override val networkTypeIconGroup = _iconGroup diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index 2c8f0a7d9e74c..415ce75345b25 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -23,7 +23,10 @@ import com.android.systemui.SysuiTestCase 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.FakeMobileIconInteractor +import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn @@ -40,6 +43,7 @@ class MobileIconViewModelTest : SysuiTestCase() { private lateinit var underTest: MobileIconViewModel private val interactor = FakeMobileIconInteractor() @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var constants: ConnectivityConstants @Before fun setUp() { @@ -53,7 +57,7 @@ class MobileIconViewModelTest : SysuiTestCase() { setNumberOfLevels(4) isDataConnected.value = true } - underTest = MobileIconViewModel(SUB_1_ID, interactor, logger) + underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants) } @Test @@ -250,6 +254,92 @@ class MobileIconViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun `data activity - null when config is off`() = + runBlocking(IMMEDIATE) { + // Create a new view model here so the constants are properly read + whenever(constants.shouldShowActivityConfig).thenReturn(false) + underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants) + + var inVisible: Boolean? = null + val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this) + + var outVisible: Boolean? = null + val outJob = underTest.activityInVisible.onEach { outVisible = it }.launchIn(this) + + var containerVisible: Boolean? = null + val containerJob = + underTest.activityInVisible.onEach { containerVisible = it }.launchIn(this) + + interactor.activity.value = + DataActivityModel( + hasActivityIn = true, + hasActivityOut = true, + ) + + assertThat(inVisible).isFalse() + assertThat(outVisible).isFalse() + assertThat(containerVisible).isFalse() + + inJob.cancel() + outJob.cancel() + containerJob.cancel() + } + + @Test + fun `data activity - config on - test indicators`() = + runBlocking(IMMEDIATE) { + // Create a new view model here so the constants are properly read + whenever(constants.shouldShowActivityConfig).thenReturn(true) + underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants) + + var inVisible: Boolean? = null + val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this) + + var outVisible: Boolean? = null + val outJob = underTest.activityOutVisible.onEach { outVisible = it }.launchIn(this) + + var containerVisible: Boolean? = null + val containerJob = + underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this) + + interactor.activity.value = + DataActivityModel( + hasActivityIn = true, + hasActivityOut = false, + ) + + yield() + + assertThat(inVisible).isTrue() + assertThat(outVisible).isFalse() + assertThat(containerVisible).isTrue() + + interactor.activity.value = + DataActivityModel( + hasActivityIn = false, + hasActivityOut = true, + ) + + assertThat(inVisible).isFalse() + assertThat(outVisible).isTrue() + assertThat(containerVisible).isTrue() + + interactor.activity.value = + DataActivityModel( + hasActivityIn = false, + hasActivityOut = false, + ) + + assertThat(inVisible).isFalse() + assertThat(outVisible).isFalse() + assertThat(containerVisible).isFalse() + + inJob.cancel() + outJob.cancel() + containerJob.cancel() + } + /** Convenience constructor for these tests */ private fun defaultSignal( level: Int = 1, From 6acee7b1eeaa421267accd62cd08f8af5eb163a6 Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Thu, 8 Dec 2022 15:58:31 -0500 Subject: [PATCH 2/2] [Sb refactor] Support NetworkName in new mobile pipeline The network name can come from one of 3 places: 1. TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED, which passes an intent containing a network name which we can derive 2. ServiceState.getOperatorAlphaShort(), from the service state changed telephony callback 3. A default value, set in the framework config `lockscreen_carrier_default` Based on the implementation that currently lives in `MobileSignalController`, we were only using `NetworkName` a.k.a. `EXTRA_SPN`, and not `NetworkNameData` a.k.a. `EXTRA_DATA_SPN`. Therefore this CL only adds support for the former. The front end does not consume this piece of data yet, since it is only needed for QuickSettings. Future CLs will add support for a LocationBasedMobileViewModel (similar to Wi-Fi), which can then start consuming the values. Test: MobileConnectionRepositoryTest Test: DemoMobileConnectionsRepositoryTest Test: MobileIconInteractorTest Bug: 238425913 Change-Id: I04fa90bf7b52aa992f9721c7301f541f6085cf68 --- .../data/model/MobileConnectionModel.kt | 23 ++-- .../mobile/data/model/NetworkNameModel.kt | 62 +++++++++++ .../repository/MobileConnectionRepository.kt | 4 + .../demo/DemoMobileConnectionsRepository.kt | 4 + .../DemoModeMobileConnectionDataSource.kt | 2 + .../demo/model/FakeNetworkEventModel.kt | 1 + .../prod/MobileConnectionRepositoryImpl.kt | 29 +++++ .../prod/MobileConnectionsRepositoryImpl.kt | 12 +++ .../domain/interactor/MobileIconInteractor.kt | 26 +++++ .../FakeMobileConnectionRepository.kt | 4 + .../DemoMobileConnectionParameterizedTest.kt | 14 ++- .../DemoMobileConnectionsRepositoryTest.kt | 3 + .../prod/MobileConnectionRepositoryTest.kt | 100 ++++++++++++++++++ .../prod/MobileConnectionsRepositoryTest.kt | 1 + .../interactor/FakeMobileIconInteractor.kt | 3 + .../interactor/MobileIconInteractorTest.kt | 39 +++++++ 16 files changed, 318 insertions(+), 9 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt index e1e9540e37f0a..6c37f94007cba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt @@ -39,33 +39,42 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod * threading complex system objects through the pipeline. */ data class MobileConnectionModel( - /** From [ServiceStateListener.onServiceStateChanged] */ + /** Fields below are from [ServiceStateListener.onServiceStateChanged] */ val isEmergencyOnly: Boolean = false, val isRoaming: Boolean = false, + /** + * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the + * current registered operator name in short alphanumeric format. In some cases this name might + * be preferred over other methods of calculating the network name + */ + val operatorAlphaShort: String? = null, - /** From [SignalStrengthsListener.onSignalStrengthsChanged] */ + /** Fields below from [SignalStrengthsListener.onSignalStrengthsChanged] */ val isGsm: Boolean = false, @IntRange(from = 0, to = 4) val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, @IntRange(from = 0, to = 4) val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, - /** Mapped from [DataConnectionStateListener.onDataConnectionStateChanged] */ + /** Fields below from [DataConnectionStateListener.onDataConnectionStateChanged] */ val dataConnectionState: DataConnectionState = Disconnected, - /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */ + /** + * Fields below from [DataActivityListener.onDataActivity]. See [TelephonyManager] for the + * values + */ val dataActivityDirection: DataActivityModel = DataActivityModel( hasActivityIn = false, hasActivityOut = false, ), - /** From [CarrierNetworkListener.onCarrierNetworkChange] */ + /** Fields below from [CarrierNetworkListener.onCarrierNetworkChange] */ val carrierNetworkChangeActive: Boolean = false, + /** Fields below from [DisplayInfoListener.onDisplayInfoChanged]. */ + /** - * 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 */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt new file mode 100644 index 0000000000000..a8cf35ad30298 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt @@ -0,0 +1,62 @@ +/* + * 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.content.Intent +import android.telephony.TelephonyManager.EXTRA_DATA_SPN +import android.telephony.TelephonyManager.EXTRA_PLMN +import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN +import android.telephony.TelephonyManager.EXTRA_SHOW_SPN + +/** + * Encapsulates the data needed to show a network name for a mobile network. The data is parsed from + * the intent sent by [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED]. + */ +sealed interface NetworkNameModel { + val name: String + + /** The default name is read from [com.android.internal.R.string.lockscreen_carrier_default] */ + data class Default(override val name: String) : NetworkNameModel + + /** + * This name has been derived from telephony intents. see + * [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED] + */ + data class Derived(override val name: String) : NetworkNameModel +} + +fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? { + val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false) + val spn = getStringExtra(EXTRA_DATA_SPN) + val showPlmn = getBooleanExtra(EXTRA_SHOW_PLMN, false) + val plmn = getStringExtra(EXTRA_PLMN) + + val str = StringBuilder() + val strData = StringBuilder() + if (showPlmn && plmn != null) { + str.append(plmn) + strData.append(plmn) + } + if (showSpn && spn != null) { + if (str.isNotEmpty()) { + str.append(separator) + } + str.append(spn) + } + + return if (str.isNotEmpty()) NetworkNameModel.Derived(str.toString()) else null +} 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 fc59f6e1eccf0..2fd415e6777f8 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 @@ -21,6 +21,7 @@ import android.telephony.SubscriptionManager import android.telephony.TelephonyCallback import android.telephony.TelephonyManager import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -58,4 +59,7 @@ interface MobileConnectionRepository { * True if the Enhanced Roaming Indicator (ERI) display number is not [TelephonyManager.ERI_OFF] */ val cdmaRoaming: StateFlow + + /** The service provider name for this network connection, or the default name */ + val networkName: StateFlow } 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 44eeb7ca24222..d3ee85f19347c 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 @@ -27,6 +27,7 @@ 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.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel 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 @@ -187,6 +188,7 @@ constructor( // This is always true here, because we split out disabled states at the data-source level connection.dataEnabled.value = true connection.isDefaultDataSubscription.value = state.dataType != null + connection.networkName.value = NetworkNameModel.Derived(state.name) connection.cdmaRoaming.value = state.roaming connection.connectionInfo.value = state.toMobileConnectionModel() @@ -266,4 +268,6 @@ class DemoMobileConnectionRepository(override val subId: Int) : MobileConnection override val isDefaultDataSubscription = MutableStateFlow(true) override val cdmaRoaming = MutableStateFlow(false) + + override val networkName = MutableStateFlow(NetworkNameModel.Derived("demo network")) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt index 2cdbc191f7ad8..a1ae8ed7329a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt @@ -99,6 +99,7 @@ constructor( val activity = getString("activity")?.toActivity() val carrierNetworkChange = getString("carriernetworkchange") == "show" val roaming = getString("roam") == "show" + val name = getString("networkname") ?: "demo mode" return Mobile( level = level, @@ -109,6 +110,7 @@ constructor( activity = activity, carrierNetworkChange = carrierNetworkChange, roaming = roaming, + name = name, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt index b8543eca69a85..8b03f71a27293 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt @@ -35,6 +35,7 @@ sealed interface FakeNetworkEventModel { @DataActivityType val activity: Int?, val carrierNetworkChange: Boolean, val roaming: Boolean, + val name: String, ) : FakeNetworkEventModel data class MobileDisabled( 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 a9e50f6b22932..7e9a9cea9b95b 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 @@ -17,26 +17,32 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod import android.content.Context +import android.content.IntentFilter import android.database.ContentObserver import android.provider.Settings.Global import android.telephony.CellSignalStrength import android.telephony.CellSignalStrengthCdma import android.telephony.ServiceState import android.telephony.SignalStrength +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.TelephonyCallback import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE import android.telephony.TelephonyManager import android.telephony.TelephonyManager.ERI_OFF +import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN +import com.android.systemui.broadcast.BroadcastDispatcher 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.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel 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.model.toNetworkNameModel 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 @@ -63,8 +69,11 @@ import kotlinx.coroutines.flow.stateIn class MobileConnectionRepositoryImpl( private val context: Context, override val subId: Int, + defaultNetworkName: NetworkNameModel, + networkNameSeparator: String, private val telephonyManager: TelephonyManager, private val globalSettings: GlobalSettings, + broadcastDispatcher: BroadcastDispatcher, defaultDataSubId: StateFlow, globalMobileDataSettingChangedEvent: Flow, mobileMappingsProxy: MobileMappingsProxy, @@ -101,6 +110,7 @@ class MobileConnectionRepositoryImpl( state.copy( isEmergencyOnly = serviceState.isEmergencyOnly, isRoaming = serviceState.roaming, + operatorAlphaShort = serviceState.operatorAlphaShort, ) trySend(state) } @@ -222,6 +232,19 @@ class MobileConnectionRepositoryImpl( .mapLatest { telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber != ERI_OFF } .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val networkName: StateFlow = + broadcastDispatcher + .broadcastFlow(IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)) { + intent, + _ -> + if (intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) != subId) { + defaultNetworkName + } else { + intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName) + override val dataEnabled: StateFlow = telephonyPollingEvent .mapLatest { dataConnectionAllowed() } @@ -237,6 +260,7 @@ class MobileConnectionRepositoryImpl( class Factory @Inject constructor( + private val broadcastDispatcher: BroadcastDispatcher, private val context: Context, private val telephonyManager: TelephonyManager, private val logger: ConnectivityPipelineLogger, @@ -247,14 +271,19 @@ class MobileConnectionRepositoryImpl( ) { fun build( subId: Int, + defaultNetworkName: NetworkNameModel, + networkNameSeparator: String, defaultDataSubId: StateFlow, globalMobileDataSettingChangedEvent: Flow, ): MobileConnectionRepository { return MobileConnectionRepositoryImpl( context, subId, + defaultNetworkName, + networkNameSeparator, telephonyManager.createForSubscriptionId(subId), globalSettings, + broadcastDispatcher, defaultDataSubId, globalMobileDataSettingChangedEvent, mobileMappingsProxy, 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 483df4759d28f..a9b3d18774fd1 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 @@ -38,12 +38,14 @@ import androidx.annotation.VisibleForTesting import com.android.internal.telephony.PhoneConstants import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.MobileMappings.Config +import com.android.systemui.R 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.MobileConnectivityModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel 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 @@ -88,6 +90,14 @@ constructor( ) : MobileConnectionsRepository { private var subIdRepositoryCache: MutableMap = mutableMapOf() + private val defaultNetworkName = + NetworkNameModel.Default( + context.getString(com.android.internal.R.string.lockscreen_carrier_default) + ) + + private val networkNameSeparator: String = + context.getString(R.string.status_bar_network_name_separator) + /** * 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 @@ -243,6 +253,8 @@ constructor( private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository { return mobileConnectionRepositoryFactory.build( subId, + defaultNetworkName, + networkNameSeparator, defaultDataSubId, globalMobileDataSettingChangedEvent, ) 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 ae39d12ecbde4..76e6a96a19d75 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,6 +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.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.util.CarrierConfigTracker @@ -56,6 +57,15 @@ interface MobileIconInteractor { /** Observable for RAT type (network type) indicator */ val networkTypeIconGroup: StateFlow + /** + * Provider name for this network connection. The name can be one of 3 values: + * 1. The default network name, if one is configured + * 2. A derived name based off of the intent [ACTION_SERVICE_PROVIDERS_UPDATED] + * 3. Or, in the case where the repository sends us the default network name, we check for an + * override in [connectionInfo.operatorAlphaShort], a value that is derived from [ServiceState] + */ + val networkName: StateFlow + /** True if this line of service is emergency-only */ val isEmergencyOnly: StateFlow @@ -93,6 +103,22 @@ class MobileIconInteractorImpl( override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled + override val networkName = + combine(connectionInfo, connectionRepository.networkName) { connection, networkName -> + if ( + networkName is NetworkNameModel.Default && connection.operatorAlphaShort != null + ) { + NetworkNameModel.Derived(connection.operatorAlphaShort) + } else { + networkName + } + } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + connectionRepository.networkName.value + ) + /** Observable for the current RAT indicator icon ([MobileIconGroup]) */ override val networkTypeIconGroup: StateFlow = combine( 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 7b9929d963d3d..59eec5327c12b 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 @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import kotlinx.coroutines.flow.MutableStateFlow // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository @@ -32,6 +33,9 @@ class FakeMobileConnectionRepository(override val subId: Int) : MobileConnection override val cdmaRoaming = MutableStateFlow(false) + override val networkName = + MutableStateFlow(NetworkNameModel.Default("default")) + fun setConnectionInfo(model: MobileConnectionModel) { _connectionInfo.value = model } 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 9b602cc1b34ea..3d5316d1f19d7 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 @@ -25,6 +25,7 @@ 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.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.util.mockito.mock @@ -98,6 +99,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC activity = testCase.activity, carrierNetworkChange = testCase.carrierNetworkChange, roaming = testCase.roaming, + name = "demo name", ) fakeNetworkEventFlow.value = networkModel @@ -121,6 +123,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC assertThat(connectionInfo.carrierNetworkChangeActive) .isEqualTo(model.carrierNetworkChange) assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming) + assertThat(conn.networkName.value).isEqualTo(NetworkNameModel.Derived(model.name)) // TODO(b/261029387): check these once we start handling them assertThat(connectionInfo.isEmergencyOnly).isFalse() @@ -144,6 +147,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC @Annotation.DataActivityType val activity: Int, val carrierNetworkChange: Boolean, val roaming: Boolean, + val name: String, ) { override fun toString(): String { return "INPUT(level=$level, " + @@ -153,7 +157,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC "inflateStrength=$inflateStrength, " + "activity=$activity, " + "carrierNetworkChange=$carrierNetworkChange, " + - "roaming=$roaming)" + "roaming=$roaming, " + + "name=$name)" } // Convenience for iterating test data and creating new cases @@ -166,6 +171,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC @Annotation.DataActivityType activity: Int? = null, carrierNetworkChange: Boolean? = null, roaming: Boolean? = null, + name: String? = null, ): TestCase = TestCase( level = level ?: this.level, @@ -176,6 +182,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC activity = activity ?: this.activity, carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange, roaming = roaming ?: this.roaming, + name = name ?: this.name, ) } @@ -204,6 +211,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC private val carrierNetworkChange = booleanList // false first so the base case doesn't have roaming set (more common) private val roaming = listOf(false, true) + private val names = listOf("name 1", "name 2") @Parameters(name = "{0}") @JvmStatic fun data() = testData() @@ -239,6 +247,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC activity.first(), carrierNetworkChange.first(), roaming.first(), + names.first(), ) val tail = @@ -249,7 +258,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC inflateStrength.map { baseCase.modifiedBy(inflateStrength = it) }, activity.map { baseCase.modifiedBy(activity = it) }, carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) }, - roaming.map { baseCase.modifiedBy(roaming = it) } + roaming.map { baseCase.modifiedBy(roaming = it) }, + names.map { baseCase.modifiedBy(name = it) }, ) .flatten() 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 8714a9e0471bf..34f30eb7c0a69 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 @@ -25,6 +25,7 @@ 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.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel 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 @@ -296,6 +297,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(connectionInfo.carrierNetworkChangeActive) .isEqualTo(model.carrierNetworkChange) assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming) + assertThat(conn.networkName.value).isEqualTo(NetworkNameModel.Derived(model.name)) // TODO(b/261029387) check these once we start handling them assertThat(connectionInfo.isEmergencyOnly).isFalse() @@ -328,4 +330,5 @@ fun validMobileEvent( activity = activity, carrierNetworkChange = carrierNetworkChange, roaming = roaming, + name = "demo name", ) 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 ebfe4f03d173f..7fa80653f29c9 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 @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod +import android.content.Intent import android.os.UserHandle import android.provider.Settings import android.telephony.CellSignalStrengthCdma @@ -40,15 +41,22 @@ import android.telephony.TelephonyManager.DATA_DISCONNECTING import android.telephony.TelephonyManager.DATA_UNKNOWN import android.telephony.TelephonyManager.ERI_OFF import android.telephony.TelephonyManager.ERI_ON +import android.telephony.TelephonyManager.EXTRA_PLMN +import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN +import android.telephony.TelephonyManager.EXTRA_SHOW_SPN +import android.telephony.TelephonyManager.EXTRA_SPN +import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID 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.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel 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.toNetworkNameModel 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 @@ -98,8 +106,11 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { MobileConnectionRepositoryImpl( context, SUB_1_ID, + DEFAULT_NAME, + SEP, telephonyManager, globalSettings, + fakeBroadcastDispatcher, connectionsRepo.defaultDataSubId, connectionsRepo.globalMobileDataSettingChangedEvent, mobileMappings, @@ -506,6 +517,74 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun `network name - default`() = + runBlocking(IMMEDIATE) { + var latest: NetworkNameModel? = null + val job = underTest.networkName.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(DEFAULT_NAME) + + job.cancel() + } + + @Test + fun `network name - uses broadcast info - returns derived`() = + runBlocking(IMMEDIATE) { + var latest: NetworkNameModel? = null + val job = underTest.networkName.onEach { latest = it }.launchIn(this) + + val intent = spnIntent() + + fakeBroadcastDispatcher.registeredReceivers.forEach { receiver -> + receiver.onReceive(context, intent) + } + + assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) + + job.cancel() + } + + @Test + fun `network name - broadcast not for this sub id - returns default`() = + runBlocking(IMMEDIATE) { + var latest: NetworkNameModel? = null + val job = underTest.networkName.onEach { latest = it }.launchIn(this) + + val intent = spnIntent(subId = 101) + + fakeBroadcastDispatcher.registeredReceivers.forEach { receiver -> + receiver.onReceive(context, intent) + } + + assertThat(latest).isEqualTo(DEFAULT_NAME) + + job.cancel() + } + + @Test + fun `network name - operatorAlphaShort - tracked`() = + runBlocking(IMMEDIATE) { + var latest: String? = null + + val job = + underTest.connectionInfo.onEach { latest = it.operatorAlphaShort }.launchIn(this) + + val shortName = "short name" + val serviceState = ServiceState() + serviceState.setOperatorName( + /* longName */ "long name", + /* shortName */ shortName, + /* numeric */ "12345", + ) + + getTelephonyCallbackForType().onServiceStateChanged(serviceState) + + assertThat(latest).isEqualTo(shortName) + + job.cancel() + } + private fun getTelephonyCallbacks(): List { val callbackCaptor = argumentCaptor() Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture()) @@ -531,10 +610,31 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { return signalStrength } + private fun spnIntent( + subId: Int = SUB_1_ID, + showSpn: Boolean = true, + spn: String = SPN, + showPlmn: Boolean = true, + plmn: String = PLMN, + ): Intent = + Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply { + putExtra(EXTRA_SUBSCRIPTION_ID, subId) + putExtra(EXTRA_SHOW_SPN, showSpn) + putExtra(EXTRA_SPN, spn) + putExtra(EXTRA_SHOW_PLMN, showPlmn) + putExtra(EXTRA_PLMN, plmn) + } + companion object { 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 DEFAULT_NAME = NetworkNameModel.Default("default name") + private const val SEP = "-" + + private const val SPN = "testSpn" + private const val PLMN = "testPlmn" } } 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 6d80acbb65ac0..3cc1e8b74668e 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 @@ -91,6 +91,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { connectionFactory = MobileConnectionRepositoryImpl.Factory( + fakeBroadcastDispatcher, context = context, telephonyManager = telephonyManager, bgDispatcher = IMMEDIATE, 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 27a0b55155107..c3519b7c81767 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 @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CellSignalStrength import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.TelephonyIcons +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.flow.MutableStateFlow @@ -36,6 +37,8 @@ class FakeMobileIconInteractor : MobileIconInteractor { private val _iconGroup = MutableStateFlow(TelephonyIcons.THREE_G) override val networkTypeIconGroup = _iconGroup + override val networkName = MutableStateFlow(NetworkNameModel.Derived("demo mode")) + private val _isEmergencyOnly = MutableStateFlow(false) override val isEmergencyOnly = _isEmergencyOnly 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 9b6f6df90174b..4dca780425e54 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 @@ -25,6 +25,7 @@ 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.MobileConnectionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel 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 @@ -392,6 +393,41 @@ class MobileIconInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun `network name - uses operatorAlphaShot when non null and repo is default`() = + runBlocking(IMMEDIATE) { + var latest: NetworkNameModel? = null + val job = underTest.networkName.onEach { latest = it }.launchIn(this) + + val testOperatorName = "operatorAlphaShort" + + // Default network name, operator name is non-null, uses the operator name + connectionRepository.networkName.value = DEFAULT_NAME + connectionRepository.setConnectionInfo( + MobileConnectionModel(operatorAlphaShort = testOperatorName) + ) + yield() + + assertThat(latest).isEqualTo(NetworkNameModel.Derived(testOperatorName)) + + // Default network name, operator name is null, uses the default + connectionRepository.setConnectionInfo(MobileConnectionModel(operatorAlphaShort = null)) + yield() + + assertThat(latest).isEqualTo(DEFAULT_NAME) + + // Derived network name, operator name non-null, uses the derived name + connectionRepository.networkName.value = DERIVED_NAME + connectionRepository.setConnectionInfo( + MobileConnectionModel(operatorAlphaShort = testOperatorName) + ) + yield() + + assertThat(latest).isEqualTo(DERIVED_NAME) + + job.cancel() + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate @@ -401,5 +437,8 @@ class MobileIconInteractorTest : SysuiTestCase() { private const val SUB_1_ID = 1 private val SUB_1 = mock().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) } + + private val DEFAULT_NAME = NetworkNameModel.Default("test default name") + private val DERIVED_NAME = NetworkNameModel.Derived("test derived name") } }