Merge changes from topic "wifi-constants" into tm-qpr-dev

* changes:
  [Sb refactor] Mobile icon view model logging
  [Sb refactor] Add logging to mobile connections pipeline
  [Sb refactor] Move shouldShowActivityConfig to ConnectivityConstants
  [Sb refactor] Add location to mobile view models
This commit is contained in:
TreeHugger Robot
2022-12-22 20:23:23 +00:00
committed by Android (Google) Code Review
34 changed files with 1050 additions and 216 deletions

View File

@@ -60,10 +60,12 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
private int mColor; private int mColor;
private final MobileIconsViewModel mMobileIconsViewModel; private final MobileIconsViewModel mMobileIconsViewModel;
private final StatusBarLocation mLocation;
public DemoStatusIcons( public DemoStatusIcons(
LinearLayout statusIcons, LinearLayout statusIcons,
MobileIconsViewModel mobileIconsViewModel, MobileIconsViewModel mobileIconsViewModel,
StatusBarLocation location,
int iconSize int iconSize
) { ) {
super(statusIcons.getContext()); super(statusIcons.getContext());
@@ -71,6 +73,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
mIconSize = iconSize; mIconSize = iconSize;
mColor = DarkIconDispatcher.DEFAULT_ICON_TINT; mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
mMobileIconsViewModel = mobileIconsViewModel; mMobileIconsViewModel = mobileIconsViewModel;
mLocation = location;
if (statusIcons instanceof StatusIconContainer) { if (statusIcons instanceof StatusIconContainer) {
setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons()); setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons());
@@ -287,7 +290,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind( ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind(
mobileContext, mobileContext,
"mobile", "mobile",
mMobileIconsViewModel.viewModelForSub(subId) mMobileIconsViewModel.viewModelForSub(subId, mLocation)
); );
// mobile always goes at the end // mobile always goes at the end

View File

@@ -359,6 +359,7 @@ public interface StatusBarIconController {
// Whether or not these icons show up in dumpsys // Whether or not these icons show up in dumpsys
protected boolean mShouldLog = false; protected boolean mShouldLog = false;
private StatusBarIconController mController; private StatusBarIconController mController;
private final StatusBarLocation mLocation;
// Enables SystemUI demo mode to take effect in this group // Enables SystemUI demo mode to take effect in this group
protected boolean mDemoable = true; protected boolean mDemoable = true;
@@ -381,11 +382,12 @@ public interface StatusBarIconController {
mContext = group.getContext(); mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize( mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size); com.android.internal.R.dimen.status_bar_icon_size);
mLocation = location;
if (statusBarPipelineFlags.runNewMobileIconsBackend()) { if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
// This starts the flow for the new pipeline, and will notify us of changes if // This starts the flow for the new pipeline, and will notify us of changes if
// {@link StatusBarPipelineFlags#useNewMobileIcons} is also true. // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel(); mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
MobileIconsBinder.bind(mGroup, mMobileIconsViewModel); MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
} else { } else {
mMobileIconsViewModel = null; mMobileIconsViewModel = null;
@@ -394,7 +396,7 @@ public interface StatusBarIconController {
if (statusBarPipelineFlags.runNewWifiIconBackend()) { if (statusBarPipelineFlags.runNewWifiIconBackend()) {
// This starts the flow for the new pipeline, and will notify us of changes if // This starts the flow for the new pipeline, and will notify us of changes if
// {@link StatusBarPipelineFlags#useNewWifiIcon} is also true. // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, location); mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
} else { } else {
mWifiViewModel = null; mWifiViewModel = null;
} }
@@ -569,7 +571,7 @@ public interface StatusBarIconController {
.constructAndBind( .constructAndBind(
mobileContext, mobileContext,
slot, slot,
mMobileIconsViewModel.viewModelForSub(subId) mMobileIconsViewModel.viewModelForSub(subId, mLocation)
); );
} }
@@ -705,7 +707,12 @@ public interface StatusBarIconController {
} }
protected DemoStatusIcons createDemoStatusIcons() { protected DemoStatusIcons createDemoStatusIcons() {
return new DemoStatusIcons((LinearLayout) mGroup, mMobileIconsViewModel, mIconSize); return new DemoStatusIcons(
(LinearLayout) mGroup,
mMobileIconsViewModel,
mLocation,
mIconSize
);
} }
} }
} }

View File

@@ -26,6 +26,8 @@ import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyCallback.SignalStrengthsListener import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager 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.mobile.data.model.DataConnectionState.Disconnected
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel 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 * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
*/ */
val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType, 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"
}
}

View File

@@ -21,22 +21,48 @@ import android.telephony.TelephonyManager.EXTRA_DATA_SPN
import android.telephony.TelephonyManager.EXTRA_PLMN import android.telephony.TelephonyManager.EXTRA_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_SPN 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 * 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]. * the intent sent by [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED].
*/ */
sealed interface NetworkNameModel { sealed interface NetworkNameModel : Diffable<NetworkNameModel> {
val name: String val name: String
/** The default name is read from [com.android.internal.R.string.lockscreen_carrier_default] */ /** 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 * This name has been derived from telephony intents. see
* [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED] * [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? { fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? {

View File

@@ -20,6 +20,7 @@ import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager 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.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -39,6 +40,13 @@ import kotlinx.coroutines.flow.StateFlow
interface MobileConnectionRepository { interface MobileConnectionRepository {
/** The subscriptionId that this connection represents */ /** The subscriptionId that this connection represents */
val subId: Int 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 * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
* listener + model. * listener + model.

View File

@@ -24,6 +24,8 @@ import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.qualifiers.Application 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.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
@@ -60,6 +62,7 @@ constructor(
private val dataSource: DemoModeMobileConnectionDataSource, private val dataSource: DemoModeMobileConnectionDataSource,
@Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope,
context: Context, context: Context,
private val logFactory: TableLogBufferFactory,
) : MobileConnectionsRepository { ) : MobileConnectionsRepository {
private var demoCommandJob: Job? = null private var demoCommandJob: Job? = null
@@ -149,7 +152,16 @@ constructor(
override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository { override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
return connectionRepoCache[subId] 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) 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 connectionInfo = MutableStateFlow(MobileConnectionModel())
override val dataEnabled = MutableStateFlow(true) override val dataEnabled = MutableStateFlow(true)

View File

@@ -36,6 +36,9 @@ import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background 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.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.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.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy 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
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject import javax.inject.Inject
@@ -59,6 +61,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -79,6 +82,7 @@ class MobileConnectionRepositoryImpl(
mobileMappingsProxy: MobileMappingsProxy, mobileMappingsProxy: MobileMappingsProxy,
bgDispatcher: CoroutineDispatcher, bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger, logger: ConnectivityPipelineLogger,
mobileLogger: TableLogBuffer,
scope: CoroutineScope, scope: CoroutineScope,
) : MobileConnectionRepository { ) : MobileConnectionRepository {
init { init {
@@ -92,10 +96,11 @@ class MobileConnectionRepositoryImpl(
private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1) private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
override val tableLogBuffer: TableLogBuffer = mobileLogger
override val connectionInfo: StateFlow<MobileConnectionModel> = run { override val connectionInfo: StateFlow<MobileConnectionModel> = run {
var state = MobileConnectionModel() var state = MobileConnectionModel()
conflatedCallbackFlow { conflatedCallbackFlow {
// TODO (b/240569788): log all of these into the connectivity logger
val callback = val callback =
object : object :
TelephonyCallback(), TelephonyCallback(),
@@ -106,6 +111,7 @@ class MobileConnectionRepositoryImpl(
TelephonyCallback.CarrierNetworkListener, TelephonyCallback.CarrierNetworkListener,
TelephonyCallback.DisplayInfoListener { TelephonyCallback.DisplayInfoListener {
override fun onServiceStateChanged(serviceState: ServiceState) { override fun onServiceStateChanged(serviceState: ServiceState) {
logger.logOnServiceStateChanged(serviceState, subId)
state = state =
state.copy( state.copy(
isEmergencyOnly = serviceState.isEmergencyOnly, isEmergencyOnly = serviceState.isEmergencyOnly,
@@ -116,6 +122,7 @@ class MobileConnectionRepositoryImpl(
} }
override fun onSignalStrengthsChanged(signalStrength: SignalStrength) { override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
logger.logOnSignalStrengthsChanged(signalStrength, subId)
val cdmaLevel = val cdmaLevel =
signalStrength signalStrength
.getCellSignalStrengths(CellSignalStrengthCdma::class.java) .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
@@ -142,12 +149,14 @@ class MobileConnectionRepositoryImpl(
dataState: Int, dataState: Int,
networkType: Int networkType: Int
) { ) {
logger.logOnDataConnectionStateChanged(dataState, networkType, subId)
state = state =
state.copy(dataConnectionState = dataState.toDataConnectionType()) state.copy(dataConnectionState = dataState.toDataConnectionType())
trySend(state) trySend(state)
} }
override fun onDataActivity(direction: Int) { override fun onDataActivity(direction: Int) {
logger.logOnDataActivity(direction, subId)
state = state =
state.copy( state.copy(
dataActivityDirection = direction.toMobileDataActivityModel() dataActivityDirection = direction.toMobileDataActivityModel()
@@ -156,6 +165,7 @@ class MobileConnectionRepositoryImpl(
} }
override fun onCarrierNetworkChange(active: Boolean) { override fun onCarrierNetworkChange(active: Boolean) {
logger.logOnCarrierNetworkChange(active, subId)
state = state.copy(carrierNetworkChangeActive = active) state = state.copy(carrierNetworkChangeActive = active)
trySend(state) trySend(state)
} }
@@ -163,6 +173,7 @@ class MobileConnectionRepositoryImpl(
override fun onDisplayInfoChanged( override fun onDisplayInfoChanged(
telephonyDisplayInfo: TelephonyDisplayInfo telephonyDisplayInfo: TelephonyDisplayInfo
) { ) {
logger.logOnDisplayInfoChanged(telephonyDisplayInfo, subId)
val networkType = val networkType =
if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) { if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
@@ -193,7 +204,11 @@ class MobileConnectionRepositoryImpl(
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
} }
.onEach { telephonyCallbackEvent.tryEmit(Unit) } .onEach { telephonyCallbackEvent.tryEmit(Unit) }
.logOutputChange(logger, "MobileSubscriptionModel") .logDiffsForTable(
mobileLogger,
columnPrefix = "MobileConnection ($subId)",
initialValue = state,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), state) .stateIn(scope, SharingStarted.WhileSubscribed(), state)
} }
@@ -243,19 +258,43 @@ class MobileConnectionRepositoryImpl(
intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName
} }
} }
.distinctUntilChanged()
.logDiffsForTable(
mobileLogger,
columnPrefix = "",
initialValue = defaultNetworkName,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName) .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
override val dataEnabled: StateFlow<Boolean> = override val dataEnabled: StateFlow<Boolean> = run {
val initial = dataConnectionAllowed()
telephonyPollingEvent telephonyPollingEvent
.mapLatest { dataConnectionAllowed() } .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 private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
override val isDefaultDataSubscription: StateFlow<Boolean> = override val isDefaultDataSubscription: StateFlow<Boolean> = run {
val initialValue = defaultDataSubId.value == subId
defaultDataSubId defaultDataSubId
.mapLatest { it == subId } .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 class Factory
@Inject @Inject
@@ -266,6 +305,7 @@ class MobileConnectionRepositoryImpl(
private val logger: ConnectivityPipelineLogger, private val logger: ConnectivityPipelineLogger,
private val globalSettings: GlobalSettings, private val globalSettings: GlobalSettings,
private val mobileMappingsProxy: MobileMappingsProxy, private val mobileMappingsProxy: MobileMappingsProxy,
private val logFactory: TableLogBufferFactory,
@Background private val bgDispatcher: CoroutineDispatcher, @Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope,
) { ) {
@@ -276,6 +316,8 @@ class MobileConnectionRepositoryImpl(
defaultDataSubId: StateFlow<Int>, defaultDataSubId: StateFlow<Int>,
globalMobileDataSettingChangedEvent: Flow<Unit>, globalMobileDataSettingChangedEvent: Flow<Unit>,
): MobileConnectionRepository { ): MobileConnectionRepository {
val mobileLogger = logFactory.create(tableBufferLogName(subId), 100)
return MobileConnectionRepositoryImpl( return MobileConnectionRepositoryImpl(
context, context,
subId, subId,
@@ -289,8 +331,13 @@ class MobileConnectionRepositoryImpl(
mobileMappingsProxy, mobileMappingsProxy,
bgDispatcher, bgDispatcher,
logger, logger,
mobileLogger,
scope, scope,
) )
} }
} }
companion object {
fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
}
} }

View File

@@ -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.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy 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
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
@@ -120,6 +121,7 @@ constructor(
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) } awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
} }
.mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } } .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
.logInputChange(logger, "onSubscriptionsChanged")
.onEach { infos -> dropUnusedReposFromCache(infos) } .onEach { infos -> dropUnusedReposFromCache(infos) }
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf()) .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
@@ -136,6 +138,8 @@ constructor(
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback) telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
} }
.distinctUntilChanged()
.logInputChange(logger, "onActiveDataSubscriptionIdChanged")
.stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID) .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> = private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
@@ -149,6 +153,7 @@ constructor(
intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID) intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
} }
.distinctUntilChanged() .distinctUntilChanged()
.logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
.onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) } .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
.stateIn( .stateIn(
scope, scope,
@@ -157,13 +162,15 @@ constructor(
) )
private val carrierConfigChangedEvent = private val carrierConfigChangedEvent =
broadcastDispatcher.broadcastFlow( broadcastDispatcher
IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED) .broadcastFlow(IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED))
) .logInputChange(logger, "ACTION_CARRIER_CONFIG_CHANGED")
override val defaultDataSubRatConfig: StateFlow<Config> = override val defaultDataSubRatConfig: StateFlow<Config> =
merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent) merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
.mapLatest { Config.readConfig(context) } .mapLatest { Config.readConfig(context) }
.distinctUntilChanged()
.logInputChange(logger, "defaultDataSubRatConfig")
.stateIn( .stateIn(
scope, scope,
SharingStarted.WhileSubscribed(), SharingStarted.WhileSubscribed(),
@@ -171,10 +178,16 @@ constructor(
) )
override val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>> = 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> = 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 { override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
if (!isValidSubId(subId)) { if (!isValidSubId(subId)) {
@@ -191,22 +204,24 @@ constructor(
* In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual * 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. * connection repositories also observe the URI for [MOBILE_DATA] + subId.
*/ */
override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow { override val globalMobileDataSettingChangedEvent: Flow<Unit> =
val observer = conflatedCallbackFlow {
object : ContentObserver(null) { val observer =
override fun onChange(selfChange: Boolean) { object : ContentObserver(null) {
trySend(Unit) override fun onChange(selfChange: Boolean) {
} trySend(Unit)
}
}
globalSettings.registerContentObserver(
globalSettings.getUriFor(MOBILE_DATA),
true,
observer
)
awaitClose { context.contentResolver.unregisterContentObserver(observer) }
} }
.logInputChange(logger, "globalMobileDataSettingChangedEvent")
globalSettings.registerContentObserver(
globalSettings.getUriFor(MOBILE_DATA),
true,
observer
)
awaitClose { context.contentResolver.unregisterContentObserver(observer) }
}
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> = override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
@@ -236,6 +251,8 @@ constructor(
awaitClose { connectivityManager.unregisterNetworkCallback(callback) } awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
} }
.distinctUntilChanged()
.logInputChange(logger, "defaultMobileNetworkConnectivity")
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel()) .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
private fun isValidSubId(subId: Int): Boolean { private fun isValidSubId(subId: Int): Boolean {

View File

@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.dagger.qualifiers.Application 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.DataConnectionState.Connected
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
@@ -35,6 +36,9 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
interface MobileIconInteractor { interface MobileIconInteractor {
/** The table log created for this connection */
val tableLogBuffer: TableLogBuffer
/** The current mobile data activity */ /** The current mobile data activity */
val activity: Flow<DataActivityModel> val activity: Flow<DataActivityModel>
@@ -97,6 +101,8 @@ class MobileIconInteractorImpl(
) : MobileIconInteractor { ) : MobileIconInteractor {
private val connectionInfo = connectionRepository.connectionInfo private val connectionInfo = connectionRepository.connectionInfo
override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
override val activity = connectionInfo.mapLatest { it.dataActivityDirection } override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled

View File

@@ -20,7 +20,6 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarIconController 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.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
@@ -70,6 +69,9 @@ constructor(
private val mobileSubIdsState: StateFlow<List<Int>> = private val mobileSubIdsState: StateFlow<List<Int>> =
mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) 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() { override fun start() {
// Only notify the icon controller if we want to *render* the new icons. // Only notify the icon controller if we want to *render* the new icons.
// Note that this flow may still run if // 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)
} }

View File

@@ -30,7 +30,7 @@ import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached 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.flow.distinctUntilChanged
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -39,7 +39,7 @@ object MobileIconBinder {
@JvmStatic @JvmStatic
fun bind( fun bind(
view: ViewGroup, view: ViewGroup,
viewModel: MobileIconViewModel, viewModel: LocationBasedMobileViewModel,
) { ) {
val activityContainer = view.requireViewById<View>(R.id.inout_container) val activityContainer = view.requireViewById<View>(R.id.inout_container)
val activityIn = view.requireViewById<ImageView>(R.id.mobile_in) val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)

View File

@@ -24,7 +24,7 @@ import com.android.systemui.R
import com.android.systemui.statusbar.BaseStatusBarFrameLayout import com.android.systemui.statusbar.BaseStatusBarFrameLayout
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON 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.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 import java.util.ArrayList
class ModernStatusBarMobileView( class ModernStatusBarMobileView(
@@ -71,7 +71,7 @@ class ModernStatusBarMobileView(
fun constructAndBind( fun constructAndBind(
context: Context, context: Context,
slot: String, slot: String,
viewModel: MobileIconViewModel, viewModel: LocationBasedMobileViewModel,
): ModernStatusBarMobileView { ): ModernStatusBarMobileView {
return (LayoutInflater.from(context) return (LayoutInflater.from(context)
.inflate(R.layout.status_bar_mobile_signal_group_new, null) .inflate(R.layout.status_bar_mobile_signal_group_new, null)

View File

@@ -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})")
}

View File

@@ -16,23 +16,40 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import android.graphics.Color
import com.android.settingslib.graph.SignalDrawable import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon 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.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor 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.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger 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 com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest 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 * 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. * subscription's information.
* *
* There will be exactly one [MobileIconViewModel] per filtered subscription offered from * 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") @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class MobileIconViewModel class MobileIconViewModel
constructor( constructor(
val subscriptionId: Int, override val subscriptionId: Int,
iconInteractor: MobileIconInteractor, iconInteractor: MobileIconInteractor,
logger: ConnectivityPipelineLogger, logger: ConnectivityPipelineLogger,
constants: ConnectivityConstants, constants: ConnectivityConstants,
) { scope: CoroutineScope,
) : MobileIconViewModelCommon {
/** Whether or not to show the error state of [SignalDrawable] */ /** Whether or not to show the error state of [SignalDrawable] */
private val showExclamationMark: Flow<Boolean> = private val showExclamationMark: Flow<Boolean> =
iconInteractor.isDefaultDataEnabled.mapLatest { !it } iconInteractor.isDefaultDataEnabled.mapLatest { !it }
/** An int consumable by [SignalDrawable] for display */ override val iconId: Flow<Int> = run {
val iconId: Flow<Int> = val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value)
combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) { combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
level, level,
numberOfLevels, numberOfLevels,
@@ -66,32 +87,56 @@ constructor(
SignalDrawable.getState(level, numberOfLevels, showExclamationMark) SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
} }
.distinctUntilChanged() .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 */ override val networkTypeIcon: Flow<Icon?> =
val networkTypeIcon: Flow<Icon?> =
combine( combine(
iconInteractor.networkTypeIconGroup, iconInteractor.networkTypeIconGroup,
iconInteractor.isDataConnected, iconInteractor.isDataConnected,
iconInteractor.isDataEnabled, iconInteractor.isDataEnabled,
iconInteractor.isDefaultConnectionFailed, iconInteractor.isDefaultConnectionFailed,
iconInteractor.alwaysShowDataRatIcon, iconInteractor.alwaysShowDataRatIcon,
) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow -> ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
val desc = val desc =
if (networkTypeIconGroup.dataContentDescription != 0) if (networkTypeIconGroup.dataContentDescription != 0)
ContentDescription.Resource(networkTypeIconGroup.dataContentDescription) ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
else null else null
val icon = Icon.Resource(networkTypeIconGroup.dataType, desc) val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
return@combine when { return@combine when {
alwaysShow -> icon alwaysShow -> icon
!dataConnected -> null !dataConnected -> null
!dataEnabled -> null !dataEnabled -> null
failedConnection -> null failedConnection -> null
else -> icon 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?> = private val activity: Flow<DataActivityModel?> =
if (!constants.shouldShowActivityConfig) { if (!constants.shouldShowActivityConfig) {
@@ -100,10 +145,39 @@ constructor(
iconInteractor.activity iconInteractor.activity
} }
val activityInVisible: Flow<Boolean> = activity.map { it?.hasActivityIn ?: false } override val activityInVisible: Flow<Boolean> =
val activityOutVisible: Flow<Boolean> = activity.map { it?.hasActivityOut ?: false } activity
val activityContainerVisible: Flow<Boolean> = .map { it?.hasActivityIn ?: false }
activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) } .distinctUntilChanged()
.logDiffsForTable(
iconInteractor.tableLogBuffer,
columnPrefix = "",
columnName = "activityInVisible",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
val tint: Flow<Int> = flowOf(Color.CYAN) override val activityOutVisible: Flow<Boolean> =
activity
.map { it?.hasActivityOut ?: false }
.distinctUntilChanged()
.logDiffsForTable(
iconInteractor.tableLogBuffer,
columnPrefix = "",
columnName = "activityOutVisible",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
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)
} }

View File

@@ -14,17 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
@file:OptIn(InternalCoroutinesApi::class)
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel 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.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView 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.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow 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 * 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 interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger, private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants, private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
) { ) {
/** TODO: do we need to cache these? */ @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
fun viewModelForSub(subId: Int): MobileIconViewModel =
MobileIconViewModel( init {
subId, scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } }
interactor.createMobileConnectionInteractorForSubId(subId), }
logger,
constants, 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 class Factory
@Inject @Inject
@@ -54,6 +74,7 @@ constructor(
private val interactor: MobileIconsInteractor, private val interactor: MobileIconsInteractor,
private val logger: ConnectivityPipelineLogger, private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants, private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
) { ) {
fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel { fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
return MobileIconsViewModel( return MobileIconsViewModel(
@@ -61,6 +82,7 @@ constructor(
interactor, interactor,
logger, logger,
constants, constants,
scope,
) )
} }
} }

View File

@@ -18,8 +18,11 @@ package com.android.systemui.statusbar.pipeline.shared
import android.net.Network import android.net.Network
import android.net.NetworkCapabilities 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.dagger.SysUISingleton
import com.android.systemui.log.dagger.StatusBarConnectivityLog
import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
@@ -28,7 +31,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@SysUISingleton @SysUISingleton
class ConnectivityPipelineLogger @Inject constructor( class ConnectivityPipelineLogger
@Inject
constructor(
@StatusBarConnectivityLog private val buffer: LogBuffer, @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. * Use this method for inputs that don't have any extra information besides their callback name.
*/ */
fun logInputChange(callbackName: String) { fun logInputChange(callbackName: String) {
buffer.log(SB_LOGGING_TAG, LogLevel.INFO, { str1 = callbackName }, { "Input: $str1" })
}
/** Logs a change in one of the **raw inputs** to the connectivity pipeline. */
fun logInputChange(callbackName: String, changeInfo: String?) {
buffer.log( buffer.log(
SB_LOGGING_TAG, SB_LOGGING_TAG,
LogLevel.INFO, LogLevel.INFO,
{ str1 = callbackName }, {
{ "Input: $str1" } str1 = callbackName
str2 = changeInfo
},
{ "Input: $str1: $str2" }
) )
} }
/** /** Logs a **data transformation** that we performed within 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"
}
)
}
/**
* Logs a **data transformation** that we performed within the connectivity pipeline.
*/
fun logTransformation(transformationName: String, oldValue: Any?, newValue: Any?) { fun logTransformation(transformationName: String, oldValue: Any?, newValue: Any?) {
if (oldValue == newValue) { if (oldValue == newValue) {
buffer.log( buffer.log(
@@ -74,9 +68,7 @@ class ConnectivityPipelineLogger @Inject constructor(
str1 = transformationName str1 = transformationName
str2 = oldValue.toString() str2 = oldValue.toString()
}, },
{ { "Transform: $str1: $str2 (transformation didn't change it)" }
"Transform: $str1: $str2 (transformation didn't change it)"
}
) )
} else { } else {
buffer.log( buffer.log(
@@ -87,27 +79,21 @@ class ConnectivityPipelineLogger @Inject constructor(
str2 = oldValue.toString() str2 = oldValue.toString()
str3 = newValue.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) { fun logOutputChange(outputParamName: String, changeInfo: String) {
buffer.log( buffer.log(
SB_LOGGING_TAG, SB_LOGGING_TAG,
LogLevel.INFO, LogLevel.INFO,
{ {
str1 = outputParamName str1 = outputParamName
str2 = changeInfo str2 = changeInfo
}, },
{ { "Output: $str1: $str2" }
"Output: $str1: $str2"
}
) )
} }
@@ -119,31 +105,101 @@ class ConnectivityPipelineLogger @Inject constructor(
int1 = network.getNetId() int1 = network.getNetId()
str1 = networkCapabilities.toString() str1 = networkCapabilities.toString()
}, },
{ { "onCapabilitiesChanged: net=$int1 capabilities=$str1" }
"onCapabilitiesChanged: net=$int1 capabilities=$str1"
}
) )
} }
fun logOnLost(network: Network) { fun logOnLost(network: Network) {
buffer.log(
SB_LOGGING_TAG,
LogLevel.INFO,
{ int1 = network.getNetId() },
{ "onLost: net=$int1" }
)
}
fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
buffer.log( buffer.log(
SB_LOGGING_TAG, SB_LOGGING_TAG,
LogLevel.INFO, 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 { companion object {
const val SB_LOGGING_TAG = "SbConnectivity" 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( fun Flow<Unit>.logInputChange(
logger: ConnectivityPipelineLogger, logger: ConnectivityPipelineLogger,
inputParamName: String, inputParamName: String,
@@ -155,26 +211,26 @@ class ConnectivityPipelineLogger @Inject constructor(
* Log a change in one of the **inputs** to the connectivity pipeline. * 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. * @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( fun <T> Flow<T>.logInputChange(
logger: ConnectivityPipelineLogger, logger: ConnectivityPipelineLogger,
inputParamName: String, inputParamName: String,
prettyPrint: (T) -> String = { it.toString() } prettyPrint: (T) -> String = { it.toString() }
): Flow<T> { ): 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. * 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. * @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( fun <T> Flow<T>.logOutputChange(
logger: ConnectivityPipelineLogger, logger: ConnectivityPipelineLogger,
outputParamName: String, outputParamName: String,
prettyPrint: (T) -> String = { it.toString() } prettyPrint: (T) -> String = { it.toString() }
): Flow<T> { ): Flow<T> {
return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) } return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) }
} }

View File

@@ -38,16 +38,12 @@ class WifiConstants @Inject constructor(
dumpManager.registerDumpable("${SB_LOGGING_TAG}WifiConstants", this) 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. */ /** True if we should always show the wifi icon when wifi is enabled and false otherwise. */
val alwaysShowIconIfEnabled = val alwaysShowIconIfEnabled =
context.resources.getBoolean(R.bool.config_showWifiIndicatorWhenEnabled) context.resources.getBoolean(R.bool.config_showWifiIndicatorWhenEnabled)
override fun dump(pw: PrintWriter, args: Array<out String>) { override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.apply { pw.apply {
println("shouldShowActivityConfig=$shouldShowActivityConfig")
println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled") println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled")
} }
} }

View File

@@ -148,7 +148,7 @@ constructor(
/** The wifi activity status. Null if we shouldn't display the activity status. */ /** The wifi activity status. Null if we shouldn't display the activity status. */
private val activity: Flow<DataActivityModel?> = private val activity: Flow<DataActivityModel?> =
if (!wifiConstants.shouldShowActivityConfig) { if (!connectivityConstants.shouldShowActivityConfig) {
flowOf(null) flowOf(null)
} else { } else {
combine(interactor.activity, interactor.ssid) { activity, ssid -> combine(interactor.activity, interactor.ssid) { activity, ssid ->

View File

@@ -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()))
}
}
}

View File

@@ -16,12 +16,16 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository 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.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository // 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()) private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
override val connectionInfo = _connectionInfo override val connectionInfo = _connectionInfo

View File

@@ -22,14 +22,17 @@ import android.telephony.TelephonyManager
import com.android.settingslib.SignalIcon import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons 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.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository
class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) : class FakeMobileConnectionsRepository(
MobileConnectionsRepository { mobileMappings: MobileMappingsProxy,
val tableLogBuffer: TableLogBuffer,
) : MobileConnectionsRepository {
val GSM_KEY = mobileMappings.toIconKey(GSM) val GSM_KEY = mobileMappings.toIconKey(GSM)
val LTE_KEY = mobileMappings.toIconKey(LTE) val LTE_KEY = mobileMappings.toIconKey(LTE)
val UMTS_KEY = mobileMappings.toIconKey(UMTS) val UMTS_KEY = mobileMappings.toIconKey(UMTS)
@@ -63,7 +66,7 @@ class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) :
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>() private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
override fun getRepoForSubId(subId: Int): MobileConnectionRepository { override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
return subIdRepos[subId] return subIdRepos[subId]
?: FakeMobileConnectionRepository(subId).also { subIdRepos[subId] = it } ?: FakeMobileConnectionRepository(subId, tableLogBuffer).also { subIdRepos[subId] = it }
} }
private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit) private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)

View File

@@ -24,6 +24,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController 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.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource 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.mock
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -69,12 +72,14 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
private lateinit var realRepo: MobileConnectionsRepositoryImpl private lateinit var realRepo: MobileConnectionsRepositoryImpl
private lateinit var demoRepo: DemoMobileConnectionsRepository private lateinit var demoRepo: DemoMobileConnectionsRepository
private lateinit var mockDataSource: DemoModeMobileConnectionDataSource private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
private lateinit var logFactory: TableLogBufferFactory
@Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager @Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var demoModeController: DemoModeController @Mock private lateinit var demoModeController: DemoModeController
@Mock private lateinit var dumpManager: DumpManager
private val globalSettings = FakeSettings() private val globalSettings = FakeSettings()
private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null) private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
@@ -86,6 +91,8 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
// Never start in demo mode // Never start in demo mode
whenever(demoModeController.isInDemoMode).thenReturn(false) whenever(demoModeController.isInDemoMode).thenReturn(false)
@@ -114,6 +121,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
dataSource = mockDataSource, dataSource = mockDataSource,
scope = scope, scope = scope,
context = context, context = context,
logFactory = logFactory,
) )
underTest = underTest =

View File

@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase 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.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel 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.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.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
@@ -54,6 +56,9 @@ import org.junit.runners.Parameterized.Parameters
@RunWith(Parameterized::class) @RunWith(Parameterized::class)
internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) : internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
SysuiTestCase() { SysuiTestCase() {
private val logFactory = TableLogBufferFactory(mock(), FakeSystemClock())
private val testDispatcher = UnconfinedTestDispatcher() private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher) private val testScope = TestScope(testDispatcher)
@@ -76,6 +81,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
dataSource = mockDataSource, dataSource = mockDataSource,
scope = testScope.backgroundScope, scope = testScope.backgroundScope,
context = context, context = context,
logFactory = logFactory,
) )
connectionsRepo.startProcessingCommands() connectionsRepo.startProcessingCommands()

View File

@@ -23,6 +23,8 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons.THREE_G import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase 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.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel 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.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.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import junit.framework.Assert import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,6 +50,9 @@ import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@SmallTest @SmallTest
class DemoMobileConnectionsRepositoryTest : SysuiTestCase() { class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
private val dumpManager: DumpManager = mock()
private val logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
private val testDispatcher = UnconfinedTestDispatcher() private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher) private val testScope = TestScope(testDispatcher)
@@ -68,6 +74,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
dataSource = mockDataSource, dataSource = mockDataSource,
scope = testScope.backgroundScope, scope = testScope.backgroundScope,
context = context, context = context,
logFactory = logFactory,
) )
underTest.startProcessingCommands() underTest.startProcessingCommands()

View File

@@ -50,6 +50,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase 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.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel 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.NetworkNameModel
@@ -87,14 +88,15 @@ import org.mockito.MockitoAnnotations
@SmallTest @SmallTest
class MobileConnectionRepositoryTest : SysuiTestCase() { class MobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl private lateinit var underTest: MobileConnectionRepositoryImpl
private lateinit var connectionsRepo: FakeMobileConnectionsRepository
@Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var tableLogger: TableLogBuffer
private val scope = CoroutineScope(IMMEDIATE) private val scope = CoroutineScope(IMMEDIATE)
private val mobileMappings = FakeMobileMappingsProxy() private val mobileMappings = FakeMobileMappingsProxy()
private val globalSettings = FakeSettings() private val globalSettings = FakeSettings()
private val connectionsRepo = FakeMobileConnectionsRepository(mobileMappings)
@Before @Before
fun setUp() { fun setUp() {
@@ -102,6 +104,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
globalSettings.userId = UserHandle.USER_ALL globalSettings.userId = UserHandle.USER_ALL
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID) whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger)
underTest = underTest =
MobileConnectionRepositoryImpl( MobileConnectionRepositoryImpl(
context, context,
@@ -116,6 +120,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
mobileMappings, mobileMappings,
IMMEDIATE, IMMEDIATE,
logger, logger,
tableLogger,
scope, scope,
) )
} }

View File

@@ -34,12 +34,15 @@ import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.R import com.android.settingslib.R
import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase 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.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel 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.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor 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.mock
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.FakeSettings
@@ -57,6 +60,7 @@ import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@@ -72,6 +76,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Mock private lateinit var subscriptionManager: SubscriptionManager @Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var logBufferFactory: TableLogBufferFactory
private val mobileMappings = FakeMobileMappingsProxy() private val mobileMappings = FakeMobileMappingsProxy()
@@ -89,6 +94,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
} }
} }
whenever(logBufferFactory.create(anyString(), anyInt())).thenAnswer { _ ->
mock<TableLogBuffer>()
}
connectionFactory = connectionFactory =
MobileConnectionRepositoryImpl.Factory( MobileConnectionRepositoryImpl.Factory(
fakeBroadcastDispatcher, fakeBroadcastDispatcher,
@@ -99,6 +108,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
logger = logger, logger = logger,
mobileMappingsProxy = mobileMappings, mobileMappingsProxy = mobileMappings,
scope = scope, scope = scope,
logFactory = logBufferFactory,
) )
underTest = underTest =
@@ -270,6 +280,32 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
@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 @Test
fun testDefaultDataSubId_updatesOnBroadcast() = fun testDefaultDataSubId_updatesOnBroadcast() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {

View File

@@ -19,11 +19,14 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CellSignalStrength import android.telephony.CellSignalStrength
import com.android.settingslib.SignalIcon import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons 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.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileIconInteractor : MobileIconInteractor { class FakeMobileIconInteractor(
override val tableLogBuffer: TableLogBuffer,
) : MobileIconInteractor {
override val alwaysShowDataRatIcon = MutableStateFlow(false) override val alwaysShowDataRatIcon = MutableStateFlow(false)
override val activity = override val activity =

View File

@@ -22,12 +22,15 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons 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.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow 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 THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
val LTE_KEY = mobileMappings.toIconKey(LTE) val LTE_KEY = mobileMappings.toIconKey(LTE)
val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G) val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
@@ -48,8 +51,7 @@ class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIco
override val isDefaultConnectionFailed = MutableStateFlow(false) override val isDefaultConnectionFailed = MutableStateFlow(false)
private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf()) override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
override val filteredSubscriptions: Flow<List<SubscriptionModel>> = _filteredSubscriptions
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false) private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
@@ -67,7 +69,7 @@ class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIco
/** Always returns a new fake interactor */ /** Always returns a new fake interactor */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor { override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
return FakeMobileIconInteractor() return FakeMobileIconInteractor(tableLogBuffer)
} }
companion object { companion object {

View File

@@ -49,8 +49,8 @@ import org.junit.Test
class MobileIconInteractorTest : SysuiTestCase() { class MobileIconInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconInteractor private lateinit var underTest: MobileIconInteractor
private val mobileMappingsProxy = FakeMobileMappingsProxy() private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy) private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID) private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID, mock())
private val scope = CoroutineScope(IMMEDIATE) private val scope = CoroutineScope(IMMEDIATE)

View File

@@ -20,6 +20,7 @@ import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase 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.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository 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.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -44,9 +46,9 @@ import org.mockito.MockitoAnnotations
@SmallTest @SmallTest
class MobileIconsInteractorTest : SysuiTestCase() { class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor private lateinit var underTest: MobileIconsInteractor
private lateinit var connectionsRepository: FakeMobileConnectionsRepository
private val userSetupRepository = FakeUserSetupRepository() private val userSetupRepository = FakeUserSetupRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy() private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy)
private val scope = CoroutineScope(IMMEDIATE) private val scope = CoroutineScope(IMMEDIATE)
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -55,6 +57,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
connectionsRepository.setMobileConnectionRepositoryMap( connectionsRepository.setMobileConnectionRepositoryMap(
mapOf( mapOf(
SUB_1_ID to CONNECTION_1, SUB_1_ID to CONNECTION_1,
@@ -290,21 +293,23 @@ class MobileIconsInteractorTest : SysuiTestCase() {
companion object { companion object {
private val IMMEDIATE = Dispatchers.Main.immediate private val IMMEDIATE = Dispatchers.Main.immediate
private val tableLogBuffer =
TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
private const val SUB_1_ID = 1 private const val SUB_1_ID = 1
private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID) 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 const val SUB_2_ID = 2
private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID) 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 const val SUB_3_ID = 3
private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true) 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 const val SUB_4_ID = 4
private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true) 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)
} }
} }

View File

@@ -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
}
}

View File

@@ -22,32 +22,42 @@ import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon 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.mobile.domain.interactor.FakeMobileIconInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach 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 kotlinx.coroutines.yield
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest @SmallTest
class MobileIconViewModelTest : SysuiTestCase() { class MobileIconViewModelTest : SysuiTestCase() {
private lateinit var underTest: MobileIconViewModel private lateinit var underTest: MobileIconViewModel
private val interactor = FakeMobileIconInteractor() private lateinit var interactor: FakeMobileIconInteractor
@Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants @Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
interactor = FakeMobileIconInteractor(tableLogBuffer)
interactor.apply { interactor.apply {
setLevel(1) setLevel(1)
setIsDefaultDataEnabled(true) setIsDefaultDataEnabled(true)
@@ -57,12 +67,13 @@ class MobileIconViewModelTest : SysuiTestCase() {
setNumberOfLevels(4) setNumberOfLevels(4)
isDataConnected.value = true isDataConnected.value = true
} }
underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants) underTest =
MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
} }
@Test @Test
fun iconId_correctLevel_notCutout() = fun iconId_correctLevel_notCutout() =
runBlocking(IMMEDIATE) { testScope.runTest {
var latest: Int? = null var latest: Int? = null
val job = underTest.iconId.onEach { latest = it }.launchIn(this) val job = underTest.iconId.onEach { latest = it }.launchIn(this)
val expected = defaultSignal() val expected = defaultSignal()
@@ -74,7 +85,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun iconId_cutout_whenDefaultDataDisabled() = fun iconId_cutout_whenDefaultDataDisabled() =
runBlocking(IMMEDIATE) { testScope.runTest {
interactor.setIsDefaultDataEnabled(false) interactor.setIsDefaultDataEnabled(false)
var latest: Int? = null var latest: Int? = null
@@ -88,7 +99,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun networkType_dataEnabled_groupIsRepresented() = fun networkType_dataEnabled_groupIsRepresented() =
runBlocking(IMMEDIATE) { testScope.runTest {
val expected = val expected =
Icon.Resource( Icon.Resource(
THREE_G.dataType, THREE_G.dataType,
@@ -106,7 +117,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun networkType_nullWhenDisabled() = fun networkType_nullWhenDisabled() =
runBlocking(IMMEDIATE) { testScope.runTest {
interactor.setIconGroup(THREE_G) interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(false) interactor.setIsDataEnabled(false)
var latest: Icon? = null var latest: Icon? = null
@@ -119,7 +130,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun networkType_nullWhenFailedConnection() = fun networkType_nullWhenFailedConnection() =
runBlocking(IMMEDIATE) { testScope.runTest {
interactor.setIconGroup(THREE_G) interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(true) interactor.setIsDataEnabled(true)
interactor.setIsFailedConnection(true) interactor.setIsFailedConnection(true)
@@ -133,7 +144,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun networkType_nullWhenDataDisconnects() = fun networkType_nullWhenDataDisconnects() =
runBlocking(IMMEDIATE) { testScope.runTest {
val initial = val initial =
Icon.Resource( Icon.Resource(
THREE_G.dataType, THREE_G.dataType,
@@ -157,7 +168,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun networkType_null_changeToDisabled() = fun networkType_null_changeToDisabled() =
runBlocking(IMMEDIATE) { testScope.runTest {
val expected = val expected =
Icon.Resource( Icon.Resource(
THREE_G.dataType, THREE_G.dataType,
@@ -180,7 +191,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun networkType_alwaysShow_shownEvenWhenDisabled() = fun networkType_alwaysShow_shownEvenWhenDisabled() =
runBlocking(IMMEDIATE) { testScope.runTest {
interactor.setIconGroup(THREE_G) interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(true) interactor.setIsDataEnabled(true)
interactor.alwaysShowDataRatIcon.value = true interactor.alwaysShowDataRatIcon.value = true
@@ -200,7 +211,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun networkType_alwaysShow_shownEvenWhenDisconnected() = fun networkType_alwaysShow_shownEvenWhenDisconnected() =
runBlocking(IMMEDIATE) { testScope.runTest {
interactor.setIconGroup(THREE_G) interactor.setIconGroup(THREE_G)
interactor.isDataConnected.value = false interactor.isDataConnected.value = false
interactor.alwaysShowDataRatIcon.value = true interactor.alwaysShowDataRatIcon.value = true
@@ -220,7 +231,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun networkType_alwaysShow_shownEvenWhenFailedConnection() = fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
runBlocking(IMMEDIATE) { testScope.runTest {
interactor.setIconGroup(THREE_G) interactor.setIconGroup(THREE_G)
interactor.setIsFailedConnection(true) interactor.setIsFailedConnection(true)
interactor.alwaysShowDataRatIcon.value = true interactor.alwaysShowDataRatIcon.value = true
@@ -240,7 +251,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun roaming() = fun roaming() =
runBlocking(IMMEDIATE) { testScope.runTest {
interactor.isRoaming.value = true interactor.isRoaming.value = true
var latest: Boolean? = null var latest: Boolean? = null
val job = underTest.roaming.onEach { latest = it }.launchIn(this) val job = underTest.roaming.onEach { latest = it }.launchIn(this)
@@ -256,10 +267,17 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun `data activity - null when config is off`() = fun `data activity - null when config is off`() =
runBlocking(IMMEDIATE) { testScope.runTest {
// Create a new view model here so the constants are properly read // Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(false) 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 var inVisible: Boolean? = null
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this) val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -288,10 +306,17 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun `data activity - config on - test indicators`() = fun `data activity - config on - test indicators`() =
runBlocking(IMMEDIATE) { testScope.runTest {
// Create a new view model here so the constants are properly read // Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(true) 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 var inVisible: Boolean? = null
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this) val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -340,16 +365,15 @@ class MobileIconViewModelTest : SysuiTestCase() {
containerJob.cancel() 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 { companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1 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)
}
} }
} }

View File

@@ -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)
}
}

View File

@@ -146,7 +146,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) { fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -183,7 +183,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) { fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -225,7 +225,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) { fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null)) wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null))
@@ -268,7 +268,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) { fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -308,7 +308,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) { fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -330,7 +330,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) { fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -352,7 +352,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -374,7 +374,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) { fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -396,7 +396,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) { fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -418,7 +418,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -440,7 +440,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -462,7 +462,7 @@ class WifiViewModelTest : SysuiTestCase() {
@Test @Test
fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) { fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true) whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel() createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)