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:
@@ -79,10 +79,10 @@ fun <T : Diffable<T>> Flow<T>.logDiffsForTable(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Each time the boolean flow is updated with a new value that's different from the previous value,
|
||||
* logs the new value to the given [tableLogBuffer].
|
||||
*/
|
||||
// Here and below: Various Flow<SomeType> extension functions that are effectively equivalent to the
|
||||
// above [logDiffsForTable] method.
|
||||
|
||||
/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
|
||||
fun Flow<Boolean>.logDiffsForTable(
|
||||
tableLogBuffer: TableLogBuffer,
|
||||
columnPrefix: String,
|
||||
@@ -100,10 +100,8 @@ fun Flow<Boolean>.logDiffsForTable(
|
||||
newVal
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Each time the Int flow is updated with a new value that's different from the previous value, logs
|
||||
* the new value to the given [tableLogBuffer].
|
||||
*/
|
||||
|
||||
/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
|
||||
fun Flow<Int>.logDiffsForTable(
|
||||
tableLogBuffer: TableLogBuffer,
|
||||
columnPrefix: String,
|
||||
@@ -122,10 +120,26 @@ fun Flow<Int>.logDiffsForTable(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Each time the String? flow is updated with a new value that's different from the previous value,
|
||||
* logs the new value to the given [tableLogBuffer].
|
||||
*/
|
||||
/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
|
||||
fun Flow<Int?>.logDiffsForTable(
|
||||
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(
|
||||
tableLogBuffer: TableLogBuffer,
|
||||
columnPrefix: String,
|
||||
@@ -143,3 +157,23 @@ fun Flow<String?>.logDiffsForTable(
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ data class TableChange(
|
||||
var columnName: String = "",
|
||||
var type: DataType = DataType.EMPTY,
|
||||
var bool: Boolean = false,
|
||||
var int: Int = 0,
|
||||
var int: Int? = null,
|
||||
var str: String? = null,
|
||||
) {
|
||||
/** 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. */
|
||||
fun set(value: Int) {
|
||||
fun set(value: Int?) {
|
||||
type = DataType.INT
|
||||
int = value
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ class TableLogBuffer(
|
||||
}
|
||||
|
||||
/** 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)
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ class TableLogBuffer(
|
||||
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)
|
||||
change.set(value)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -118,5 +118,12 @@ abstract class StatusBarPipelineModule {
|
||||
fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
|
||||
return factory.create("AirplaneTableLog", 30)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
@MobileSummaryLog
|
||||
fun provideMobileSummaryLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
|
||||
return factory.create("MobileSummaryLog", 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package com.android.systemui.statusbar.pipeline.mobile.data.model
|
||||
|
||||
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 */
|
||||
data class MobileConnectivityModel(
|
||||
@@ -24,4 +26,24 @@ data class MobileConnectivityModel(
|
||||
val isConnected: Boolean = false,
|
||||
/** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,9 +91,6 @@ class CarrierMergedConnectionRepository(
|
||||
.map { it.toMobileConnectionModel() }
|
||||
.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.
|
||||
override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
|
||||
|
||||
|
||||
@@ -117,11 +117,22 @@ class FullMobileConnectionRepository(
|
||||
override val connectionInfo =
|
||||
activeRepo
|
||||
.flatMapLatest { it.connectionInfo }
|
||||
.logDiffsForTable(
|
||||
tableLogBuffer,
|
||||
columnPrefix = "",
|
||||
initialValue = activeRepo.value.connectionInfo.value,
|
||||
)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)
|
||||
|
||||
override val dataEnabled =
|
||||
activeRepo
|
||||
.flatMapLatest { it.dataEnabled }
|
||||
.logDiffsForTable(
|
||||
tableLogBuffer,
|
||||
columnPrefix = "",
|
||||
columnName = "dataEnabled",
|
||||
initialValue = activeRepo.value.dataEnabled.value,
|
||||
)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value)
|
||||
|
||||
override val numberOfLevels =
|
||||
@@ -132,6 +143,11 @@ class FullMobileConnectionRepository(
|
||||
override val networkName =
|
||||
activeRepo
|
||||
.flatMapLatest { it.networkName }
|
||||
.logDiffsForTable(
|
||||
tableLogBuffer,
|
||||
columnPrefix = "",
|
||||
initialValue = activeRepo.value.networkName.value,
|
||||
)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value)
|
||||
|
||||
class Factory
|
||||
@@ -168,7 +184,7 @@ class FullMobileConnectionRepository(
|
||||
const val MOBILE_CONNECTION_BUFFER_SIZE = 100
|
||||
|
||||
/** 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]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.Background
|
||||
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.NetworkNameModel
|
||||
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.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
@@ -87,7 +85,7 @@ class MobileConnectionRepositoryImpl(
|
||||
private val mobileMappingsProxy: MobileMappingsProxy,
|
||||
bgDispatcher: CoroutineDispatcher,
|
||||
logger: ConnectivityPipelineLogger,
|
||||
mobileLogger: TableLogBuffer,
|
||||
override val tableLogBuffer: TableLogBuffer,
|
||||
scope: CoroutineScope,
|
||||
) : MobileConnectionRepository {
|
||||
init {
|
||||
@@ -101,8 +99,6 @@ class MobileConnectionRepositoryImpl(
|
||||
|
||||
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
|
||||
* new callback should be added to this listener and funneled through callbackEvents via a data
|
||||
@@ -243,11 +239,6 @@ class MobileConnectionRepositoryImpl(
|
||||
val initial = MobileConnectionModel()
|
||||
callbackEvents
|
||||
.scan(initial, ::updateConnectionState)
|
||||
.logDiffsForTable(
|
||||
mobileLogger,
|
||||
columnPrefix = "MobileConnection ($subId)",
|
||||
initialValue = initial,
|
||||
)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
|
||||
}
|
||||
|
||||
@@ -285,24 +276,12 @@ class MobileConnectionRepositoryImpl(
|
||||
intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.logDiffsForTable(
|
||||
mobileLogger,
|
||||
columnPrefix = "",
|
||||
initialValue = defaultNetworkName,
|
||||
)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
|
||||
|
||||
override val dataEnabled = run {
|
||||
val initial = telephonyManager.isDataConnectionAllowed
|
||||
callbackEvents
|
||||
.mapNotNull { (it as? CallbackEvent.OnDataEnabledChanged)?.enabled }
|
||||
.logDiffsForTable(
|
||||
mobileLogger,
|
||||
columnPrefix = "",
|
||||
columnName = "dataEnabled",
|
||||
initialValue = initial
|
||||
)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import com.android.systemui.log.table.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.NetworkNameModel
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
|
||||
@@ -82,6 +85,7 @@ constructor(
|
||||
private val subscriptionManager: SubscriptionManager,
|
||||
private val telephonyManager: TelephonyManager,
|
||||
private val logger: ConnectivityPipelineLogger,
|
||||
@MobileSummaryLog private val tableLogger: TableLogBuffer,
|
||||
mobileMappingsProxy: MobileMappingsProxy,
|
||||
broadcastDispatcher: BroadcastDispatcher,
|
||||
private val context: Context,
|
||||
@@ -114,6 +118,12 @@ constructor(
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.logDiffsForTable(
|
||||
tableLogger,
|
||||
LOGGING_PREFIX,
|
||||
columnName = "carrierMergedSubId",
|
||||
initialValue = null,
|
||||
)
|
||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
private val mobileSubscriptionsChangeEvent: Flow<Unit> = conflatedCallbackFlow {
|
||||
@@ -139,8 +149,14 @@ constructor(
|
||||
override val subscriptions: StateFlow<List<SubscriptionModel>> =
|
||||
merge(mobileSubscriptionsChangeEvent, carrierMergedSubId)
|
||||
.mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
|
||||
.logInputChange(logger, "onSubscriptionsChanged")
|
||||
.onEach { infos -> updateRepos(infos) }
|
||||
.distinctUntilChanged()
|
||||
.logDiffsForTable(
|
||||
tableLogger,
|
||||
LOGGING_PREFIX,
|
||||
columnName = "subscriptions",
|
||||
initialValue = listOf(),
|
||||
)
|
||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
|
||||
|
||||
/** StateFlow that keeps track of the current active mobile data subscription */
|
||||
@@ -157,7 +173,12 @@ constructor(
|
||||
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.logInputChange(logger, "onActiveDataSubscriptionIdChanged")
|
||||
.logDiffsForTable(
|
||||
tableLogger,
|
||||
LOGGING_PREFIX,
|
||||
columnName = "activeSubId",
|
||||
initialValue = INVALID_SUBSCRIPTION_ID,
|
||||
)
|
||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
|
||||
|
||||
private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
|
||||
@@ -171,7 +192,12 @@ constructor(
|
||||
intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
|
||||
.logDiffsForTable(
|
||||
tableLogger,
|
||||
LOGGING_PREFIX,
|
||||
columnName = "defaultSubId",
|
||||
initialValue = SubscriptionManager.getDefaultDataSubscriptionId(),
|
||||
)
|
||||
.onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
|
||||
.stateIn(
|
||||
scope,
|
||||
@@ -247,7 +273,11 @@ constructor(
|
||||
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.logInputChange(logger, "defaultMobileNetworkConnectivity")
|
||||
.logDiffsForTable(
|
||||
tableLogger,
|
||||
columnPrefix = "$LOGGING_PREFIX.defaultConnection",
|
||||
initialValue = MobileConnectivityModel(),
|
||||
)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
|
||||
|
||||
/**
|
||||
@@ -321,4 +351,8 @@ constructor(
|
||||
subscriptionId = subscriptionId,
|
||||
isOpportunistic = isOpportunistic,
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val LOGGING_PREFIX = "Repo"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
interface MobileIconInteractor {
|
||||
@@ -109,6 +111,9 @@ interface MobileIconInteractor {
|
||||
|
||||
/** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
|
||||
val numberOfLevels: StateFlow<Int>
|
||||
|
||||
/** See [MobileIconsInteractor.isForceHidden]. */
|
||||
val isForceHidden: Flow<Boolean>
|
||||
}
|
||||
|
||||
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
|
||||
@@ -124,6 +129,7 @@ class MobileIconInteractorImpl(
|
||||
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
|
||||
defaultDataSubId: StateFlow<Int>,
|
||||
override val isDefaultConnectionFailed: StateFlow<Boolean>,
|
||||
override val isForceHidden: Flow<Boolean>,
|
||||
connectionRepository: MobileConnectionRepository,
|
||||
) : MobileIconInteractor {
|
||||
private val connectionInfo = connectionRepository.connectionInfo
|
||||
@@ -181,6 +187,16 @@ class MobileIconInteractorImpl(
|
||||
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)
|
||||
|
||||
override val isEmergencyOnly: StateFlow<Boolean> =
|
||||
|
||||
@@ -23,12 +23,17 @@ import com.android.settingslib.SignalIcon.MobileIconGroup
|
||||
import com.android.settingslib.mobile.TelephonyIcons
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
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.SubscriptionModel
|
||||
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.UserSetupRepository
|
||||
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 javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -42,8 +47,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
|
||||
@@ -88,6 +93,10 @@ interface MobileIconsInteractor {
|
||||
val isDefaultConnectionFailed: StateFlow<Boolean>
|
||||
/** True once the user has been set up */
|
||||
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
|
||||
* subId. Will throw if the ID is invalid
|
||||
@@ -104,6 +113,8 @@ constructor(
|
||||
private val mobileConnectionsRepo: MobileConnectionsRepository,
|
||||
private val carrierConfigTracker: CarrierConfigTracker,
|
||||
private val logger: ConnectivityPipelineLogger,
|
||||
@MobileSummaryLog private val tableLogger: TableLogBuffer,
|
||||
connectivityRepository: ConnectivityRepository,
|
||||
userSetupRepo: UserSetupRepository,
|
||||
@Application private val scope: CoroutineScope,
|
||||
) : MobileIconsInteractor {
|
||||
@@ -173,7 +184,13 @@ constructor(
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.onEach { logger.logFilteredSubscriptionsChanged(it) }
|
||||
.logDiffsForTable(
|
||||
tableLogger,
|
||||
LOGGING_PREFIX,
|
||||
columnName = "filteredSubscriptions",
|
||||
initialValue = listOf(),
|
||||
)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
|
||||
|
||||
override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
|
||||
|
||||
@@ -195,6 +212,12 @@ constructor(
|
||||
delay(2000)
|
||||
emit(false)
|
||||
}
|
||||
.logDiffsForTable(
|
||||
tableLogger,
|
||||
LOGGING_PREFIX,
|
||||
columnName = "forcingValidation",
|
||||
initialValue = false,
|
||||
)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
|
||||
|
||||
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
|
||||
@@ -211,6 +234,12 @@ constructor(
|
||||
networkConnectivity
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.logDiffsForTable(
|
||||
tableLogger,
|
||||
columnPrefix = "$LOGGING_PREFIX.defaultConnection",
|
||||
initialValue = mobileConnectionsRepo.defaultMobileNetworkConnectivity.value,
|
||||
)
|
||||
.stateIn(
|
||||
scope,
|
||||
SharingStarted.WhileSubscribed(),
|
||||
@@ -259,10 +288,21 @@ constructor(
|
||||
!connectivityModel.isValidated
|
||||
}
|
||||
}
|
||||
.logDiffsForTable(
|
||||
tableLogger,
|
||||
LOGGING_PREFIX,
|
||||
columnName = "isDefaultConnectionFailed",
|
||||
initialValue = false,
|
||||
)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
|
||||
|
||||
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 */
|
||||
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
|
||||
MobileIconInteractorImpl(
|
||||
@@ -275,6 +315,11 @@ constructor(
|
||||
defaultMobileIconGroup,
|
||||
defaultDataSubId,
|
||||
isDefaultConnectionFailed,
|
||||
isForceHidden,
|
||||
mobileConnectionsRepo.getRepoForSubId(subId),
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val LOGGING_PREFIX = "Intr"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,10 +91,17 @@ object MobileIconBinder {
|
||||
}
|
||||
}
|
||||
|
||||
launch { viewModel.isVisible.collect { isVisible -> view.isVisible = isVisible } }
|
||||
|
||||
// Set the icon for the triangle
|
||||
launch {
|
||||
viewModel.iconId.distinctUntilChanged().collect { iconId ->
|
||||
mobileDrawable.level = iconId
|
||||
viewModel.icon.distinctUntilChanged().collect { icon ->
|
||||
mobileDrawable.level =
|
||||
SignalDrawable.getState(
|
||||
icon.level,
|
||||
icon.numberOfLevels,
|
||||
icon.showExclamationMark,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,8 +155,7 @@ object MobileIconBinder {
|
||||
|
||||
return object : ModernStatusBarViewBinding {
|
||||
override fun getShouldIconBeVisible(): Boolean {
|
||||
// If this view model exists, then the icon should be visible.
|
||||
return true
|
||||
return viewModel.isVisible.value
|
||||
}
|
||||
|
||||
override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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.Icon
|
||||
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.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.ConnectivityPipelineLogger
|
||||
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -37,14 +38,14 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
/** Common interface for all of the location-based mobile icon view models. */
|
||||
interface MobileIconViewModelCommon {
|
||||
val subscriptionId: Int
|
||||
/** An int consumable by [SignalDrawable] for display */
|
||||
val iconId: Flow<Int>
|
||||
/** True if this view should be visible at all. */
|
||||
val isVisible: StateFlow<Boolean>
|
||||
val icon: Flow<SignalIconModel>
|
||||
val contentDescription: Flow<ContentDescription>
|
||||
val roaming: Flow<Boolean>
|
||||
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
|
||||
@@ -73,7 +74,7 @@ class MobileIconViewModel
|
||||
constructor(
|
||||
override val subscriptionId: Int,
|
||||
iconInteractor: MobileIconInteractor,
|
||||
logger: ConnectivityPipelineLogger,
|
||||
airplaneModeInteractor: AirplaneModeInteractor,
|
||||
constants: ConnectivityConstants,
|
||||
scope: CoroutineScope,
|
||||
) : MobileIconViewModelCommon {
|
||||
@@ -81,8 +82,28 @@ constructor(
|
||||
private val showExclamationMark: Flow<Boolean> =
|
||||
iconInteractor.isDefaultDataEnabled.mapLatest { !it }
|
||||
|
||||
override val iconId: Flow<Int> = run {
|
||||
val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value)
|
||||
override val isVisible: StateFlow<Boolean> =
|
||||
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(
|
||||
iconInteractor.level,
|
||||
iconInteractor.numberOfLevels,
|
||||
@@ -90,16 +111,15 @@ constructor(
|
||||
iconInteractor.isInService,
|
||||
) { level, numberOfLevels, showExclamationMark, isInService ->
|
||||
if (!isInService) {
|
||||
SignalDrawable.getEmptyState(numberOfLevels)
|
||||
SignalIconModel.createEmptyState(numberOfLevels)
|
||||
} else {
|
||||
SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
|
||||
SignalIconModel(level, numberOfLevels, showExclamationMark)
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.logDiffsForTable(
|
||||
iconInteractor.tableLogBuffer,
|
||||
columnPrefix = "",
|
||||
columnName = "iconId",
|
||||
columnPrefix = "icon",
|
||||
initialValue = initial,
|
||||
)
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
|
||||
@@ -124,14 +144,22 @@ constructor(
|
||||
|
||||
private val showNetworkTypeIcon: Flow<Boolean> =
|
||||
combine(
|
||||
iconInteractor.isDataConnected,
|
||||
iconInteractor.isDataEnabled,
|
||||
iconInteractor.isDefaultConnectionFailed,
|
||||
iconInteractor.alwaysShowDataRatIcon,
|
||||
iconInteractor.isConnected,
|
||||
) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected ->
|
||||
alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected)
|
||||
}
|
||||
iconInteractor.isDataConnected,
|
||||
iconInteractor.isDataEnabled,
|
||||
iconInteractor.isDefaultConnectionFailed,
|
||||
iconInteractor.alwaysShowDataRatIcon,
|
||||
iconInteractor.isConnected,
|
||||
) { dataConnected, dataEnabled, failedConnection, alwaysShow, 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?> =
|
||||
combine(
|
||||
@@ -149,14 +177,6 @@ constructor(
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
// This is done as an onEach side effect since Icon is not Diffable (yet)
|
||||
iconInteractor.tableLogBuffer.logChange(
|
||||
prefix = "",
|
||||
columnName = "networkTypeIcon",
|
||||
value = it.toString(),
|
||||
)
|
||||
}
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
override val roaming: StateFlow<Boolean> =
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.statusbar.phone.StatusBarLocation
|
||||
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.ui.view.ModernStatusBarMobileView
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
|
||||
@@ -39,6 +41,7 @@ class MobileIconsViewModel
|
||||
constructor(
|
||||
val subscriptionIdsFlow: StateFlow<List<Int>>,
|
||||
private val interactor: MobileIconsInteractor,
|
||||
private val airplaneModeInteractor: AirplaneModeInteractor,
|
||||
private val logger: ConnectivityPipelineLogger,
|
||||
private val constants: ConnectivityConstants,
|
||||
@Application private val scope: CoroutineScope,
|
||||
@@ -56,7 +59,7 @@ constructor(
|
||||
?: MobileIconViewModel(
|
||||
subId,
|
||||
interactor.createMobileConnectionInteractorForSubId(subId),
|
||||
logger,
|
||||
airplaneModeInteractor,
|
||||
constants,
|
||||
scope,
|
||||
)
|
||||
@@ -74,10 +77,12 @@ constructor(
|
||||
subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
|
||||
}
|
||||
|
||||
@SysUISingleton
|
||||
class Factory
|
||||
@Inject
|
||||
constructor(
|
||||
private val interactor: MobileIconsInteractor,
|
||||
private val airplaneModeInteractor: AirplaneModeInteractor,
|
||||
private val logger: ConnectivityPipelineLogger,
|
||||
private val constants: ConnectivityConstants,
|
||||
@Application private val scope: CoroutineScope,
|
||||
@@ -87,6 +92,7 @@ constructor(
|
||||
return MobileIconsViewModel(
|
||||
subscriptionIdsFlow,
|
||||
interactor,
|
||||
airplaneModeInteractor,
|
||||
logger,
|
||||
constants,
|
||||
scope,
|
||||
|
||||
@@ -204,15 +204,6 @@ constructor(
|
||||
|
||||
// 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>) {
|
||||
buffer.log(
|
||||
SB_LOGGING_TAG,
|
||||
|
||||
@@ -275,6 +275,52 @@ class LogDiffsForTableTest : SysuiTestCase() {
|
||||
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
|
||||
fun int_logsUpdates() =
|
||||
testScope.runTest {
|
||||
@@ -1030,6 +1076,246 @@ class LogDiffsForTableTest : SysuiTestCase() {
|
||||
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 {
|
||||
val outputWriter = StringWriter()
|
||||
tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf())
|
||||
|
||||
@@ -35,6 +35,17 @@ class TableChangeTest : SysuiTestCase() {
|
||||
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
|
||||
fun setBoolean_isBoolean() {
|
||||
val underTest = TableChange()
|
||||
@@ -57,6 +68,17 @@ class TableChangeTest : SysuiTestCase() {
|
||||
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
|
||||
fun setThenReset_isEmpty() {
|
||||
val underTest = TableChange()
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.demomode.DemoMode
|
||||
import com.android.systemui.demomode.DemoModeController
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import com.android.systemui.log.table.TableLogBufferFactory
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
|
||||
@@ -81,6 +82,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
|
||||
@Mock private lateinit var subscriptionManager: SubscriptionManager
|
||||
@Mock private lateinit var telephonyManager: TelephonyManager
|
||||
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||
@Mock private lateinit var summaryLogger: TableLogBuffer
|
||||
@Mock private lateinit var demoModeController: DemoModeController
|
||||
@Mock private lateinit var dumpManager: DumpManager
|
||||
|
||||
@@ -114,6 +116,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() {
|
||||
subscriptionManager,
|
||||
telephonyManager,
|
||||
logger,
|
||||
summaryLogger,
|
||||
mobileMappings,
|
||||
fakeBroadcastDispatcher,
|
||||
context,
|
||||
|
||||
@@ -16,20 +16,32 @@
|
||||
|
||||
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 com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import com.android.systemui.log.table.TableLogBufferFactory
|
||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.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.repository.FakeMobileConnectionRepository
|
||||
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.eq
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@@ -52,9 +64,10 @@ import org.mockito.Mockito.verify
|
||||
class FullMobileConnectionRepositoryTest : SysuiTestCase() {
|
||||
private lateinit var underTest: FullMobileConnectionRepository
|
||||
|
||||
private val systemClock = FakeSystemClock()
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
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 carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
|
||||
|
||||
@@ -347,8 +360,214 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
|
||||
.isSameInstanceAs(connection1Repeat.tableLogBuffer)
|
||||
}
|
||||
|
||||
// TODO(b/238425913): Verify that the logging switches correctly (once the carrier merged repo
|
||||
// implements logging).
|
||||
@Test
|
||||
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) {
|
||||
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 {
|
||||
const val SUB_ID = 42
|
||||
private val DEFAULT_NAME = NetworkNameModel.Default("default name")
|
||||
private const val SEP = "-"
|
||||
private const val BUFFER_SEPARATOR = "|"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.toMobileDataActivityModel
|
||||
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.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -86,7 +85,6 @@ import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||
@@ -670,16 +668,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
|
||||
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 {
|
||||
val cbs = getTelephonyCallbacks().filterIsInstance<T>()
|
||||
assertThat(cbs.size).isEqualTo(1)
|
||||
return cbs[0]
|
||||
return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
|
||||
}
|
||||
|
||||
/** Convenience constructor for SignalStrength */
|
||||
|
||||
@@ -84,6 +84,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
|
||||
@Mock private lateinit var subscriptionManager: SubscriptionManager
|
||||
@Mock private lateinit var telephonyManager: TelephonyManager
|
||||
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||
@Mock private lateinit var summaryLogger: TableLogBuffer
|
||||
@Mock private lateinit var logBufferFactory: TableLogBufferFactory
|
||||
|
||||
private val mobileMappings = FakeMobileMappingsProxy()
|
||||
@@ -157,6 +158,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
|
||||
subscriptionManager,
|
||||
telephonyManager,
|
||||
logger,
|
||||
summaryLogger,
|
||||
mobileMappings,
|
||||
fakeBroadcastDispatcher,
|
||||
context,
|
||||
@@ -616,6 +618,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
|
||||
subscriptionManager,
|
||||
telephonyManager,
|
||||
logger,
|
||||
summaryLogger,
|
||||
mobileMappings,
|
||||
fakeBroadcastDispatcher,
|
||||
context,
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,8 @@ class FakeMobileIconInteractor(
|
||||
private val _numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
|
||||
override val numberOfLevels = _numberOfLevels
|
||||
|
||||
override val isForceHidden = MutableStateFlow(false)
|
||||
|
||||
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
|
||||
_iconGroup.value = group
|
||||
}
|
||||
|
||||
@@ -73,6 +73,8 @@ class FakeMobileIconsInteractor(
|
||||
private val _isUserSetup = MutableStateFlow(true)
|
||||
override val isUserSetup = _isUserSetup
|
||||
|
||||
override val isForceHidden = MutableStateFlow(false)
|
||||
|
||||
/** Always returns a new fake interactor */
|
||||
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
|
||||
return FakeMobileIconInteractor(tableLogBuffer)
|
||||
|
||||
@@ -66,6 +66,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
|
||||
mobileIconsInteractor.defaultMobileIconGroup,
|
||||
mobileIconsInteractor.defaultDataSubId,
|
||||
mobileIconsInteractor.isDefaultConnectionFailed,
|
||||
mobileIconsInteractor.isForceHidden,
|
||||
connectionRepository,
|
||||
)
|
||||
}
|
||||
@@ -550,6 +551,21 @@ class MobileIconInteractorTest : SysuiTestCase() {
|
||||
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 {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
|
||||
|
||||
@@ -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.FakeUserSetupRepository
|
||||
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.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
@@ -50,6 +52,7 @@ import org.mockito.MockitoAnnotations
|
||||
@SmallTest
|
||||
class MobileIconsInteractorTest : SysuiTestCase() {
|
||||
private lateinit var underTest: MobileIconsInteractor
|
||||
private lateinit var connectivityRepository: FakeConnectivityRepository
|
||||
private lateinit var connectionsRepository: FakeMobileConnectionsRepository
|
||||
private val userSetupRepository = FakeUserSetupRepository()
|
||||
private val mobileMappingsProxy = FakeMobileMappingsProxy()
|
||||
@@ -63,6 +66,8 @@ class MobileIconsInteractorTest : SysuiTestCase() {
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
connectivityRepository = FakeConnectivityRepository()
|
||||
|
||||
connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
|
||||
connectionsRepository.setMobileConnectionRepositoryMap(
|
||||
mapOf(
|
||||
@@ -79,6 +84,8 @@ class MobileIconsInteractorTest : SysuiTestCase() {
|
||||
connectionsRepository,
|
||||
carrierConfigTracker,
|
||||
logger = mock(),
|
||||
tableLogger = mock(),
|
||||
connectivityRepository,
|
||||
userSetupRepository,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
@@ -609,6 +616,32 @@ class MobileIconsInteractorTest : SysuiTestCase() {
|
||||
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 {
|
||||
private val tableLogBuffer =
|
||||
TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
|
||||
|
||||
@@ -29,12 +29,14 @@ import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import com.android.systemui.statusbar.StatusBarIconView
|
||||
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.ui.viewmodel.LocationBasedMobileViewModel
|
||||
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.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.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -58,31 +60,37 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
|
||||
@Mock private lateinit var tableLogBuffer: TableLogBuffer
|
||||
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||
@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
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
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)
|
||||
|
||||
val interactor = FakeMobileIconInteractor(tableLogBuffer)
|
||||
|
||||
val viewModelCommon =
|
||||
MobileIconViewModel(
|
||||
subscriptionId = 1,
|
||||
interactor,
|
||||
logger,
|
||||
constants,
|
||||
testScope.backgroundScope,
|
||||
airplaneModeRepository = FakeAirplaneModeRepository()
|
||||
airplaneModeInteractor =
|
||||
AirplaneModeInteractor(
|
||||
airplaneModeRepository,
|
||||
FakeConnectivityRepository(),
|
||||
)
|
||||
viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
|
||||
|
||||
interactor = FakeMobileIconInteractor(tableLogBuffer)
|
||||
createViewModel()
|
||||
}
|
||||
|
||||
// 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
|
||||
fun setVisibleState_icon_iconShownDotHidden() {
|
||||
@@ -130,7 +138,25 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
ViewUtils.attachView(view)
|
||||
@@ -141,6 +167,34 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
|
||||
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
|
||||
fun onDarkChanged_iconHasNewColor() {
|
||||
whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
|
||||
@@ -184,6 +238,18 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
|
||||
private fun View.getDotView(): View {
|
||||
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"
|
||||
|
||||
@@ -21,10 +21,13 @@ import com.android.settingslib.mobile.TelephonyIcons
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
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.ui.model.SignalIconModel
|
||||
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -46,8 +49,8 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
|
||||
private lateinit var qsIcon: QsMobileIconViewModel
|
||||
private lateinit var keyguardIcon: KeyguardMobileIconViewModel
|
||||
private lateinit var interactor: FakeMobileIconInteractor
|
||||
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
|
||||
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
|
||||
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||
@Mock private lateinit var constants: ConnectivityConstants
|
||||
@Mock private lateinit var tableLogBuffer: TableLogBuffer
|
||||
|
||||
@@ -57,6 +60,11 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
airplaneModeInteractor =
|
||||
AirplaneModeInteractor(
|
||||
FakeAirplaneModeRepository(),
|
||||
FakeConnectivityRepository(),
|
||||
)
|
||||
interactor = FakeMobileIconInteractor(tableLogBuffer)
|
||||
interactor.apply {
|
||||
setLevel(1)
|
||||
@@ -68,7 +76,13 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
|
||||
isDataConnected.value = true
|
||||
}
|
||||
commonImpl =
|
||||
MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
|
||||
MobileIconViewModel(
|
||||
SUB_1_ID,
|
||||
interactor,
|
||||
airplaneModeInteractor,
|
||||
constants,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
|
||||
homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
|
||||
qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
|
||||
@@ -78,14 +92,14 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun `location based view models receive same icon id when common impl updates`() =
|
||||
testScope.runTest {
|
||||
var latestHome: Int? = null
|
||||
val homeJob = homeIcon.iconId.onEach { latestHome = it }.launchIn(this)
|
||||
var latestHome: SignalIconModel? = null
|
||||
val homeJob = homeIcon.icon.onEach { latestHome = it }.launchIn(this)
|
||||
|
||||
var latestQs: Int? = null
|
||||
val qsJob = qsIcon.iconId.onEach { latestQs = it }.launchIn(this)
|
||||
var latestQs: SignalIconModel? = null
|
||||
val qsJob = qsIcon.icon.onEach { latestQs = it }.launchIn(this)
|
||||
|
||||
var latestKeyguard: Int? = null
|
||||
val keyguardJob = keyguardIcon.iconId.onEach { latestKeyguard = it }.launchIn(this)
|
||||
var latestKeyguard: SignalIconModel? = null
|
||||
val keyguardJob = keyguardIcon.icon.onEach { latestKeyguard = it }.launchIn(this)
|
||||
|
||||
var expected = defaultSignal(level = 1)
|
||||
|
||||
|
||||
@@ -19,16 +19,18 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
|
||||
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.systemui.SysuiTestCase
|
||||
import com.android.systemui.common.shared.model.ContentDescription
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import com.android.systemui.statusbar.pipeline.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.ui.model.SignalIconModel
|
||||
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.repository.FakeConnectivityRepository
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -49,7 +51,8 @@ import org.mockito.MockitoAnnotations
|
||||
class MobileIconViewModelTest : SysuiTestCase() {
|
||||
private lateinit var underTest: MobileIconViewModel
|
||||
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 tableLogBuffer: TableLogBuffer
|
||||
|
||||
@@ -59,6 +62,15 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(constants.hasDataCapabilities).thenReturn(true)
|
||||
|
||||
airplaneModeRepository = FakeAirplaneModeRepository()
|
||||
airplaneModeInteractor =
|
||||
AirplaneModeInteractor(
|
||||
airplaneModeRepository,
|
||||
FakeConnectivityRepository(),
|
||||
)
|
||||
|
||||
interactor = FakeMobileIconInteractor(tableLogBuffer)
|
||||
interactor.apply {
|
||||
setLevel(1)
|
||||
@@ -69,15 +81,94 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
||||
setNumberOfLevels(4)
|
||||
isDataConnected.value = true
|
||||
}
|
||||
underTest =
|
||||
MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
|
||||
createAndSetViewModel()
|
||||
}
|
||||
|
||||
@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
|
||||
fun iconId_correctLevel_notCutout() =
|
||||
testScope.runTest {
|
||||
var latest: Int? = null
|
||||
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
|
||||
var latest: SignalIconModel? = null
|
||||
val job = underTest.icon.onEach { latest = it }.launchIn(this)
|
||||
val expected = defaultSignal()
|
||||
|
||||
assertThat(latest).isEqualTo(expected)
|
||||
@@ -90,8 +181,8 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
interactor.setIsDefaultDataEnabled(false)
|
||||
|
||||
var latest: Int? = null
|
||||
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
|
||||
var latest: SignalIconModel? = null
|
||||
val job = underTest.icon.onEach { latest = it }.launchIn(this)
|
||||
val expected = defaultSignal(level = 1, connected = false)
|
||||
|
||||
assertThat(latest).isEqualTo(expected)
|
||||
@@ -102,8 +193,8 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun `icon - uses empty state - when not in service`() =
|
||||
testScope.runTest {
|
||||
var latest: Int? = null
|
||||
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
|
||||
var latest: SignalIconModel? = null
|
||||
val job = underTest.icon.onEach { latest = it }.launchIn(this)
|
||||
|
||||
interactor.isInService.value = false
|
||||
|
||||
@@ -364,14 +455,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
// Create a new view model here so the constants are properly read
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(false)
|
||||
underTest =
|
||||
MobileIconViewModel(
|
||||
SUB_1_ID,
|
||||
interactor,
|
||||
logger,
|
||||
constants,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
createAndSetViewModel()
|
||||
|
||||
var inVisible: Boolean? = null
|
||||
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
|
||||
@@ -403,14 +487,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
||||
testScope.runTest {
|
||||
// Create a new view model here so the constants are properly read
|
||||
whenever(constants.shouldShowActivityConfig).thenReturn(true)
|
||||
underTest =
|
||||
MobileIconViewModel(
|
||||
SUB_1_ID,
|
||||
interactor,
|
||||
logger,
|
||||
constants,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
createAndSetViewModel()
|
||||
|
||||
var inVisible: Boolean? = null
|
||||
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
|
||||
@@ -459,6 +536,16 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
||||
containerJob.cancel()
|
||||
}
|
||||
|
||||
private fun createAndSetViewModel() {
|
||||
underTest = MobileIconViewModel(
|
||||
SUB_1_ID,
|
||||
interactor,
|
||||
airplaneModeInteractor,
|
||||
constants,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SUB_1_ID = 1
|
||||
|
||||
@@ -466,10 +553,11 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
||||
fun defaultSignal(
|
||||
level: Int = 1,
|
||||
connected: Boolean = true,
|
||||
): Int {
|
||||
return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
|
||||
): SignalIconModel {
|
||||
return SignalIconModel(level, numberOfLevels = 4, showExclamationMark = !connected)
|
||||
}
|
||||
|
||||
fun emptySignal(): Int = SignalDrawable.getEmptyState(4)
|
||||
fun emptySignal(): SignalIconModel =
|
||||
SignalIconModel(level = 0, numberOfLevels = 4, showExclamationMark = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,14 @@ import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.phone.StatusBarLocation
|
||||
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.domain.interactor.FakeMobileIconsInteractor
|
||||
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
|
||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -46,6 +49,7 @@ class MobileIconsViewModelTest : SysuiTestCase() {
|
||||
private lateinit var underTest: MobileIconsViewModel
|
||||
private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
|
||||
|
||||
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
|
||||
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
|
||||
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||
@Mock private lateinit var constants: ConnectivityConstants
|
||||
@@ -57,6 +61,12 @@ class MobileIconsViewModelTest : SysuiTestCase() {
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
airplaneModeInteractor =
|
||||
AirplaneModeInteractor(
|
||||
FakeAirplaneModeRepository(),
|
||||
FakeConnectivityRepository(),
|
||||
)
|
||||
|
||||
val subscriptionIdsFlow =
|
||||
interactor.filteredSubscriptions
|
||||
.map { subs -> subs.map { it.subscriptionId } }
|
||||
@@ -66,6 +76,7 @@ class MobileIconsViewModelTest : SysuiTestCase() {
|
||||
MobileIconsViewModel(
|
||||
subscriptionIdsFlow,
|
||||
interactor,
|
||||
airplaneModeInteractor,
|
||||
logger,
|
||||
constants,
|
||||
testScope.backgroundScope,
|
||||
|
||||
Reference in New Issue
Block a user