Merge "[Sb refactor] Add dataEnabled tracking to mobile repos" into tm-qpr-dev am: 10e2996b33

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

Change-Id: I85875ecfae57b8254b391a0c1f0ceab589e82e0e
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Evan Laird
2022-10-28 21:10:01 +00:00
committed by Automerger Merge Worker
12 changed files with 285 additions and 45 deletions

View File

@@ -0,0 +1,40 @@
/*
* 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.TelephonyManager.DATA_CONNECTED
import android.telephony.TelephonyManager.DATA_CONNECTING
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
import android.telephony.TelephonyManager.DataState
/** Internal enum representation of the telephony data connection states */
enum class DataConnectionState(@DataState val dataState: Int) {
Connected(DATA_CONNECTED),
Connecting(DATA_CONNECTING),
Disconnected(DATA_DISCONNECTED),
Disconnecting(DATA_DISCONNECTING),
}
fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
when (this) {
DATA_CONNECTED -> DataConnectionState.Connected
DATA_CONNECTING -> DataConnectionState.Connecting
DATA_DISCONNECTED -> DataConnectionState.Disconnected
DATA_DISCONNECTING -> DataConnectionState.Disconnecting
else -> throw IllegalArgumentException("unknown data state received")
}

View File

@@ -28,6 +28,7 @@ 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 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
/** /**
* 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
@@ -49,14 +50,14 @@ data class MobileSubscriptionModel(
@IntRange(from = 0, to = 4) @IntRange(from = 0, to = 4)
val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
/** Comes directly from [DataConnectionStateListener.onDataConnectionStateChanged] */ /** Mapped from [DataConnectionStateListener.onDataConnectionStateChanged] */
val dataConnectionState: Int? = null, val dataConnectionState: DataConnectionState = Disconnected,
/** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */ /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
@DataActivityType val dataActivityDirection: Int? = null, @DataActivityType val dataActivityDirection: Int? = null,
/** From [CarrierNetworkListener.onCarrierNetworkChange] */ /** From [CarrierNetworkListener.onCarrierNetworkChange] */
val carrierNetworkChangeActive: Boolean? = null, val carrierNetworkChangeActive: Boolean = false,
/** /**
* From [DisplayInfoListener.onDisplayInfoChanged]. * From [DisplayInfoListener.onDisplayInfoChanged].

View File

@@ -31,7 +31,9 @@ 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.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import java.lang.IllegalStateException import java.lang.IllegalStateException
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
@@ -42,7 +44,7 @@ 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.onEach import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
/** /**
@@ -62,13 +64,15 @@ interface MobileConnectionRepository {
* listener + model. * listener + model.
*/ */
val subscriptionModelFlow: Flow<MobileSubscriptionModel> val subscriptionModelFlow: Flow<MobileSubscriptionModel>
/** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
val dataEnabled: Flow<Boolean>
} }
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl( class MobileConnectionRepositoryImpl(
private val subId: Int, private val subId: Int,
telephonyManager: TelephonyManager, private val telephonyManager: TelephonyManager,
bgDispatcher: CoroutineDispatcher, bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger, logger: ConnectivityPipelineLogger,
scope: CoroutineScope, scope: CoroutineScope,
@@ -127,7 +131,8 @@ class MobileConnectionRepositoryImpl(
dataState: Int, dataState: Int,
networkType: Int networkType: Int
) { ) {
state = state.copy(dataConnectionState = dataState) state =
state.copy(dataConnectionState = dataState.toDataConnectionType())
trySend(state) trySend(state)
} }
@@ -160,10 +165,21 @@ class MobileConnectionRepositoryImpl(
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback) telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
} }
.onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) } .logOutputChange(logger, "MobileSubscriptionModel")
.stateIn(scope, SharingStarted.WhileSubscribed(), state) .stateIn(scope, SharingStarted.WhileSubscribed(), state)
} }
/**
* There are a few cases where we will need to poll [TelephonyManager] so we can update some
* internal state where callbacks aren't provided. Any of those events should be merged into
* this flow, which can be used to trigger the polling.
*/
private val telephonyPollingEvent: Flow<Unit> = subscriptionModelFlow.map {}
override val dataEnabled: Flow<Boolean> = telephonyPollingEvent.map { dataConnectionAllowed() }
private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
class Factory class Factory
@Inject @Inject
constructor( constructor(

View File

@@ -29,6 +29,9 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
interface MobileIconInteractor { interface MobileIconInteractor {
/** Observable for the data enabled state of this connection */
val isDataEnabled: Flow<Boolean>
/** Observable for RAT type (network type) indicator */ /** Observable for RAT type (network type) indicator */
val networkTypeIconGroup: Flow<MobileIconGroup> val networkTypeIconGroup: Flow<MobileIconGroup>
@@ -54,6 +57,8 @@ class MobileIconInteractorImpl(
) : MobileIconInteractor { ) : MobileIconInteractor {
private val mobileStatusInfo = connectionRepository.subscriptionModelFlow private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
override val isDataEnabled: Flow<Boolean> = connectionRepository.dataEnabled
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */ /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
override val networkTypeIconGroup: Flow<MobileIconGroup> = override val networkTypeIconGroup: Flow<MobileIconGroup> =
combine( combine(

View File

@@ -23,12 +23,14 @@ import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
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
@@ -47,28 +49,38 @@ import kotlinx.coroutines.flow.stateIn
* icon * icon
*/ */
interface MobileIconsInteractor { interface MobileIconsInteractor {
/** List of subscriptions, potentially filtered for CBRS */
val filteredSubscriptions: Flow<List<SubscriptionInfo>> val filteredSubscriptions: Flow<List<SubscriptionInfo>>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>> val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
val defaultMobileIconGroup: Flow<MobileIconGroup> val defaultMobileIconGroup: Flow<MobileIconGroup>
/** True once the user has been set up */
val isUserSetup: Flow<Boolean> val isUserSetup: Flow<Boolean>
/**
* Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
* subId. Will throw if the ID is invalid
*/
fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
} }
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton @SysUISingleton
class MobileIconsInteractorImpl class MobileIconsInteractorImpl
@Inject @Inject
constructor( constructor(
private val mobileSubscriptionRepo: MobileConnectionsRepository, private val mobileConnectionsRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker, private val carrierConfigTracker: CarrierConfigTracker,
private val mobileMappingsProxy: MobileMappingsProxy, private val mobileMappingsProxy: MobileMappingsProxy,
userSetupRepo: UserSetupRepository, userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope, @Application private val scope: CoroutineScope,
) : MobileIconsInteractor { ) : MobileIconsInteractor {
private val activeMobileDataSubscriptionId = private val activeMobileDataSubscriptionId =
mobileSubscriptionRepo.activeMobileDataSubscriptionId mobileConnectionsRepo.activeMobileDataSubscriptionId
private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> = private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
mobileSubscriptionRepo.subscriptionsFlow mobileConnectionsRepo.subscriptionsFlow
/** /**
* Generally, SystemUI wants to show iconography for each subscription that is listed by * Generally, SystemUI wants to show iconography for each subscription that is listed by
@@ -119,13 +131,13 @@ constructor(
* subscription Id. This mapping is the same for every subscription. * subscription Id. This mapping is the same for every subscription.
*/ */
override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> = override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
mobileSubscriptionRepo.defaultDataSubRatConfig mobileConnectionsRepo.defaultDataSubRatConfig
.map { mobileMappingsProxy.mapIconSets(it) } .map { mobileMappingsProxy.mapIconSets(it) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf()) .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */ /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> = override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
mobileSubscriptionRepo.defaultDataSubRatConfig mobileConnectionsRepo.defaultDataSubRatConfig
.map { mobileMappingsProxy.getDefaultIcons(it) } .map { mobileMappingsProxy.getDefaultIcons(it) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G) .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
@@ -137,6 +149,6 @@ constructor(
defaultMobileIconMapping, defaultMobileIconMapping,
defaultMobileIconGroup, defaultMobileIconGroup,
mobileMappingsProxy, mobileMappingsProxy,
mobileSubscriptionRepo.getRepoForSubId(subId), mobileConnectionsRepo.getRepoForSubId(subId),
) )
} }

View File

@@ -28,7 +28,6 @@ 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
@@ -59,12 +58,18 @@ constructor(
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
var networkTypeIcon: Flow<Icon?> = var networkTypeIcon: Flow<Icon?> =
iconInteractor.networkTypeIconGroup.map { combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) {
val desc = networkTypeIconGroup,
if (it.dataContentDescription != 0) isDataEnabled ->
ContentDescription.Resource(it.dataContentDescription) if (!isDataEnabled) {
else null null
Icon.Resource(it.dataType, desc) } else {
val desc =
if (networkTypeIconGroup.dataContentDescription != 0)
ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
else null
Icon.Resource(networkTypeIconGroup.dataType, desc)
}
} }
var tint: Flow<Int> = flowOf(Color.CYAN) var tint: Flow<Int> = flowOf(Color.CYAN)

View File

@@ -24,7 +24,14 @@ class FakeMobileConnectionRepository : MobileConnectionRepository {
private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel()) private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
private val _dataEnabled = MutableStateFlow(true)
override val dataEnabled = _dataEnabled
fun setMobileSubscriptionModel(model: MobileSubscriptionModel) { fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
_subscriptionsModelFlow.value = model _subscriptionsModelFlow.value = model
} }
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
} }

View File

@@ -50,7 +50,7 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository {
_activeMobileDataSubscriptionId.value = subId _activeMobileDataSubscriptionId.value = subId
} }
fun setMobileConnectionRepositoryForId(subId: Int, repo: MobileConnectionRepository) { fun setMobileConnectionRepositoryMap(connections: Map<Int, MobileConnectionRepository>) {
subIdRepos[subId] = repo connections.forEach { entry -> subIdRepos[entry.key] = entry.value }
} }
} }

View File

@@ -20,16 +20,20 @@ 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.ServiceStateListener import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.DATA_CONNECTED
import android.telephony.TelephonyManager.DATA_CONNECTING
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
@@ -59,7 +63,6 @@ import org.mockito.MockitoAnnotations
class MobileConnectionRepositoryTest : SysuiTestCase() { class MobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl private lateinit var underTest: MobileConnectionRepositoryImpl
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger @Mock private lateinit var logger: ConnectivityPipelineLogger
@@ -148,16 +151,61 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
} }
@Test @Test
fun testFlowForSubId_dataConnectionState() = fun testFlowForSubId_dataConnectionState_connected() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null var latest: MobileSubscriptionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this) val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback = val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>() getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(100, 200 /* unused */) callback.onDataConnectionStateChanged(DATA_CONNECTED, 200 /* unused */)
assertThat(latest?.dataConnectionState).isEqualTo(100) assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connected)
job.cancel()
}
@Test
fun testFlowForSubId_dataConnectionState_connecting() =
runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_CONNECTING, 200 /* unused */)
assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connecting)
job.cancel()
}
@Test
fun testFlowForSubId_dataConnectionState_disconnected() =
runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_DISCONNECTED, 200 /* unused */)
assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnected)
job.cancel()
}
@Test
fun testFlowForSubId_dataConnectionState_disconnecting() =
runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null
val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_DISCONNECTING, 200 /* unused */)
assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnecting)
job.cancel() job.cancel()
} }
@@ -241,6 +289,32 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
@Test
fun dataEnabled_isEnabled() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
var latest: Boolean? = null
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun dataEnabled_isDisabled() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
var latest: Boolean? = null
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
private fun getTelephonyCallbacks(): List<TelephonyCallback> { private fun getTelephonyCallbacks(): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>() val callbackCaptor = argumentCaptor<TelephonyCallback>()
Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture()) Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())

View File

@@ -22,19 +22,22 @@ import com.android.settingslib.mobile.TelephonyIcons
import kotlinx.coroutines.flow.MutableStateFlow 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.THREE_G)
override val networkTypeIconGroup = _iconGroup override val networkTypeIconGroup = _iconGroup
private val _isEmergencyOnly = MutableStateFlow<Boolean>(false) private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly = _isEmergencyOnly override val isEmergencyOnly = _isEmergencyOnly
private val _level = MutableStateFlow<Int>(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) private val _isDataEnabled = MutableStateFlow(true)
override val isDataEnabled = _isDataEnabled
private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
override val level = _level override val level = _level
private val _numberOfLevels = MutableStateFlow<Int>(4) private val _numberOfLevels = MutableStateFlow(4)
override val numberOfLevels = _numberOfLevels override val numberOfLevels = _numberOfLevels
private val _cutOut = MutableStateFlow<Boolean>(false) private val _cutOut = MutableStateFlow(false)
override val cutOut = _cutOut override val cutOut = _cutOut
fun setIconGroup(group: SignalIcon.MobileIconGroup) { fun setIconGroup(group: SignalIcon.MobileIconGroup) {
@@ -45,6 +48,10 @@ class FakeMobileIconInteractor : MobileIconInteractor {
_isEmergencyOnly.value = emergency _isEmergencyOnly.value = emergency
} }
fun setIsDataEnabled(enabled: Boolean) {
_isDataEnabled.value = enabled
}
fun setLevel(level: Int) { fun setLevel(level: Int) {
_level.value = level _level.value = level
} }

View File

@@ -19,6 +19,7 @@ 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.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -41,7 +42,7 @@ 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 = FakeMobileConnectionsRepository() private val connectionsRepository = FakeMobileConnectionsRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy() private val mobileMappingsProxy = FakeMobileMappingsProxy()
private val scope = CoroutineScope(IMMEDIATE) private val scope = CoroutineScope(IMMEDIATE)
@@ -50,9 +51,20 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
connectionsRepository.setMobileConnectionRepositoryMap(
mapOf(
SUB_1_ID to CONNECTION_1,
SUB_2_ID to CONNECTION_2,
SUB_3_ID to CONNECTION_3,
SUB_4_ID to CONNECTION_4,
)
)
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
underTest = underTest =
MobileIconsInteractorImpl( MobileIconsInteractorImpl(
subscriptionsRepository, connectionsRepository,
carrierConfigTracker, carrierConfigTracker,
mobileMappingsProxy, mobileMappingsProxy,
userSetupRepository, userSetupRepository,
@@ -76,7 +88,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() = fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
var latest: List<SubscriptionInfo>? = null var latest: List<SubscriptionInfo>? = null
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
@@ -89,8 +101,8 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() = fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false) .thenReturn(false)
@@ -106,8 +118,8 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() = fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false) .thenReturn(false)
@@ -123,8 +135,8 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() = fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP)) connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true) .thenReturn(true)
@@ -141,8 +153,8 @@ class MobileIconsInteractorTest : SysuiTestCase() {
@Test @Test
fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() = fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
runBlocking(IMMEDIATE) { runBlocking(IMMEDIATE) {
subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP)) connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true) .thenReturn(true)
@@ -162,10 +174,12 @@ class MobileIconsInteractorTest : 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 val CONNECTION_1 = FakeMobileConnectionRepository()
private const val SUB_2_ID = 2 private const val SUB_2_ID = 2
private val SUB_2 = private val SUB_2 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
private val CONNECTION_2 = FakeMobileConnectionRepository()
private const val SUB_3_ID = 3 private const val SUB_3_ID = 3
private val SUB_3_OPP = private val SUB_3_OPP =
@@ -173,6 +187,7 @@ class MobileIconsInteractorTest : SysuiTestCase() {
whenever(it.subscriptionId).thenReturn(SUB_3_ID) whenever(it.subscriptionId).thenReturn(SUB_3_ID)
whenever(it.isOpportunistic).thenReturn(true) whenever(it.isOpportunistic).thenReturn(true)
} }
private val CONNECTION_3 = FakeMobileConnectionRepository()
private const val SUB_4_ID = 4 private const val SUB_4_ID = 4
private val SUB_4_OPP = private val SUB_4_OPP =
@@ -180,5 +195,6 @@ class MobileIconsInteractorTest : SysuiTestCase() {
whenever(it.subscriptionId).thenReturn(SUB_4_ID) whenever(it.subscriptionId).thenReturn(SUB_4_ID)
whenever(it.isOpportunistic).thenReturn(true) whenever(it.isOpportunistic).thenReturn(true)
} }
private val CONNECTION_4 = FakeMobileConnectionRepository()
} }
} }

View File

@@ -18,8 +18,10 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.settingslib.graph.SignalDrawable import com.android.settingslib.graph.SignalDrawable
import com.android.settingslib.mobile.TelephonyIcons import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
@@ -27,6 +29,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
@@ -44,7 +47,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
interactor.apply { interactor.apply {
setLevel(1) setLevel(1)
setCutOut(false) setCutOut(false)
setIconGroup(TelephonyIcons.THREE_G) setIconGroup(THREE_G)
setIsEmergencyOnly(false) setIsEmergencyOnly(false)
setNumberOfLevels(4) setNumberOfLevels(4)
} }
@@ -62,6 +65,60 @@ class MobileIconViewModelTest : SysuiTestCase() {
job.cancel() job.cancel()
} }
@Test
fun networkType_dataEnabled_groupIsRepresented() =
runBlocking(IMMEDIATE) {
val expected =
Icon.Resource(
THREE_G.dataType,
ContentDescription.Resource(THREE_G.dataContentDescription)
)
interactor.setIconGroup(THREE_G)
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(expected)
job.cancel()
}
@Test
fun networkType_nullWhenDisabled() =
runBlocking(IMMEDIATE) {
interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(false)
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
assertThat(latest).isNull()
job.cancel()
}
@Test
fun networkType_null_changeToDisabled() =
runBlocking(IMMEDIATE) {
val expected =
Icon.Resource(
THREE_G.dataType,
ContentDescription.Resource(THREE_G.dataContentDescription)
)
interactor.setIconGroup(THREE_G)
interactor.setIsDataEnabled(true)
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(expected)
interactor.setIsDataEnabled(false)
yield()
assertThat(latest).isNull()
job.cancel()
}
companion object { companion object {
private val IMMEDIATE = Dispatchers.Main.immediate private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1 private const val SUB_1_ID = 1