From 0fb69e956b6320a591509dc585febf55a17e19f1 Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Wed, 7 Dec 2022 18:00:25 -0500 Subject: [PATCH] [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,