Merge changes I37ab7767,I13890cdc,Idd5fa020,I6f614d61 into tm-qpr-dev
* changes: [SB refactor] Wire up DataConnectionState to the view model [SB refactor] Add connectivity tracking to the mobile pipeline [SB refactor] Upgrade most data sources to `StateFlow` [SB refactor] Track the default subscription and its connection status
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.statusbar.pipeline.mobile.data.model
|
||||||
|
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
|
||||||
|
/** Provides information about a mobile network connection */
|
||||||
|
data class MobileConnectivityModel(
|
||||||
|
/** Whether mobile is the connected transport see [NetworkCapabilities.TRANSPORT_CELLULAR] */
|
||||||
|
val isConnected: Boolean = false,
|
||||||
|
/** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
|
||||||
|
val isValidated: Boolean = false,
|
||||||
|
)
|
||||||
@@ -16,11 +16,15 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.ContentObserver
|
||||||
|
import android.provider.Settings.Global
|
||||||
import android.telephony.CellSignalStrength
|
import android.telephony.CellSignalStrength
|
||||||
import android.telephony.CellSignalStrengthCdma
|
import android.telephony.CellSignalStrengthCdma
|
||||||
import android.telephony.ServiceState
|
import android.telephony.ServiceState
|
||||||
import android.telephony.SignalStrength
|
import android.telephony.SignalStrength
|
||||||
import android.telephony.SubscriptionInfo
|
import android.telephony.SubscriptionInfo
|
||||||
|
import android.telephony.SubscriptionManager
|
||||||
import android.telephony.TelephonyCallback
|
import android.telephony.TelephonyCallback
|
||||||
import android.telephony.TelephonyDisplayInfo
|
import android.telephony.TelephonyDisplayInfo
|
||||||
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
|
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
|
||||||
@@ -34,6 +38,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetwork
|
|||||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
|
||||||
|
import com.android.systemui.util.settings.GlobalSettings
|
||||||
import java.lang.IllegalStateException
|
import java.lang.IllegalStateException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
@@ -42,9 +47,12 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||||||
import kotlinx.coroutines.asExecutor
|
import kotlinx.coroutines.asExecutor
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,14 +73,23 @@ interface MobileConnectionRepository {
|
|||||||
*/
|
*/
|
||||||
val subscriptionModelFlow: Flow<MobileSubscriptionModel>
|
val subscriptionModelFlow: Flow<MobileSubscriptionModel>
|
||||||
/** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
|
/** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
|
||||||
val dataEnabled: Flow<Boolean>
|
val dataEnabled: StateFlow<Boolean>
|
||||||
|
/**
|
||||||
|
* True if this connection represents the default subscription per
|
||||||
|
* [SubscriptionManager.getDefaultDataSubscriptionId]
|
||||||
|
*/
|
||||||
|
val isDefaultDataSubscription: StateFlow<Boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class MobileConnectionRepositoryImpl(
|
class MobileConnectionRepositoryImpl(
|
||||||
|
private val context: Context,
|
||||||
private val subId: Int,
|
private val subId: Int,
|
||||||
private val telephonyManager: TelephonyManager,
|
private val telephonyManager: TelephonyManager,
|
||||||
|
private val globalSettings: GlobalSettings,
|
||||||
|
defaultDataSubId: StateFlow<Int>,
|
||||||
|
globalMobileDataSettingChangedEvent: Flow<Unit>,
|
||||||
bgDispatcher: CoroutineDispatcher,
|
bgDispatcher: CoroutineDispatcher,
|
||||||
logger: ConnectivityPipelineLogger,
|
logger: ConnectivityPipelineLogger,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
@@ -86,6 +103,8 @@ class MobileConnectionRepositoryImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||||
|
|
||||||
override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
|
override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
|
||||||
var state = MobileSubscriptionModel()
|
var state = MobileSubscriptionModel()
|
||||||
conflatedCallbackFlow {
|
conflatedCallbackFlow {
|
||||||
@@ -165,33 +184,75 @@ class MobileConnectionRepositoryImpl(
|
|||||||
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
|
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
|
||||||
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
|
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
|
||||||
}
|
}
|
||||||
|
.onEach { telephonyCallbackEvent.tryEmit(Unit) }
|
||||||
.logOutputChange(logger, "MobileSubscriptionModel")
|
.logOutputChange(logger, "MobileSubscriptionModel")
|
||||||
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
|
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Produces whenever the mobile data setting changes for this subId */
|
||||||
|
private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
|
||||||
|
val observer =
|
||||||
|
object : ContentObserver(null) {
|
||||||
|
override fun onChange(selfChange: Boolean) {
|
||||||
|
trySend(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globalSettings.registerContentObserver(
|
||||||
|
globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"),
|
||||||
|
/* notifyForDescendants */ true,
|
||||||
|
observer
|
||||||
|
)
|
||||||
|
|
||||||
|
awaitClose { context.contentResolver.unregisterContentObserver(observer) }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There are a few cases where we will need to poll [TelephonyManager] so we can update some
|
* There are a few cases where we will need to poll [TelephonyManager] so we can update some
|
||||||
* internal state where callbacks aren't provided. Any of those events should be merged into
|
* internal state where callbacks aren't provided. Any of those events should be merged into
|
||||||
* this flow, which can be used to trigger the polling.
|
* this flow, which can be used to trigger the polling.
|
||||||
*/
|
*/
|
||||||
private val telephonyPollingEvent: Flow<Unit> = subscriptionModelFlow.map {}
|
private val telephonyPollingEvent: Flow<Unit> =
|
||||||
|
merge(
|
||||||
|
telephonyCallbackEvent,
|
||||||
|
localMobileDataSettingChangedEvent,
|
||||||
|
globalMobileDataSettingChangedEvent,
|
||||||
|
)
|
||||||
|
|
||||||
override val dataEnabled: Flow<Boolean> = telephonyPollingEvent.map { dataConnectionAllowed() }
|
override val dataEnabled: StateFlow<Boolean> =
|
||||||
|
telephonyPollingEvent
|
||||||
|
.mapLatest { dataConnectionAllowed() }
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
|
||||||
|
|
||||||
private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
|
private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
|
||||||
|
|
||||||
|
override val isDefaultDataSubscription: StateFlow<Boolean> =
|
||||||
|
defaultDataSubId
|
||||||
|
.mapLatest { it == subId }
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
|
||||||
|
|
||||||
class Factory
|
class Factory
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
|
private val context: Context,
|
||||||
private val telephonyManager: TelephonyManager,
|
private val telephonyManager: TelephonyManager,
|
||||||
private val logger: ConnectivityPipelineLogger,
|
private val logger: ConnectivityPipelineLogger,
|
||||||
|
private val globalSettings: GlobalSettings,
|
||||||
@Background private val bgDispatcher: CoroutineDispatcher,
|
@Background private val bgDispatcher: CoroutineDispatcher,
|
||||||
@Application private val scope: CoroutineScope,
|
@Application private val scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
fun build(subId: Int): MobileConnectionRepository {
|
fun build(
|
||||||
|
subId: Int,
|
||||||
|
defaultDataSubId: StateFlow<Int>,
|
||||||
|
globalMobileDataSettingChangedEvent: Flow<Unit>,
|
||||||
|
): MobileConnectionRepository {
|
||||||
return MobileConnectionRepositoryImpl(
|
return MobileConnectionRepositoryImpl(
|
||||||
|
context,
|
||||||
subId,
|
subId,
|
||||||
telephonyManager.createForSubscriptionId(subId),
|
telephonyManager.createForSubscriptionId(subId),
|
||||||
|
globalSettings,
|
||||||
|
defaultDataSubId,
|
||||||
|
globalMobileDataSettingChangedEvent,
|
||||||
bgDispatcher,
|
bgDispatcher,
|
||||||
logger,
|
logger,
|
||||||
scope,
|
scope,
|
||||||
|
|||||||
@@ -16,15 +16,27 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.database.ContentObserver
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.ConnectivityManager.NetworkCallback
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
|
||||||
|
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.provider.Settings.Global.MOBILE_DATA
|
||||||
import android.telephony.CarrierConfigManager
|
import android.telephony.CarrierConfigManager
|
||||||
import android.telephony.SubscriptionInfo
|
import android.telephony.SubscriptionInfo
|
||||||
import android.telephony.SubscriptionManager
|
import android.telephony.SubscriptionManager
|
||||||
|
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
||||||
import android.telephony.TelephonyCallback
|
import android.telephony.TelephonyCallback
|
||||||
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
|
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import com.android.internal.telephony.PhoneConstants
|
||||||
import com.android.settingslib.mobile.MobileMappings
|
import com.android.settingslib.mobile.MobileMappings
|
||||||
import com.android.settingslib.mobile.MobileMappings.Config
|
import com.android.settingslib.mobile.MobileMappings.Config
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
@@ -32,7 +44,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.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||||
|
import com.android.systemui.util.settings.GlobalSettings
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -40,10 +54,12 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||||||
import kotlinx.coroutines.asExecutor
|
import kotlinx.coroutines.asExecutor
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -57,13 +73,22 @@ interface MobileConnectionsRepository {
|
|||||||
val subscriptionsFlow: Flow<List<SubscriptionInfo>>
|
val subscriptionsFlow: Flow<List<SubscriptionInfo>>
|
||||||
|
|
||||||
/** Observable for the subscriptionId of the current mobile data connection */
|
/** Observable for the subscriptionId of the current mobile data connection */
|
||||||
val activeMobileDataSubscriptionId: Flow<Int>
|
val activeMobileDataSubscriptionId: StateFlow<Int>
|
||||||
|
|
||||||
/** Observable for [MobileMappings.Config] tracking the defaults */
|
/** Observable for [MobileMappings.Config] tracking the defaults */
|
||||||
val defaultDataSubRatConfig: StateFlow<Config>
|
val defaultDataSubRatConfig: StateFlow<Config>
|
||||||
|
|
||||||
|
/** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
|
||||||
|
val defaultDataSubId: StateFlow<Int>
|
||||||
|
|
||||||
|
/** The current connectivity status for the default mobile network connection */
|
||||||
|
val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
|
||||||
|
|
||||||
/** Get or create a repository for the line of service for the given subscription ID */
|
/** Get or create a repository for the line of service for the given subscription ID */
|
||||||
fun getRepoForSubId(subId: Int): MobileConnectionRepository
|
fun getRepoForSubId(subId: Int): MobileConnectionRepository
|
||||||
|
|
||||||
|
/** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
|
||||||
|
val globalMobileDataSettingChangedEvent: Flow<Unit>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
@@ -72,10 +97,12 @@ interface MobileConnectionsRepository {
|
|||||||
class MobileConnectionsRepositoryImpl
|
class MobileConnectionsRepositoryImpl
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
|
private val connectivityManager: ConnectivityManager,
|
||||||
private val subscriptionManager: SubscriptionManager,
|
private val subscriptionManager: SubscriptionManager,
|
||||||
private val telephonyManager: TelephonyManager,
|
private val telephonyManager: TelephonyManager,
|
||||||
private val logger: ConnectivityPipelineLogger,
|
private val logger: ConnectivityPipelineLogger,
|
||||||
broadcastDispatcher: BroadcastDispatcher,
|
broadcastDispatcher: BroadcastDispatcher,
|
||||||
|
private val globalSettings: GlobalSettings,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@Background private val bgDispatcher: CoroutineDispatcher,
|
@Background private val bgDispatcher: CoroutineDispatcher,
|
||||||
@Application private val scope: CoroutineScope,
|
@Application private val scope: CoroutineScope,
|
||||||
@@ -121,17 +148,26 @@ constructor(
|
|||||||
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
|
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
|
||||||
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
|
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
|
||||||
}
|
}
|
||||||
|
.stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
|
||||||
|
|
||||||
|
private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
|
||||||
|
MutableSharedFlow(extraBufferCapacity = 1)
|
||||||
|
|
||||||
|
override val defaultDataSubId: StateFlow<Int> =
|
||||||
|
broadcastDispatcher
|
||||||
|
.broadcastFlow(
|
||||||
|
IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
|
||||||
|
) { intent, _ ->
|
||||||
|
intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope,
|
scope,
|
||||||
started = SharingStarted.WhileSubscribed(),
|
SharingStarted.WhileSubscribed(),
|
||||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
SubscriptionManager.getDefaultDataSubscriptionId()
|
||||||
)
|
)
|
||||||
|
|
||||||
private val defaultDataSubChangedEvent =
|
|
||||||
broadcastDispatcher.broadcastFlow(
|
|
||||||
IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
|
|
||||||
)
|
|
||||||
|
|
||||||
private val carrierConfigChangedEvent =
|
private val carrierConfigChangedEvent =
|
||||||
broadcastDispatcher.broadcastFlow(
|
broadcastDispatcher.broadcastFlow(
|
||||||
IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
|
IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
|
||||||
@@ -148,9 +184,8 @@ constructor(
|
|||||||
* This flow will produce whenever the default data subscription or the carrier config changes.
|
* This flow will produce whenever the default data subscription or the carrier config changes.
|
||||||
*/
|
*/
|
||||||
override val defaultDataSubRatConfig: StateFlow<Config> =
|
override val defaultDataSubRatConfig: StateFlow<Config> =
|
||||||
combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ ->
|
merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
|
||||||
Config.readConfig(context)
|
.mapLatest { Config.readConfig(context) }
|
||||||
}
|
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope,
|
scope,
|
||||||
SharingStarted.WhileSubscribed(),
|
SharingStarted.WhileSubscribed(),
|
||||||
@@ -168,6 +203,57 @@ constructor(
|
|||||||
?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
|
?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
|
||||||
|
* connection repositories also observe the URI for [MOBILE_DATA] + subId.
|
||||||
|
*/
|
||||||
|
override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
|
||||||
|
val observer =
|
||||||
|
object : ContentObserver(null) {
|
||||||
|
override fun onChange(selfChange: Boolean) {
|
||||||
|
trySend(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globalSettings.registerContentObserver(
|
||||||
|
globalSettings.getUriFor(MOBILE_DATA),
|
||||||
|
true,
|
||||||
|
observer
|
||||||
|
)
|
||||||
|
|
||||||
|
awaitClose { context.contentResolver.unregisterContentObserver(observer) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
|
||||||
|
conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
|
||||||
|
override fun onLost(network: Network) {
|
||||||
|
// Send a disconnected model when lost. Maybe should create a sealed
|
||||||
|
// type or null here?
|
||||||
|
trySend(MobileConnectivityModel())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCapabilitiesChanged(
|
||||||
|
network: Network,
|
||||||
|
caps: NetworkCapabilities
|
||||||
|
) {
|
||||||
|
trySend(
|
||||||
|
MobileConnectivityModel(
|
||||||
|
isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
|
||||||
|
isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectivityManager.registerDefaultNetworkCallback(callback)
|
||||||
|
|
||||||
|
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
|
||||||
|
}
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
|
||||||
|
|
||||||
private fun isValidSubId(subId: Int): Boolean {
|
private fun isValidSubId(subId: Int): Boolean {
|
||||||
subscriptionsFlow.value.forEach {
|
subscriptionsFlow.value.forEach {
|
||||||
if (it.subscriptionId == subId) {
|
if (it.subscriptionId == subId) {
|
||||||
@@ -181,7 +267,11 @@ constructor(
|
|||||||
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
|
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
|
||||||
|
|
||||||
private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
|
private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
|
||||||
return mobileConnectionRepositoryFactory.build(subId)
|
return mobileConnectionRepositoryFactory.build(
|
||||||
|
subId,
|
||||||
|
defaultDataSubId,
|
||||||
|
globalMobileDataSettingChangedEvent,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
|
private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import kotlinx.coroutines.CoroutineDispatcher
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
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.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
@@ -40,7 +39,7 @@ import kotlinx.coroutines.withContext
|
|||||||
*/
|
*/
|
||||||
interface UserSetupRepository {
|
interface UserSetupRepository {
|
||||||
/** Observable tracking [DeviceProvisionedController.isUserSetup] */
|
/** Observable tracking [DeviceProvisionedController.isUserSetup] */
|
||||||
val isUserSetupFlow: Flow<Boolean>
|
val isUserSetupFlow: StateFlow<Boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
|
|||||||
@@ -18,81 +18,109 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
|
|||||||
|
|
||||||
import android.telephony.CarrierConfigManager
|
import android.telephony.CarrierConfigManager
|
||||||
import com.android.settingslib.SignalIcon.MobileIconGroup
|
import com.android.settingslib.SignalIcon.MobileIconGroup
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
|
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
|
||||||
import com.android.systemui.util.CarrierConfigTracker
|
import com.android.systemui.util.CarrierConfigTracker
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
interface MobileIconInteractor {
|
interface MobileIconInteractor {
|
||||||
|
/** Only true if mobile is the default transport but is not validated, otherwise false */
|
||||||
|
val isDefaultConnectionFailed: StateFlow<Boolean>
|
||||||
|
|
||||||
|
/** True when telephony tells us that the data state is CONNECTED */
|
||||||
|
val isDataConnected: StateFlow<Boolean>
|
||||||
|
|
||||||
|
// TODO(b/256839546): clarify naming of default vs active
|
||||||
|
/** True if we want to consider the data connection enabled */
|
||||||
|
val isDefaultDataEnabled: StateFlow<Boolean>
|
||||||
|
|
||||||
/** Observable for the data enabled state of this connection */
|
/** Observable for the data enabled state of this connection */
|
||||||
val isDataEnabled: Flow<Boolean>
|
val isDataEnabled: StateFlow<Boolean>
|
||||||
|
|
||||||
/** Observable for RAT type (network type) indicator */
|
/** Observable for RAT type (network type) indicator */
|
||||||
val networkTypeIconGroup: Flow<MobileIconGroup>
|
val networkTypeIconGroup: StateFlow<MobileIconGroup>
|
||||||
|
|
||||||
/** True if this line of service is emergency-only */
|
/** True if this line of service is emergency-only */
|
||||||
val isEmergencyOnly: Flow<Boolean>
|
val isEmergencyOnly: StateFlow<Boolean>
|
||||||
|
|
||||||
/** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
|
/** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
|
||||||
val level: Flow<Int>
|
val level: StateFlow<Int>
|
||||||
|
|
||||||
/** 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: Flow<Int>
|
val numberOfLevels: StateFlow<Int>
|
||||||
|
|
||||||
/** True when we want to draw an icon that makes room for the exclamation mark */
|
|
||||||
val cutOut: 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 */
|
||||||
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class MobileIconInteractorImpl(
|
class MobileIconInteractorImpl(
|
||||||
defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
|
@Application scope: CoroutineScope,
|
||||||
defaultMobileIconGroup: Flow<MobileIconGroup>,
|
defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
|
||||||
|
defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
|
||||||
|
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
|
||||||
|
override val isDefaultConnectionFailed: StateFlow<Boolean>,
|
||||||
mobileMappingsProxy: MobileMappingsProxy,
|
mobileMappingsProxy: MobileMappingsProxy,
|
||||||
connectionRepository: MobileConnectionRepository,
|
connectionRepository: MobileConnectionRepository,
|
||||||
) : MobileIconInteractor {
|
) : MobileIconInteractor {
|
||||||
private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
|
private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
|
||||||
|
|
||||||
override val isDataEnabled: Flow<Boolean> = connectionRepository.dataEnabled
|
override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
|
||||||
|
|
||||||
|
override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
|
||||||
|
|
||||||
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
|
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
|
||||||
override val networkTypeIconGroup: Flow<MobileIconGroup> =
|
override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
|
||||||
combine(
|
combine(
|
||||||
mobileStatusInfo,
|
mobileStatusInfo,
|
||||||
defaultMobileIconMapping,
|
defaultMobileIconMapping,
|
||||||
defaultMobileIconGroup,
|
defaultMobileIconGroup,
|
||||||
) { info, mapping, defaultGroup ->
|
) { info, mapping, defaultGroup ->
|
||||||
val lookupKey =
|
val lookupKey =
|
||||||
when (val resolved = info.resolvedNetworkType) {
|
when (val resolved = info.resolvedNetworkType) {
|
||||||
is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
|
is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
|
||||||
is OverrideNetworkType -> mobileMappingsProxy.toIconKeyOverride(resolved.type)
|
is OverrideNetworkType ->
|
||||||
}
|
mobileMappingsProxy.toIconKeyOverride(resolved.type)
|
||||||
mapping[lookupKey] ?: defaultGroup
|
}
|
||||||
}
|
mapping[lookupKey] ?: defaultGroup
|
||||||
|
|
||||||
override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
|
|
||||||
|
|
||||||
override val level: Flow<Int> =
|
|
||||||
mobileStatusInfo.map { mobileModel ->
|
|
||||||
// TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
|
|
||||||
if (mobileModel.isGsm) {
|
|
||||||
mobileModel.primaryLevel
|
|
||||||
} else {
|
|
||||||
mobileModel.cdmaLevel
|
|
||||||
}
|
}
|
||||||
}
|
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
|
||||||
|
|
||||||
|
override val isEmergencyOnly: StateFlow<Boolean> =
|
||||||
|
mobileStatusInfo
|
||||||
|
.mapLatest { it.isEmergencyOnly }
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
|
||||||
|
|
||||||
|
override val level: StateFlow<Int> =
|
||||||
|
mobileStatusInfo
|
||||||
|
.mapLatest { mobileModel ->
|
||||||
|
// TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
|
||||||
|
if (mobileModel.isGsm) {
|
||||||
|
mobileModel.primaryLevel
|
||||||
|
} else {
|
||||||
|
mobileModel.cdmaLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
|
* This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
|
||||||
* once it's wired up inside of [CarrierConfigTracker]
|
* once it's wired up inside of [CarrierConfigTracker]
|
||||||
*/
|
*/
|
||||||
override val numberOfLevels: Flow<Int> = flowOf(4)
|
override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4)
|
||||||
|
|
||||||
/** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
|
override val isDataConnected: StateFlow<Boolean> =
|
||||||
// TODO: find a better name for this?
|
mobileStatusInfo
|
||||||
override val cutOut: Flow<Boolean> = flowOf(false)
|
.mapLatest { subscriptionModel -> subscriptionModel.dataConnectionState == Connected }
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
|
|||||||
import android.telephony.CarrierConfigManager
|
import android.telephony.CarrierConfigManager
|
||||||
import android.telephony.SubscriptionInfo
|
import android.telephony.SubscriptionInfo
|
||||||
import android.telephony.SubscriptionManager
|
import android.telephony.SubscriptionManager
|
||||||
|
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
||||||
import com.android.settingslib.SignalIcon.MobileIconGroup
|
import com.android.settingslib.SignalIcon.MobileIconGroup
|
||||||
import com.android.settingslib.mobile.TelephonyIcons
|
import com.android.settingslib.mobile.TelephonyIcons
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
@@ -35,7 +36,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.map
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,12 +54,16 @@ import kotlinx.coroutines.flow.stateIn
|
|||||||
interface MobileIconsInteractor {
|
interface MobileIconsInteractor {
|
||||||
/** List of subscriptions, potentially filtered for CBRS */
|
/** List of subscriptions, potentially filtered for CBRS */
|
||||||
val filteredSubscriptions: Flow<List<SubscriptionInfo>>
|
val filteredSubscriptions: Flow<List<SubscriptionInfo>>
|
||||||
|
/** True if the active mobile data subscription has data enabled */
|
||||||
|
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
|
||||||
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
|
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
|
||||||
val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
|
val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
|
||||||
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
|
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
|
||||||
val defaultMobileIconGroup: Flow<MobileIconGroup>
|
val defaultMobileIconGroup: StateFlow<MobileIconGroup>
|
||||||
|
/** True only if the default network is mobile, and validation also failed */
|
||||||
|
val isDefaultConnectionFailed: StateFlow<Boolean>
|
||||||
/** True once the user has been set up */
|
/** True once the user has been set up */
|
||||||
val isUserSetup: Flow<Boolean>
|
val isUserSetup: StateFlow<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
|
||||||
@@ -79,6 +86,22 @@ constructor(
|
|||||||
private val activeMobileDataSubscriptionId =
|
private val activeMobileDataSubscriptionId =
|
||||||
mobileConnectionsRepo.activeMobileDataSubscriptionId
|
mobileConnectionsRepo.activeMobileDataSubscriptionId
|
||||||
|
|
||||||
|
private val activeMobileDataConnectionRepo: StateFlow<MobileConnectionRepository?> =
|
||||||
|
activeMobileDataSubscriptionId
|
||||||
|
.mapLatest { activeId ->
|
||||||
|
if (activeId == INVALID_SUBSCRIPTION_ID) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
mobileConnectionsRepo.getRepoForSubId(activeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
|
||||||
|
|
||||||
|
override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
|
||||||
|
activeMobileDataConnectionRepo
|
||||||
|
.flatMapLatest { it?.dataEnabled ?: flowOf(false) }
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
|
||||||
|
|
||||||
private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
|
private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
|
||||||
mobileConnectionsRepo.subscriptionsFlow
|
mobileConnectionsRepo.subscriptionsFlow
|
||||||
|
|
||||||
@@ -132,22 +155,40 @@ constructor(
|
|||||||
*/
|
*/
|
||||||
override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
|
override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
|
||||||
mobileConnectionsRepo.defaultDataSubRatConfig
|
mobileConnectionsRepo.defaultDataSubRatConfig
|
||||||
.map { mobileMappingsProxy.mapIconSets(it) }
|
.mapLatest { mobileMappingsProxy.mapIconSets(it) }
|
||||||
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
|
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
|
||||||
|
|
||||||
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
|
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
|
||||||
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
|
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
|
||||||
mobileConnectionsRepo.defaultDataSubRatConfig
|
mobileConnectionsRepo.defaultDataSubRatConfig
|
||||||
.map { mobileMappingsProxy.getDefaultIcons(it) }
|
.mapLatest { mobileMappingsProxy.getDefaultIcons(it) }
|
||||||
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
|
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
|
||||||
|
|
||||||
override val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
|
/**
|
||||||
|
* We want to show an error state when cellular has actually failed to validate, but not if some
|
||||||
|
* other transport type is active, because then we expect there not to be validation.
|
||||||
|
*/
|
||||||
|
override val isDefaultConnectionFailed: StateFlow<Boolean> =
|
||||||
|
mobileConnectionsRepo.defaultMobileNetworkConnectivity
|
||||||
|
.mapLatest { connectivityModel ->
|
||||||
|
if (!connectivityModel.isConnected) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
!connectivityModel.isValidated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
|
||||||
|
|
||||||
|
override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
|
||||||
|
|
||||||
/** 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(
|
||||||
|
scope,
|
||||||
|
activeDataConnectionHasDataEnabled,
|
||||||
defaultMobileIconMapping,
|
defaultMobileIconMapping,
|
||||||
defaultMobileIconGroup,
|
defaultMobileIconGroup,
|
||||||
|
isDefaultConnectionFailed,
|
||||||
mobileMappingsProxy,
|
mobileMappingsProxy,
|
||||||
mobileConnectionsRepo.getRepoForSubId(subId),
|
mobileConnectionsRepo.getRepoForSubId(subId),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,10 +24,12 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIc
|
|||||||
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
|
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
|
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
|
||||||
@@ -39,29 +41,38 @@ import kotlinx.coroutines.flow.flowOf
|
|||||||
*
|
*
|
||||||
* TODO: figure out where carrier merged and VCN models go (probably here?)
|
* TODO: figure out where carrier merged and VCN models go (probably here?)
|
||||||
*/
|
*/
|
||||||
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class MobileIconViewModel
|
class MobileIconViewModel
|
||||||
constructor(
|
constructor(
|
||||||
val subscriptionId: Int,
|
val subscriptionId: Int,
|
||||||
iconInteractor: MobileIconInteractor,
|
iconInteractor: MobileIconInteractor,
|
||||||
logger: ConnectivityPipelineLogger,
|
logger: ConnectivityPipelineLogger,
|
||||||
) {
|
) {
|
||||||
|
/** Whether or not to show the error state of [SignalDrawable] */
|
||||||
|
private val showExclamationMark: Flow<Boolean> =
|
||||||
|
iconInteractor.isDefaultDataEnabled.mapLatest { !it }
|
||||||
|
|
||||||
/** An int consumable by [SignalDrawable] for display */
|
/** An int consumable by [SignalDrawable] for display */
|
||||||
var iconId: Flow<Int> =
|
val iconId: Flow<Int> =
|
||||||
combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
|
combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
|
||||||
level,
|
level,
|
||||||
numberOfLevels,
|
numberOfLevels,
|
||||||
cutOut ->
|
showExclamationMark ->
|
||||||
SignalDrawable.getState(level, numberOfLevels, cutOut)
|
SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
|
||||||
}
|
}
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.logOutputChange(logger, "iconId($subscriptionId)")
|
.logOutputChange(logger, "iconId($subscriptionId)")
|
||||||
|
|
||||||
/** 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 */
|
||||||
var networkTypeIcon: Flow<Icon?> =
|
val networkTypeIcon: Flow<Icon?> =
|
||||||
combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) {
|
combine(
|
||||||
networkTypeIconGroup,
|
iconInteractor.networkTypeIconGroup,
|
||||||
isDataEnabled ->
|
iconInteractor.isDataConnected,
|
||||||
if (!isDataEnabled) {
|
iconInteractor.isDataEnabled,
|
||||||
|
iconInteractor.isDefaultConnectionFailed,
|
||||||
|
) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection ->
|
||||||
|
if (!dataConnected || !dataEnabled || failedConnection) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
val desc =
|
val desc =
|
||||||
@@ -72,5 +83,5 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tint: Flow<Int> = flowOf(Color.CYAN)
|
val tint: Flow<Int> = flowOf(Color.CYAN)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,18 @@
|
|||||||
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
||||||
|
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
class FakeMobileConnectionRepository : MobileConnectionRepository {
|
class FakeMobileConnectionRepository : MobileConnectionRepository {
|
||||||
private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
|
private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
|
||||||
override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
|
override val subscriptionModelFlow = _subscriptionsModelFlow
|
||||||
|
|
||||||
private val _dataEnabled = MutableStateFlow(true)
|
private val _dataEnabled = MutableStateFlow(true)
|
||||||
override val dataEnabled = _dataEnabled
|
override val dataEnabled = _dataEnabled
|
||||||
|
|
||||||
|
private val _isDefaultDataSubscription = MutableStateFlow(true)
|
||||||
|
override val isDefaultDataSubscription = _isDefaultDataSubscription
|
||||||
|
|
||||||
fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
|
fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
|
||||||
_subscriptionsModelFlow.value = model
|
_subscriptionsModelFlow.value = model
|
||||||
}
|
}
|
||||||
@@ -34,4 +36,8 @@ class FakeMobileConnectionRepository : MobileConnectionRepository {
|
|||||||
fun setDataEnabled(enabled: Boolean) {
|
fun setDataEnabled(enabled: Boolean) {
|
||||||
_dataEnabled.value = enabled
|
_dataEnabled.value = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setIsDefaultDataSubscription(isDefault: Boolean) {
|
||||||
|
_isDefaultDataSubscription.value = isDefault
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,9 @@
|
|||||||
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
||||||
|
|
||||||
import android.telephony.SubscriptionInfo
|
import android.telephony.SubscriptionInfo
|
||||||
import android.telephony.SubscriptionManager
|
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
||||||
import com.android.settingslib.mobile.MobileMappings.Config
|
import com.android.settingslib.mobile.MobileMappings.Config
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
@@ -26,18 +27,26 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository {
|
|||||||
private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
|
private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
|
||||||
override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
|
override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
|
||||||
|
|
||||||
private val _activeMobileDataSubscriptionId =
|
private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
|
||||||
MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
|
||||||
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
|
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
|
||||||
|
|
||||||
private val _defaultDataSubRatConfig = MutableStateFlow(Config())
|
private val _defaultDataSubRatConfig = MutableStateFlow(Config())
|
||||||
override val defaultDataSubRatConfig = _defaultDataSubRatConfig
|
override val defaultDataSubRatConfig = _defaultDataSubRatConfig
|
||||||
|
|
||||||
|
private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
|
||||||
|
override val defaultDataSubId = _defaultDataSubId
|
||||||
|
|
||||||
|
private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel())
|
||||||
|
override val defaultMobileNetworkConnectivity = _mobileConnectivity
|
||||||
|
|
||||||
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
|
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
|
||||||
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
|
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
|
||||||
return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
|
return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
|
||||||
|
override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
|
||||||
|
|
||||||
fun setSubscriptions(subs: List<SubscriptionInfo>) {
|
fun setSubscriptions(subs: List<SubscriptionInfo>) {
|
||||||
_subscriptionsFlow.value = subs
|
_subscriptionsFlow.value = subs
|
||||||
}
|
}
|
||||||
@@ -46,6 +55,18 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository {
|
|||||||
_defaultDataSubRatConfig.value = config
|
_defaultDataSubRatConfig.value = config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setDefaultDataSubId(id: Int) {
|
||||||
|
_defaultDataSubId.value = id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMobileConnectivity(model: MobileConnectivityModel) {
|
||||||
|
_mobileConnectivity.value = model
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun triggerGlobalMobileDataSettingChangedEvent() {
|
||||||
|
_globalMobileDataSettingChangedEvent.emit(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
fun setActiveMobileDataSubscriptionId(subId: Int) {
|
fun setActiveMobileDataSubscriptionId(subId: Int) {
|
||||||
_activeMobileDataSubscriptionId.value = subId
|
_activeMobileDataSubscriptionId.value = subId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,12 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
/** Defaults to `true` */
|
/** Defaults to `true` */
|
||||||
class FakeUserSetupRepository : UserSetupRepository {
|
class FakeUserSetupRepository : UserSetupRepository {
|
||||||
private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||||
override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
|
override val isUserSetupFlow = _isUserSetup
|
||||||
|
|
||||||
fun setUserSetup(setup: Boolean) {
|
fun setUserSetup(setup: Boolean) {
|
||||||
_isUserSetup.value = setup
|
_isUserSetup.value = setup
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
||||||
|
|
||||||
|
import android.os.UserHandle
|
||||||
|
import android.provider.Settings
|
||||||
import android.telephony.CellSignalStrengthCdma
|
import android.telephony.CellSignalStrengthCdma
|
||||||
import android.telephony.ServiceState
|
import android.telephony.ServiceState
|
||||||
import android.telephony.SignalStrength
|
import android.telephony.SignalStrength
|
||||||
@@ -42,6 +44,7 @@ import com.android.systemui.util.mockito.any
|
|||||||
import com.android.systemui.util.mockito.argumentCaptor
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
import com.android.systemui.util.mockito.mock
|
import com.android.systemui.util.mockito.mock
|
||||||
import com.android.systemui.util.mockito.whenever
|
import com.android.systemui.util.mockito.whenever
|
||||||
|
import com.android.systemui.util.settings.FakeSettings
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -67,16 +70,23 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||||
|
|
||||||
private val scope = CoroutineScope(IMMEDIATE)
|
private val scope = CoroutineScope(IMMEDIATE)
|
||||||
|
private val globalSettings = FakeSettings()
|
||||||
|
private val connectionsRepo = FakeMobileConnectionsRepository()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
globalSettings.userId = UserHandle.USER_ALL
|
||||||
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
|
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
|
||||||
|
|
||||||
underTest =
|
underTest =
|
||||||
MobileConnectionRepositoryImpl(
|
MobileConnectionRepositoryImpl(
|
||||||
|
context,
|
||||||
SUB_1_ID,
|
SUB_1_ID,
|
||||||
telephonyManager,
|
telephonyManager,
|
||||||
|
globalSettings,
|
||||||
|
connectionsRepo.defaultDataSubId,
|
||||||
|
connectionsRepo.globalMobileDataSettingChangedEvent,
|
||||||
IMMEDIATE,
|
IMMEDIATE,
|
||||||
logger,
|
logger,
|
||||||
scope,
|
scope,
|
||||||
@@ -290,14 +300,20 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun dataEnabled_isEnabled() =
|
fun dataEnabled_initial_false() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
|
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
|
||||||
|
|
||||||
var latest: Boolean? = null
|
assertThat(underTest.dataEnabled.value).isFalse()
|
||||||
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
|
}
|
||||||
|
|
||||||
assertThat(latest).isTrue()
|
@Test
|
||||||
|
fun dataEnabled_isEnabled_true() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
|
||||||
|
val job = underTest.dataEnabled.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(underTest.dataEnabled.value).isTrue()
|
||||||
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
@@ -306,10 +322,59 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
|
|||||||
fun dataEnabled_isDisabled() =
|
fun dataEnabled_isDisabled() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
|
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
|
||||||
|
val job = underTest.dataEnabled.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(underTest.dataEnabled.value).isFalse()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isDefaultDataSubscription_isDefault() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
connectionsRepo.setDefaultDataSubId(SUB_1_ID)
|
||||||
|
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isTrue()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isDefaultDataSubscription_isNotDefault() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
// Our subId is SUB_1_ID
|
||||||
|
connectionsRepo.setDefaultDataSubId(123)
|
||||||
|
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isFalse()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isDataConnectionAllowed_subIdSettingUpdate_valueUpdated() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
val subIdSettingName = "${Settings.Global.MOBILE_DATA}$SUB_1_ID"
|
||||||
|
|
||||||
var latest: Boolean? = null
|
var latest: Boolean? = null
|
||||||
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
|
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
// We don't read the setting directly, we query telephony when changes happen
|
||||||
|
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
|
||||||
|
globalSettings.putInt(subIdSettingName, 0)
|
||||||
|
assertThat(latest).isFalse()
|
||||||
|
|
||||||
|
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
|
||||||
|
globalSettings.putInt(subIdSettingName, 1)
|
||||||
|
assertThat(latest).isTrue()
|
||||||
|
|
||||||
|
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
|
||||||
|
globalSettings.putInt(subIdSettingName, 0)
|
||||||
assertThat(latest).isFalse()
|
assertThat(latest).isFalse()
|
||||||
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
|
|||||||
@@ -16,26 +16,33 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
package com.android.systemui.statusbar.pipeline.mobile.data.repository
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
|
||||||
|
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
||||||
|
import android.provider.Settings
|
||||||
import android.telephony.SubscriptionInfo
|
import android.telephony.SubscriptionInfo
|
||||||
import android.telephony.SubscriptionManager
|
import android.telephony.SubscriptionManager
|
||||||
import android.telephony.TelephonyCallback
|
import android.telephony.TelephonyCallback
|
||||||
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
|
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.internal.telephony.PhoneConstants
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||||
import com.android.systemui.util.mockito.any
|
import com.android.systemui.util.mockito.any
|
||||||
import com.android.systemui.util.mockito.argumentCaptor
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
import com.android.systemui.util.mockito.mock
|
import com.android.systemui.util.mockito.mock
|
||||||
import com.android.systemui.util.mockito.nullable
|
|
||||||
import com.android.systemui.util.mockito.whenever
|
import com.android.systemui.util.mockito.whenever
|
||||||
|
import com.android.systemui.util.settings.FakeSettings
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@@ -43,7 +50,6 @@ import org.junit.After
|
|||||||
import org.junit.Assert.assertThrows
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.ArgumentMatchers
|
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
@@ -54,32 +60,26 @@ import org.mockito.MockitoAnnotations
|
|||||||
class MobileConnectionsRepositoryTest : SysuiTestCase() {
|
class MobileConnectionsRepositoryTest : SysuiTestCase() {
|
||||||
private lateinit var underTest: MobileConnectionsRepositoryImpl
|
private lateinit var underTest: MobileConnectionsRepositoryImpl
|
||||||
|
|
||||||
|
@Mock private lateinit var connectivityManager: ConnectivityManager
|
||||||
@Mock private lateinit var subscriptionManager: SubscriptionManager
|
@Mock private lateinit var subscriptionManager: SubscriptionManager
|
||||||
@Mock private lateinit var telephonyManager: TelephonyManager
|
@Mock private lateinit var telephonyManager: TelephonyManager
|
||||||
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||||
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
|
|
||||||
|
|
||||||
private val scope = CoroutineScope(IMMEDIATE)
|
private val scope = CoroutineScope(IMMEDIATE)
|
||||||
|
private val globalSettings = FakeSettings()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
whenever(
|
|
||||||
broadcastDispatcher.broadcastFlow(
|
|
||||||
any(),
|
|
||||||
nullable(),
|
|
||||||
ArgumentMatchers.anyInt(),
|
|
||||||
nullable(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.thenReturn(flowOf(Unit))
|
|
||||||
|
|
||||||
underTest =
|
underTest =
|
||||||
MobileConnectionsRepositoryImpl(
|
MobileConnectionsRepositoryImpl(
|
||||||
|
connectivityManager,
|
||||||
subscriptionManager,
|
subscriptionManager,
|
||||||
telephonyManager,
|
telephonyManager,
|
||||||
logger,
|
logger,
|
||||||
broadcastDispatcher,
|
fakeBroadcastDispatcher,
|
||||||
|
globalSettings,
|
||||||
context,
|
context,
|
||||||
IMMEDIATE,
|
IMMEDIATE,
|
||||||
scope,
|
scope,
|
||||||
@@ -214,6 +214,139 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
|
|||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDefaultDataSubId_updatesOnBroadcast() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var latest: Int? = null
|
||||||
|
val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
|
||||||
|
receiver.onReceive(
|
||||||
|
context,
|
||||||
|
Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
|
||||||
|
.putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(SUB_2_ID)
|
||||||
|
|
||||||
|
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
|
||||||
|
receiver.onReceive(
|
||||||
|
context,
|
||||||
|
Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
|
||||||
|
.putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(SUB_1_ID)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mobileConnectivity_default() {
|
||||||
|
assertThat(underTest.defaultMobileNetworkConnectivity.value)
|
||||||
|
.isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mobileConnectivity_isConnected_isValidated() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
val caps = createCapabilities(connected = true, validated = true)
|
||||||
|
|
||||||
|
var latest: MobileConnectivityModel? = null
|
||||||
|
val job =
|
||||||
|
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
|
||||||
|
|
||||||
|
assertThat(latest)
|
||||||
|
.isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = true))
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun globalMobileDataSettingsChangedEvent_producesOnSettingChange() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var produced = false
|
||||||
|
val job =
|
||||||
|
underTest.globalMobileDataSettingChangedEvent
|
||||||
|
.onEach { produced = true }
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(produced).isFalse()
|
||||||
|
|
||||||
|
globalSettings.putInt(Settings.Global.MOBILE_DATA, 0)
|
||||||
|
|
||||||
|
assertThat(produced).isTrue()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mobileConnectivity_isConnected_isNotValidated() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
val caps = createCapabilities(connected = true, validated = false)
|
||||||
|
|
||||||
|
var latest: MobileConnectivityModel? = null
|
||||||
|
val job =
|
||||||
|
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
|
||||||
|
|
||||||
|
assertThat(latest)
|
||||||
|
.isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = false))
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mobileConnectivity_isNotConnected_isNotValidated() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
val caps = createCapabilities(connected = false, validated = false)
|
||||||
|
|
||||||
|
var latest: MobileConnectivityModel? = null
|
||||||
|
val job =
|
||||||
|
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
|
||||||
|
|
||||||
|
assertThat(latest)
|
||||||
|
.isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** In practice, I don't think this state can ever happen (!connected, validated) */
|
||||||
|
@Test
|
||||||
|
fun mobileConnectivity_isNotConnected_isValidated() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
val caps = createCapabilities(connected = false, validated = true)
|
||||||
|
|
||||||
|
var latest: MobileConnectivityModel? = null
|
||||||
|
val job =
|
||||||
|
underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(MobileConnectivityModel(false, true))
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
|
||||||
|
mock<NetworkCapabilities>().also {
|
||||||
|
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)
|
||||||
|
whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(validated)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
|
||||||
|
val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
|
||||||
|
verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
|
||||||
|
return callbackCaptor.value!!
|
||||||
|
}
|
||||||
|
|
||||||
private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
|
private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
|
||||||
val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
|
val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
|
||||||
verify(subscriptionManager)
|
verify(subscriptionManager)
|
||||||
@@ -242,5 +375,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
|
|||||||
private const val SUB_2_ID = 2
|
private const val SUB_2_ID = 2
|
||||||
private val SUB_2 =
|
private val SUB_2 =
|
||||||
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
|
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
|
||||||
|
|
||||||
|
private const val NET_ID = 123
|
||||||
|
private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,18 +28,23 @@ class FakeMobileIconInteractor : MobileIconInteractor {
|
|||||||
private val _isEmergencyOnly = MutableStateFlow(false)
|
private val _isEmergencyOnly = MutableStateFlow(false)
|
||||||
override val isEmergencyOnly = _isEmergencyOnly
|
override val isEmergencyOnly = _isEmergencyOnly
|
||||||
|
|
||||||
|
private val _isFailedConnection = MutableStateFlow(false)
|
||||||
|
override val isDefaultConnectionFailed = _isFailedConnection
|
||||||
|
|
||||||
|
override val isDataConnected = MutableStateFlow(true)
|
||||||
|
|
||||||
private val _isDataEnabled = MutableStateFlow(true)
|
private val _isDataEnabled = MutableStateFlow(true)
|
||||||
override val isDataEnabled = _isDataEnabled
|
override val isDataEnabled = _isDataEnabled
|
||||||
|
|
||||||
|
private val _isDefaultDataEnabled = MutableStateFlow(true)
|
||||||
|
override val isDefaultDataEnabled = _isDefaultDataEnabled
|
||||||
|
|
||||||
private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
|
private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
|
||||||
override val level = _level
|
override val level = _level
|
||||||
|
|
||||||
private val _numberOfLevels = MutableStateFlow(4)
|
private val _numberOfLevels = MutableStateFlow(4)
|
||||||
override val numberOfLevels = _numberOfLevels
|
override val numberOfLevels = _numberOfLevels
|
||||||
|
|
||||||
private val _cutOut = MutableStateFlow(false)
|
|
||||||
override val cutOut = _cutOut
|
|
||||||
|
|
||||||
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
|
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
|
||||||
_iconGroup.value = group
|
_iconGroup.value = group
|
||||||
}
|
}
|
||||||
@@ -52,6 +57,14 @@ class FakeMobileIconInteractor : MobileIconInteractor {
|
|||||||
_isDataEnabled.value = enabled
|
_isDataEnabled.value = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setIsDefaultDataEnabled(disabled: Boolean) {
|
||||||
|
_isDefaultDataEnabled.value = disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setIsFailedConnection(failed: Boolean) {
|
||||||
|
_isFailedConnection.value = failed
|
||||||
|
}
|
||||||
|
|
||||||
fun setLevel(level: Int) {
|
fun setLevel(level: Int) {
|
||||||
_level.value = level
|
_level.value = level
|
||||||
}
|
}
|
||||||
@@ -59,8 +72,4 @@ class FakeMobileIconInteractor : MobileIconInteractor {
|
|||||||
fun setNumberOfLevels(num: Int) {
|
fun setNumberOfLevels(num: Int) {
|
||||||
_numberOfLevels.value = num
|
_numberOfLevels.value = num
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCutOut(cutOut: Boolean) {
|
|
||||||
_cutOut.value = cutOut
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ import com.android.settingslib.mobile.TelephonyIcons
|
|||||||
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
|
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) :
|
class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
|
||||||
MobileIconsInteractor {
|
|
||||||
val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
|
val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
|
||||||
val LTE_KEY = mobileMappings.toIconKey(LTE)
|
val LTE_KEY = mobileMappings.toIconKey(LTE)
|
||||||
val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
|
val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
|
||||||
@@ -46,9 +45,14 @@ class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy)
|
|||||||
FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
|
FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override val isDefaultConnectionFailed = MutableStateFlow(false)
|
||||||
|
|
||||||
private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
|
private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
|
||||||
override val filteredSubscriptions = _filteredSubscriptions
|
override val filteredSubscriptions = _filteredSubscriptions
|
||||||
|
|
||||||
|
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
|
||||||
|
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
|
||||||
|
|
||||||
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
|
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
|
||||||
override val defaultMobileIconMapping = _defaultMobileIconMapping
|
override val defaultMobileIconMapping = _defaultMobileIconMapping
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
|
|||||||
import com.android.settingslib.SignalIcon.MobileIconGroup
|
import com.android.settingslib.SignalIcon.MobileIconGroup
|
||||||
import com.android.settingslib.mobile.TelephonyIcons
|
import com.android.settingslib.mobile.TelephonyIcons
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
|
||||||
@@ -34,6 +35,7 @@ import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsPro
|
|||||||
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
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -49,12 +51,17 @@ class MobileIconInteractorTest : SysuiTestCase() {
|
|||||||
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
|
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
|
||||||
private val connectionRepository = FakeMobileConnectionRepository()
|
private val connectionRepository = FakeMobileConnectionRepository()
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(IMMEDIATE)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
underTest =
|
underTest =
|
||||||
MobileIconInteractorImpl(
|
MobileIconInteractorImpl(
|
||||||
|
scope,
|
||||||
|
mobileIconsInteractor.activeDataConnectionHasDataEnabled,
|
||||||
mobileIconsInteractor.defaultMobileIconMapping,
|
mobileIconsInteractor.defaultMobileIconMapping,
|
||||||
mobileIconsInteractor.defaultMobileIconGroup,
|
mobileIconsInteractor.defaultMobileIconGroup,
|
||||||
|
mobileIconsInteractor.isDefaultConnectionFailed,
|
||||||
mobileMappingsProxy,
|
mobileMappingsProxy,
|
||||||
connectionRepository,
|
connectionRepository,
|
||||||
)
|
)
|
||||||
@@ -196,6 +203,66 @@ class MobileIconInteractorTest : SysuiTestCase() {
|
|||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_isDefaultDataEnabled_matchesParent() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
|
||||||
|
assertThat(latest).isTrue()
|
||||||
|
|
||||||
|
mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
|
||||||
|
assertThat(latest).isFalse()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_isDefaultConnectionFailed_matchedParent() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
val job = underTest.isDefaultConnectionFailed.launchIn(this)
|
||||||
|
|
||||||
|
mobileIconsInteractor.isDefaultConnectionFailed.value = false
|
||||||
|
assertThat(underTest.isDefaultConnectionFailed.value).isFalse()
|
||||||
|
|
||||||
|
mobileIconsInteractor.isDefaultConnectionFailed.value = true
|
||||||
|
assertThat(underTest.isDefaultConnectionFailed.value).isTrue()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dataState_connected() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
connectionRepository.setMobileSubscriptionModel(
|
||||||
|
MobileSubscriptionModel(dataConnectionState = DataConnectionState.Connected)
|
||||||
|
)
|
||||||
|
yield()
|
||||||
|
|
||||||
|
assertThat(latest).isTrue()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dataState_notConnected() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
connectionRepository.setMobileSubscriptionModel(
|
||||||
|
MobileSubscriptionModel(dataConnectionState = DataConnectionState.Disconnected)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(latest).isFalse()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,10 @@
|
|||||||
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
|
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
|
||||||
|
|
||||||
import android.telephony.SubscriptionInfo
|
import android.telephony.SubscriptionInfo
|
||||||
|
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
|
||||||
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.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
|
||||||
@@ -32,6 +34,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.yield
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -168,6 +171,92 @@ class MobileIconsInteractorTest : SysuiTestCase() {
|
|||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun activeDataConnection_turnedOn() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
CONNECTION_1.setDataEnabled(true)
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job =
|
||||||
|
underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isTrue()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun activeDataConnection_turnedOff() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
CONNECTION_1.setDataEnabled(true)
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job =
|
||||||
|
underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
CONNECTION_1.setDataEnabled(false)
|
||||||
|
yield()
|
||||||
|
|
||||||
|
assertThat(latest).isFalse()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun activeDataConnection_invalidSubId() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job =
|
||||||
|
underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
|
||||||
|
yield()
|
||||||
|
|
||||||
|
// An invalid active subId should tell us that data is off
|
||||||
|
assertThat(latest).isFalse()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun failedConnection_connected_validated_notFailed() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
|
||||||
|
connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true))
|
||||||
|
yield()
|
||||||
|
|
||||||
|
assertThat(latest).isFalse()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun failedConnection_notConnected_notValidated_notFailed() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
connectionsRepository.setMobileConnectivity(MobileConnectivityModel(false, false))
|
||||||
|
yield()
|
||||||
|
|
||||||
|
assertThat(latest).isFalse()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun failedConnection_connected_notValidated_failed() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, false))
|
||||||
|
yield()
|
||||||
|
|
||||||
|
assertThat(latest).isTrue()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||||
|
|
||||||
|
|||||||
@@ -46,10 +46,12 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
|||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
interactor.apply {
|
interactor.apply {
|
||||||
setLevel(1)
|
setLevel(1)
|
||||||
setCutOut(false)
|
setIsDefaultDataEnabled(true)
|
||||||
|
setIsFailedConnection(false)
|
||||||
setIconGroup(THREE_G)
|
setIconGroup(THREE_G)
|
||||||
setIsEmergencyOnly(false)
|
setIsEmergencyOnly(false)
|
||||||
setNumberOfLevels(4)
|
setNumberOfLevels(4)
|
||||||
|
isDataConnected.value = true
|
||||||
}
|
}
|
||||||
underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
|
underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
|
||||||
}
|
}
|
||||||
@@ -59,8 +61,23 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
|||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
var latest: Int? = null
|
var latest: Int? = null
|
||||||
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
|
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
|
||||||
|
val expected = defaultSignal()
|
||||||
|
|
||||||
assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
|
assertThat(latest).isEqualTo(expected)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun iconId_cutout_whenDefaultDataDisabled() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
interactor.setIsDefaultDataEnabled(false)
|
||||||
|
|
||||||
|
var latest: Int? = null
|
||||||
|
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
|
||||||
|
val expected = defaultSignal(level = 1, connected = false)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(expected)
|
||||||
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
@@ -96,6 +113,44 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
|||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun networkType_nullWhenFailedConnection() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
interactor.setIconGroup(THREE_G)
|
||||||
|
interactor.setIsDataEnabled(true)
|
||||||
|
interactor.setIsFailedConnection(true)
|
||||||
|
var latest: Icon? = null
|
||||||
|
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isNull()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun networkType_nullWhenDataDisconnects() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
val initial =
|
||||||
|
Icon.Resource(
|
||||||
|
THREE_G.dataType,
|
||||||
|
ContentDescription.Resource(THREE_G.dataContentDescription)
|
||||||
|
)
|
||||||
|
|
||||||
|
interactor.setIconGroup(THREE_G)
|
||||||
|
var latest: Icon? = null
|
||||||
|
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
interactor.setIconGroup(THREE_G)
|
||||||
|
assertThat(latest).isEqualTo(initial)
|
||||||
|
|
||||||
|
interactor.isDataConnected.value = false
|
||||||
|
yield()
|
||||||
|
|
||||||
|
assertThat(latest).isNull()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun networkType_null_changeToDisabled() =
|
fun networkType_null_changeToDisabled() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
@@ -119,6 +174,14 @@ class MobileIconViewModelTest : SysuiTestCase() {
|
|||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Convenience constructor for these tests */
|
||||||
|
private fun defaultSignal(
|
||||||
|
level: Int = 1,
|
||||||
|
connected: Boolean = true,
|
||||||
|
): Int {
|
||||||
|
return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||||
private const val SUB_1_ID = 1
|
private const val SUB_1_ID = 1
|
||||||
|
|||||||
Reference in New Issue
Block a user