Merge changes Id53733ac,I38e917cf into tm-qpr-dev
* changes: [Sb refactor] Move subIdFlow into its own repository [Status bar refactor] Implement the lookup from network type to RAT icon
This commit is contained in:
@@ -18,10 +18,14 @@ package com.android.systemui.statusbar.pipeline.dagger
|
|||||||
|
|
||||||
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
|
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
|
||||||
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
|
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryImpl
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
|
||||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
|
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
|
||||||
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
|
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
|
||||||
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
|
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
|
||||||
@@ -41,10 +45,16 @@ abstract class StatusBarPipelineModule {
|
|||||||
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
|
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun mobileSubscriptionRepository(
|
abstract fun mobileConnectionsRepository(
|
||||||
impl: MobileSubscriptionRepositoryImpl
|
impl: MobileConnectionsRepositoryImpl
|
||||||
): MobileSubscriptionRepository
|
): MobileConnectionsRepository
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
|
abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import android.telephony.TelephonyCallback.ServiceStateListener
|
|||||||
import android.telephony.TelephonyCallback.SignalStrengthsListener
|
import android.telephony.TelephonyCallback.SignalStrengthsListener
|
||||||
import android.telephony.TelephonyDisplayInfo
|
import android.telephony.TelephonyDisplayInfo
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
|
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data class containing all of the relevant information for a particular line of service, known as
|
* Data class containing all of the relevant information for a particular line of service, known as
|
||||||
@@ -57,6 +58,11 @@ data class MobileSubscriptionModel(
|
|||||||
/** From [CarrierNetworkListener.onCarrierNetworkChange] */
|
/** From [CarrierNetworkListener.onCarrierNetworkChange] */
|
||||||
val carrierNetworkChangeActive: Boolean? = null,
|
val carrierNetworkChangeActive: Boolean? = null,
|
||||||
|
|
||||||
/** From [DisplayInfoListener.onDisplayInfoChanged] */
|
/**
|
||||||
val displayInfo: TelephonyDisplayInfo? = null
|
* From [DisplayInfoListener.onDisplayInfoChanged].
|
||||||
|
*
|
||||||
|
* [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
|
||||||
|
* [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
|
||||||
|
*/
|
||||||
|
val resolvedNetworkType: ResolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* 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.telephony.Annotation.NetworkType
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SysUI type to represent the [NetworkType] that we pull out of [TelephonyDisplayInfo]. Depending
|
||||||
|
* on whether or not the display info contains an override type, we may have to call different
|
||||||
|
* methods on [MobileMappingsProxy] to generate an icon lookup key.
|
||||||
|
*/
|
||||||
|
sealed interface ResolvedNetworkType {
|
||||||
|
@NetworkType val type: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DefaultNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
|
||||||
|
|
||||||
|
data class OverrideNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
|
||||||
@@ -21,23 +21,18 @@ 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.TelephonyCallback.ActiveDataSubscriptionIdListener
|
|
||||||
import android.telephony.TelephonyCallback.CarrierNetworkListener
|
|
||||||
import android.telephony.TelephonyCallback.DataActivityListener
|
|
||||||
import android.telephony.TelephonyCallback.DataConnectionStateListener
|
|
||||||
import android.telephony.TelephonyCallback.DisplayInfoListener
|
|
||||||
import android.telephony.TelephonyCallback.ServiceStateListener
|
|
||||||
import android.telephony.TelephonyCallback.SignalStrengthsListener
|
|
||||||
import android.telephony.TelephonyDisplayInfo
|
import android.telephony.TelephonyDisplayInfo
|
||||||
|
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
import androidx.annotation.VisibleForTesting
|
|
||||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
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.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.shared.ConnectivityPipelineLogger
|
||||||
|
import java.lang.IllegalStateException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -47,110 +42,64 @@ import kotlinx.coroutines.channels.awaitClose
|
|||||||
import kotlinx.coroutines.flow.Flow
|
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.onEach
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repo for monitoring the complete active subscription info list, to be consumed and filtered based
|
* Every mobile line of service can be identified via a [SubscriptionInfo] object. We set up a
|
||||||
* on various policy
|
* repository for each individual, tracked subscription via [MobileConnectionsRepository], and this
|
||||||
|
* repository is responsible for setting up a [TelephonyManager] object tied to its subscriptionId
|
||||||
|
*
|
||||||
|
* There should only ever be one [MobileConnectionRepository] per subscription, since
|
||||||
|
* [TelephonyManager] limits the number of callbacks that can be registered per process.
|
||||||
|
*
|
||||||
|
* This repository should have all of the relevant information for a single line of service, which
|
||||||
|
* eventually becomes a single icon in the status bar.
|
||||||
*/
|
*/
|
||||||
interface MobileSubscriptionRepository {
|
interface MobileConnectionRepository {
|
||||||
/** Observable list of current mobile subscriptions */
|
/**
|
||||||
val subscriptionsFlow: Flow<List<SubscriptionInfo>>
|
* A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
|
||||||
|
* listener + model.
|
||||||
/** Observable for the subscriptionId of the current mobile data connection */
|
*/
|
||||||
val activeMobileDataSubscriptionId: Flow<Int>
|
val subscriptionModelFlow: Flow<MobileSubscriptionModel>
|
||||||
|
|
||||||
/** Get or create an observable for the given subscription ID */
|
|
||||||
fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@SysUISingleton
|
class MobileConnectionRepositoryImpl(
|
||||||
class MobileSubscriptionRepositoryImpl
|
private val subId: Int,
|
||||||
@Inject
|
telephonyManager: TelephonyManager,
|
||||||
constructor(
|
bgDispatcher: CoroutineDispatcher,
|
||||||
private val subscriptionManager: SubscriptionManager,
|
logger: ConnectivityPipelineLogger,
|
||||||
private val telephonyManager: TelephonyManager,
|
scope: CoroutineScope,
|
||||||
@Background private val bgDispatcher: CoroutineDispatcher,
|
) : MobileConnectionRepository {
|
||||||
@Application private val scope: CoroutineScope,
|
init {
|
||||||
) : MobileSubscriptionRepository {
|
if (telephonyManager.subscriptionId != subId) {
|
||||||
private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()
|
throw IllegalStateException(
|
||||||
|
"TelephonyManager should be created with subId($subId). " +
|
||||||
/**
|
"Found ${telephonyManager.subscriptionId} instead."
|
||||||
* State flow that emits the set of mobile data subscriptions, each represented by its own
|
|
||||||
* [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
|
|
||||||
* info object, but for now we keep track of the infos themselves.
|
|
||||||
*/
|
|
||||||
override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
|
|
||||||
conflatedCallbackFlow {
|
|
||||||
val callback =
|
|
||||||
object : SubscriptionManager.OnSubscriptionsChangedListener() {
|
|
||||||
override fun onSubscriptionsChanged() {
|
|
||||||
trySend(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriptionManager.addOnSubscriptionsChangedListener(
|
|
||||||
bgDispatcher.asExecutor(),
|
|
||||||
callback,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
|
|
||||||
}
|
|
||||||
.mapLatest { fetchSubscriptionsList() }
|
|
||||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
|
|
||||||
|
|
||||||
/** StateFlow that keeps track of the current active mobile data subscription */
|
|
||||||
override val activeMobileDataSubscriptionId: StateFlow<Int> =
|
|
||||||
conflatedCallbackFlow {
|
|
||||||
val callback =
|
|
||||||
object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
|
|
||||||
override fun onActiveDataSubscriptionIdChanged(subId: Int) {
|
|
||||||
trySend(subId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
|
override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
|
||||||
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
|
|
||||||
}
|
|
||||||
.stateIn(
|
|
||||||
scope,
|
|
||||||
started = SharingStarted.WhileSubscribed(),
|
|
||||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Each mobile subscription needs its own flow, which comes from registering listeners on the
|
|
||||||
* system. Use this method to create those flows and cache them for reuse
|
|
||||||
*/
|
|
||||||
override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
|
|
||||||
return subIdFlowCache[subId]
|
|
||||||
?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
|
|
||||||
|
|
||||||
private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
|
|
||||||
var state = MobileSubscriptionModel()
|
var state = MobileSubscriptionModel()
|
||||||
conflatedCallbackFlow {
|
conflatedCallbackFlow {
|
||||||
val phony = telephonyManager.createForSubscriptionId(subId)
|
|
||||||
// TODO (b/240569788): log all of these into the connectivity logger
|
// TODO (b/240569788): log all of these into the connectivity logger
|
||||||
val callback =
|
val callback =
|
||||||
object :
|
object :
|
||||||
TelephonyCallback(),
|
TelephonyCallback(),
|
||||||
ServiceStateListener,
|
TelephonyCallback.ServiceStateListener,
|
||||||
SignalStrengthsListener,
|
TelephonyCallback.SignalStrengthsListener,
|
||||||
DataConnectionStateListener,
|
TelephonyCallback.DataConnectionStateListener,
|
||||||
DataActivityListener,
|
TelephonyCallback.DataActivityListener,
|
||||||
CarrierNetworkListener,
|
TelephonyCallback.CarrierNetworkListener,
|
||||||
DisplayInfoListener {
|
TelephonyCallback.DisplayInfoListener {
|
||||||
override fun onServiceStateChanged(serviceState: ServiceState) {
|
override fun onServiceStateChanged(serviceState: ServiceState) {
|
||||||
state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
|
state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
|
||||||
trySend(state)
|
trySend(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
|
override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
|
||||||
val cdmaLevel =
|
val cdmaLevel =
|
||||||
signalStrength
|
signalStrength
|
||||||
@@ -173,6 +122,7 @@ constructor(
|
|||||||
)
|
)
|
||||||
trySend(state)
|
trySend(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDataConnectionStateChanged(
|
override fun onDataConnectionStateChanged(
|
||||||
dataState: Int,
|
dataState: Int,
|
||||||
networkType: Int
|
networkType: Int
|
||||||
@@ -180,31 +130,56 @@ constructor(
|
|||||||
state = state.copy(dataConnectionState = dataState)
|
state = state.copy(dataConnectionState = dataState)
|
||||||
trySend(state)
|
trySend(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDataActivity(direction: Int) {
|
override fun onDataActivity(direction: Int) {
|
||||||
state = state.copy(dataActivityDirection = direction)
|
state = state.copy(dataActivityDirection = direction)
|
||||||
trySend(state)
|
trySend(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCarrierNetworkChange(active: Boolean) {
|
override fun onCarrierNetworkChange(active: Boolean) {
|
||||||
state = state.copy(carrierNetworkChangeActive = active)
|
state = state.copy(carrierNetworkChangeActive = active)
|
||||||
trySend(state)
|
trySend(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisplayInfoChanged(
|
override fun onDisplayInfoChanged(
|
||||||
telephonyDisplayInfo: TelephonyDisplayInfo
|
telephonyDisplayInfo: TelephonyDisplayInfo
|
||||||
) {
|
) {
|
||||||
state = state.copy(displayInfo = telephonyDisplayInfo)
|
val networkType =
|
||||||
|
if (
|
||||||
|
telephonyDisplayInfo.overrideNetworkType ==
|
||||||
|
OVERRIDE_NETWORK_TYPE_NONE
|
||||||
|
) {
|
||||||
|
DefaultNetworkType(telephonyDisplayInfo.networkType)
|
||||||
|
} else {
|
||||||
|
OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
|
||||||
|
}
|
||||||
|
state = state.copy(resolvedNetworkType = networkType)
|
||||||
trySend(state)
|
trySend(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
|
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
|
||||||
awaitClose {
|
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
|
||||||
phony.unregisterTelephonyCallback(callback)
|
|
||||||
// Release the cached flow
|
|
||||||
subIdFlowCache.remove(subId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) }
|
||||||
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
|
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
|
class Factory
|
||||||
withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val telephonyManager: TelephonyManager,
|
||||||
|
private val logger: ConnectivityPipelineLogger,
|
||||||
|
@Background private val bgDispatcher: CoroutineDispatcher,
|
||||||
|
@Application private val scope: CoroutineScope,
|
||||||
|
) {
|
||||||
|
fun build(subId: Int): MobileConnectionRepository {
|
||||||
|
return MobileConnectionRepositoryImpl(
|
||||||
|
subId,
|
||||||
|
telephonyManager.createForSubscriptionId(subId),
|
||||||
|
bgDispatcher,
|
||||||
|
logger,
|
||||||
|
scope,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* 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.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.telephony.CarrierConfigManager
|
||||||
|
import android.telephony.SubscriptionInfo
|
||||||
|
import android.telephony.SubscriptionManager
|
||||||
|
import android.telephony.TelephonyCallback
|
||||||
|
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
|
||||||
|
import android.telephony.TelephonyManager
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import com.android.settingslib.mobile.MobileMappings
|
||||||
|
import com.android.settingslib.mobile.MobileMappings.Config
|
||||||
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.asExecutor
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repo for monitoring the complete active subscription info list, to be consumed and filtered based
|
||||||
|
* on various policy
|
||||||
|
*/
|
||||||
|
interface MobileConnectionsRepository {
|
||||||
|
/** Observable list of current mobile subscriptions */
|
||||||
|
val subscriptionsFlow: Flow<List<SubscriptionInfo>>
|
||||||
|
|
||||||
|
/** Observable for the subscriptionId of the current mobile data connection */
|
||||||
|
val activeMobileDataSubscriptionId: Flow<Int>
|
||||||
|
|
||||||
|
/** Observable for [MobileMappings.Config] tracking the defaults */
|
||||||
|
val defaultDataSubRatConfig: StateFlow<Config>
|
||||||
|
|
||||||
|
/** Get or create a repository for the line of service for the given subscription ID */
|
||||||
|
fun getRepoForSubId(subId: Int): MobileConnectionRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@SysUISingleton
|
||||||
|
class MobileConnectionsRepositoryImpl
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val subscriptionManager: SubscriptionManager,
|
||||||
|
private val telephonyManager: TelephonyManager,
|
||||||
|
private val logger: ConnectivityPipelineLogger,
|
||||||
|
broadcastDispatcher: BroadcastDispatcher,
|
||||||
|
private val context: Context,
|
||||||
|
@Background private val bgDispatcher: CoroutineDispatcher,
|
||||||
|
@Application private val scope: CoroutineScope,
|
||||||
|
private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
|
||||||
|
) : MobileConnectionsRepository {
|
||||||
|
private val subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State flow that emits the set of mobile data subscriptions, each represented by its own
|
||||||
|
* [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
|
||||||
|
* info object, but for now we keep track of the infos themselves.
|
||||||
|
*/
|
||||||
|
override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
|
||||||
|
conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : SubscriptionManager.OnSubscriptionsChangedListener() {
|
||||||
|
override fun onSubscriptionsChanged() {
|
||||||
|
trySend(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionManager.addOnSubscriptionsChangedListener(
|
||||||
|
bgDispatcher.asExecutor(),
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
|
||||||
|
}
|
||||||
|
.mapLatest { fetchSubscriptionsList() }
|
||||||
|
.onEach { infos -> dropUnusedReposFromCache(infos) }
|
||||||
|
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
|
||||||
|
|
||||||
|
/** StateFlow that keeps track of the current active mobile data subscription */
|
||||||
|
override val activeMobileDataSubscriptionId: StateFlow<Int> =
|
||||||
|
conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
|
||||||
|
override fun onActiveDataSubscriptionIdChanged(subId: Int) {
|
||||||
|
trySend(subId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
|
||||||
|
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
|
||||||
|
}
|
||||||
|
.stateIn(
|
||||||
|
scope,
|
||||||
|
started = SharingStarted.WhileSubscribed(),
|
||||||
|
SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
private val defaultDataSubChangedEvent =
|
||||||
|
broadcastDispatcher.broadcastFlow(
|
||||||
|
IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val carrierConfigChangedEvent =
|
||||||
|
broadcastDispatcher.broadcastFlow(
|
||||||
|
IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Config] is an object that tracks relevant configuration flags for a given subscription ID.
|
||||||
|
* In the case of [MobileMappings], it's hard-coded to check the default data subscription's
|
||||||
|
* config, so this will apply to every icon that we care about.
|
||||||
|
*
|
||||||
|
* Relevant bits in the config are things like
|
||||||
|
* [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
|
||||||
|
*
|
||||||
|
* This flow will produce whenever the default data subscription or the carrier config changes.
|
||||||
|
*/
|
||||||
|
override val defaultDataSubRatConfig: StateFlow<Config> =
|
||||||
|
combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ ->
|
||||||
|
Config.readConfig(context)
|
||||||
|
}
|
||||||
|
.stateIn(
|
||||||
|
scope,
|
||||||
|
SharingStarted.WhileSubscribed(),
|
||||||
|
initialValue = Config.readConfig(context)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
|
||||||
|
if (!isValidSubId(subId)) {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"subscriptionId $subId is not in the list of valid subscriptions"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return subIdRepositoryCache[subId]
|
||||||
|
?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isValidSubId(subId: Int): Boolean {
|
||||||
|
subscriptionsFlow.value.forEach {
|
||||||
|
if (it.subscriptionId == subId) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
|
||||||
|
|
||||||
|
private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
|
||||||
|
return mobileConnectionRepositoryFactory.build(subId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
|
||||||
|
// Remove any connection repository from the cache that isn't in the new set of IDs. They
|
||||||
|
// will get garbage collected once their subscribers go away
|
||||||
|
val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
|
||||||
|
|
||||||
|
subIdRepositoryCache.keys.forEach {
|
||||||
|
if (!currentValidSubscriptionIds.contains(it)) {
|
||||||
|
subIdRepositoryCache.remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
|
||||||
|
withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
|
||||||
|
}
|
||||||
@@ -17,32 +17,58 @@
|
|||||||
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
|
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
|
||||||
|
|
||||||
import android.telephony.CarrierConfigManager
|
import android.telephony.CarrierConfigManager
|
||||||
import com.android.settingslib.SignalIcon
|
import com.android.settingslib.SignalIcon.MobileIconGroup
|
||||||
import com.android.settingslib.mobile.TelephonyIcons
|
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.OverrideNetworkType
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
|
||||||
|
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.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
interface MobileIconInteractor {
|
interface MobileIconInteractor {
|
||||||
/** Identifier for RAT type indicator */
|
/** Observable for RAT type (network type) indicator */
|
||||||
val iconGroup: Flow<SignalIcon.MobileIconGroup>
|
val networkTypeIconGroup: Flow<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: Flow<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: Flow<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: Flow<Int>
|
||||||
|
|
||||||
/** True when we want to draw an icon that makes room for the exclamation mark */
|
/** True when we want to draw an icon that makes room for the exclamation mark */
|
||||||
val cutOut: Flow<Boolean>
|
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 */
|
||||||
class MobileIconInteractorImpl(
|
class MobileIconInteractorImpl(
|
||||||
mobileStatusInfo: Flow<MobileSubscriptionModel>,
|
defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
|
||||||
|
defaultMobileIconGroup: Flow<MobileIconGroup>,
|
||||||
|
mobileMappingsProxy: MobileMappingsProxy,
|
||||||
|
connectionRepository: MobileConnectionRepository,
|
||||||
) : MobileIconInteractor {
|
) : MobileIconInteractor {
|
||||||
override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
|
private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
|
||||||
|
|
||||||
|
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
|
||||||
|
override val networkTypeIconGroup: Flow<MobileIconGroup> =
|
||||||
|
combine(
|
||||||
|
mobileStatusInfo,
|
||||||
|
defaultMobileIconMapping,
|
||||||
|
defaultMobileIconGroup,
|
||||||
|
) { info, mapping, defaultGroup ->
|
||||||
|
val lookupKey =
|
||||||
|
when (val resolved = info.resolvedNetworkType) {
|
||||||
|
is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
|
||||||
|
is OverrideNetworkType -> mobileMappingsProxy.toIconKeyOverride(resolved.type)
|
||||||
|
}
|
||||||
|
mapping[lookupKey] ?: defaultGroup
|
||||||
|
}
|
||||||
|
|
||||||
override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
|
override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
|
||||||
|
|
||||||
override val level: Flow<Int> =
|
override val level: Flow<Int> =
|
||||||
|
|||||||
@@ -19,29 +19,51 @@ 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 com.android.settingslib.SignalIcon.MobileIconGroup
|
||||||
|
import com.android.settingslib.mobile.TelephonyIcons
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
|
||||||
import com.android.systemui.util.CarrierConfigTracker
|
import com.android.systemui.util.CarrierConfigTracker
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Business layer logic for mobile subscription icons
|
* Business layer logic for the set of mobile subscription icons.
|
||||||
*
|
*
|
||||||
* Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s
|
* This interactor represents known set of mobile subscriptions (represented by [SubscriptionInfo]).
|
||||||
* that the system knows about. They obey policy that depends on OEM, carrier, and locale configs
|
* The list of subscriptions is filtered based on the opportunistic flags on the infos.
|
||||||
|
*
|
||||||
|
* It provides the default mapping between the telephony display info and the icon group that
|
||||||
|
* represents each RAT (LTE, 3G, etc.), as well as can produce an interactor for each individual
|
||||||
|
* icon
|
||||||
*/
|
*/
|
||||||
|
interface MobileIconsInteractor {
|
||||||
|
val filteredSubscriptions: Flow<List<SubscriptionInfo>>
|
||||||
|
val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
|
||||||
|
val defaultMobileIconGroup: Flow<MobileIconGroup>
|
||||||
|
val isUserSetup: Flow<Boolean>
|
||||||
|
fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
|
||||||
|
}
|
||||||
|
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class MobileIconsInteractor
|
class MobileIconsInteractorImpl
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val mobileSubscriptionRepo: MobileSubscriptionRepository,
|
private val mobileSubscriptionRepo: MobileConnectionsRepository,
|
||||||
private val carrierConfigTracker: CarrierConfigTracker,
|
private val carrierConfigTracker: CarrierConfigTracker,
|
||||||
|
private val mobileMappingsProxy: MobileMappingsProxy,
|
||||||
userSetupRepo: UserSetupRepository,
|
userSetupRepo: UserSetupRepository,
|
||||||
) {
|
@Application private val scope: CoroutineScope,
|
||||||
|
) : MobileIconsInteractor {
|
||||||
private val activeMobileDataSubscriptionId =
|
private val activeMobileDataSubscriptionId =
|
||||||
mobileSubscriptionRepo.activeMobileDataSubscriptionId
|
mobileSubscriptionRepo.activeMobileDataSubscriptionId
|
||||||
|
|
||||||
@@ -61,7 +83,7 @@ constructor(
|
|||||||
* [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
|
* [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
|
||||||
* and by checking which subscription is opportunistic, or which one is active.
|
* and by checking which subscription is opportunistic, or which one is active.
|
||||||
*/
|
*/
|
||||||
val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
|
override val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
|
||||||
combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
|
combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
|
||||||
->
|
->
|
||||||
// Based on the old logic,
|
// Based on the old logic,
|
||||||
@@ -92,15 +114,29 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
|
/**
|
||||||
|
* Mapping from network type to [MobileIconGroup] using the config generated for the default
|
||||||
|
* subscription Id. This mapping is the same for every subscription.
|
||||||
|
*/
|
||||||
|
override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
|
||||||
|
mobileSubscriptionRepo.defaultDataSubRatConfig
|
||||||
|
.map { mobileMappingsProxy.mapIconSets(it) }
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
|
||||||
|
|
||||||
|
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
|
||||||
|
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
|
||||||
|
mobileSubscriptionRepo.defaultDataSubRatConfig
|
||||||
|
.map { mobileMappingsProxy.getDefaultIcons(it) }
|
||||||
|
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
|
||||||
|
|
||||||
|
override val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
|
||||||
|
|
||||||
/** Vends out new [MobileIconInteractor] for a particular subId */
|
/** Vends out new [MobileIconInteractor] for a particular subId */
|
||||||
fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
|
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
|
||||||
MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId))
|
MobileIconInteractorImpl(
|
||||||
|
defaultMobileIconMapping,
|
||||||
/**
|
defaultMobileIconGroup,
|
||||||
* Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
|
mobileMappingsProxy,
|
||||||
*/
|
mobileSubscriptionRepo.getRepoForSubId(subId),
|
||||||
private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
|
)
|
||||||
mobileSubscriptionRepo.getFlowForSubId(subId)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
package com.android.systemui.statusbar.pipeline.mobile.ui.binder
|
package com.android.systemui.statusbar.pipeline.mobile.ui.binder
|
||||||
|
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
|
import android.view.View.GONE
|
||||||
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
@@ -24,6 +26,7 @@ import androidx.lifecycle.Lifecycle
|
|||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.android.settingslib.graph.SignalDrawable
|
import com.android.settingslib.graph.SignalDrawable
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.common.ui.binder.IconViewBinder
|
||||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
|
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
@@ -37,6 +40,7 @@ object MobileIconBinder {
|
|||||||
view: ViewGroup,
|
view: ViewGroup,
|
||||||
viewModel: MobileIconViewModel,
|
viewModel: MobileIconViewModel,
|
||||||
) {
|
) {
|
||||||
|
val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
|
||||||
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
|
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
|
||||||
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
|
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
|
||||||
|
|
||||||
@@ -52,10 +56,20 @@ object MobileIconBinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the network type icon
|
||||||
|
launch {
|
||||||
|
viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
|
||||||
|
dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
|
||||||
|
networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set the tint
|
// Set the tint
|
||||||
launch {
|
launch {
|
||||||
viewModel.tint.collect { tint ->
|
viewModel.tint.collect { tint ->
|
||||||
iconView.imageTintList = ColorStateList.valueOf(tint)
|
val tintList = ColorStateList.valueOf(tint)
|
||||||
|
iconView.imageTintList = tintList
|
||||||
|
networkTypeView.imageTintList = tintList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
|
|||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import com.android.settingslib.graph.SignalDrawable
|
import com.android.settingslib.graph.SignalDrawable
|
||||||
|
import com.android.systemui.common.shared.model.ContentDescription
|
||||||
|
import com.android.systemui.common.shared.model.Icon
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
|
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
|
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
|
||||||
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||||
@@ -26,6 +28,7 @@ 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.map
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@@ -54,5 +57,15 @@ constructor(
|
|||||||
.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 */
|
||||||
|
var networkTypeIcon: Flow<Icon?> =
|
||||||
|
iconInteractor.networkTypeIconGroup.map {
|
||||||
|
val desc =
|
||||||
|
if (it.dataContentDescription != 0)
|
||||||
|
ContentDescription.Resource(it.dataContentDescription)
|
||||||
|
else null
|
||||||
|
Icon.Resource(it.dataType, desc)
|
||||||
|
}
|
||||||
|
|
||||||
var tint: Flow<Int> = flowOf(Color.CYAN)
|
var tint: Flow<Int> = flowOf(Color.CYAN)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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.util
|
||||||
|
|
||||||
|
import android.telephony.Annotation.NetworkType
|
||||||
|
import android.telephony.TelephonyDisplayInfo
|
||||||
|
import com.android.settingslib.SignalIcon.MobileIconGroup
|
||||||
|
import com.android.settingslib.mobile.MobileMappings
|
||||||
|
import com.android.settingslib.mobile.MobileMappings.Config
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MobileMappings] owns the logic on creating the map from [TelephonyDisplayInfo] to
|
||||||
|
* [MobileIconGroup]. It creates that hash map and also manages the creation of lookup keys. This
|
||||||
|
* interface allows us to proxy those calls to the static java methods in SettingsLib and also fake
|
||||||
|
* them out in tests
|
||||||
|
*/
|
||||||
|
interface MobileMappingsProxy {
|
||||||
|
fun mapIconSets(config: Config): Map<String, MobileIconGroup>
|
||||||
|
fun getDefaultIcons(config: Config): MobileIconGroup
|
||||||
|
fun toIconKey(@NetworkType networkType: Int): String
|
||||||
|
fun toIconKeyOverride(@NetworkType networkType: Int): String
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Injectable wrapper class for [MobileMappings] */
|
||||||
|
class MobileMappingsProxyImpl @Inject constructor() : MobileMappingsProxy {
|
||||||
|
override fun mapIconSets(config: Config): Map<String, MobileIconGroup> =
|
||||||
|
MobileMappings.mapIconSets(config)
|
||||||
|
|
||||||
|
override fun getDefaultIcons(config: Config): MobileIconGroup =
|
||||||
|
MobileMappings.getDefaultIcons(config)
|
||||||
|
|
||||||
|
override fun toIconKey(@NetworkType networkType: Int): String =
|
||||||
|
MobileMappings.toIconKey(networkType)
|
||||||
|
|
||||||
|
override fun toIconKeyOverride(networkType: Int): String =
|
||||||
|
MobileMappings.toDisplayIconKey(networkType)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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.repository
|
||||||
|
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
class FakeMobileConnectionRepository : MobileConnectionRepository {
|
||||||
|
private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
|
||||||
|
override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
|
||||||
|
|
||||||
|
fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
|
||||||
|
_subscriptionsModelFlow.value = model
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,11 +18,11 @@ 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
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
|
import com.android.settingslib.mobile.MobileMappings.Config
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
|
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
|
||||||
|
|
||||||
@@ -30,22 +30,27 @@ class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
|
|||||||
MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
||||||
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
|
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
|
||||||
|
|
||||||
private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
|
private val _defaultDataSubRatConfig = MutableStateFlow(Config())
|
||||||
override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
|
override val defaultDataSubRatConfig = _defaultDataSubRatConfig
|
||||||
return subIdFlows[subId]
|
|
||||||
?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
|
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
|
||||||
|
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
|
||||||
|
return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSubscriptions(subs: List<SubscriptionInfo>) {
|
fun setSubscriptions(subs: List<SubscriptionInfo>) {
|
||||||
_subscriptionsFlow.value = subs
|
_subscriptionsFlow.value = subs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setDefaultDataSubRatConfig(config: Config) {
|
||||||
|
_defaultDataSubRatConfig.value = config
|
||||||
|
}
|
||||||
|
|
||||||
fun setActiveMobileDataSubscriptionId(subId: Int) {
|
fun setActiveMobileDataSubscriptionId(subId: Int) {
|
||||||
_activeMobileDataSubscriptionId.value = subId
|
_activeMobileDataSubscriptionId.value = subId
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
|
fun setMobileConnectionRepositoryForId(subId: Int, repo: MobileConnectionRepository) {
|
||||||
val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
|
subIdRepos[subId] = repo
|
||||||
subscription.value = model
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,18 +22,18 @@ import android.telephony.SignalStrength
|
|||||||
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.CarrierNetworkListener
|
|
||||||
import android.telephony.TelephonyCallback.DataActivityListener
|
|
||||||
import android.telephony.TelephonyCallback.DataConnectionStateListener
|
|
||||||
import android.telephony.TelephonyCallback.DisplayInfoListener
|
|
||||||
import android.telephony.TelephonyCallback.ServiceStateListener
|
import android.telephony.TelephonyCallback.ServiceStateListener
|
||||||
import android.telephony.TelephonyCallback.SignalStrengthsListener
|
|
||||||
import android.telephony.TelephonyDisplayInfo
|
import android.telephony.TelephonyDisplayInfo
|
||||||
|
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
|
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
|
||||||
|
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.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.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
|
||||||
@@ -50,28 +50,32 @@ import org.junit.After
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@SmallTest
|
@SmallTest
|
||||||
class MobileSubscriptionRepositoryTest : SysuiTestCase() {
|
class MobileConnectionRepositoryTest : SysuiTestCase() {
|
||||||
private lateinit var underTest: MobileSubscriptionRepositoryImpl
|
private lateinit var underTest: MobileConnectionRepositoryImpl
|
||||||
|
|
||||||
@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
|
||||||
|
|
||||||
private val scope = CoroutineScope(IMMEDIATE)
|
private val scope = CoroutineScope(IMMEDIATE)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
|
||||||
|
|
||||||
underTest =
|
underTest =
|
||||||
MobileSubscriptionRepositoryImpl(
|
MobileConnectionRepositoryImpl(
|
||||||
subscriptionManager,
|
SUB_1_ID,
|
||||||
telephonyManager,
|
telephonyManager,
|
||||||
IMMEDIATE,
|
IMMEDIATE,
|
||||||
|
logger,
|
||||||
scope,
|
scope,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -81,79 +85,11 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
|
|||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSubscriptions_initiallyEmpty() =
|
|
||||||
runBlocking(IMMEDIATE) {
|
|
||||||
assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSubscriptions_listUpdates() =
|
|
||||||
runBlocking(IMMEDIATE) {
|
|
||||||
var latest: List<SubscriptionInfo>? = null
|
|
||||||
|
|
||||||
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
|
|
||||||
|
|
||||||
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
|
||||||
.thenReturn(listOf(SUB_1, SUB_2))
|
|
||||||
getSubscriptionCallback().onSubscriptionsChanged()
|
|
||||||
|
|
||||||
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
|
|
||||||
|
|
||||||
job.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSubscriptions_removingSub_updatesList() =
|
|
||||||
runBlocking(IMMEDIATE) {
|
|
||||||
var latest: List<SubscriptionInfo>? = null
|
|
||||||
|
|
||||||
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
|
|
||||||
|
|
||||||
// WHEN 2 networks show up
|
|
||||||
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
|
||||||
.thenReturn(listOf(SUB_1, SUB_2))
|
|
||||||
getSubscriptionCallback().onSubscriptionsChanged()
|
|
||||||
|
|
||||||
// WHEN one network is removed
|
|
||||||
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
|
||||||
.thenReturn(listOf(SUB_2))
|
|
||||||
getSubscriptionCallback().onSubscriptionsChanged()
|
|
||||||
|
|
||||||
// THEN the subscriptions list represents the newest change
|
|
||||||
assertThat(latest).isEqualTo(listOf(SUB_2))
|
|
||||||
|
|
||||||
job.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
|
|
||||||
runBlocking(IMMEDIATE) {
|
|
||||||
assertThat(underTest.activeMobileDataSubscriptionId.value)
|
|
||||||
.isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testActiveDataSubscriptionId_updates() =
|
|
||||||
runBlocking(IMMEDIATE) {
|
|
||||||
var active: Int? = null
|
|
||||||
|
|
||||||
val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
|
|
||||||
|
|
||||||
getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
|
|
||||||
|
|
||||||
assertThat(active).isEqualTo(SUB_2_ID)
|
|
||||||
|
|
||||||
job.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFlowForSubId_default() =
|
fun testFlowForSubId_default() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
|
||||||
|
|
||||||
var latest: MobileSubscriptionModel? = null
|
var latest: MobileSubscriptionModel? = null
|
||||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
assertThat(latest).isEqualTo(MobileSubscriptionModel())
|
assertThat(latest).isEqualTo(MobileSubscriptionModel())
|
||||||
|
|
||||||
@@ -163,10 +99,8 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testFlowForSubId_emergencyOnly() =
|
fun testFlowForSubId_emergencyOnly() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
|
||||||
|
|
||||||
var latest: MobileSubscriptionModel? = null
|
var latest: MobileSubscriptionModel? = null
|
||||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
val serviceState = ServiceState()
|
val serviceState = ServiceState()
|
||||||
serviceState.isEmergencyOnly = true
|
serviceState.isEmergencyOnly = true
|
||||||
@@ -181,10 +115,8 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testFlowForSubId_emergencyOnly_toggles() =
|
fun testFlowForSubId_emergencyOnly_toggles() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
|
||||||
|
|
||||||
var latest: MobileSubscriptionModel? = null
|
var latest: MobileSubscriptionModel? = null
|
||||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
val callback = getTelephonyCallbackForType<ServiceStateListener>()
|
val callback = getTelephonyCallbackForType<ServiceStateListener>()
|
||||||
val serviceState = ServiceState()
|
val serviceState = ServiceState()
|
||||||
@@ -201,13 +133,11 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testFlowForSubId_signalStrengths_levelsUpdate() =
|
fun testFlowForSubId_signalStrengths_levelsUpdate() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
|
||||||
|
|
||||||
var latest: MobileSubscriptionModel? = null
|
var latest: MobileSubscriptionModel? = null
|
||||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
|
val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
|
||||||
val strength = signalStrength(1, 2, true)
|
val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
|
||||||
callback.onSignalStrengthsChanged(strength)
|
callback.onSignalStrengthsChanged(strength)
|
||||||
|
|
||||||
assertThat(latest?.isGsm).isEqualTo(true)
|
assertThat(latest?.isGsm).isEqualTo(true)
|
||||||
@@ -220,12 +150,11 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testFlowForSubId_dataConnectionState() =
|
fun testFlowForSubId_dataConnectionState() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
|
||||||
|
|
||||||
var latest: MobileSubscriptionModel? = null
|
var latest: MobileSubscriptionModel? = null
|
||||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
|
val callback =
|
||||||
|
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
|
||||||
callback.onDataConnectionStateChanged(100, 200 /* unused */)
|
callback.onDataConnectionStateChanged(100, 200 /* unused */)
|
||||||
|
|
||||||
assertThat(latest?.dataConnectionState).isEqualTo(100)
|
assertThat(latest?.dataConnectionState).isEqualTo(100)
|
||||||
@@ -236,12 +165,10 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testFlowForSubId_dataActivity() =
|
fun testFlowForSubId_dataActivity() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
|
||||||
|
|
||||||
var latest: MobileSubscriptionModel? = null
|
var latest: MobileSubscriptionModel? = null
|
||||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
val callback = getTelephonyCallbackForType<DataActivityListener>()
|
val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
|
||||||
callback.onDataActivity(3)
|
callback.onDataActivity(3)
|
||||||
|
|
||||||
assertThat(latest?.dataActivityDirection).isEqualTo(3)
|
assertThat(latest?.dataActivityDirection).isEqualTo(3)
|
||||||
@@ -252,12 +179,10 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testFlowForSubId_carrierNetworkChange() =
|
fun testFlowForSubId_carrierNetworkChange() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
|
||||||
|
|
||||||
var latest: MobileSubscriptionModel? = null
|
var latest: MobileSubscriptionModel? = null
|
||||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
|
val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
|
||||||
callback.onCarrierNetworkChange(true)
|
callback.onCarrierNetworkChange(true)
|
||||||
|
|
||||||
assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
|
assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
|
||||||
@@ -266,65 +191,59 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFlowForSubId_displayInfo() =
|
fun subscriptionFlow_networkType_default() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
|
||||||
|
|
||||||
var latest: MobileSubscriptionModel? = null
|
var latest: MobileSubscriptionModel? = null
|
||||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
val callback = getTelephonyCallbackForType<DisplayInfoListener>()
|
val type = NETWORK_TYPE_UNKNOWN
|
||||||
val ti = mock<TelephonyDisplayInfo>()
|
val expected = DefaultNetworkType(type)
|
||||||
|
|
||||||
|
assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun subscriptionFlow_networkType_updatesUsingDefault() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var latest: MobileSubscriptionModel? = null
|
||||||
|
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
|
||||||
|
val type = NETWORK_TYPE_LTE
|
||||||
|
val expected = DefaultNetworkType(type)
|
||||||
|
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
|
||||||
callback.onDisplayInfoChanged(ti)
|
callback.onDisplayInfoChanged(ti)
|
||||||
|
|
||||||
assertThat(latest?.displayInfo).isEqualTo(ti)
|
assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
|
||||||
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFlowForSubId_isCached() =
|
fun subscriptionFlow_networkType_updatesUsingOverride() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
|
||||||
|
|
||||||
val state1 = underTest.getFlowForSubId(SUB_1_ID)
|
|
||||||
val state2 = underTest.getFlowForSubId(SUB_1_ID)
|
|
||||||
|
|
||||||
assertThat(state1).isEqualTo(state2)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testFlowForSubId_isRemovedAfterFinish() =
|
|
||||||
runBlocking(IMMEDIATE) {
|
|
||||||
whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
|
|
||||||
|
|
||||||
var latest: MobileSubscriptionModel? = null
|
var latest: MobileSubscriptionModel? = null
|
||||||
|
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
// Start collecting on some flow
|
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
|
||||||
val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
|
val type = OVERRIDE_NETWORK_TYPE_LTE_CA
|
||||||
|
val expected = OverrideNetworkType(type)
|
||||||
|
val ti =
|
||||||
|
mock<TelephonyDisplayInfo>().also {
|
||||||
|
whenever(it.overrideNetworkType).thenReturn(type)
|
||||||
|
}
|
||||||
|
callback.onDisplayInfoChanged(ti)
|
||||||
|
|
||||||
// There should be once cached flow now
|
assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
|
||||||
assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
|
|
||||||
|
|
||||||
// When the job is canceled, the cache should be cleared
|
|
||||||
job.cancel()
|
job.cancel()
|
||||||
|
|
||||||
assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
|
|
||||||
val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
|
|
||||||
verify(subscriptionManager)
|
|
||||||
.addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
|
|
||||||
return callbackCaptor.value!!
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
|
|
||||||
getTelephonyCallbackForType()
|
|
||||||
|
|
||||||
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
|
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
|
||||||
val callbackCaptor = argumentCaptor<TelephonyCallback>()
|
val callbackCaptor = argumentCaptor<TelephonyCallback>()
|
||||||
verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
|
Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
|
||||||
return callbackCaptor.allValues
|
return callbackCaptor.allValues
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,9 +271,5 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
|
|||||||
private const val SUB_1_ID = 1
|
private const val SUB_1_ID = 1
|
||||||
private val SUB_1 =
|
private val SUB_1 =
|
||||||
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
|
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
|
||||||
|
|
||||||
private const val SUB_2_ID = 2
|
|
||||||
private val SUB_2 =
|
|
||||||
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
* 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.repository
|
||||||
|
|
||||||
|
import android.telephony.SubscriptionInfo
|
||||||
|
import android.telephony.SubscriptionManager
|
||||||
|
import android.telephony.TelephonyCallback
|
||||||
|
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
|
||||||
|
import android.telephony.TelephonyManager
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
|
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.android.systemui.util.mockito.nullable
|
||||||
|
import com.android.systemui.util.mockito.whenever
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertThrows
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.ArgumentMatchers
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@SmallTest
|
||||||
|
class MobileConnectionsRepositoryTest : SysuiTestCase() {
|
||||||
|
private lateinit var underTest: MobileConnectionsRepositoryImpl
|
||||||
|
|
||||||
|
@Mock private lateinit var subscriptionManager: SubscriptionManager
|
||||||
|
@Mock private lateinit var telephonyManager: TelephonyManager
|
||||||
|
@Mock private lateinit var logger: ConnectivityPipelineLogger
|
||||||
|
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(IMMEDIATE)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
whenever(
|
||||||
|
broadcastDispatcher.broadcastFlow(
|
||||||
|
any(),
|
||||||
|
nullable(),
|
||||||
|
ArgumentMatchers.anyInt(),
|
||||||
|
nullable(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.thenReturn(flowOf(Unit))
|
||||||
|
|
||||||
|
underTest =
|
||||||
|
MobileConnectionsRepositoryImpl(
|
||||||
|
subscriptionManager,
|
||||||
|
telephonyManager,
|
||||||
|
logger,
|
||||||
|
broadcastDispatcher,
|
||||||
|
context,
|
||||||
|
IMMEDIATE,
|
||||||
|
scope,
|
||||||
|
mock(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSubscriptions_initiallyEmpty() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSubscriptions_listUpdates() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var latest: List<SubscriptionInfo>? = null
|
||||||
|
|
||||||
|
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
||||||
|
.thenReturn(listOf(SUB_1, SUB_2))
|
||||||
|
getSubscriptionCallback().onSubscriptionsChanged()
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSubscriptions_removingSub_updatesList() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var latest: List<SubscriptionInfo>? = null
|
||||||
|
|
||||||
|
val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
// WHEN 2 networks show up
|
||||||
|
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
||||||
|
.thenReturn(listOf(SUB_1, SUB_2))
|
||||||
|
getSubscriptionCallback().onSubscriptionsChanged()
|
||||||
|
|
||||||
|
// WHEN one network is removed
|
||||||
|
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
||||||
|
.thenReturn(listOf(SUB_2))
|
||||||
|
getSubscriptionCallback().onSubscriptionsChanged()
|
||||||
|
|
||||||
|
// THEN the subscriptions list represents the newest change
|
||||||
|
assertThat(latest).isEqualTo(listOf(SUB_2))
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
assertThat(underTest.activeMobileDataSubscriptionId.value)
|
||||||
|
.isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testActiveDataSubscriptionId_updates() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
var active: Int? = null
|
||||||
|
|
||||||
|
val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
|
||||||
|
|
||||||
|
getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
|
||||||
|
.onActiveDataSubscriptionIdChanged(SUB_2_ID)
|
||||||
|
|
||||||
|
assertThat(active).isEqualTo(SUB_2_ID)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConnectionRepository_validSubId_isCached() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
val job = underTest.subscriptionsFlow.launchIn(this)
|
||||||
|
|
||||||
|
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
||||||
|
.thenReturn(listOf(SUB_1))
|
||||||
|
getSubscriptionCallback().onSubscriptionsChanged()
|
||||||
|
|
||||||
|
val repo1 = underTest.getRepoForSubId(SUB_1_ID)
|
||||||
|
val repo2 = underTest.getRepoForSubId(SUB_1_ID)
|
||||||
|
|
||||||
|
assertThat(repo1).isSameInstanceAs(repo2)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConnectionCache_clearsInvalidSubscriptions() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
val job = underTest.subscriptionsFlow.launchIn(this)
|
||||||
|
|
||||||
|
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
||||||
|
.thenReturn(listOf(SUB_1, SUB_2))
|
||||||
|
getSubscriptionCallback().onSubscriptionsChanged()
|
||||||
|
|
||||||
|
// Get repos to trigger caching
|
||||||
|
val repo1 = underTest.getRepoForSubId(SUB_1_ID)
|
||||||
|
val repo2 = underTest.getRepoForSubId(SUB_2_ID)
|
||||||
|
|
||||||
|
assertThat(underTest.getSubIdRepoCache())
|
||||||
|
.containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
|
||||||
|
|
||||||
|
// SUB_2 disappears
|
||||||
|
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
|
||||||
|
.thenReturn(listOf(SUB_1))
|
||||||
|
getSubscriptionCallback().onSubscriptionsChanged()
|
||||||
|
|
||||||
|
assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConnectionRepository_invalidSubId_throws() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
val job = underTest.subscriptionsFlow.launchIn(this)
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException::class.java) {
|
||||||
|
underTest.getRepoForSubId(SUB_1_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
|
||||||
|
val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
|
||||||
|
verify(subscriptionManager)
|
||||||
|
.addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
|
||||||
|
return callbackCaptor.value!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
|
||||||
|
val callbackCaptor = argumentCaptor<TelephonyCallback>()
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||||
|
private const val SUB_1_ID = 1
|
||||||
|
private val SUB_1 =
|
||||||
|
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
|
||||||
|
|
||||||
|
private const val SUB_2_ID = 2
|
||||||
|
private val SUB_2 =
|
||||||
|
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
|
|
||||||
class FakeMobileIconInteractor : MobileIconInteractor {
|
class FakeMobileIconInteractor : MobileIconInteractor {
|
||||||
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
|
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
|
||||||
override val iconGroup = _iconGroup
|
override val networkTypeIconGroup = _iconGroup
|
||||||
|
|
||||||
private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
|
private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
|
||||||
override val isEmergencyOnly = _isEmergencyOnly
|
override val isEmergencyOnly = _isEmergencyOnly
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* 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.domain.interactor
|
||||||
|
|
||||||
|
import android.telephony.SubscriptionInfo
|
||||||
|
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
|
||||||
|
import android.telephony.TelephonyManager.NETWORK_TYPE_GSM
|
||||||
|
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
|
||||||
|
import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
|
||||||
|
import com.android.settingslib.SignalIcon.MobileIconGroup
|
||||||
|
import com.android.settingslib.mobile.TelephonyIcons
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) :
|
||||||
|
MobileIconsInteractor {
|
||||||
|
val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
|
||||||
|
val LTE_KEY = mobileMappings.toIconKey(LTE)
|
||||||
|
val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
|
||||||
|
val FIVE_G_OVERRIDE_KEY = mobileMappings.toIconKeyOverride(FIVE_G_OVERRIDE)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To avoid a reliance on [MobileMappings], we'll build a simpler map from network type to
|
||||||
|
* mobile icon. See TelephonyManager.NETWORK_TYPES for a list of types and [TelephonyIcons] for
|
||||||
|
* the exhaustive set of icons
|
||||||
|
*/
|
||||||
|
val TEST_MAPPING: Map<String, MobileIconGroup> =
|
||||||
|
mapOf(
|
||||||
|
THREE_G_KEY to TelephonyIcons.THREE_G,
|
||||||
|
LTE_KEY to TelephonyIcons.LTE,
|
||||||
|
FOUR_G_KEY to TelephonyIcons.FOUR_G,
|
||||||
|
FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
|
||||||
|
override val filteredSubscriptions = _filteredSubscriptions
|
||||||
|
|
||||||
|
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
|
||||||
|
override val defaultMobileIconMapping = _defaultMobileIconMapping
|
||||||
|
|
||||||
|
private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
|
||||||
|
override val defaultMobileIconGroup = _defaultMobileIconGroup
|
||||||
|
|
||||||
|
private val _isUserSetup = MutableStateFlow(true)
|
||||||
|
override val isUserSetup = _isUserSetup
|
||||||
|
|
||||||
|
/** Always returns a new fake interactor */
|
||||||
|
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
|
||||||
|
return FakeMobileIconInteractor()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DEFAULT_ICON = TelephonyIcons.G
|
||||||
|
|
||||||
|
// Use [MobileMappings] to define some simple definitions
|
||||||
|
const val THREE_G = NETWORK_TYPE_GSM
|
||||||
|
const val LTE = NETWORK_TYPE_LTE
|
||||||
|
const val FOUR_G = NETWORK_TYPE_UMTS
|
||||||
|
const val FIVE_G_OVERRIDE = OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,10 +18,19 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
|
|||||||
|
|
||||||
import android.telephony.CellSignalStrength
|
import android.telephony.CellSignalStrength
|
||||||
import android.telephony.SubscriptionInfo
|
import android.telephony.SubscriptionInfo
|
||||||
|
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.settingslib.SignalIcon.MobileIconGroup
|
||||||
|
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.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.repository.FakeMobileSubscriptionRepository
|
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
|
||||||
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
|
||||||
@@ -29,26 +38,33 @@ 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.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
class MobileIconInteractorTest : SysuiTestCase() {
|
class MobileIconInteractorTest : SysuiTestCase() {
|
||||||
private lateinit var underTest: MobileIconInteractor
|
private lateinit var underTest: MobileIconInteractor
|
||||||
private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
|
private val mobileMappingsProxy = FakeMobileMappingsProxy()
|
||||||
private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
|
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
|
||||||
|
private val connectionRepository = FakeMobileConnectionRepository()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
underTest = MobileIconInteractorImpl(sub1Flow)
|
underTest =
|
||||||
|
MobileIconInteractorImpl(
|
||||||
|
mobileIconsInteractor.defaultMobileIconMapping,
|
||||||
|
mobileIconsInteractor.defaultMobileIconGroup,
|
||||||
|
mobileMappingsProxy,
|
||||||
|
connectionRepository,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun gsm_level_default_unknown() =
|
fun gsm_level_default_unknown() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
mobileSubscriptionRepository.setMobileSubscriptionModel(
|
connectionRepository.setMobileSubscriptionModel(
|
||||||
MobileSubscriptionModel(isGsm = true),
|
MobileSubscriptionModel(isGsm = true),
|
||||||
SUB_1_ID
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var latest: Int? = null
|
var latest: Int? = null
|
||||||
@@ -62,13 +78,12 @@ class MobileIconInteractorTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun gsm_usesGsmLevel() =
|
fun gsm_usesGsmLevel() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
mobileSubscriptionRepository.setMobileSubscriptionModel(
|
connectionRepository.setMobileSubscriptionModel(
|
||||||
MobileSubscriptionModel(
|
MobileSubscriptionModel(
|
||||||
isGsm = true,
|
isGsm = true,
|
||||||
primaryLevel = GSM_LEVEL,
|
primaryLevel = GSM_LEVEL,
|
||||||
cdmaLevel = CDMA_LEVEL
|
cdmaLevel = CDMA_LEVEL
|
||||||
),
|
),
|
||||||
SUB_1_ID
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var latest: Int? = null
|
var latest: Int? = null
|
||||||
@@ -82,9 +97,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun cdma_level_default_unknown() =
|
fun cdma_level_default_unknown() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
mobileSubscriptionRepository.setMobileSubscriptionModel(
|
connectionRepository.setMobileSubscriptionModel(
|
||||||
MobileSubscriptionModel(isGsm = false),
|
MobileSubscriptionModel(isGsm = false),
|
||||||
SUB_1_ID
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var latest: Int? = null
|
var latest: Int? = null
|
||||||
@@ -97,13 +111,12 @@ class MobileIconInteractorTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun cdma_usesCdmaLevel() =
|
fun cdma_usesCdmaLevel() =
|
||||||
runBlocking(IMMEDIATE) {
|
runBlocking(IMMEDIATE) {
|
||||||
mobileSubscriptionRepository.setMobileSubscriptionModel(
|
connectionRepository.setMobileSubscriptionModel(
|
||||||
MobileSubscriptionModel(
|
MobileSubscriptionModel(
|
||||||
isGsm = false,
|
isGsm = false,
|
||||||
primaryLevel = GSM_LEVEL,
|
primaryLevel = GSM_LEVEL,
|
||||||
cdmaLevel = CDMA_LEVEL
|
cdmaLevel = CDMA_LEVEL
|
||||||
),
|
),
|
||||||
SUB_1_ID
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var latest: Int? = null
|
var latest: Int? = null
|
||||||
@@ -114,6 +127,75 @@ class MobileIconInteractorTest : SysuiTestCase() {
|
|||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun iconGroup_three_g() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
connectionRepository.setMobileSubscriptionModel(
|
||||||
|
MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
|
||||||
|
)
|
||||||
|
|
||||||
|
var latest: MobileIconGroup? = null
|
||||||
|
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(TelephonyIcons.THREE_G)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun iconGroup_updates_on_change() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
connectionRepository.setMobileSubscriptionModel(
|
||||||
|
MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
|
||||||
|
)
|
||||||
|
|
||||||
|
var latest: MobileIconGroup? = null
|
||||||
|
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
connectionRepository.setMobileSubscriptionModel(
|
||||||
|
MobileSubscriptionModel(
|
||||||
|
resolvedNetworkType = DefaultNetworkType(FOUR_G),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
yield()
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(TelephonyIcons.FOUR_G)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun iconGroup_5g_override_type() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
connectionRepository.setMobileSubscriptionModel(
|
||||||
|
MobileSubscriptionModel(resolvedNetworkType = OverrideNetworkType(FIVE_G_OVERRIDE)),
|
||||||
|
)
|
||||||
|
|
||||||
|
var latest: MobileIconGroup? = null
|
||||||
|
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(TelephonyIcons.NR_5G)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun iconGroup_default_if_no_lookup() =
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
connectionRepository.setMobileSubscriptionModel(
|
||||||
|
MobileSubscriptionModel(
|
||||||
|
resolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
var latest: MobileIconGroup? = null
|
||||||
|
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(FakeMobileIconsInteractor.DEFAULT_ICON)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||||
|
|
||||||
@@ -123,9 +205,5 @@ class MobileIconInteractorTest : SysuiTestCase() {
|
|||||||
private const val SUB_1_ID = 1
|
private const val SUB_1_ID = 1
|
||||||
private val SUB_1 =
|
private val SUB_1 =
|
||||||
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
|
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
|
||||||
|
|
||||||
private const val SUB_2_ID = 2
|
|
||||||
private val SUB_2 =
|
|
||||||
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
|
|||||||
import android.telephony.SubscriptionInfo
|
import android.telephony.SubscriptionInfo
|
||||||
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.repository.FakeMobileSubscriptionRepository
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
|
||||||
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
|
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
|
||||||
|
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
|
||||||
import com.android.systemui.util.CarrierConfigTracker
|
import com.android.systemui.util.CarrierConfigTracker
|
||||||
import com.android.systemui.util.mockito.mock
|
import com.android.systemui.util.mockito.mock
|
||||||
import com.android.systemui.util.mockito.whenever
|
import com.android.systemui.util.mockito.whenever
|
||||||
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
|
||||||
@@ -39,7 +41,9 @@ import org.mockito.MockitoAnnotations
|
|||||||
class MobileIconsInteractorTest : SysuiTestCase() {
|
class MobileIconsInteractorTest : SysuiTestCase() {
|
||||||
private lateinit var underTest: MobileIconsInteractor
|
private lateinit var underTest: MobileIconsInteractor
|
||||||
private val userSetupRepository = FakeUserSetupRepository()
|
private val userSetupRepository = FakeUserSetupRepository()
|
||||||
private val subscriptionsRepository = FakeMobileSubscriptionRepository()
|
private val subscriptionsRepository = FakeMobileConnectionsRepository()
|
||||||
|
private val mobileMappingsProxy = FakeMobileMappingsProxy()
|
||||||
|
private val scope = CoroutineScope(IMMEDIATE)
|
||||||
|
|
||||||
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
|
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
|
||||||
|
|
||||||
@@ -47,10 +51,12 @@ class MobileIconsInteractorTest : SysuiTestCase() {
|
|||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
underTest =
|
underTest =
|
||||||
MobileIconsInteractor(
|
MobileIconsInteractorImpl(
|
||||||
subscriptionsRepository,
|
subscriptionsRepository,
|
||||||
carrierConfigTracker,
|
carrierConfigTracker,
|
||||||
|
mobileMappingsProxy,
|
||||||
userSetupRepository,
|
userSetupRepository,
|
||||||
|
scope
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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.util
|
||||||
|
|
||||||
|
import com.android.settingslib.SignalIcon.MobileIconGroup
|
||||||
|
import com.android.settingslib.mobile.MobileMappings.Config
|
||||||
|
import com.android.settingslib.mobile.TelephonyIcons
|
||||||
|
|
||||||
|
class FakeMobileMappingsProxy : MobileMappingsProxy {
|
||||||
|
private var iconMap = mapOf<String, MobileIconGroup>()
|
||||||
|
private var defaultIcons = TelephonyIcons.THREE_G
|
||||||
|
|
||||||
|
fun setIconMap(map: Map<String, MobileIconGroup>) {
|
||||||
|
iconMap = map
|
||||||
|
}
|
||||||
|
override fun mapIconSets(config: Config): Map<String, MobileIconGroup> = iconMap
|
||||||
|
fun getIconMap() = iconMap
|
||||||
|
|
||||||
|
fun setDefaultIcons(group: MobileIconGroup) {
|
||||||
|
defaultIcons = group
|
||||||
|
}
|
||||||
|
override fun getDefaultIcons(config: Config): MobileIconGroup = defaultIcons
|
||||||
|
fun getDefaultIcons(): MobileIconGroup = defaultIcons
|
||||||
|
|
||||||
|
override fun toIconKey(networkType: Int): String {
|
||||||
|
return networkType.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toIconKeyOverride(networkType: Int): String {
|
||||||
|
return toIconKey(networkType) + "_override"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user