Merge changes I5986ba47,I8ddeafe4,I385e2050,I1f8b2b4e,Iab75c65a, ... into tm-qpr-dev am: d756cb556d

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21302234

Change-Id: I78c5fa223410a1c996184dc87c3b04859cdb3aa5
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Caitlin Shkuratov
2023-02-08 15:02:20 +00:00
committed by Automerger Merge Worker
32 changed files with 1270 additions and 157 deletions

View File

@@ -79,10 +79,10 @@ fun <T : Diffable<T>> Flow<T>.logDiffsForTable(
} }
} }
/** // Here and below: Various Flow<SomeType> extension functions that are effectively equivalent to the
* Each time the boolean flow is updated with a new value that's different from the previous value, // above [logDiffsForTable] method.
* logs the new value to the given [tableLogBuffer].
*/ /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
fun Flow<Boolean>.logDiffsForTable( fun Flow<Boolean>.logDiffsForTable(
tableLogBuffer: TableLogBuffer, tableLogBuffer: TableLogBuffer,
columnPrefix: String, columnPrefix: String,
@@ -100,10 +100,8 @@ fun Flow<Boolean>.logDiffsForTable(
newVal newVal
} }
} }
/**
* Each time the Int flow is updated with a new value that's different from the previous value, logs /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
* the new value to the given [tableLogBuffer].
*/
fun Flow<Int>.logDiffsForTable( fun Flow<Int>.logDiffsForTable(
tableLogBuffer: TableLogBuffer, tableLogBuffer: TableLogBuffer,
columnPrefix: String, columnPrefix: String,
@@ -122,10 +120,26 @@ fun Flow<Int>.logDiffsForTable(
} }
} }
/** /** See [logDiffsForTable(TableLogBuffer, String, T)]. */
* Each time the String? flow is updated with a new value that's different from the previous value, fun Flow<Int?>.logDiffsForTable(
* logs the new value to the given [tableLogBuffer]. tableLogBuffer: TableLogBuffer,
*/ columnPrefix: String,
columnName: String,
initialValue: Int?,
): Flow<Int?> {
val initialValueFun = {
tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
initialValue
}
return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int? ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
newVal
}
}
/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
fun Flow<String?>.logDiffsForTable( fun Flow<String?>.logDiffsForTable(
tableLogBuffer: TableLogBuffer, tableLogBuffer: TableLogBuffer,
columnPrefix: String, columnPrefix: String,
@@ -143,3 +157,23 @@ fun Flow<String?>.logDiffsForTable(
newVal newVal
} }
} }
/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
fun <T> Flow<List<T>>.logDiffsForTable(
tableLogBuffer: TableLogBuffer,
columnPrefix: String,
columnName: String,
initialValue: List<T>,
): Flow<List<T>> {
val initialValueFun = {
tableLogBuffer.logChange(columnPrefix, columnName, initialValue.toString())
initialValue
}
return this.pairwiseBy(initialValueFun) { prevVal, newVal: List<T> ->
if (prevVal != newVal) {
// TODO(b/267761156): Can we log list changes without using toString?
tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString())
}
newVal
}
}

View File

@@ -27,7 +27,7 @@ data class TableChange(
var columnName: String = "", var columnName: String = "",
var type: DataType = DataType.EMPTY, var type: DataType = DataType.EMPTY,
var bool: Boolean = false, var bool: Boolean = false,
var int: Int = 0, var int: Int? = null,
var str: String? = null, var str: String? = null,
) { ) {
/** Resets to default values so that the object can be recycled. */ /** Resets to default values so that the object can be recycled. */
@@ -54,7 +54,7 @@ data class TableChange(
} }
/** Sets this to store an int change. */ /** Sets this to store an int change. */
fun set(value: Int) { fun set(value: Int?) {
type = DataType.INT type = DataType.INT
int = value int = value
} }

View File

@@ -138,7 +138,7 @@ class TableLogBuffer(
} }
/** Logs a Int change. */ /** Logs a Int change. */
fun logChange(prefix: String, columnName: String, value: Int) { fun logChange(prefix: String, columnName: String, value: Int?) {
logChange(systemClock.currentTimeMillis(), prefix, columnName, value) logChange(systemClock.currentTimeMillis(), prefix, columnName, value)
} }
@@ -155,7 +155,7 @@ class TableLogBuffer(
change.set(value) change.set(value)
} }
private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int) { private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int?) {
val change = obtain(timestamp, prefix, columnName) val change = obtain(timestamp, prefix, columnName)
change.set(value) change.set(value)
} }

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 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.dagger
import javax.inject.Qualifier
/**
* Logs for mobile data that's **the same across all connections**.
*
* This buffer should only be used for the mobile parent classes like [MobileConnectionsRepository]
* and [MobileIconsInteractor]. It should *not* be used for classes that represent an individual
* connection, like [MobileConnectionRepository] or [MobileIconInteractor].
*/
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class MobileSummaryLog

View File

@@ -118,5 +118,12 @@ abstract class StatusBarPipelineModule {
fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer { fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
return factory.create("AirplaneTableLog", 30) return factory.create("AirplaneTableLog", 30)
} }
@Provides
@SysUISingleton
@MobileSummaryLog
fun provideMobileSummaryLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
return factory.create("MobileSummaryLog", 100)
}
} }
} }

View File

@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
/** Provides information about a mobile network connection */ /** Provides information about a mobile network connection */
data class MobileConnectivityModel( data class MobileConnectivityModel(
@@ -24,4 +26,24 @@ data class MobileConnectivityModel(
val isConnected: Boolean = false, val isConnected: Boolean = false,
/** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */ /** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
val isValidated: Boolean = false, val isValidated: Boolean = false,
) ) : Diffable<MobileConnectivityModel> {
// TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
override fun logDiffs(prevVal: MobileConnectivityModel, row: TableRowLogger) {
if (prevVal.isConnected != isConnected) {
row.logChange(COL_IS_CONNECTED, isConnected)
}
if (prevVal.isValidated != isValidated) {
row.logChange(COL_IS_VALIDATED, isValidated)
}
}
override fun logFull(row: TableRowLogger) {
row.logChange(COL_IS_CONNECTED, isConnected)
row.logChange(COL_IS_VALIDATED, isValidated)
}
companion object {
private const val COL_IS_CONNECTED = "isConnected"
private const val COL_IS_VALIDATED = "isValidated"
}
}

View File

@@ -91,9 +91,6 @@ class CarrierMergedConnectionRepository(
.map { it.toMobileConnectionModel() } .map { it.toMobileConnectionModel() }
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel()) .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel())
// TODO(b/238425913): Add logging to this class.
// TODO(b/238425913): Make sure SignalStrength.getEmptyState is used when appropriate.
// Carrier merged is never roaming. // Carrier merged is never roaming.
override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()

View File

@@ -117,11 +117,22 @@ class FullMobileConnectionRepository(
override val connectionInfo = override val connectionInfo =
activeRepo activeRepo
.flatMapLatest { it.connectionInfo } .flatMapLatest { it.connectionInfo }
.logDiffsForTable(
tableLogBuffer,
columnPrefix = "",
initialValue = activeRepo.value.connectionInfo.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)
override val dataEnabled = override val dataEnabled =
activeRepo activeRepo
.flatMapLatest { it.dataEnabled } .flatMapLatest { it.dataEnabled }
.logDiffsForTable(
tableLogBuffer,
columnPrefix = "",
columnName = "dataEnabled",
initialValue = activeRepo.value.dataEnabled.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value)
override val numberOfLevels = override val numberOfLevels =
@@ -132,6 +143,11 @@ class FullMobileConnectionRepository(
override val networkName = override val networkName =
activeRepo activeRepo
.flatMapLatest { it.networkName } .flatMapLatest { it.networkName }
.logDiffsForTable(
tableLogBuffer,
columnPrefix = "",
initialValue = activeRepo.value.networkName.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value)
class Factory class Factory
@@ -168,7 +184,7 @@ class FullMobileConnectionRepository(
const val MOBILE_CONNECTION_BUFFER_SIZE = 100 const val MOBILE_CONNECTION_BUFFER_SIZE = 100
/** Returns a log buffer name for a mobile connection with the given [subId]. */ /** Returns a log buffer name for a mobile connection with the given [subId]. */
fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]" fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]"
} }
} }
} }

View File

@@ -36,7 +36,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
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.TableLogBuffer
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
@@ -62,7 +61,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
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.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
@@ -87,7 +85,7 @@ class MobileConnectionRepositoryImpl(
private val mobileMappingsProxy: MobileMappingsProxy, private val mobileMappingsProxy: MobileMappingsProxy,
bgDispatcher: CoroutineDispatcher, bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger, logger: ConnectivityPipelineLogger,
mobileLogger: TableLogBuffer, override val tableLogBuffer: TableLogBuffer,
scope: CoroutineScope, scope: CoroutineScope,
) : MobileConnectionRepository { ) : MobileConnectionRepository {
init { init {
@@ -101,8 +99,6 @@ class MobileConnectionRepositoryImpl(
private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1) private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
override val tableLogBuffer: TableLogBuffer = mobileLogger
/** /**
* This flow defines the single shared connection to system_server via TelephonyCallback. Any * This flow defines the single shared connection to system_server via TelephonyCallback. Any
* new callback should be added to this listener and funneled through callbackEvents via a data * new callback should be added to this listener and funneled through callbackEvents via a data
@@ -243,11 +239,6 @@ class MobileConnectionRepositoryImpl(
val initial = MobileConnectionModel() val initial = MobileConnectionModel()
callbackEvents callbackEvents
.scan(initial, ::updateConnectionState) .scan(initial, ::updateConnectionState)
.logDiffsForTable(
mobileLogger,
columnPrefix = "MobileConnection ($subId)",
initialValue = initial,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), initial) .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
} }
@@ -285,24 +276,12 @@ 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 = run { override val dataEnabled = run {
val initial = telephonyManager.isDataConnectionAllowed val initial = telephonyManager.isDataConnectionAllowed
callbackEvents callbackEvents
.mapNotNull { (it as? CallbackEvent.OnDataEnabledChanged)?.enabled } .mapNotNull { (it as? CallbackEvent.OnDataEnabledChanged)?.enabled }
.logDiffsForTable(
mobileLogger,
columnPrefix = "",
columnName = "dataEnabled",
initialValue = initial
)
.stateIn(scope, SharingStarted.WhileSubscribed(), initial) .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
} }

View File

@@ -42,6 +42,9 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
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.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
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.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -82,6 +85,7 @@ constructor(
private val subscriptionManager: SubscriptionManager, private val subscriptionManager: SubscriptionManager,
private val telephonyManager: TelephonyManager, private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger, private val logger: ConnectivityPipelineLogger,
@MobileSummaryLog private val tableLogger: TableLogBuffer,
mobileMappingsProxy: MobileMappingsProxy, mobileMappingsProxy: MobileMappingsProxy,
broadcastDispatcher: BroadcastDispatcher, broadcastDispatcher: BroadcastDispatcher,
private val context: Context, private val context: Context,
@@ -114,6 +118,12 @@ constructor(
} }
} }
.distinctUntilChanged() .distinctUntilChanged()
.logDiffsForTable(
tableLogger,
LOGGING_PREFIX,
columnName = "carrierMergedSubId",
initialValue = null,
)
.stateIn(scope, started = SharingStarted.WhileSubscribed(), null) .stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
private val mobileSubscriptionsChangeEvent: Flow<Unit> = conflatedCallbackFlow { private val mobileSubscriptionsChangeEvent: Flow<Unit> = conflatedCallbackFlow {
@@ -139,8 +149,14 @@ constructor(
override val subscriptions: StateFlow<List<SubscriptionModel>> = override val subscriptions: StateFlow<List<SubscriptionModel>> =
merge(mobileSubscriptionsChangeEvent, carrierMergedSubId) merge(mobileSubscriptionsChangeEvent, carrierMergedSubId)
.mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } } .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
.logInputChange(logger, "onSubscriptionsChanged")
.onEach { infos -> updateRepos(infos) } .onEach { infos -> updateRepos(infos) }
.distinctUntilChanged()
.logDiffsForTable(
tableLogger,
LOGGING_PREFIX,
columnName = "subscriptions",
initialValue = listOf(),
)
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf()) .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
/** StateFlow that keeps track of the current active mobile data subscription */ /** StateFlow that keeps track of the current active mobile data subscription */
@@ -157,7 +173,12 @@ constructor(
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
} }
.distinctUntilChanged() .distinctUntilChanged()
.logInputChange(logger, "onActiveDataSubscriptionIdChanged") .logDiffsForTable(
tableLogger,
LOGGING_PREFIX,
columnName = "activeSubId",
initialValue = INVALID_SUBSCRIPTION_ID,
)
.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> =
@@ -171,7 +192,12 @@ 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") .logDiffsForTable(
tableLogger,
LOGGING_PREFIX,
columnName = "defaultSubId",
initialValue = SubscriptionManager.getDefaultDataSubscriptionId(),
)
.onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) } .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
.stateIn( .stateIn(
scope, scope,
@@ -247,7 +273,11 @@ constructor(
awaitClose { connectivityManager.unregisterNetworkCallback(callback) } awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
} }
.distinctUntilChanged() .distinctUntilChanged()
.logInputChange(logger, "defaultMobileNetworkConnectivity") .logDiffsForTable(
tableLogger,
columnPrefix = "$LOGGING_PREFIX.defaultConnection",
initialValue = MobileConnectivityModel(),
)
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel()) .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
/** /**
@@ -321,4 +351,8 @@ constructor(
subscriptionId = subscriptionId, subscriptionId = subscriptionId,
isOpportunistic = isOpportunistic, isOpportunistic = isOpportunistic,
) )
companion object {
private const val LOGGING_PREFIX = "Repo"
}
} }

View File

@@ -33,7 +33,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
interface MobileIconInteractor { interface MobileIconInteractor {
@@ -109,6 +111,9 @@ interface MobileIconInteractor {
/** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */ /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
val numberOfLevels: StateFlow<Int> val numberOfLevels: StateFlow<Int>
/** See [MobileIconsInteractor.isForceHidden]. */
val isForceHidden: Flow<Boolean>
} }
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */ /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
@@ -124,6 +129,7 @@ class MobileIconInteractorImpl(
defaultMobileIconGroup: StateFlow<MobileIconGroup>, defaultMobileIconGroup: StateFlow<MobileIconGroup>,
defaultDataSubId: StateFlow<Int>, defaultDataSubId: StateFlow<Int>,
override val isDefaultConnectionFailed: StateFlow<Boolean>, override val isDefaultConnectionFailed: StateFlow<Boolean>,
override val isForceHidden: Flow<Boolean>,
connectionRepository: MobileConnectionRepository, connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor { ) : MobileIconInteractor {
private val connectionInfo = connectionRepository.connectionInfo private val connectionInfo = connectionRepository.connectionInfo
@@ -181,6 +187,16 @@ class MobileIconInteractorImpl(
else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
} }
} }
.distinctUntilChanged()
.onEach {
// Doesn't use [logDiffsForTable] because [MobileIconGroup] can't implement the
// [Diffable] interface.
tableLogBuffer.logChange(
prefix = "",
columnName = "networkTypeIcon",
value = it.name
)
}
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value) .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
override val isEmergencyOnly: StateFlow<Boolean> = override val isEmergencyOnly: StateFlow<Boolean> =

View File

@@ -23,12 +23,17 @@ import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons
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.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
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.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
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.data.repository.UserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
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.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -42,8 +47,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
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 import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.transformLatest
@@ -88,6 +93,10 @@ interface MobileIconsInteractor {
val isDefaultConnectionFailed: StateFlow<Boolean> val isDefaultConnectionFailed: StateFlow<Boolean>
/** True once the user has been set up */ /** True once the user has been set up */
val isUserSetup: StateFlow<Boolean> val isUserSetup: StateFlow<Boolean>
/** True if we're configured to force-hide the mobile icons and false otherwise. */
val isForceHidden: Flow<Boolean>
/** /**
* Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
* subId. Will throw if the ID is invalid * subId. Will throw if the ID is invalid
@@ -104,6 +113,8 @@ constructor(
private val mobileConnectionsRepo: MobileConnectionsRepository, private val mobileConnectionsRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker, private val carrierConfigTracker: CarrierConfigTracker,
private val logger: ConnectivityPipelineLogger, private val logger: ConnectivityPipelineLogger,
@MobileSummaryLog private val tableLogger: TableLogBuffer,
connectivityRepository: ConnectivityRepository,
userSetupRepo: UserSetupRepository, userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope,
) : MobileIconsInteractor { ) : MobileIconsInteractor {
@@ -173,7 +184,13 @@ constructor(
} }
} }
.distinctUntilChanged() .distinctUntilChanged()
.onEach { logger.logFilteredSubscriptionsChanged(it) } .logDiffsForTable(
tableLogger,
LOGGING_PREFIX,
columnName = "filteredSubscriptions",
initialValue = listOf(),
)
.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
@@ -195,6 +212,12 @@ constructor(
delay(2000) delay(2000)
emit(false) emit(false)
} }
.logDiffsForTable(
tableLogger,
LOGGING_PREFIX,
columnName = "forcingValidation",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> = override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
@@ -211,6 +234,12 @@ constructor(
networkConnectivity networkConnectivity
} }
} }
.distinctUntilChanged()
.logDiffsForTable(
tableLogger,
columnPrefix = "$LOGGING_PREFIX.defaultConnection",
initialValue = mobileConnectionsRepo.defaultMobileNetworkConnectivity.value,
)
.stateIn( .stateIn(
scope, scope,
SharingStarted.WhileSubscribed(), SharingStarted.WhileSubscribed(),
@@ -259,10 +288,21 @@ constructor(
!connectivityModel.isValidated !connectivityModel.isValidated
} }
} }
.logDiffsForTable(
tableLogger,
LOGGING_PREFIX,
columnName = "isDefaultConnectionFailed",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false) .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
override val isForceHidden: Flow<Boolean> =
connectivityRepository.forceHiddenSlots
.map { it.contains(ConnectivitySlot.MOBILE) }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
/** Vends out new [MobileIconInteractor] for a particular subId */ /** Vends out new [MobileIconInteractor] for a particular subId */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
MobileIconInteractorImpl( MobileIconInteractorImpl(
@@ -275,6 +315,11 @@ constructor(
defaultMobileIconGroup, defaultMobileIconGroup,
defaultDataSubId, defaultDataSubId,
isDefaultConnectionFailed, isDefaultConnectionFailed,
isForceHidden,
mobileConnectionsRepo.getRepoForSubId(subId), mobileConnectionsRepo.getRepoForSubId(subId),
) )
companion object {
private const val LOGGING_PREFIX = "Intr"
}
} }

View File

@@ -91,10 +91,17 @@ object MobileIconBinder {
} }
} }
launch { viewModel.isVisible.collect { isVisible -> view.isVisible = isVisible } }
// Set the icon for the triangle // Set the icon for the triangle
launch { launch {
viewModel.iconId.distinctUntilChanged().collect { iconId -> viewModel.icon.distinctUntilChanged().collect { icon ->
mobileDrawable.level = iconId mobileDrawable.level =
SignalDrawable.getState(
icon.level,
icon.numberOfLevels,
icon.showExclamationMark,
)
} }
} }
@@ -148,8 +155,7 @@ object MobileIconBinder {
return object : ModernStatusBarViewBinding { return object : ModernStatusBarViewBinding {
override fun getShouldIconBeVisible(): Boolean { override fun getShouldIconBeVisible(): Boolean {
// If this view model exists, then the icon should be visible. return viewModel.isVisible.value
return true
} }
override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) { override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 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.model
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
/** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */
data class SignalIconModel(
val level: Int,
val numberOfLevels: Int,
val showExclamationMark: Boolean,
) : Diffable<SignalIconModel> {
// TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) {
if (prevVal.level != level) {
row.logChange(COL_LEVEL, level)
}
if (prevVal.numberOfLevels != numberOfLevels) {
row.logChange(COL_NUM_LEVELS, numberOfLevels)
}
if (prevVal.showExclamationMark != showExclamationMark) {
row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
}
}
override fun logFull(row: TableRowLogger) {
row.logChange(COL_LEVEL, level)
row.logChange(COL_NUM_LEVELS, numberOfLevels)
row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
}
companion object {
/** Creates a [SignalIconModel] representing an empty and invalidated state. */
fun createEmptyState(numberOfLevels: Int) =
SignalIconModel(level = 0, numberOfLevels, showExclamationMark = true)
private const val COL_LEVEL = "level"
private const val COL_NUM_LEVELS = "numLevels"
private const val COL_SHOW_EXCLAMATION = "showExclamation"
}
}

View File

@@ -22,10 +22,11 @@ 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.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
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.mobile.ui.model.SignalIconModel
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.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,14 +38,14 @@ 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 import kotlinx.coroutines.flow.stateIn
/** Common interface for all of the location-based mobile icon view models. */ /** Common interface for all of the location-based mobile icon view models. */
interface MobileIconViewModelCommon { interface MobileIconViewModelCommon {
val subscriptionId: Int val subscriptionId: Int
/** An int consumable by [SignalDrawable] for display */ /** True if this view should be visible at all. */
val iconId: Flow<Int> val isVisible: StateFlow<Boolean>
val icon: Flow<SignalIconModel>
val contentDescription: Flow<ContentDescription> val contentDescription: Flow<ContentDescription>
val roaming: Flow<Boolean> val roaming: Flow<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
@@ -73,7 +74,7 @@ class MobileIconViewModel
constructor( constructor(
override val subscriptionId: Int, override val subscriptionId: Int,
iconInteractor: MobileIconInteractor, iconInteractor: MobileIconInteractor,
logger: ConnectivityPipelineLogger, airplaneModeInteractor: AirplaneModeInteractor,
constants: ConnectivityConstants, constants: ConnectivityConstants,
scope: CoroutineScope, scope: CoroutineScope,
) : MobileIconViewModelCommon { ) : MobileIconViewModelCommon {
@@ -81,8 +82,28 @@ constructor(
private val showExclamationMark: Flow<Boolean> = private val showExclamationMark: Flow<Boolean> =
iconInteractor.isDefaultDataEnabled.mapLatest { !it } iconInteractor.isDefaultDataEnabled.mapLatest { !it }
override val iconId: Flow<Int> = run { override val isVisible: StateFlow<Boolean> =
val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value) if (!constants.hasDataCapabilities) {
flowOf(false)
} else {
combine(
airplaneModeInteractor.isAirplaneMode,
iconInteractor.isForceHidden,
) { isAirplaneMode, isForceHidden ->
!isAirplaneMode && !isForceHidden
}
}
.distinctUntilChanged()
.logDiffsForTable(
iconInteractor.tableLogBuffer,
columnPrefix = "",
columnName = "visible",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val icon: Flow<SignalIconModel> = run {
val initial = SignalIconModel.createEmptyState(iconInteractor.numberOfLevels.value)
combine( combine(
iconInteractor.level, iconInteractor.level,
iconInteractor.numberOfLevels, iconInteractor.numberOfLevels,
@@ -90,16 +111,15 @@ constructor(
iconInteractor.isInService, iconInteractor.isInService,
) { level, numberOfLevels, showExclamationMark, isInService -> ) { level, numberOfLevels, showExclamationMark, isInService ->
if (!isInService) { if (!isInService) {
SignalDrawable.getEmptyState(numberOfLevels) SignalIconModel.createEmptyState(numberOfLevels)
} else { } else {
SignalDrawable.getState(level, numberOfLevels, showExclamationMark) SignalIconModel(level, numberOfLevels, showExclamationMark)
} }
} }
.distinctUntilChanged() .distinctUntilChanged()
.logDiffsForTable( .logDiffsForTable(
iconInteractor.tableLogBuffer, iconInteractor.tableLogBuffer,
columnPrefix = "", columnPrefix = "icon",
columnName = "iconId",
initialValue = initial, initialValue = initial,
) )
.stateIn(scope, SharingStarted.WhileSubscribed(), initial) .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
@@ -132,6 +152,14 @@ constructor(
) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected -> ) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected ->
alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected) alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected)
} }
.distinctUntilChanged()
.logDiffsForTable(
iconInteractor.tableLogBuffer,
columnPrefix = "",
columnName = "showNetworkTypeIcon",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val networkTypeIcon: Flow<Icon?> = override val networkTypeIcon: Flow<Icon?> =
combine( combine(
@@ -149,14 +177,6 @@ constructor(
} }
} }
.distinctUntilChanged() .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) .stateIn(scope, SharingStarted.WhileSubscribed(), null)
override val roaming: StateFlow<Boolean> = override val roaming: StateFlow<Boolean> =

View File

@@ -17,9 +17,11 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
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.StatusBarLocation import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
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
@@ -39,6 +41,7 @@ class MobileIconsViewModel
constructor( constructor(
val subscriptionIdsFlow: StateFlow<List<Int>>, val subscriptionIdsFlow: StateFlow<List<Int>>,
private val interactor: MobileIconsInteractor, private val interactor: MobileIconsInteractor,
private val airplaneModeInteractor: AirplaneModeInteractor,
private val logger: ConnectivityPipelineLogger, private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants, private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope,
@@ -56,7 +59,7 @@ constructor(
?: MobileIconViewModel( ?: MobileIconViewModel(
subId, subId,
interactor.createMobileConnectionInteractorForSubId(subId), interactor.createMobileConnectionInteractorForSubId(subId),
logger, airplaneModeInteractor,
constants, constants,
scope, scope,
) )
@@ -74,10 +77,12 @@ constructor(
subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) } subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
} }
@SysUISingleton
class Factory class Factory
@Inject @Inject
constructor( constructor(
private val interactor: MobileIconsInteractor, private val interactor: MobileIconsInteractor,
private val airplaneModeInteractor: AirplaneModeInteractor,
private val logger: ConnectivityPipelineLogger, private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants, private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope,
@@ -87,6 +92,7 @@ constructor(
return MobileIconsViewModel( return MobileIconsViewModel(
subscriptionIdsFlow, subscriptionIdsFlow,
interactor, interactor,
airplaneModeInteractor,
logger, logger,
constants, constants,
scope, scope,

View File

@@ -204,15 +204,6 @@ constructor(
// TODO(b/238425913): We should split this class into mobile-specific and wifi-specific loggers. // TODO(b/238425913): We should split this class into mobile-specific and wifi-specific loggers.
fun logFilteredSubscriptionsChanged(subs: List<SubscriptionModel>) {
buffer.log(
SB_LOGGING_TAG,
LogLevel.INFO,
{ str1 = subs.toString() },
{ "Filtered subscriptions updated: $str1" },
)
}
fun logUiAdapterSubIdsUpdated(subs: List<Int>) { fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
buffer.log( buffer.log(
SB_LOGGING_TAG, SB_LOGGING_TAG,

View File

@@ -275,6 +275,52 @@ class LogDiffsForTableTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
@Test
fun intNullable_logsNull() =
testScope.runTest {
systemClock.setCurrentTimeMillis(100L)
val flow = flow {
for (int in listOf(null, 6, null, 8)) {
systemClock.advanceTime(100L)
emit(int)
}
}
val flowWithLogging =
flow.logDiffsForTable(
tableLogBuffer,
COLUMN_PREFIX,
COLUMN_NAME,
initialValue = 1234,
)
val job = launch { flowWithLogging.collect() }
val logs = dumpLog()
assertThat(logs)
.contains(
TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "1234"
)
assertThat(logs)
.contains(
TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "null"
)
assertThat(logs)
.contains(
TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "6"
)
assertThat(logs)
.contains(
TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "null"
)
assertThat(logs)
.contains(
TABLE_LOG_DATE_FORMAT.format(500L) + SEPARATOR + FULL_NAME + SEPARATOR + "8"
)
job.cancel()
}
@Test @Test
fun int_logsUpdates() = fun int_logsUpdates() =
testScope.runTest { testScope.runTest {
@@ -1030,6 +1076,246 @@ class LogDiffsForTableTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
// ---- Flow<List<T>> tests ----
@Test
fun list_doesNotLogWhenNotCollected() {
val flow = flowOf(listOf(5), listOf(6), listOf(7))
flow.logDiffsForTable(
tableLogBuffer,
COLUMN_PREFIX,
COLUMN_NAME,
initialValue = listOf(1234),
)
val logs = dumpLog()
assertThat(logs).doesNotContain(COLUMN_PREFIX)
assertThat(logs).doesNotContain(COLUMN_NAME)
assertThat(logs).doesNotContain("1234")
}
@Test
fun list_logsInitialWhenCollected() =
testScope.runTest {
val flow = flowOf(listOf(5), listOf(6), listOf(7))
val flowWithLogging =
flow.logDiffsForTable(
tableLogBuffer,
COLUMN_PREFIX,
COLUMN_NAME,
initialValue = listOf(1234),
)
systemClock.setCurrentTimeMillis(3000L)
val job = launch { flowWithLogging.collect() }
val logs = dumpLog()
assertThat(logs)
.contains(
TABLE_LOG_DATE_FORMAT.format(3000L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf(1234).toString()
)
job.cancel()
}
@Test
fun list_logsUpdates() =
testScope.runTest {
systemClock.setCurrentTimeMillis(100L)
val listItems =
listOf(listOf("val1", "val2"), listOf("val3"), listOf("val4", "val5", "val6"))
val flow = flow {
for (list in listItems) {
systemClock.advanceTime(100L)
emit(list)
}
}
val flowWithLogging =
flow.logDiffsForTable(
tableLogBuffer,
COLUMN_PREFIX,
COLUMN_NAME,
initialValue = listOf("val0", "val00"),
)
val job = launch { flowWithLogging.collect() }
val logs = dumpLog()
assertThat(logs)
.contains(
TABLE_LOG_DATE_FORMAT.format(100L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf("val0", "val00").toString()
)
assertThat(logs)
.contains(
TABLE_LOG_DATE_FORMAT.format(200L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf("val1", "val2").toString()
)
assertThat(logs)
.contains(
TABLE_LOG_DATE_FORMAT.format(300L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf("val3").toString()
)
assertThat(logs)
.contains(
TABLE_LOG_DATE_FORMAT.format(400L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf("val4", "val5", "val6").toString()
)
job.cancel()
}
@Test
fun list_doesNotLogIfSameValue() =
testScope.runTest {
systemClock.setCurrentTimeMillis(100L)
val listItems =
listOf(
listOf("val0", "val00"),
listOf("val1"),
listOf("val1"),
listOf("val1", "val2"),
)
val flow = flow {
for (bool in listItems) {
systemClock.advanceTime(100L)
emit(bool)
}
}
val flowWithLogging =
flow.logDiffsForTable(
tableLogBuffer,
COLUMN_PREFIX,
COLUMN_NAME,
initialValue = listOf("val0", "val00"),
)
val job = launch { flowWithLogging.collect() }
val logs = dumpLog()
val expected1 =
TABLE_LOG_DATE_FORMAT.format(100L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf("val0", "val00").toString()
val expected3 =
TABLE_LOG_DATE_FORMAT.format(300L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf("val1").toString()
val expected5 =
TABLE_LOG_DATE_FORMAT.format(500L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf("val1", "val2").toString()
assertThat(logs).contains(expected1)
assertThat(logs).contains(expected3)
assertThat(logs).contains(expected5)
val unexpected2 =
TABLE_LOG_DATE_FORMAT.format(200L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf("val0", "val00")
val unexpected4 =
TABLE_LOG_DATE_FORMAT.format(400L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf("val1")
assertThat(logs).doesNotContain(unexpected2)
assertThat(logs).doesNotContain(unexpected4)
job.cancel()
}
@Test
fun list_worksForStateFlows() =
testScope.runTest {
val flow = MutableStateFlow(listOf(1111))
val flowWithLogging =
flow.logDiffsForTable(
tableLogBuffer,
COLUMN_PREFIX,
COLUMN_NAME,
initialValue = listOf(1111),
)
systemClock.setCurrentTimeMillis(50L)
val job = launch { flowWithLogging.collect() }
assertThat(dumpLog())
.contains(
TABLE_LOG_DATE_FORMAT.format(50L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf(1111).toString()
)
systemClock.setCurrentTimeMillis(100L)
flow.emit(listOf(2222, 3333))
assertThat(dumpLog())
.contains(
TABLE_LOG_DATE_FORMAT.format(100L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf(2222, 3333).toString()
)
systemClock.setCurrentTimeMillis(200L)
flow.emit(listOf(3333, 4444))
assertThat(dumpLog())
.contains(
TABLE_LOG_DATE_FORMAT.format(200L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf(3333, 4444).toString()
)
// Doesn't log duplicates
systemClock.setCurrentTimeMillis(300L)
flow.emit(listOf(3333, 4444))
assertThat(dumpLog())
.doesNotContain(
TABLE_LOG_DATE_FORMAT.format(300L) +
SEPARATOR +
FULL_NAME +
SEPARATOR +
listOf(3333, 4444).toString()
)
job.cancel()
}
private fun dumpLog(): String { private fun dumpLog(): String {
val outputWriter = StringWriter() val outputWriter = StringWriter()
tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf()) tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf())

View File

@@ -35,6 +35,17 @@ class TableChangeTest : SysuiTestCase() {
assertThat(underTest.getVal()).isEqualTo("fakeValue") assertThat(underTest.getVal()).isEqualTo("fakeValue")
} }
@Test
fun setString_null() {
val underTest = TableChange()
underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
underTest.set(null as String?)
assertThat(underTest.hasData()).isTrue()
assertThat(underTest.getVal()).isEqualTo("null")
}
@Test @Test
fun setBoolean_isBoolean() { fun setBoolean_isBoolean() {
val underTest = TableChange() val underTest = TableChange()
@@ -57,6 +68,17 @@ class TableChangeTest : SysuiTestCase() {
assertThat(underTest.getVal()).isEqualTo("8900") assertThat(underTest.getVal()).isEqualTo("8900")
} }
@Test
fun setInt_null() {
val underTest = TableChange()
underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
underTest.set(null as Int?)
assertThat(underTest.hasData()).isTrue()
assertThat(underTest.getVal()).isEqualTo("null")
}
@Test @Test
fun setThenReset_isEmpty() { fun setThenReset_isEmpty() {
val underTest = TableChange() val underTest = TableChange()

View File

@@ -25,6 +25,7 @@ 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.dump.DumpManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory 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
@@ -81,6 +82,7 @@ class MobileRepositorySwitcherTest : 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 summaryLogger: TableLogBuffer
@Mock private lateinit var demoModeController: DemoModeController @Mock private lateinit var demoModeController: DemoModeController
@Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var dumpManager: DumpManager
@@ -114,6 +116,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
subscriptionManager, subscriptionManager,
telephonyManager, telephonyManager,
logger, logger,
summaryLogger,
mobileMappings, mobileMappings,
fakeBroadcastDispatcher, fakeBroadcastDispatcher,
context, context,

View File

@@ -16,20 +16,32 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
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.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.log.table.TableLogBufferFactory
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.MobileConnectionModel.Companion.COL_EMERGENCY
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.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
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.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq 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.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -52,9 +64,10 @@ import org.mockito.Mockito.verify
class FullMobileConnectionRepositoryTest : SysuiTestCase() { class FullMobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: FullMobileConnectionRepository private lateinit var underTest: FullMobileConnectionRepository
private val systemClock = FakeSystemClock()
private val testDispatcher = UnconfinedTestDispatcher() private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher) private val testScope = TestScope(testDispatcher)
private val tableLogBuffer = mock<TableLogBuffer>() private val tableLogBuffer = TableLogBuffer(maxSize = 100, name = "TestName", systemClock)
private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>() private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>() private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
@@ -347,8 +360,214 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
.isSameInstanceAs(connection1Repeat.tableLogBuffer) .isSameInstanceAs(connection1Repeat.tableLogBuffer)
} }
// TODO(b/238425913): Verify that the logging switches correctly (once the carrier merged repo @Test
// implements logging). fun connectionInfo_logging_notCarrierMerged_getsUpdates() =
testScope.runTest {
// SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
val telephonyManager = mock<TelephonyManager>()
createRealMobileRepo(telephonyManager)
createRealCarrierMergedRepo(FakeWifiRepository())
initializeRepo(startingIsCarrierMerged = false)
val job = underTest.connectionInfo.launchIn(this)
// WHEN we set up some mobile connection info
val serviceState = ServiceState()
serviceState.setOperatorName("longName", "OpTypical", "1")
serviceState.isEmergencyOnly = false
getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
.onServiceStateChanged(serviceState)
// THEN it's logged to the buffer
assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpTypical")
assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}false")
// WHEN we update mobile connection info
val serviceState2 = ServiceState()
serviceState2.setOperatorName("longName", "OpDiff", "1")
serviceState2.isEmergencyOnly = true
getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
.onServiceStateChanged(serviceState2)
// THEN the updates are logged
assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpDiff")
assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}true")
job.cancel()
}
@Test
fun connectionInfo_logging_carrierMerged_getsUpdates() =
testScope.runTest {
// SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
createRealMobileRepo(mock())
val wifiRepository = FakeWifiRepository()
createRealCarrierMergedRepo(wifiRepository)
initializeRepo(startingIsCarrierMerged = true)
val job = underTest.connectionInfo.launchIn(this)
// WHEN we set up carrier merged info
val networkId = 2
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
networkId,
SUB_ID,
level = 3,
)
)
// THEN the carrier merged info is logged
assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
// WHEN we update the info
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
networkId,
SUB_ID,
level = 1,
)
)
// THEN the updates are logged
assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
job.cancel()
}
@Test
fun connectionInfo_logging_updatesWhenCarrierMergedUpdates() =
testScope.runTest {
// SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
val telephonyManager = mock<TelephonyManager>()
createRealMobileRepo(telephonyManager)
val wifiRepository = FakeWifiRepository()
createRealCarrierMergedRepo(wifiRepository)
initializeRepo(startingIsCarrierMerged = false)
val job = underTest.connectionInfo.launchIn(this)
// WHEN we set up some mobile connection info
val signalStrength = mock<SignalStrength>()
whenever(signalStrength.level).thenReturn(1)
getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
.onSignalStrengthsChanged(signalStrength)
// THEN it's logged to the buffer
assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
// WHEN isCarrierMerged is set to true
val networkId = 2
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
networkId,
SUB_ID,
level = 3,
)
)
underTest.setIsCarrierMerged(true)
// THEN the carrier merged info is logged
assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
// WHEN the carrier merge network is updated
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
networkId,
SUB_ID,
level = 4,
)
)
// THEN the new level is logged
assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
// WHEN isCarrierMerged is set to false
underTest.setIsCarrierMerged(false)
// THEN the typical info is logged
// Note: Since our first logs also had the typical info, we need to search the log
// contents for after our carrier merged level log.
val fullBuffer = dumpBuffer()
val carrierMergedContentIndex = fullBuffer.indexOf("${BUFFER_SEPARATOR}4")
val bufferAfterCarrierMerged = fullBuffer.substring(carrierMergedContentIndex)
assertThat(bufferAfterCarrierMerged).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
// WHEN the normal network is updated
val newMobileInfo =
MobileConnectionModel(
operatorAlphaShort = "Mobile Operator 2",
primaryLevel = 0,
)
mobileRepo.setConnectionInfo(newMobileInfo)
// THEN the new level is logged
assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}0")
job.cancel()
}
@Test
fun connectionInfo_logging_doesNotLogUpdatesForNotActiveRepo() =
testScope.runTest {
// SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
val telephonyManager = mock<TelephonyManager>()
createRealMobileRepo(telephonyManager)
val wifiRepository = FakeWifiRepository()
createRealCarrierMergedRepo(wifiRepository)
// WHEN isCarrierMerged = false
initializeRepo(startingIsCarrierMerged = false)
val job = underTest.connectionInfo.launchIn(this)
val signalStrength = mock<SignalStrength>()
whenever(signalStrength.level).thenReturn(1)
getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
.onSignalStrengthsChanged(signalStrength)
// THEN updates to the carrier merged level aren't logged
val networkId = 2
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
networkId,
SUB_ID,
level = 4,
)
)
assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
networkId,
SUB_ID,
level = 3,
)
)
assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
// WHEN isCarrierMerged is set to true
underTest.setIsCarrierMerged(true)
// THEN updates to the normal level aren't logged
whenever(signalStrength.level).thenReturn(5)
getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
.onSignalStrengthsChanged(signalStrength)
assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}5")
whenever(signalStrength.level).thenReturn(6)
getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
.onSignalStrengthsChanged(signalStrength)
assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}6")
job.cancel()
}
private fun initializeRepo(startingIsCarrierMerged: Boolean) { private fun initializeRepo(startingIsCarrierMerged: Boolean) {
underTest = underTest =
@@ -364,9 +583,68 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
) )
} }
private fun createRealMobileRepo(
telephonyManager: TelephonyManager,
): MobileConnectionRepositoryImpl {
whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID)
val realRepo =
MobileConnectionRepositoryImpl(
context,
SUB_ID,
defaultNetworkName = NetworkNameModel.Default("default"),
networkNameSeparator = SEP,
telephonyManager,
systemUiCarrierConfig = mock(),
fakeBroadcastDispatcher,
mobileMappingsProxy = mock(),
testDispatcher,
logger = mock(),
tableLogBuffer,
testScope.backgroundScope,
)
whenever(
mobileFactory.build(
eq(SUB_ID),
any(),
eq(DEFAULT_NAME),
eq(SEP),
)
)
.thenReturn(realRepo)
return realRepo
}
private fun createRealCarrierMergedRepo(
wifiRepository: FakeWifiRepository,
): CarrierMergedConnectionRepository {
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setIsWifiDefault(true)
val realRepo =
CarrierMergedConnectionRepository(
SUB_ID,
tableLogBuffer,
defaultNetworkName = NetworkNameModel.Default("default"),
testScope.backgroundScope,
wifiRepository,
)
whenever(carrierMergedFactory.build(eq(SUB_ID), any(), eq(DEFAULT_NAME)))
.thenReturn(realRepo)
return realRepo
}
private fun dumpBuffer(): String {
val outputWriter = StringWriter()
tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf())
return outputWriter.toString()
}
private companion object { private companion object {
const val SUB_ID = 42 const val SUB_ID = 42
private val DEFAULT_NAME = NetworkNameModel.Default("default name") private val DEFAULT_NAME = NetworkNameModel.Default("default name")
private const val SEP = "-" private const val SEP = "-"
private const val BUFFER_SEPARATOR = "|"
} }
} }

View File

@@ -71,7 +71,6 @@ 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.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
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.mock import com.android.systemui.util.mockito.mock
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
@@ -86,7 +85,6 @@ import org.junit.After
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.Mockito
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -670,16 +668,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>()
Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
return callbackCaptor.allValues
}
private inline fun <reified T> getTelephonyCallbackForType(): T { private inline fun <reified T> getTelephonyCallbackForType(): T {
val cbs = getTelephonyCallbacks().filterIsInstance<T>() return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
assertThat(cbs.size).isEqualTo(1)
return cbs[0]
} }
/** Convenience constructor for SignalStrength */ /** Convenience constructor for SignalStrength */

View File

@@ -84,6 +84,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 summaryLogger: TableLogBuffer
@Mock private lateinit var logBufferFactory: TableLogBufferFactory @Mock private lateinit var logBufferFactory: TableLogBufferFactory
private val mobileMappings = FakeMobileMappingsProxy() private val mobileMappings = FakeMobileMappingsProxy()
@@ -157,6 +158,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
subscriptionManager, subscriptionManager,
telephonyManager, telephonyManager,
logger, logger,
summaryLogger,
mobileMappings, mobileMappings,
fakeBroadcastDispatcher, fakeBroadcastDispatcher,
context, context,
@@ -616,6 +618,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
subscriptionManager, subscriptionManager,
telephonyManager, telephonyManager,
logger, logger,
summaryLogger,
mobileMappings, mobileMappings,
fakeBroadcastDispatcher, fakeBroadcastDispatcher,
context, context,

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 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.repository.prod
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.google.common.truth.Truth.assertThat
import org.mockito.Mockito.verify
/** Helper methods for telephony-related callbacks for mobile tests. */
object MobileTelephonyHelpers {
fun getTelephonyCallbacks(mockTelephonyManager: TelephonyManager): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>()
verify(mockTelephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
return callbackCaptor.allValues
}
inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
assertThat(cbs.size).isEqualTo(1)
return cbs[0]
}
}

View File

@@ -71,6 +71,8 @@ class FakeMobileIconInteractor(
private val _numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS) private val _numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
override val numberOfLevels = _numberOfLevels override val numberOfLevels = _numberOfLevels
override val isForceHidden = MutableStateFlow(false)
fun setIconGroup(group: SignalIcon.MobileIconGroup) { fun setIconGroup(group: SignalIcon.MobileIconGroup) {
_iconGroup.value = group _iconGroup.value = group
} }

View File

@@ -73,6 +73,8 @@ class FakeMobileIconsInteractor(
private val _isUserSetup = MutableStateFlow(true) private val _isUserSetup = MutableStateFlow(true)
override val isUserSetup = _isUserSetup override val isUserSetup = _isUserSetup
override val isForceHidden = MutableStateFlow(false)
/** 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(tableLogBuffer) return FakeMobileIconInteractor(tableLogBuffer)

View File

@@ -66,6 +66,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
mobileIconsInteractor.defaultMobileIconGroup, mobileIconsInteractor.defaultMobileIconGroup,
mobileIconsInteractor.defaultDataSubId, mobileIconsInteractor.defaultDataSubId,
mobileIconsInteractor.isDefaultConnectionFailed, mobileIconsInteractor.isDefaultConnectionFailed,
mobileIconsInteractor.isForceHidden,
connectionRepository, connectionRepository,
) )
} }
@@ -550,6 +551,21 @@ class MobileIconInteractorTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
@Test
fun isForceHidden_matchesParent() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
mobileIconsInteractor.isForceHidden.value = true
assertThat(latest).isTrue()
mobileIconsInteractor.isForceHidden.value = false
assertThat(latest).isFalse()
job.cancel()
}
companion object { companion object {
private val IMMEDIATE = Dispatchers.Main.immediate private val IMMEDIATE = Dispatchers.Main.immediate

View File

@@ -27,6 +27,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobile
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
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.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.CarrierConfigTracker
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
@@ -50,6 +52,7 @@ 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 connectivityRepository: FakeConnectivityRepository
private lateinit var connectionsRepository: FakeMobileConnectionsRepository private lateinit var connectionsRepository: FakeMobileConnectionsRepository
private val userSetupRepository = FakeUserSetupRepository() private val userSetupRepository = FakeUserSetupRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy() private val mobileMappingsProxy = FakeMobileMappingsProxy()
@@ -63,6 +66,8 @@ class MobileIconsInteractorTest : SysuiTestCase() {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
connectivityRepository = FakeConnectivityRepository()
connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer) connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
connectionsRepository.setMobileConnectionRepositoryMap( connectionsRepository.setMobileConnectionRepositoryMap(
mapOf( mapOf(
@@ -79,6 +84,8 @@ class MobileIconsInteractorTest : SysuiTestCase() {
connectionsRepository, connectionsRepository,
carrierConfigTracker, carrierConfigTracker,
logger = mock(), logger = mock(),
tableLogger = mock(),
connectivityRepository,
userSetupRepository, userSetupRepository,
testScope.backgroundScope, testScope.backgroundScope,
) )
@@ -609,6 +616,32 @@ class MobileIconsInteractorTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
@Test
fun isForceHidden_repoHasMobileHidden_true() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
assertThat(latest).isFalse()
job.cancel()
}
companion object { companion object {
private val tableLogBuffer = private val tableLogBuffer =
TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock()) TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())

View File

@@ -29,12 +29,14 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
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.mobile.ui.viewmodel.LocationBasedMobileViewModel import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
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.data.repository.FakeConnectivityRepository
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.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -58,31 +60,37 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var tableLogBuffer: TableLogBuffer @Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants @Mock private lateinit var constants: ConnectivityConstants
private lateinit var interactor: FakeMobileIconInteractor
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
private lateinit var viewModelCommon: MobileIconViewModel
private lateinit var viewModel: LocationBasedMobileViewModel private lateinit var viewModel: LocationBasedMobileViewModel
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
// This line was necessary to make the onDarkChanged and setStaticDrawableColor tests pass.
// But, it maybe *shouldn't* be necessary.
whenever(constants.hasDataCapabilities).thenReturn(true)
testableLooper = TestableLooper.get(this) testableLooper = TestableLooper.get(this)
val interactor = FakeMobileIconInteractor(tableLogBuffer) airplaneModeRepository = FakeAirplaneModeRepository()
airplaneModeInteractor =
val viewModelCommon = AirplaneModeInteractor(
MobileIconViewModel( airplaneModeRepository,
subscriptionId = 1, FakeConnectivityRepository(),
interactor,
logger,
constants,
testScope.backgroundScope,
) )
viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
interactor = FakeMobileIconInteractor(tableLogBuffer)
createViewModel()
} }
// Note: The following tests are more like integration tests, since they stand up a full // Note: The following tests are more like integration tests, since they stand up a full
// [WifiViewModel] and test the interactions between the view, view-binder, and view-model. // [MobileIconViewModel] and test the interactions between the view, view-binder, and
// view-model.
@Test @Test
fun setVisibleState_icon_iconShownDotHidden() { fun setVisibleState_icon_iconShownDotHidden() {
@@ -130,7 +138,25 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
} }
@Test @Test
fun isIconVisible_alwaysTrue() { fun isIconVisible_noData_outputsFalse() {
whenever(constants.hasDataCapabilities).thenReturn(false)
createViewModel()
val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
assertThat(view.isIconVisible).isFalse()
ViewUtils.detachView(view)
}
@Test
fun isIconVisible_hasData_outputsTrue() {
whenever(constants.hasDataCapabilities).thenReturn(true)
createViewModel()
val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view) ViewUtils.attachView(view)
@@ -141,6 +167,34 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
ViewUtils.detachView(view) ViewUtils.detachView(view)
} }
@Test
fun isIconVisible_notAirplaneMode_outputsTrue() {
airplaneModeRepository.setIsAirplaneMode(false)
val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
assertThat(view.isIconVisible).isTrue()
ViewUtils.detachView(view)
}
@Test
fun isIconVisible_airplaneMode_outputsTrue() {
airplaneModeRepository.setIsAirplaneMode(true)
val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
assertThat(view.isIconVisible).isFalse()
ViewUtils.detachView(view)
}
@Test @Test
fun onDarkChanged_iconHasNewColor() { fun onDarkChanged_iconHasNewColor() {
whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false) whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
@@ -184,6 +238,18 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
private fun View.getDotView(): View { private fun View.getDotView(): View {
return this.requireViewById(R.id.status_bar_dot) return this.requireViewById(R.id.status_bar_dot)
} }
private fun createViewModel() {
viewModelCommon =
MobileIconViewModel(
subscriptionId = 1,
interactor,
airplaneModeInteractor,
constants,
testScope.backgroundScope,
)
viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
}
} }
private const val SLOT_NAME = "TestSlotName" private const val SLOT_NAME = "TestSlotName"

View File

@@ -21,10 +21,13 @@ import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
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.mobile.ui.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal 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.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
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.flow.launchIn import kotlinx.coroutines.flow.launchIn
@@ -46,8 +49,8 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
private lateinit var qsIcon: QsMobileIconViewModel private lateinit var qsIcon: QsMobileIconViewModel
private lateinit var keyguardIcon: KeyguardMobileIconViewModel private lateinit var keyguardIcon: KeyguardMobileIconViewModel
private lateinit var interactor: FakeMobileIconInteractor private lateinit var interactor: FakeMobileIconInteractor
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants @Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer @Mock private lateinit var tableLogBuffer: TableLogBuffer
@@ -57,6 +60,11 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
airplaneModeInteractor =
AirplaneModeInteractor(
FakeAirplaneModeRepository(),
FakeConnectivityRepository(),
)
interactor = FakeMobileIconInteractor(tableLogBuffer) interactor = FakeMobileIconInteractor(tableLogBuffer)
interactor.apply { interactor.apply {
setLevel(1) setLevel(1)
@@ -68,7 +76,13 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
isDataConnected.value = true isDataConnected.value = true
} }
commonImpl = commonImpl =
MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope) MobileIconViewModel(
SUB_1_ID,
interactor,
airplaneModeInteractor,
constants,
testScope.backgroundScope,
)
homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags) homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags) qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
@@ -78,14 +92,14 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun `location based view models receive same icon id when common impl updates`() = fun `location based view models receive same icon id when common impl updates`() =
testScope.runTest { testScope.runTest {
var latestHome: Int? = null var latestHome: SignalIconModel? = null
val homeJob = homeIcon.iconId.onEach { latestHome = it }.launchIn(this) val homeJob = homeIcon.icon.onEach { latestHome = it }.launchIn(this)
var latestQs: Int? = null var latestQs: SignalIconModel? = null
val qsJob = qsIcon.iconId.onEach { latestQs = it }.launchIn(this) val qsJob = qsIcon.icon.onEach { latestQs = it }.launchIn(this)
var latestKeyguard: Int? = null var latestKeyguard: SignalIconModel? = null
val keyguardJob = keyguardIcon.iconId.onEach { latestKeyguard = it }.launchIn(this) val keyguardJob = keyguardIcon.icon.onEach { latestKeyguard = it }.launchIn(this)
var expected = defaultSignal(level = 1) var expected = defaultSignal(level = 1)

View File

@@ -19,16 +19,18 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
import com.android.settingslib.graph.SignalDrawable
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.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.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
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.mobile.ui.model.SignalIconModel
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.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
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.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,7 +51,8 @@ import org.mockito.MockitoAnnotations
class MobileIconViewModelTest : SysuiTestCase() { class MobileIconViewModelTest : SysuiTestCase() {
private lateinit var underTest: MobileIconViewModel private lateinit var underTest: MobileIconViewModel
private lateinit var interactor: FakeMobileIconInteractor private lateinit var interactor: FakeMobileIconInteractor
@Mock private lateinit var logger: ConnectivityPipelineLogger private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var constants: ConnectivityConstants @Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer @Mock private lateinit var tableLogBuffer: TableLogBuffer
@@ -59,6 +62,15 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
whenever(constants.hasDataCapabilities).thenReturn(true)
airplaneModeRepository = FakeAirplaneModeRepository()
airplaneModeInteractor =
AirplaneModeInteractor(
airplaneModeRepository,
FakeConnectivityRepository(),
)
interactor = FakeMobileIconInteractor(tableLogBuffer) interactor = FakeMobileIconInteractor(tableLogBuffer)
interactor.apply { interactor.apply {
setLevel(1) setLevel(1)
@@ -69,15 +81,94 @@ class MobileIconViewModelTest : SysuiTestCase() {
setNumberOfLevels(4) setNumberOfLevels(4)
isDataConnected.value = true isDataConnected.value = true
} }
underTest = createAndSetViewModel()
MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope) }
@Test
fun isVisible_notDataCapable_alwaysFalse() =
testScope.runTest {
// Create a new view model here so the constants are properly read
whenever(constants.hasDataCapabilities).thenReturn(false)
createAndSetViewModel()
var latest: Boolean? = null
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun isVisible_notAirplane_notForceHidden_true() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(false)
interactor.isForceHidden.value = false
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun isVisible_airplane_false() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(true)
interactor.isForceHidden.value = false
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun isVisible_forceHidden_false() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(false)
interactor.isForceHidden.value = true
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun isVisible_respondsToUpdates() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(false)
interactor.isForceHidden.value = false
assertThat(latest).isTrue()
airplaneModeRepository.setIsAirplaneMode(true)
assertThat(latest).isFalse()
airplaneModeRepository.setIsAirplaneMode(false)
assertThat(latest).isTrue()
interactor.isForceHidden.value = true
assertThat(latest).isFalse()
job.cancel()
} }
@Test @Test
fun iconId_correctLevel_notCutout() = fun iconId_correctLevel_notCutout() =
testScope.runTest { testScope.runTest {
var latest: Int? = null var latest: SignalIconModel? = null
val job = underTest.iconId.onEach { latest = it }.launchIn(this) val job = underTest.icon.onEach { latest = it }.launchIn(this)
val expected = defaultSignal() val expected = defaultSignal()
assertThat(latest).isEqualTo(expected) assertThat(latest).isEqualTo(expected)
@@ -90,8 +181,8 @@ class MobileIconViewModelTest : SysuiTestCase() {
testScope.runTest { testScope.runTest {
interactor.setIsDefaultDataEnabled(false) interactor.setIsDefaultDataEnabled(false)
var latest: Int? = null var latest: SignalIconModel? = null
val job = underTest.iconId.onEach { latest = it }.launchIn(this) val job = underTest.icon.onEach { latest = it }.launchIn(this)
val expected = defaultSignal(level = 1, connected = false) val expected = defaultSignal(level = 1, connected = false)
assertThat(latest).isEqualTo(expected) assertThat(latest).isEqualTo(expected)
@@ -102,8 +193,8 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Test @Test
fun `icon - uses empty state - when not in service`() = fun `icon - uses empty state - when not in service`() =
testScope.runTest { testScope.runTest {
var latest: Int? = null var latest: SignalIconModel? = null
val job = underTest.iconId.onEach { latest = it }.launchIn(this) val job = underTest.icon.onEach { latest = it }.launchIn(this)
interactor.isInService.value = false interactor.isInService.value = false
@@ -364,14 +455,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
testScope.runTest { 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 = createAndSetViewModel()
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)
@@ -403,14 +487,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
testScope.runTest { 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 = createAndSetViewModel()
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)
@@ -459,6 +536,16 @@ class MobileIconViewModelTest : SysuiTestCase() {
containerJob.cancel() containerJob.cancel()
} }
private fun createAndSetViewModel() {
underTest = MobileIconViewModel(
SUB_1_ID,
interactor,
airplaneModeInteractor,
constants,
testScope.backgroundScope,
)
}
companion object { companion object {
private const val SUB_1_ID = 1 private const val SUB_1_ID = 1
@@ -466,10 +553,11 @@ class MobileIconViewModelTest : SysuiTestCase() {
fun defaultSignal( fun defaultSignal(
level: Int = 1, level: Int = 1,
connected: Boolean = true, connected: Boolean = true,
): Int { ): SignalIconModel {
return SignalDrawable.getState(level, /* numLevels */ 4, !connected) return SignalIconModel(level, numberOfLevels = 4, showExclamationMark = !connected)
} }
fun emptySignal(): Int = SignalDrawable.getEmptyState(4) fun emptySignal(): SignalIconModel =
SignalIconModel(level = 0, numberOfLevels = 4, showExclamationMark = true)
} }
} }

View File

@@ -20,11 +20,14 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
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.domain.interactor.FakeMobileIconsInteractor 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.mobile.util.FakeMobileMappingsProxy
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.repository.FakeConnectivityRepository
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -46,6 +49,7 @@ class MobileIconsViewModelTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsViewModel private lateinit var underTest: MobileIconsViewModel
private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants @Mock private lateinit var constants: ConnectivityConstants
@@ -57,6 +61,12 @@ class MobileIconsViewModelTest : SysuiTestCase() {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
airplaneModeInteractor =
AirplaneModeInteractor(
FakeAirplaneModeRepository(),
FakeConnectivityRepository(),
)
val subscriptionIdsFlow = val subscriptionIdsFlow =
interactor.filteredSubscriptions interactor.filteredSubscriptions
.map { subs -> subs.map { it.subscriptionId } } .map { subs -> subs.map { it.subscriptionId } }
@@ -66,6 +76,7 @@ class MobileIconsViewModelTest : SysuiTestCase() {
MobileIconsViewModel( MobileIconsViewModel(
subscriptionIdsFlow, subscriptionIdsFlow,
interactor, interactor,
airplaneModeInteractor,
logger, logger,
constants, constants,
testScope.backgroundScope, testScope.backgroundScope,