diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 57cdc9eed5770..44e5cd9543ef7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -387,7 +387,7 @@ public interface StatusBarIconController { if (statusBarPipelineFlags.runNewMobileIconsBackend()) { // This starts the flow for the new pipeline, and will notify us of changes if // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true. - mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel(); + mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel(); MobileIconsBinder.bind(mGroup, mMobileIconsViewModel); } else { mMobileIconsViewModel = 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 2fd415e6777f8..40e9ba1a46c73 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -20,6 +20,7 @@ import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.TelephonyCallback import android.telephony.TelephonyManager +import com.android.systemui.log.table.TableLogBuffer 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 @@ -39,6 +40,13 @@ import kotlinx.coroutines.flow.StateFlow interface MobileConnectionRepository { /** The subscriptionId that this connection represents */ val subId: Int + + /** + * The table log buffer created for this connection. Will have the name "MobileConnectionLog + * [subId]" + */ + val tableLogBuffer: TableLogBuffer + /** * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single * listener + model. 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 d3ee85f19347c..b252de8dd3890 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 @@ -24,6 +24,8 @@ import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory 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 @@ -60,6 +62,7 @@ constructor( private val dataSource: DemoModeMobileConnectionDataSource, @Application private val scope: CoroutineScope, context: Context, + private val logFactory: TableLogBufferFactory, ) : MobileConnectionsRepository { private var demoCommandJob: Job? = null @@ -149,7 +152,16 @@ constructor( override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository { return connectionRepoCache[subId] - ?: DemoMobileConnectionRepository(subId).also { connectionRepoCache[subId] = it } + ?: createDemoMobileConnectionRepo(subId).also { connectionRepoCache[subId] = it } + } + + private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository { + val tableLogBuffer = logFactory.create("DemoMobileConnectionLog [$subId]", 100) + + return DemoMobileConnectionRepository( + subId, + tableLogBuffer, + ) } override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit) @@ -260,7 +272,10 @@ constructor( } } -class DemoMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository { +class DemoMobileConnectionRepository( + override val subId: Int, + override val tableLogBuffer: TableLogBuffer, +) : MobileConnectionRepository { override val connectionInfo = MutableStateFlow(MobileConnectionModel()) override val dataEnabled = MutableStateFlow(true) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index 50b29f4c90281..0b9e1583898e5 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 @@ -96,6 +96,8 @@ class MobileConnectionRepositoryImpl( private val telephonyCallbackEvent = MutableSharedFlow(extraBufferCapacity = 1) + override val tableLogBuffer: TableLogBuffer = mobileLogger + override val connectionInfo: StateFlow = run { var state = MobileConnectionModel() conflatedCallbackFlow { 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 76e6a96a19d75..e6686dce7bbc7 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 @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CarrierConfigManager import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.table.TableLogBuffer 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 @@ -35,6 +36,9 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn interface MobileIconInteractor { + /** The table log created for this connection */ + val tableLogBuffer: TableLogBuffer + /** The current mobile data activity */ val activity: Flow @@ -97,6 +101,8 @@ class MobileIconInteractorImpl( ) : MobileIconInteractor { private val connectionInfo = connectionRepository.connectionInfo + override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer + override val activity = connectionInfo.mapLatest { it.dataActivityDirection } override val isDataEnabled: StateFlow = connectionRepository.dataEnabled diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt index 62fa723dbf04e..829a5cad65047 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt @@ -20,7 +20,6 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.phone.StatusBarIconController -import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel @@ -70,6 +69,9 @@ constructor( private val mobileSubIdsState: StateFlow> = mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) + /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */ + val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState) + override fun start() { // Only notify the icon controller if we want to *render* the new icons. // Note that this flow may still run if @@ -81,12 +83,4 @@ constructor( } } } - - /** - * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's - * lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with - * the old view system. - */ - fun createMobileIconsViewModel(): MobileIconsViewModel = - iconsViewModelFactory.create(mobileSubIdsState) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt index 070634dedd2ff..b0dc41f45488d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt @@ -18,7 +18,10 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import android.graphics.Color import com.android.systemui.statusbar.phone.StatusBarLocation +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf /** @@ -30,34 +33,50 @@ import kotlinx.coroutines.flow.flowOf */ abstract class LocationBasedMobileViewModel( val commonImpl: MobileIconViewModelCommon, + val logger: ConnectivityPipelineLogger, ) : MobileIconViewModelCommon by commonImpl { abstract val tint: Flow companion object { fun viewModelForLocation( commonImpl: MobileIconViewModelCommon, + logger: ConnectivityPipelineLogger, loc: StatusBarLocation, ): LocationBasedMobileViewModel = when (loc) { - StatusBarLocation.HOME -> HomeMobileIconViewModel(commonImpl) - StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl) - StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl) + StatusBarLocation.HOME -> HomeMobileIconViewModel(commonImpl, logger) + StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl, logger) + StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, logger) } } } class HomeMobileIconViewModel( commonImpl: MobileIconViewModelCommon, -) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl) { - override val tint: Flow = flowOf(Color.CYAN) + logger: ConnectivityPipelineLogger, +) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) { + override val tint: Flow = + flowOf(Color.CYAN) + .distinctUntilChanged() + .logOutputChange(logger, "HOME tint(${commonImpl.subscriptionId})") } -class QsMobileIconViewModel(commonImpl: MobileIconViewModelCommon) : - MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl) { - override val tint: Flow = flowOf(Color.GREEN) +class QsMobileIconViewModel( + commonImpl: MobileIconViewModelCommon, + logger: ConnectivityPipelineLogger, +) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) { + override val tint: Flow = + flowOf(Color.GREEN) + .distinctUntilChanged() + .logOutputChange(logger, "QS tint(${commonImpl.subscriptionId})") } -class KeyguardMobileIconViewModel(commonImpl: MobileIconViewModelCommon) : - MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl) { - override val tint: Flow = flowOf(Color.MAGENTA) +class KeyguardMobileIconViewModel( + commonImpl: MobileIconViewModelCommon, + logger: ConnectivityPipelineLogger, +) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) { + override val tint: Flow = + flowOf(Color.MAGENTA) + .distinctUntilChanged() + .logOutputChange(logger, "KEYGUARD tint(${commonImpl.subscriptionId})") } 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 226409f66e157..2d6ac4efd5128 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 @@ -16,23 +16,27 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel -import android.graphics.Color import com.android.settingslib.graph.SignalDrawable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon +import com.android.systemui.log.table.logDiffsForTable 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.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow 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 +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn /** Common interface for all of the location-based mobile icon view models. */ interface MobileIconViewModelCommon { @@ -53,7 +57,12 @@ interface MobileIconViewModelCommon { * subscription's information. * * There will be exactly one [MobileIconViewModel] per filtered subscription offered from - * [MobileIconsInteractor.filteredSubscriptions] + * [MobileIconsInteractor.filteredSubscriptions]. + * + * For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon] + * interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view + * model gets the exact same information, as well as allows us to log that unified state only once + * per icon. */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @@ -63,12 +72,14 @@ constructor( iconInteractor: MobileIconInteractor, logger: ConnectivityPipelineLogger, constants: ConnectivityConstants, + scope: CoroutineScope, ) : MobileIconViewModelCommon { /** Whether or not to show the error state of [SignalDrawable] */ private val showExclamationMark: Flow = iconInteractor.isDefaultDataEnabled.mapLatest { !it } - override val iconId: Flow = + override val iconId: Flow = run { + val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value) combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) { level, numberOfLevels, @@ -76,31 +87,56 @@ constructor( SignalDrawable.getState(level, numberOfLevels, showExclamationMark) } .distinctUntilChanged() - .logOutputChange(logger, "iconId($subscriptionId)") + .logDiffsForTable( + iconInteractor.tableLogBuffer, + columnPrefix = "", + columnName = "iconId", + initialValue = initial, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), initial) + } override val networkTypeIcon: Flow = combine( - iconInteractor.networkTypeIconGroup, - iconInteractor.isDataConnected, - iconInteractor.isDataEnabled, - iconInteractor.isDefaultConnectionFailed, - iconInteractor.alwaysShowDataRatIcon, - ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow -> - val desc = - if (networkTypeIconGroup.dataContentDescription != 0) - ContentDescription.Resource(networkTypeIconGroup.dataContentDescription) - else null - val icon = Icon.Resource(networkTypeIconGroup.dataType, desc) - return@combine when { - alwaysShow -> icon - !dataConnected -> null - !dataEnabled -> null - failedConnection -> null - else -> icon + iconInteractor.networkTypeIconGroup, + iconInteractor.isDataConnected, + iconInteractor.isDataEnabled, + iconInteractor.isDefaultConnectionFailed, + iconInteractor.alwaysShowDataRatIcon, + ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow -> + val desc = + if (networkTypeIconGroup.dataContentDescription != 0) + ContentDescription.Resource(networkTypeIconGroup.dataContentDescription) + else null + val icon = Icon.Resource(networkTypeIconGroup.dataType, desc) + return@combine when { + alwaysShow -> icon + !dataConnected -> null + !dataEnabled -> null + failedConnection -> null + else -> icon + } } - } + .distinctUntilChanged() + .onEach { + // This is done as an onEach side effect since Icon is not Diffable (yet) + iconInteractor.tableLogBuffer.logChange( + prefix = "", + columnName = "networkTypeIcon", + value = it.toString(), + ) + } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) - override val roaming: Flow = iconInteractor.isRoaming + override val roaming: StateFlow = + iconInteractor.isRoaming + .logDiffsForTable( + iconInteractor.tableLogBuffer, + columnPrefix = "", + columnName = "roaming", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) private val activity: Flow = if (!constants.shouldShowActivityConfig) { @@ -109,10 +145,39 @@ constructor( iconInteractor.activity } - override val activityInVisible: Flow = activity.map { it?.hasActivityIn ?: false } - override val activityOutVisible: Flow = activity.map { it?.hasActivityOut ?: false } - override val activityContainerVisible: Flow = - activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) } + override val activityInVisible: Flow = + activity + .map { it?.hasActivityIn ?: false } + .distinctUntilChanged() + .logDiffsForTable( + iconInteractor.tableLogBuffer, + columnPrefix = "", + columnName = "activityInVisible", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) - val tint: Flow = flowOf(Color.CYAN) + override val activityOutVisible: Flow = + activity + .map { it?.hasActivityOut ?: false } + .distinctUntilChanged() + .logDiffsForTable( + iconInteractor.tableLogBuffer, + columnPrefix = "", + columnName = "activityOutVisible", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val activityContainerVisible: Flow = + activity + .map { it != null && (it.hasActivityIn || it.hasActivityOut) } + .distinctUntilChanged() + .logDiffsForTable( + iconInteractor.tableLogBuffer, + columnPrefix = "", + columnName = "activityContainerVisible", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) } 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 3beb96aa8cf4a..b9318b181aaf3 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 @@ -14,18 +14,19 @@ * limitations under the License. */ -@file:OptIn(InternalCoroutinesApi::class) - package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel +import androidx.annotation.VisibleForTesting +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.phone.StatusBarLocation 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 +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch /** * View model for describing the system's current mobile cellular connections. The result is a list @@ -39,18 +40,32 @@ constructor( private val interactor: MobileIconsInteractor, private val logger: ConnectivityPipelineLogger, private val constants: ConnectivityConstants, + @Application private val scope: CoroutineScope, ) { - /** TODO: do we need to cache these? */ + @VisibleForTesting val mobileIconSubIdCache = mutableMapOf() + + init { + scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } + } + fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel { val common = - MobileIconViewModel( - subId, - interactor.createMobileConnectionInteractorForSubId(subId), - logger, - constants, - ) + mobileIconSubIdCache[subId] + ?: MobileIconViewModel( + subId, + interactor.createMobileConnectionInteractorForSubId(subId), + logger, + constants, + scope, + ) + .also { mobileIconSubIdCache[subId] = it } - return LocationBasedMobileViewModel.viewModelForLocation(common, location) + return LocationBasedMobileViewModel.viewModelForLocation(common, logger, location) + } + + private fun removeInvalidModelsFromCache(subIds: List) { + val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } + subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) } } class Factory @@ -59,6 +74,7 @@ constructor( private val interactor: MobileIconsInteractor, private val logger: ConnectivityPipelineLogger, private val constants: ConnectivityConstants, + @Application private val scope: CoroutineScope, ) { fun create(subscriptionIdsFlow: StateFlow>): MobileIconsViewModel { return MobileIconsViewModel( @@ -66,6 +82,7 @@ constructor( interactor, logger, constants, + scope, ) } } 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 59eec5327c12b..d6a9ee325b2e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -16,12 +16,16 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository +import com.android.systemui.log.table.TableLogBuffer 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 -class FakeMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository { +class FakeMobileConnectionRepository( + override val subId: Int, + override val tableLogBuffer: TableLogBuffer, +) : MobileConnectionRepository { private val _connectionInfo = MutableStateFlow(MobileConnectionModel()) override val connectionInfo = _connectionInfo diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt index 04d3cdd89ab70..7f93328ee95e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt @@ -22,14 +22,17 @@ import android.telephony.TelephonyManager import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.TelephonyIcons +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import kotlinx.coroutines.flow.MutableStateFlow // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository -class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) : - MobileConnectionsRepository { +class FakeMobileConnectionsRepository( + mobileMappings: MobileMappingsProxy, + val tableLogBuffer: TableLogBuffer, +) : MobileConnectionsRepository { val GSM_KEY = mobileMappings.toIconKey(GSM) val LTE_KEY = mobileMappings.toIconKey(LTE) val UMTS_KEY = mobileMappings.toIconKey(UMTS) @@ -63,7 +66,7 @@ class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) : private val subIdRepos = mutableMapOf() override fun getRepoForSubId(subId: Int): MobileConnectionRepository { return subIdRepos[subId] - ?: FakeMobileConnectionRepository(subId).also { subIdRepos[subId] = it } + ?: FakeMobileConnectionRepository(subId, tableLogBuffer).also { subIdRepos[subId] = it } } private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index 18ae90db881a3..5d377a8658a57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -24,6 +24,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource @@ -37,6 +39,7 @@ import com.android.systemui.util.mockito.kotlinArgumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -69,12 +72,14 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { private lateinit var realRepo: MobileConnectionsRepositoryImpl private lateinit var demoRepo: DemoMobileConnectionsRepository private lateinit var mockDataSource: DemoModeMobileConnectionDataSource + private lateinit var logFactory: TableLogBufferFactory @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var subscriptionManager: SubscriptionManager @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var demoModeController: DemoModeController + @Mock private lateinit var dumpManager: DumpManager private val globalSettings = FakeSettings() private val fakeNetworkEventsFlow = MutableStateFlow(null) @@ -86,6 +91,8 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock()) + // Never start in demo mode whenever(demoModeController.isInDemoMode).thenReturn(false) @@ -114,6 +121,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { dataSource = mockDataSource, scope = scope, context = context, + logFactory = logFactory, ) underTest = 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 3d5316d1f19d7..210208532dd45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt @@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase +import com.android.systemui.log.table.TableLogBufferFactory 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 @@ -30,6 +31,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model 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.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel @@ -54,6 +56,9 @@ import org.junit.runners.Parameterized.Parameters @RunWith(Parameterized::class) internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) : SysuiTestCase() { + + private val logFactory = TableLogBufferFactory(mock(), FakeSystemClock()) + private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -76,6 +81,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC dataSource = mockDataSource, scope = testScope.backgroundScope, context = context, + logFactory = logFactory, ) connectionsRepo.startProcessingCommands() 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 34f30eb7c0a69..cdbe75e855bcb 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 @@ -23,6 +23,8 @@ import androidx.test.filters.SmallTest import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.TelephonyIcons.THREE_G import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.table.TableLogBufferFactory 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 @@ -32,6 +34,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model 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.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import junit.framework.Assert import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -47,6 +50,9 @@ import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) @SmallTest class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { + private val dumpManager: DumpManager = mock() + private val logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock()) + private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -68,6 +74,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { dataSource = mockDataSource, scope = testScope.backgroundScope, context = context, + logFactory = logFactory, ) underTest.startProcessingCommands() 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 9a3e95826c84b..7970443f69b1b 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 @@ -88,6 +88,7 @@ import org.mockito.MockitoAnnotations @SmallTest class MobileConnectionRepositoryTest : SysuiTestCase() { private lateinit var underTest: MobileConnectionRepositoryImpl + private lateinit var connectionsRepo: FakeMobileConnectionsRepository @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: ConnectivityPipelineLogger @@ -96,7 +97,6 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { private val scope = CoroutineScope(IMMEDIATE) private val mobileMappings = FakeMobileMappingsProxy() private val globalSettings = FakeSettings() - private val connectionsRepo = FakeMobileConnectionsRepository(mobileMappings) @Before fun setUp() { @@ -104,6 +104,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { globalSettings.userId = UserHandle.USER_ALL whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID) + connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger) + underTest = MobileConnectionRepositoryImpl( context, 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 c3519b7c81767..c49458909c78b 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,14 @@ 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.log.table.TableLogBuffer 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 -class FakeMobileIconInteractor : MobileIconInteractor { +class FakeMobileIconInteractor( + override val tableLogBuffer: TableLogBuffer, +) : MobileIconInteractor { override val alwaysShowDataRatIcon = MutableStateFlow(false) override val activity = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index 9f300e9e0cf38..19e5516b58a27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -22,12 +22,15 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_LTE import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.TelephonyIcons +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor { +class FakeMobileIconsInteractor( + mobileMappings: MobileMappingsProxy, + val tableLogBuffer: TableLogBuffer, +) : MobileIconsInteractor { val THREE_G_KEY = mobileMappings.toIconKey(THREE_G) val LTE_KEY = mobileMappings.toIconKey(LTE) val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G) @@ -48,8 +51,7 @@ class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIco override val isDefaultConnectionFailed = MutableStateFlow(false) - private val _filteredSubscriptions = MutableStateFlow>(listOf()) - override val filteredSubscriptions: Flow> = _filteredSubscriptions + override val filteredSubscriptions = MutableStateFlow>(listOf()) private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false) override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled @@ -67,7 +69,7 @@ class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIco /** Always returns a new fake interactor */ override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor { - return FakeMobileIconInteractor() + return FakeMobileIconInteractor(tableLogBuffer) } companion object { 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 4dca780425e54..83c5055a6edae 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 @@ -49,8 +49,8 @@ import org.junit.Test class MobileIconInteractorTest : SysuiTestCase() { private lateinit var underTest: MobileIconInteractor private val mobileMappingsProxy = FakeMobileMappingsProxy() - private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy) - private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID) + private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock()) + private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID, mock()) private val scope = CoroutineScope(IMMEDIATE) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 85578942ba86a..2fa3467587cce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -20,6 +20,7 @@ import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository @@ -28,6 +29,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSe import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -44,9 +46,9 @@ import org.mockito.MockitoAnnotations @SmallTest class MobileIconsInteractorTest : SysuiTestCase() { private lateinit var underTest: MobileIconsInteractor + private lateinit var connectionsRepository: FakeMobileConnectionsRepository private val userSetupRepository = FakeUserSetupRepository() private val mobileMappingsProxy = FakeMobileMappingsProxy() - private val connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy) private val scope = CoroutineScope(IMMEDIATE) @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker @@ -55,6 +57,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer) connectionsRepository.setMobileConnectionRepositoryMap( mapOf( SUB_1_ID to CONNECTION_1, @@ -290,21 +293,23 @@ class MobileIconsInteractorTest : SysuiTestCase() { companion object { private val IMMEDIATE = Dispatchers.Main.immediate + private val tableLogBuffer = + TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock()) private const val SUB_1_ID = 1 private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID) - private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID) + private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer) private const val SUB_2_ID = 2 private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID) - private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID) + private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, tableLogBuffer) private const val SUB_3_ID = 3 private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true) - private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID) + private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, tableLogBuffer) private const val SUB_4_ID = 4 private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true) - private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID) + private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, tableLogBuffer) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt index 52232cbecf98b..043d55a73076e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import androidx.test.filters.SmallTest import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants @@ -43,9 +44,10 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { private lateinit var homeIcon: HomeMobileIconViewModel private lateinit var qsIcon: QsMobileIconViewModel private lateinit var keyguardIcon: KeyguardMobileIconViewModel - private val interactor = FakeMobileIconInteractor() + private lateinit var interactor: FakeMobileIconInteractor @Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var constants: ConnectivityConstants + @Mock private lateinit var tableLogBuffer: TableLogBuffer private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -53,6 +55,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + interactor = FakeMobileIconInteractor(tableLogBuffer) interactor.apply { setLevel(1) setIsDefaultDataEnabled(true) @@ -62,11 +65,12 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { setNumberOfLevels(4) isDataConnected.value = true } - commonImpl = MobileIconViewModel(SUB_1_ID, interactor, logger, constants) + commonImpl = + MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope) - homeIcon = HomeMobileIconViewModel(commonImpl) - qsIcon = QsMobileIconViewModel(commonImpl) - keyguardIcon = KeyguardMobileIconViewModel(commonImpl) + homeIcon = HomeMobileIconViewModel(commonImpl, logger) + qsIcon = QsMobileIconViewModel(commonImpl, logger) + keyguardIcon = KeyguardMobileIconViewModel(commonImpl, logger) } @Test 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 76437379e1a7c..50221bc97badb 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 @@ -22,32 +22,42 @@ import com.android.settingslib.mobile.TelephonyIcons.THREE_G 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.log.table.TableLogBuffer 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.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import kotlinx.coroutines.yield import org.junit.Before import org.junit.Test import org.mockito.Mock import org.mockito.MockitoAnnotations +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest class MobileIconViewModelTest : SysuiTestCase() { private lateinit var underTest: MobileIconViewModel - private val interactor = FakeMobileIconInteractor() + private lateinit var interactor: FakeMobileIconInteractor @Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var constants: ConnectivityConstants + @Mock private lateinit var tableLogBuffer: TableLogBuffer + + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) @Before fun setUp() { MockitoAnnotations.initMocks(this) + interactor = FakeMobileIconInteractor(tableLogBuffer) interactor.apply { setLevel(1) setIsDefaultDataEnabled(true) @@ -57,12 +67,13 @@ class MobileIconViewModelTest : SysuiTestCase() { setNumberOfLevels(4) isDataConnected.value = true } - underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants) + underTest = + MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope) } @Test fun iconId_correctLevel_notCutout() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Int? = null val job = underTest.iconId.onEach { latest = it }.launchIn(this) val expected = defaultSignal() @@ -74,7 +85,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun iconId_cutout_whenDefaultDataDisabled() = - runBlocking(IMMEDIATE) { + testScope.runTest { interactor.setIsDefaultDataEnabled(false) var latest: Int? = null @@ -88,7 +99,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_dataEnabled_groupIsRepresented() = - runBlocking(IMMEDIATE) { + testScope.runTest { val expected = Icon.Resource( THREE_G.dataType, @@ -106,7 +117,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_nullWhenDisabled() = - runBlocking(IMMEDIATE) { + testScope.runTest { interactor.setIconGroup(THREE_G) interactor.setIsDataEnabled(false) var latest: Icon? = null @@ -119,7 +130,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_nullWhenFailedConnection() = - runBlocking(IMMEDIATE) { + testScope.runTest { interactor.setIconGroup(THREE_G) interactor.setIsDataEnabled(true) interactor.setIsFailedConnection(true) @@ -133,7 +144,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_nullWhenDataDisconnects() = - runBlocking(IMMEDIATE) { + testScope.runTest { val initial = Icon.Resource( THREE_G.dataType, @@ -157,7 +168,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_null_changeToDisabled() = - runBlocking(IMMEDIATE) { + testScope.runTest { val expected = Icon.Resource( THREE_G.dataType, @@ -180,7 +191,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_alwaysShow_shownEvenWhenDisabled() = - runBlocking(IMMEDIATE) { + testScope.runTest { interactor.setIconGroup(THREE_G) interactor.setIsDataEnabled(true) interactor.alwaysShowDataRatIcon.value = true @@ -200,7 +211,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_alwaysShow_shownEvenWhenDisconnected() = - runBlocking(IMMEDIATE) { + testScope.runTest { interactor.setIconGroup(THREE_G) interactor.isDataConnected.value = false interactor.alwaysShowDataRatIcon.value = true @@ -220,7 +231,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_alwaysShow_shownEvenWhenFailedConnection() = - runBlocking(IMMEDIATE) { + testScope.runTest { interactor.setIconGroup(THREE_G) interactor.setIsFailedConnection(true) interactor.alwaysShowDataRatIcon.value = true @@ -240,7 +251,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun roaming() = - runBlocking(IMMEDIATE) { + testScope.runTest { interactor.isRoaming.value = true var latest: Boolean? = null val job = underTest.roaming.onEach { latest = it }.launchIn(this) @@ -256,10 +267,17 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun `data activity - null when config is off`() = - runBlocking(IMMEDIATE) { + testScope.runTest { // 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) + underTest = + MobileIconViewModel( + SUB_1_ID, + interactor, + logger, + constants, + testScope.backgroundScope, + ) var inVisible: Boolean? = null val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this) @@ -288,10 +306,17 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun `data activity - config on - test indicators`() = - runBlocking(IMMEDIATE) { + testScope.runTest { // 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) + underTest = + MobileIconViewModel( + SUB_1_ID, + interactor, + logger, + constants, + testScope.backgroundScope, + ) var inVisible: Boolean? = null val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this) @@ -341,7 +366,6 @@ class MobileIconViewModelTest : SysuiTestCase() { } companion object { - private val IMMEDIATE = Dispatchers.Main.immediate private const val SUB_1_ID = 1 /** Convenience constructor for these tests */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt new file mode 100644 index 0000000000000..d6cb76260f0bb --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt @@ -0,0 +1,106 @@ +/* + * 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.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.StatusBarLocation +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class MobileIconsViewModelTest : SysuiTestCase() { + private lateinit var underTest: MobileIconsViewModel + private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) + + @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var constants: ConnectivityConstants + + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + val subscriptionIdsFlow = + interactor.filteredSubscriptions + .map { subs -> subs.map { it.subscriptionId } } + .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf()) + + underTest = + MobileIconsViewModel( + subscriptionIdsFlow, + interactor, + logger, + constants, + testScope.backgroundScope, + ) + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + } + + @Test + fun `caching - mobile icon view model is reused for same sub id`() = + testScope.runTest { + val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME) + val model2 = underTest.viewModelForSub(1, StatusBarLocation.QS) + + assertThat(model1.commonImpl).isSameInstanceAs(model2.commonImpl) + } + + @Test + fun `caching - invalid view models are removed from cache when sub disappears`() = + testScope.runTest { + // Retrieve models to trigger caching + val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME) + val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS) + + // Both impls are cached + assertThat(underTest.mobileIconSubIdCache) + .containsExactly(1, model1.commonImpl, 2, model2.commonImpl) + + // SUB_1 is removed from the list... + interactor.filteredSubscriptions.value = listOf(SUB_2) + + // ... and dropped from the cache + assertThat(underTest.mobileIconSubIdCache).containsExactly(2, model2.commonImpl) + } + + companion object { + private val SUB_1 = SubscriptionModel(subscriptionId = 1, isOpportunistic = false) + private val SUB_2 = SubscriptionModel(subscriptionId = 2, isOpportunistic = false) + } +}