diff options
| author | 2022-12-22 21:13:08 +0000 | |
|---|---|---|
| committer | 2022-12-22 21:13:08 +0000 | |
| commit | 98634c47e78f26866da7076be353f19cd4139889 (patch) | |
| tree | a639092d8c14859ebbbb6d68523bed5180a6f615 | |
| parent | 61ada77fb04f0302bde7cc55d4269afac852fe82 (diff) | |
| parent | 5bba501a33beeb12e3cf0355759e489c3c564275 (diff) | |
Merge changes from topic "wifi-constants" into tm-qpr-dev am: 5bba501a33
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20761388
Change-Id: Ib195aabcd9ab779db888c5bb6d1727f475c96f3f
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
34 files changed, 1048 insertions, 214 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index c7be2193e9b5..5569718f4a4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -60,10 +60,12 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da private int mColor; private final MobileIconsViewModel mMobileIconsViewModel; + private final StatusBarLocation mLocation; public DemoStatusIcons( LinearLayout statusIcons, MobileIconsViewModel mobileIconsViewModel, + StatusBarLocation location, int iconSize ) { super(statusIcons.getContext()); @@ -71,6 +73,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da mIconSize = iconSize; mColor = DarkIconDispatcher.DEFAULT_ICON_TINT; mMobileIconsViewModel = mobileIconsViewModel; + mLocation = location; if (statusIcons instanceof StatusIconContainer) { setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons()); @@ -287,7 +290,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind( mobileContext, "mobile", - mMobileIconsViewModel.viewModelForSub(subId) + mMobileIconsViewModel.viewModelForSub(subId, mLocation) ); // mobile always goes at the end 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 df3ab493a4da..44e5cd9543ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -359,6 +359,7 @@ public interface StatusBarIconController { // Whether or not these icons show up in dumpsys protected boolean mShouldLog = false; private StatusBarIconController mController; + private final StatusBarLocation mLocation; // Enables SystemUI demo mode to take effect in this group protected boolean mDemoable = true; @@ -381,11 +382,12 @@ public interface StatusBarIconController { mContext = group.getContext(); mIconSize = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); + mLocation = location; 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; @@ -394,7 +396,7 @@ public interface StatusBarIconController { if (statusBarPipelineFlags.runNewWifiIconBackend()) { // This starts the flow for the new pipeline, and will notify us of changes if // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true. - mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, location); + mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation); } else { mWifiViewModel = null; } @@ -569,7 +571,7 @@ public interface StatusBarIconController { .constructAndBind( mobileContext, slot, - mMobileIconsViewModel.viewModelForSub(subId) + mMobileIconsViewModel.viewModelForSub(subId, mLocation) ); } @@ -705,7 +707,12 @@ public interface StatusBarIconController { } protected DemoStatusIcons createDemoStatusIcons() { - return new DemoStatusIcons((LinearLayout) mGroup, mMobileIconsViewModel, mIconSize); + return new DemoStatusIcons( + (LinearLayout) mGroup, + mMobileIconsViewModel, + mLocation, + mIconSize + ); } } } 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 6c37f94007cb..1aa954ff48cf 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 @@ -26,6 +26,8 @@ import android.telephony.TelephonyCallback.ServiceStateListener import android.telephony.TelephonyCallback.SignalStrengthsListener import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyManager +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel @@ -79,4 +81,72 @@ data class MobileConnectionModel( * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon */ val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType, -) +) : Diffable<MobileConnectionModel> { + override fun logDiffs(prevVal: MobileConnectionModel, row: TableRowLogger) { + if (prevVal.dataConnectionState != dataConnectionState) { + row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString()) + } + + if (prevVal.isEmergencyOnly != isEmergencyOnly) { + row.logChange(COL_EMERGENCY, isEmergencyOnly) + } + + if (prevVal.isRoaming != isRoaming) { + row.logChange(COL_ROAMING, isRoaming) + } + + if (prevVal.operatorAlphaShort != operatorAlphaShort) { + row.logChange(COL_OPERATOR, operatorAlphaShort) + } + + if (prevVal.isGsm != isGsm) { + row.logChange(COL_IS_GSM, isGsm) + } + + if (prevVal.cdmaLevel != cdmaLevel) { + row.logChange(COL_CDMA_LEVEL, cdmaLevel) + } + + if (prevVal.primaryLevel != primaryLevel) { + row.logChange(COL_PRIMARY_LEVEL, primaryLevel) + } + + if (prevVal.dataActivityDirection != dataActivityDirection) { + row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString()) + } + + if (prevVal.carrierNetworkChangeActive != carrierNetworkChangeActive) { + row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive) + } + + if (prevVal.resolvedNetworkType != resolvedNetworkType) { + row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString()) + } + } + + override fun logFull(row: TableRowLogger) { + row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString()) + row.logChange(COL_EMERGENCY, isEmergencyOnly) + row.logChange(COL_ROAMING, isRoaming) + row.logChange(COL_OPERATOR, operatorAlphaShort) + row.logChange(COL_IS_GSM, isGsm) + row.logChange(COL_CDMA_LEVEL, cdmaLevel) + row.logChange(COL_PRIMARY_LEVEL, primaryLevel) + row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString()) + row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive) + row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString()) + } + + companion object { + const val COL_EMERGENCY = "EmergencyOnly" + const val COL_ROAMING = "Roaming" + const val COL_OPERATOR = "OperatorName" + const val COL_IS_GSM = "IsGsm" + const val COL_CDMA_LEVEL = "CdmaLevel" + const val COL_PRIMARY_LEVEL = "PrimaryLevel" + const val COL_CONNECTION_STATE = "ConnectionState" + const val COL_ACTIVITY_DIRECTION = "DataActivity" + const val COL_CARRIER_NETWORK_CHANGE = "CarrierNetworkChangeActive" + const val COL_RESOLVED_NETWORK_TYPE = "NetworkType" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt index a8cf35ad3029..c50d82a66c76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt @@ -21,22 +21,48 @@ import android.telephony.TelephonyManager.EXTRA_DATA_SPN import android.telephony.TelephonyManager.EXTRA_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_SPN +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger /** * Encapsulates the data needed to show a network name for a mobile network. The data is parsed from * the intent sent by [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED]. */ -sealed interface NetworkNameModel { +sealed interface NetworkNameModel : Diffable<NetworkNameModel> { val name: String /** The default name is read from [com.android.internal.R.string.lockscreen_carrier_default] */ - data class Default(override val name: String) : NetworkNameModel + data class Default(override val name: String) : NetworkNameModel { + override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) { + if (prevVal !is Default || prevVal.name != name) { + row.logChange(COL_NETWORK_NAME, "Default($name)") + } + } + + override fun logFull(row: TableRowLogger) { + row.logChange(COL_NETWORK_NAME, "Default($name)") + } + } /** * This name has been derived from telephony intents. see * [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED] */ - data class Derived(override val name: String) : NetworkNameModel + data class Derived(override val name: String) : NetworkNameModel { + override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) { + if (prevVal !is Derived || prevVal.name != name) { + row.logChange(COL_NETWORK_NAME, "Derived($name)") + } + } + + override fun logFull(row: TableRowLogger) { + row.logChange(COL_NETWORK_NAME, "Derived($name)") + } + } + + companion object { + const val COL_NETWORK_NAME = "networkName" + } } fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? { 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 2fd415e6777f..40e9ba1a46c7 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 d3ee85f19347..b252de8dd389 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 7e9a9cea9b95..0b9e1583898e 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 @@ -36,6 +36,9 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType @@ -46,7 +49,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameMo import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject @@ -59,6 +61,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach @@ -79,6 +82,7 @@ class MobileConnectionRepositoryImpl( mobileMappingsProxy: MobileMappingsProxy, bgDispatcher: CoroutineDispatcher, logger: ConnectivityPipelineLogger, + mobileLogger: TableLogBuffer, scope: CoroutineScope, ) : MobileConnectionRepository { init { @@ -92,10 +96,11 @@ class MobileConnectionRepositoryImpl( private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1) + override val tableLogBuffer: TableLogBuffer = mobileLogger + override val connectionInfo: StateFlow<MobileConnectionModel> = run { var state = MobileConnectionModel() conflatedCallbackFlow { - // TODO (b/240569788): log all of these into the connectivity logger val callback = object : TelephonyCallback(), @@ -106,6 +111,7 @@ class MobileConnectionRepositoryImpl( TelephonyCallback.CarrierNetworkListener, TelephonyCallback.DisplayInfoListener { override fun onServiceStateChanged(serviceState: ServiceState) { + logger.logOnServiceStateChanged(serviceState, subId) state = state.copy( isEmergencyOnly = serviceState.isEmergencyOnly, @@ -116,6 +122,7 @@ class MobileConnectionRepositoryImpl( } override fun onSignalStrengthsChanged(signalStrength: SignalStrength) { + logger.logOnSignalStrengthsChanged(signalStrength, subId) val cdmaLevel = signalStrength .getCellSignalStrengths(CellSignalStrengthCdma::class.java) @@ -142,12 +149,14 @@ class MobileConnectionRepositoryImpl( dataState: Int, networkType: Int ) { + logger.logOnDataConnectionStateChanged(dataState, networkType, subId) state = state.copy(dataConnectionState = dataState.toDataConnectionType()) trySend(state) } override fun onDataActivity(direction: Int) { + logger.logOnDataActivity(direction, subId) state = state.copy( dataActivityDirection = direction.toMobileDataActivityModel() @@ -156,6 +165,7 @@ class MobileConnectionRepositoryImpl( } override fun onCarrierNetworkChange(active: Boolean) { + logger.logOnCarrierNetworkChange(active, subId) state = state.copy(carrierNetworkChangeActive = active) trySend(state) } @@ -163,6 +173,7 @@ class MobileConnectionRepositoryImpl( override fun onDisplayInfoChanged( telephonyDisplayInfo: TelephonyDisplayInfo ) { + logger.logOnDisplayInfoChanged(telephonyDisplayInfo, subId) val networkType = if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) { @@ -193,7 +204,11 @@ class MobileConnectionRepositoryImpl( awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } } .onEach { telephonyCallbackEvent.tryEmit(Unit) } - .logOutputChange(logger, "MobileSubscriptionModel") + .logDiffsForTable( + mobileLogger, + columnPrefix = "MobileConnection ($subId)", + initialValue = state, + ) .stateIn(scope, SharingStarted.WhileSubscribed(), state) } @@ -243,19 +258,43 @@ class MobileConnectionRepositoryImpl( intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName } } + .distinctUntilChanged() + .logDiffsForTable( + mobileLogger, + columnPrefix = "", + initialValue = defaultNetworkName, + ) .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName) - override val dataEnabled: StateFlow<Boolean> = + override val dataEnabled: StateFlow<Boolean> = run { + val initial = dataConnectionAllowed() telephonyPollingEvent .mapLatest { dataConnectionAllowed() } - .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed()) + .distinctUntilChanged() + .logDiffsForTable( + mobileLogger, + columnPrefix = "", + columnName = "dataEnabled", + initialValue = initial, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), initial) + } private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed - override val isDefaultDataSubscription: StateFlow<Boolean> = + override val isDefaultDataSubscription: StateFlow<Boolean> = run { + val initialValue = defaultDataSubId.value == subId defaultDataSubId .mapLatest { it == subId } - .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId) + .distinctUntilChanged() + .logDiffsForTable( + mobileLogger, + columnPrefix = "", + columnName = "isDefaultDataSub", + initialValue = initialValue, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue) + } class Factory @Inject @@ -266,6 +305,7 @@ class MobileConnectionRepositoryImpl( private val logger: ConnectivityPipelineLogger, private val globalSettings: GlobalSettings, private val mobileMappingsProxy: MobileMappingsProxy, + private val logFactory: TableLogBufferFactory, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, ) { @@ -276,6 +316,8 @@ class MobileConnectionRepositoryImpl( defaultDataSubId: StateFlow<Int>, globalMobileDataSettingChangedEvent: Flow<Unit>, ): MobileConnectionRepository { + val mobileLogger = logFactory.create(tableBufferLogName(subId), 100) + return MobileConnectionRepositoryImpl( context, subId, @@ -289,8 +331,13 @@ class MobileConnectionRepositoryImpl( mobileMappingsProxy, bgDispatcher, logger, + mobileLogger, scope, ) } } + + companion object { + fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]" + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index a9b3d18774fd..d407abeb2315 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConn import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -120,6 +121,7 @@ constructor( awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) } } .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } } + .logInputChange(logger, "onSubscriptionsChanged") .onEach { infos -> dropUnusedReposFromCache(infos) } .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf()) @@ -136,6 +138,8 @@ constructor( telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback) awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } } + .distinctUntilChanged() + .logInputChange(logger, "onActiveDataSubscriptionIdChanged") .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID) private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> = @@ -149,6 +153,7 @@ constructor( intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID) } .distinctUntilChanged() + .logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED") .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) } .stateIn( scope, @@ -157,13 +162,15 @@ constructor( ) private val carrierConfigChangedEvent = - broadcastDispatcher.broadcastFlow( - IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED) - ) + broadcastDispatcher + .broadcastFlow(IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) + .logInputChange(logger, "ACTION_CARRIER_CONFIG_CHANGED") override val defaultDataSubRatConfig: StateFlow<Config> = merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent) .mapLatest { Config.readConfig(context) } + .distinctUntilChanged() + .logInputChange(logger, "defaultDataSubRatConfig") .stateIn( scope, SharingStarted.WhileSubscribed(), @@ -171,10 +178,16 @@ constructor( ) override val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>> = - defaultDataSubRatConfig.map { mobileMappingsProxy.mapIconSets(it) } + defaultDataSubRatConfig + .map { mobileMappingsProxy.mapIconSets(it) } + .distinctUntilChanged() + .logInputChange(logger, "defaultMobileIconMapping") override val defaultMobileIconGroup: Flow<MobileIconGroup> = - defaultDataSubRatConfig.map { mobileMappingsProxy.getDefaultIcons(it) } + defaultDataSubRatConfig + .map { mobileMappingsProxy.getDefaultIcons(it) } + .distinctUntilChanged() + .logInputChange(logger, "defaultMobileIconGroup") override fun getRepoForSubId(subId: Int): MobileConnectionRepository { if (!isValidSubId(subId)) { @@ -191,22 +204,24 @@ constructor( * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual * connection repositories also observe the URI for [MOBILE_DATA] + subId. */ - override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow { - val observer = - object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - trySend(Unit) - } - } + override val globalMobileDataSettingChangedEvent: Flow<Unit> = + conflatedCallbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } - globalSettings.registerContentObserver( - globalSettings.getUriFor(MOBILE_DATA), - true, - observer - ) + globalSettings.registerContentObserver( + globalSettings.getUriFor(MOBILE_DATA), + true, + observer + ) - awaitClose { context.contentResolver.unregisterContentObserver(observer) } - } + awaitClose { context.contentResolver.unregisterContentObserver(observer) } + } + .logInputChange(logger, "globalMobileDataSettingChangedEvent") @SuppressLint("MissingPermission") override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> = @@ -236,6 +251,8 @@ constructor( awaitClose { connectivityManager.unregisterNetworkCallback(callback) } } + .distinctUntilChanged() + .logInputChange(logger, "defaultMobileNetworkConnectivity") .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel()) private fun isValidSubId(subId: Int): Boolean { 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 76e6a96a19d7..e6686dce7bbc 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<DataActivityModel> @@ -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<Boolean> = 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 62fa723dbf04..829a5cad6504 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<List<Int>> = 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/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index 545e624273f1..ab442b5ab4de 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 @@ -30,7 +30,7 @@ import com.android.settingslib.graph.SignalDrawable import com.android.systemui.R import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch @@ -39,7 +39,7 @@ object MobileIconBinder { @JvmStatic fun bind( view: ViewGroup, - viewModel: MobileIconViewModel, + viewModel: LocationBasedMobileViewModel, ) { val activityContainer = view.requireViewById<View>(R.id.inout_container) val activityIn = view.requireViewById<ImageView>(R.id.mobile_in) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt index 0ab7bcd96844..e86fee24fe4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt @@ -24,7 +24,7 @@ import com.android.systemui.R import com.android.systemui.statusbar.BaseStatusBarFrameLayout import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder -import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import java.util.ArrayList class ModernStatusBarMobileView( @@ -71,7 +71,7 @@ class ModernStatusBarMobileView( fun constructAndBind( context: Context, slot: String, - viewModel: MobileIconViewModel, + viewModel: LocationBasedMobileViewModel, ): ModernStatusBarMobileView { return (LayoutInflater.from(context) .inflate(R.layout.status_bar_mobile_signal_group_new, null) 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 new file mode 100644 index 000000000000..b0dc41f45488 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt @@ -0,0 +1,82 @@ +/* + * 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 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 + +/** + * A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This + * allows the mobile icon to change some view parameters at different locations + * + * @param commonImpl for convenience, this class wraps a base interface that can provides all of the + * common implementations between locations. See [MobileIconViewModel] + */ +abstract class LocationBasedMobileViewModel( + val commonImpl: MobileIconViewModelCommon, + val logger: ConnectivityPipelineLogger, +) : MobileIconViewModelCommon by commonImpl { + abstract val tint: Flow<Int> + + companion object { + fun viewModelForLocation( + commonImpl: MobileIconViewModelCommon, + logger: ConnectivityPipelineLogger, + loc: StatusBarLocation, + ): LocationBasedMobileViewModel = + when (loc) { + StatusBarLocation.HOME -> HomeMobileIconViewModel(commonImpl, logger) + StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl, logger) + StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, logger) + } + } +} + +class HomeMobileIconViewModel( + commonImpl: MobileIconViewModelCommon, + logger: ConnectivityPipelineLogger, +) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) { + override val tint: Flow<Int> = + flowOf(Color.CYAN) + .distinctUntilChanged() + .logOutputChange(logger, "HOME tint(${commonImpl.subscriptionId})") +} + +class QsMobileIconViewModel( + commonImpl: MobileIconViewModelCommon, + logger: ConnectivityPipelineLogger, +) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) { + override val tint: Flow<Int> = + flowOf(Color.GREEN) + .distinctUntilChanged() + .logOutputChange(logger, "QS tint(${commonImpl.subscriptionId})") +} + +class KeyguardMobileIconViewModel( + commonImpl: MobileIconViewModelCommon, + logger: ConnectivityPipelineLogger, +) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) { + override val tint: Flow<Int> = + 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 961283f57def..2d6ac4efd512 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,40 @@ 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 { + val subscriptionId: Int + /** An int consumable by [SignalDrawable] for display */ + val iconId: Flow<Int> + val roaming: Flow<Boolean> + /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ + val networkTypeIcon: Flow<Icon?> + val activityInVisible: Flow<Boolean> + val activityOutVisible: Flow<Boolean> + val activityContainerVisible: Flow<Boolean> +} /** * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over @@ -40,25 +57,29 @@ import kotlinx.coroutines.flow.mapLatest * subscription's information. * * There will be exactly one [MobileIconViewModel] per filtered subscription offered from - * [MobileIconsInteractor.filteredSubscriptions] + * [MobileIconsInteractor.filteredSubscriptions]. * - * TODO: figure out where carrier merged and VCN models go (probably here?) + * 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) class MobileIconViewModel constructor( - val subscriptionId: Int, + override val subscriptionId: Int, iconInteractor: MobileIconInteractor, logger: ConnectivityPipelineLogger, constants: ConnectivityConstants, -) { + scope: CoroutineScope, +) : MobileIconViewModelCommon { /** Whether or not to show the error state of [SignalDrawable] */ private val showExclamationMark: Flow<Boolean> = iconInteractor.isDefaultDataEnabled.mapLatest { !it } - /** An int consumable by [SignalDrawable] for display */ - val iconId: Flow<Int> = + override val iconId: Flow<Int> = run { + val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value) combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) { level, numberOfLevels, @@ -66,32 +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) + } - /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ - val networkTypeIcon: Flow<Icon?> = + override val networkTypeIcon: Flow<Icon?> = 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) - val roaming: Flow<Boolean> = iconInteractor.isRoaming + override val roaming: StateFlow<Boolean> = + iconInteractor.isRoaming + .logDiffsForTable( + iconInteractor.tableLogBuffer, + columnPrefix = "", + columnName = "roaming", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) private val activity: Flow<DataActivityModel?> = if (!constants.shouldShowActivityConfig) { @@ -100,10 +145,39 @@ constructor( iconInteractor.activity } - val activityInVisible: Flow<Boolean> = activity.map { it?.hasActivityIn ?: false } - val activityOutVisible: Flow<Boolean> = activity.map { it?.hasActivityOut ?: false } - val activityContainerVisible: Flow<Boolean> = - activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) } + override val activityInVisible: Flow<Boolean> = + activity + .map { it?.hasActivityIn ?: false } + .distinctUntilChanged() + .logDiffsForTable( + iconInteractor.tableLogBuffer, + columnPrefix = "", + columnName = "activityInVisible", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val activityOutVisible: Flow<Boolean> = + activity + .map { it?.hasActivityOut ?: false } + .distinctUntilChanged() + .logDiffsForTable( + iconInteractor.tableLogBuffer, + columnPrefix = "", + columnName = "activityOutVisible", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) - val tint: Flow<Int> = flowOf(Color.CYAN) + override val activityContainerVisible: Flow<Boolean> = + 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 0b41d319f9dc..b9318b181aaf 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,17 +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 @@ -38,15 +40,33 @@ 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? */ - fun viewModelForSub(subId: Int): MobileIconViewModel = - MobileIconViewModel( - subId, - interactor.createMobileConnectionInteractorForSubId(subId), - logger, - constants, - ) + @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>() + + init { + scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } + } + + fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel { + val common = + mobileIconSubIdCache[subId] + ?: MobileIconViewModel( + subId, + interactor.createMobileConnectionInteractorForSubId(subId), + logger, + constants, + scope, + ) + .also { mobileIconSubIdCache[subId] = it } + + return LocationBasedMobileViewModel.viewModelForLocation(common, logger, location) + } + + private fun removeInvalidModelsFromCache(subIds: List<Int>) { + val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } + subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) } + } class Factory @Inject @@ -54,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<List<Int>>): MobileIconsViewModel { return MobileIconsViewModel( @@ -61,6 +82,7 @@ constructor( interactor, logger, constants, + scope, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt index d3cf32fb44ce..d3ff3573dae2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt @@ -18,8 +18,11 @@ package com.android.systemui.statusbar.pipeline.shared import android.net.Network import android.net.NetworkCapabilities -import com.android.systemui.log.dagger.StatusBarConnectivityLog +import android.telephony.ServiceState +import android.telephony.SignalStrength +import android.telephony.TelephonyDisplayInfo import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.dagger.StatusBarConnectivityLog import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString @@ -28,7 +31,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.onEach @SysUISingleton -class ConnectivityPipelineLogger @Inject constructor( +class ConnectivityPipelineLogger +@Inject +constructor( @StatusBarConnectivityLog private val buffer: LogBuffer, ) { /** @@ -37,34 +42,23 @@ class ConnectivityPipelineLogger @Inject constructor( * Use this method for inputs that don't have any extra information besides their callback name. */ fun logInputChange(callbackName: String) { - buffer.log( - SB_LOGGING_TAG, - LogLevel.INFO, - { str1 = callbackName }, - { "Input: $str1" } - ) + buffer.log(SB_LOGGING_TAG, LogLevel.INFO, { str1 = callbackName }, { "Input: $str1" }) } - /** - * Logs a change in one of the **raw inputs** to the connectivity pipeline. - */ + /** Logs a change in one of the **raw inputs** to the connectivity pipeline. */ fun logInputChange(callbackName: String, changeInfo: String?) { buffer.log( - SB_LOGGING_TAG, - LogLevel.INFO, - { - str1 = callbackName - str2 = changeInfo - }, - { - "Input: $str1: $str2" - } + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = callbackName + str2 = changeInfo + }, + { "Input: $str1: $str2" } ) } - /** - * Logs a **data transformation** that we performed within the connectivity pipeline. - */ + /** Logs a **data transformation** that we performed within the connectivity pipeline. */ fun logTransformation(transformationName: String, oldValue: Any?, newValue: Any?) { if (oldValue == newValue) { buffer.log( @@ -74,9 +68,7 @@ class ConnectivityPipelineLogger @Inject constructor( str1 = transformationName str2 = oldValue.toString() }, - { - "Transform: $str1: $str2 (transformation didn't change it)" - } + { "Transform: $str1: $str2 (transformation didn't change it)" } ) } else { buffer.log( @@ -87,27 +79,21 @@ class ConnectivityPipelineLogger @Inject constructor( str2 = oldValue.toString() str3 = newValue.toString() }, - { - "Transform: $str1: $str2 -> $str3" - } + { "Transform: $str1: $str2 -> $str3" } ) } } - /** - * Logs a change in one of the **outputs** to the connectivity pipeline. - */ + /** Logs a change in one of the **outputs** to the connectivity pipeline. */ fun logOutputChange(outputParamName: String, changeInfo: String) { buffer.log( - SB_LOGGING_TAG, - LogLevel.INFO, - { - str1 = outputParamName - str2 = changeInfo - }, - { - "Output: $str1: $str2" - } + SB_LOGGING_TAG, + LogLevel.INFO, + { + str1 = outputParamName + str2 = changeInfo + }, + { "Output: $str1: $str2" } ) } @@ -119,9 +105,7 @@ class ConnectivityPipelineLogger @Inject constructor( int1 = network.getNetId() str1 = networkCapabilities.toString() }, - { - "onCapabilitiesChanged: net=$int1 capabilities=$str1" - } + { "onCapabilitiesChanged: net=$int1 capabilities=$str1" } ) } @@ -129,21 +113,93 @@ class ConnectivityPipelineLogger @Inject constructor( buffer.log( SB_LOGGING_TAG, LogLevel.INFO, + { int1 = network.getNetId() }, + { "onLost: net=$int1" } + ) + } + + fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, { - int1 = network.getNetId() + int1 = subId + bool1 = serviceState.isEmergencyOnly + bool2 = serviceState.roaming + str1 = serviceState.operatorAlphaShort }, { - "onLost: net=$int1" + "onServiceStateChanged: subId=$int1 emergencyOnly=$bool1 roaming=$bool2" + + " operator=$str1" } ) } + fun logOnSignalStrengthsChanged(signalStrength: SignalStrength, subId: Int) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + int1 = subId + str1 = signalStrength.toString() + }, + { "onSignalStrengthsChanged: subId=$int1 strengths=$str1" } + ) + } + + fun logOnDataConnectionStateChanged(dataState: Int, networkType: Int, subId: Int) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + int1 = subId + int2 = dataState + str1 = networkType.toString() + }, + { "onDataConnectionStateChanged: subId=$int1 dataState=$int2 networkType=$str1" }, + ) + } + + fun logOnDataActivity(direction: Int, subId: Int) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + int1 = subId + int2 = direction + }, + { "onDataActivity: subId=$int1 direction=$int2" }, + ) + } + + fun logOnCarrierNetworkChange(active: Boolean, subId: Int) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + int1 = subId + bool1 = active + }, + { "onCarrierNetworkChange: subId=$int1 active=$bool1" }, + ) + } + + fun logOnDisplayInfoChanged(displayInfo: TelephonyDisplayInfo, subId: Int) { + buffer.log( + SB_LOGGING_TAG, + LogLevel.INFO, + { + int1 = subId + str1 = displayInfo.toString() + }, + { "onDisplayInfoChanged: subId=$int1 displayInfo=$str1" }, + ) + } + companion object { const val SB_LOGGING_TAG = "SbConnectivity" - /** - * Log a change in one of the **inputs** to the connectivity pipeline. - */ + /** Log a change in one of the **inputs** to the connectivity pipeline. */ fun Flow<Unit>.logInputChange( logger: ConnectivityPipelineLogger, inputParamName: String, @@ -155,26 +211,26 @@ class ConnectivityPipelineLogger @Inject constructor( * Log a change in one of the **inputs** to the connectivity pipeline. * * @param prettyPrint an optional function to transform the value into a readable string. - * [toString] is used if no custom function is provided. + * [toString] is used if no custom function is provided. */ fun <T> Flow<T>.logInputChange( logger: ConnectivityPipelineLogger, inputParamName: String, prettyPrint: (T) -> String = { it.toString() } ): Flow<T> { - return this.onEach {logger.logInputChange(inputParamName, prettyPrint(it)) } + return this.onEach { logger.logInputChange(inputParamName, prettyPrint(it)) } } /** * Log a change in one of the **outputs** to the connectivity pipeline. * * @param prettyPrint an optional function to transform the value into a readable string. - * [toString] is used if no custom function is provided. + * [toString] is used if no custom function is provided. */ fun <T> Flow<T>.logOutputChange( - logger: ConnectivityPipelineLogger, - outputParamName: String, - prettyPrint: (T) -> String = { it.toString() } + logger: ConnectivityPipelineLogger, + outputParamName: String, + prettyPrint: (T) -> String = { it.toString() } ): Flow<T> { return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt index 3c0eb910ad89..4f7fe28c1e7c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt @@ -38,16 +38,12 @@ class WifiConstants @Inject constructor( dumpManager.registerDumpable("${SB_LOGGING_TAG}WifiConstants", this) } - /** True if we should show the activityIn/activityOut icons and false otherwise. */ - val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity) - /** True if we should always show the wifi icon when wifi is enabled and false otherwise. */ val alwaysShowIconIfEnabled = context.resources.getBoolean(R.bool.config_showWifiIndicatorWhenEnabled) override fun dump(pw: PrintWriter, args: Array<out String>) { pw.apply { - println("shouldShowActivityConfig=$shouldShowActivityConfig") println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled") } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt index 07a7595a2e00..ab464cc78905 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt @@ -148,7 +148,7 @@ constructor( /** The wifi activity status. Null if we shouldn't display the activity status. */ private val activity: Flow<DataActivityModel?> = - if (!wifiConstants.shouldShowActivityConfig) { + if (!connectivityConstants.shouldShowActivityConfig) { flowOf(null) } else { combine(interactor.activity, interactor.ssid) { activity, ssid -> diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt new file mode 100644 index 000000000000..f822ba0f0a62 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.data.model + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.log.table.TableRowLogger +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CARRIER_NETWORK_CHANGE +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CDMA_LEVEL +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CONNECTION_STATE +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_IS_GSM +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_RESOLVED_NETWORK_TYPE +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ROAMING +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +@SmallTest +class MobileConnectionModelTest : SysuiTestCase() { + + @Test + fun `log diff - initial log contains all columns`() { + val logger = TestLogger() + val connection = MobileConnectionModel() + + connection.logFull(logger) + + assertThat(logger.changes) + .contains(Pair(COL_EMERGENCY, connection.isEmergencyOnly.toString())) + assertThat(logger.changes).contains(Pair(COL_ROAMING, connection.isRoaming.toString())) + assertThat(logger.changes) + .contains(Pair(COL_OPERATOR, connection.operatorAlphaShort.toString())) + assertThat(logger.changes).contains(Pair(COL_IS_GSM, connection.isGsm.toString())) + assertThat(logger.changes).contains(Pair(COL_CDMA_LEVEL, connection.cdmaLevel.toString())) + assertThat(logger.changes) + .contains(Pair(COL_PRIMARY_LEVEL, connection.primaryLevel.toString())) + assertThat(logger.changes) + .contains(Pair(COL_CONNECTION_STATE, connection.dataConnectionState.toString())) + assertThat(logger.changes) + .contains(Pair(COL_ACTIVITY_DIRECTION, connection.dataActivityDirection.toString())) + assertThat(logger.changes) + .contains( + Pair(COL_CARRIER_NETWORK_CHANGE, connection.carrierNetworkChangeActive.toString()) + ) + assertThat(logger.changes) + .contains(Pair(COL_RESOLVED_NETWORK_TYPE, connection.resolvedNetworkType.toString())) + } + + @Test + fun `log diff - primary level changes - only level is logged`() { + val logger = TestLogger() + val connectionOld = MobileConnectionModel(primaryLevel = 1) + + val connectionNew = MobileConnectionModel(primaryLevel = 2) + + connectionNew.logDiffs(connectionOld, logger) + + assertThat(logger.changes).isEqualTo(listOf(Pair(COL_PRIMARY_LEVEL, "2"))) + } + + private class TestLogger : TableRowLogger { + val changes = mutableListOf<Pair<String, String>>() + + override fun logChange(columnName: String, value: String?) { + changes.add(Pair(columnName, value.toString())) + } + + override fun logChange(columnName: String, value: Int) { + changes.add(Pair(columnName, value.toString())) + } + + override fun logChange(columnName: String, value: Boolean) { + changes.add(Pair(columnName, value.toString())) + } + } +} 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 59eec5327c12..d6a9ee325b2e 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 04d3cdd89ab7..7f93328ee95e 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<Int, MobileConnectionRepository>() 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 18ae90db881a..5d377a8658a5 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<FakeNetworkEventModel?>(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 3d5316d1f19d..210208532dd4 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 34f30eb7c0a6..cdbe75e855bc 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 7fa80653f29c..7970443f69b1 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 @@ -50,6 +50,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_LTE import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.log.table.TableLogBuffer 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 @@ -87,14 +88,15 @@ 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 + @Mock private lateinit var tableLogger: TableLogBuffer private val scope = CoroutineScope(IMMEDIATE) private val mobileMappings = FakeMobileMappingsProxy() private val globalSettings = FakeSettings() - private val connectionsRepo = FakeMobileConnectionsRepository(mobileMappings) @Before fun setUp() { @@ -102,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, @@ -116,6 +120,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { mobileMappings, IMMEDIATE, logger, + tableLogger, scope, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 3cc1e8b74668..b8cd7a4f6e0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -34,12 +34,15 @@ import com.android.internal.telephony.PhoneConstants import com.android.settingslib.R import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings @@ -57,6 +60,7 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -72,6 +76,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Mock private lateinit var subscriptionManager: SubscriptionManager @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var logBufferFactory: TableLogBufferFactory private val mobileMappings = FakeMobileMappingsProxy() @@ -89,6 +94,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } } + whenever(logBufferFactory.create(anyString(), anyInt())).thenAnswer { _ -> + mock<TableLogBuffer>() + } + connectionFactory = MobileConnectionRepositoryImpl.Factory( fakeBroadcastDispatcher, @@ -99,6 +108,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { logger = logger, mobileMappingsProxy = mobileMappings, scope = scope, + logFactory = logBufferFactory, ) underTest = @@ -271,6 +281,32 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test + fun `connection repository - log buffer contains sub id in its name`() = + runBlocking(IMMEDIATE) { + val job = underTest.subscriptions.launchIn(this) + + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(SUB_1, SUB_2)) + getSubscriptionCallback().onSubscriptionsChanged() + + // Get repos to trigger creation + underTest.getRepoForSubId(SUB_1_ID) + verify(logBufferFactory) + .create( + eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_1_ID)), + anyInt(), + ) + underTest.getRepoForSubId(SUB_2_ID) + verify(logBufferFactory) + .create( + eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_2_ID)), + anyInt(), + ) + + job.cancel() + } + + @Test fun testDefaultDataSubId_updatesOnBroadcast() = runBlocking(IMMEDIATE) { var latest: Int? = null 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 c3519b7c8176..c49458909c78 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 9f300e9e0cf3..19e5516b58a2 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<List<SubscriptionModel>>(listOf()) - override val filteredSubscriptions: Flow<List<SubscriptionModel>> = _filteredSubscriptions + override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(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 4dca780425e5..83c5055a6eda 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 85578942ba86..2fa3467587cc 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 new file mode 100644 index 000000000000..043d55a73076 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -0,0 +1,109 @@ +/* + * 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.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 +import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +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 LocationBasedMobileIconViewModelTest : SysuiTestCase() { + private lateinit var commonImpl: MobileIconViewModelCommon + private lateinit var homeIcon: HomeMobileIconViewModel + private lateinit var qsIcon: QsMobileIconViewModel + private lateinit var keyguardIcon: KeyguardMobileIconViewModel + 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) + setIsFailedConnection(false) + setIconGroup(TelephonyIcons.THREE_G) + setIsEmergencyOnly(false) + setNumberOfLevels(4) + isDataConnected.value = true + } + commonImpl = + MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope) + + homeIcon = HomeMobileIconViewModel(commonImpl, logger) + qsIcon = QsMobileIconViewModel(commonImpl, logger) + keyguardIcon = KeyguardMobileIconViewModel(commonImpl, logger) + } + + @Test + fun `location based view models receive same icon id when common impl updates`() = + testScope.runTest { + var latestHome: Int? = null + val homeJob = homeIcon.iconId.onEach { latestHome = it }.launchIn(this) + + var latestQs: Int? = null + val qsJob = qsIcon.iconId.onEach { latestQs = it }.launchIn(this) + + var latestKeyguard: Int? = null + val keyguardJob = keyguardIcon.iconId.onEach { latestKeyguard = it }.launchIn(this) + + var expected = defaultSignal(level = 1) + + assertThat(latestHome).isEqualTo(expected) + assertThat(latestQs).isEqualTo(expected) + assertThat(latestKeyguard).isEqualTo(expected) + + interactor.setLevel(2) + expected = defaultSignal(level = 2) + + assertThat(latestHome).isEqualTo(expected) + assertThat(latestQs).isEqualTo(expected) + assertThat(latestKeyguard).isEqualTo(expected) + + homeJob.cancel() + qsJob.cancel() + keyguardJob.cancel() + } + + companion object { + private const val SUB_1_ID = 1 + } +} 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 415ce75345b2..50221bc97bad 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) @@ -340,16 +365,15 @@ class MobileIconViewModelTest : SysuiTestCase() { containerJob.cancel() } - /** Convenience constructor for these tests */ - private fun defaultSignal( - level: Int = 1, - connected: Boolean = true, - ): Int { - return SignalDrawable.getState(level, /* numLevels */ 4, !connected) - } - companion object { - private val IMMEDIATE = Dispatchers.Main.immediate private const val SUB_1_ID = 1 + + /** Convenience constructor for these tests */ + fun defaultSignal( + level: Int = 1, + connected: Boolean = true, + ): Int { + return SignalDrawable.getState(level, /* numLevels */ 4, !connected) + } } } 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 000000000000..d6cb76260f0b --- /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) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt index b47f177bbf24..41584347c0f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -146,7 +146,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false) createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) @@ -183,7 +183,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false) createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) @@ -225,7 +225,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null)) @@ -268,7 +268,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) @@ -308,7 +308,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) @@ -330,7 +330,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) @@ -352,7 +352,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) @@ -374,7 +374,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) @@ -396,7 +396,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) @@ -418,7 +418,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) @@ -440,7 +440,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) @@ -462,7 +462,7 @@ class WifiViewModelTest : SysuiTestCase() { @Test fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) { - whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) |