[Status bar refactor] Add Roaming to the mobile pipeline

Copied the logic exactly as it is from the old pipeline. Roaming is
defined as:

True if (!carrierNetworkChange && ((isGsm && serviceState.roaming) ||
(!isGsm && getCdmaEnhancedRoamingIndicatorDisplayNumber != ERI_OFF)))

Test: MobileConnectionRepositoryTest
Test: DemoMobileConnectionsRepositoryTest
Test: MobileIconInteractorTest
Test: MobileIconViewModelTest
Test: manual in demo mode
Bug: 238425913
Change-Id: If8b9b46e44175c9865ad396023f9f5e49b2105a8
This commit is contained in:
Evan Laird
2022-12-07 14:28:46 -05:00
parent 08ea7a84f1
commit 2856487c83
16 changed files with 247 additions and 5 deletions

View File

@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionS
data class MobileConnectionModel(
/** From [ServiceStateListener.onServiceStateChanged] */
val isEmergencyOnly: Boolean = false,
val isRoaming: Boolean = false,
/** From [SignalStrengthsListener.onSignalStrengthsChanged] */
val isGsm: Boolean = false,

View File

@@ -50,4 +50,12 @@ interface MobileConnectionRepository {
* [SubscriptionManager.getDefaultDataSubscriptionId]
*/
val isDefaultDataSubscription: StateFlow<Boolean>
/**
* See [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber]. This bit only matters if
* the connection type is CDMA.
*
* True if the Enhanced Roaming Indicator (ERI) display number is not [TelephonyManager.ERI_OFF]
*/
val cdmaRoaming: StateFlow<Boolean>
}

View File

@@ -186,6 +186,7 @@ constructor(
connection.dataEnabled.value = true
connection.isDefaultDataSubscription.value = state.dataType != null
connection.cdmaRoaming.value = state.roaming
connection.connectionInfo.value = state.toMobileConnectionModel()
}
@@ -229,6 +230,7 @@ constructor(
private fun Mobile.toMobileConnectionModel(): MobileConnectionModel {
return MobileConnectionModel(
isEmergencyOnly = false, // TODO(b/261029387): not yet supported
isRoaming = roaming,
isGsm = false, // TODO(b/261029387): not yet supported
cdmaLevel = level ?: 0,
primaryLevel = level ?: 0,
@@ -260,4 +262,6 @@ class DemoMobileConnectionRepository(override val subId: Int) : MobileConnection
override val dataEnabled = MutableStateFlow(true)
override val isDefaultDataSubscription = MutableStateFlow(true)
override val cdmaRoaming = MutableStateFlow(false)
}

View File

@@ -98,6 +98,7 @@ constructor(
val inflateStrength = getString("inflate")?.toBoolean()
val activity = getString("activity")?.toActivity()
val carrierNetworkChange = getString("carriernetworkchange") == "show"
val roaming = getString("roam") == "show"
return Mobile(
level = level,
@@ -107,6 +108,7 @@ constructor(
inflateStrength = inflateStrength,
activity = activity,
carrierNetworkChange = carrierNetworkChange,
roaming = roaming,
)
}
}

View File

@@ -34,6 +34,7 @@ sealed interface FakeNetworkEventModel {
val inflateStrength: Boolean?,
@DataActivityType val activity: Int?,
val carrierNetworkChange: Boolean,
val roaming: Boolean,
) : FakeNetworkEventModel
data class MobileDisabled(

View File

@@ -27,6 +27,7 @@ import android.telephony.TelephonyCallback
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.ERI_OFF
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
@@ -95,7 +96,11 @@ class MobileConnectionRepositoryImpl(
TelephonyCallback.CarrierNetworkListener,
TelephonyCallback.DisplayInfoListener {
override fun onServiceStateChanged(serviceState: ServiceState) {
state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
state =
state.copy(
isEmergencyOnly = serviceState.isEmergencyOnly,
isRoaming = serviceState.roaming,
)
trySend(state)
}
@@ -208,6 +213,11 @@ class MobileConnectionRepositoryImpl(
globalMobileDataSettingChangedEvent,
)
override val cdmaRoaming: StateFlow<Boolean> =
telephonyPollingEvent
.mapLatest { telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber != ERI_OFF }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val dataEnabled: StateFlow<Boolean> =
telephonyPollingEvent
.mapLatest { dataConnectionAllowed() }

View File

@@ -54,6 +54,13 @@ interface MobileIconInteractor {
/** True if this line of service is emergency-only */
val isEmergencyOnly: StateFlow<Boolean>
/**
* True if this connection is considered roaming. The roaming bit can come from [ServiceState],
* or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
* connection to be roaming while carrier network change is active
*/
val isRoaming: StateFlow<Boolean>
/** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
val level: StateFlow<Int>
@@ -95,6 +102,18 @@ class MobileIconInteractorImpl(
.mapLatest { it.isEmergencyOnly }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isRoaming: StateFlow<Boolean> =
combine(connectionInfo, connectionRepository.cdmaRoaming) { connection, cdmaRoaming ->
if (connection.carrierNetworkChangeActive) {
false
} else if (connection.isGsm) {
connection.isRoaming
} else {
cdmaRoaming
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val level: StateFlow<Int> =
connectionInfo
.mapLatest { connection ->

View File

@@ -21,6 +21,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Space
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -29,7 +30,6 @@ import com.android.systemui.R
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -43,6 +43,8 @@ object MobileIconBinder {
val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
view.isVisible = true
iconView.isVisible = true
@@ -64,12 +66,21 @@ object MobileIconBinder {
}
}
// Set the roaming indicator
launch {
viewModel.roaming.distinctUntilChanged().collect { isRoaming ->
roamingView.isVisible = isRoaming
roamingSpace.isVisible = isRoaming
}
}
// Set the tint
launch {
viewModel.tint.collect { tint ->
val tintList = ColorStateList.valueOf(tint)
iconView.imageTintList = tintList
networkTypeView.imageTintList = tintList
roamingView.imageTintList = tintList
}
}
}

View File

@@ -87,5 +87,7 @@ constructor(
}
}
val roaming: Flow<Boolean> = iconInteractor.isRoaming
val tint: Flow<Int> = flowOf(Color.CYAN)
}

View File

@@ -30,6 +30,8 @@ class FakeMobileConnectionRepository(override val subId: Int) : MobileConnection
private val _isDefaultDataSubscription = MutableStateFlow(true)
override val isDefaultDataSubscription = _isDefaultDataSubscription
override val cdmaRoaming = MutableStateFlow(false)
fun setConnectionInfo(model: MobileConnectionModel) {
_connectionInfo.value = model
}

View File

@@ -95,6 +95,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
inflateStrength = testCase.inflateStrength,
activity = testCase.activity,
carrierNetworkChange = testCase.carrierNetworkChange,
roaming = testCase.roaming,
)
fakeNetworkEventFlow.value = networkModel
@@ -116,6 +117,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
assertThat(connectionInfo.carrierNetworkChangeActive)
.isEqualTo(model.carrierNetworkChange)
assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
// TODO(b/261029387): check these once we start handling them
assertThat(connectionInfo.isEmergencyOnly).isFalse()
@@ -138,6 +140,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
val inflateStrength: Boolean,
@Annotation.DataActivityType val activity: Int,
val carrierNetworkChange: Boolean,
val roaming: Boolean,
) {
override fun toString(): String {
return "INPUT(level=$level, " +
@@ -146,7 +149,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
"carrierId=$carrierId, " +
"inflateStrength=$inflateStrength, " +
"activity=$activity, " +
"carrierNetworkChange=$carrierNetworkChange)"
"carrierNetworkChange=$carrierNetworkChange, " +
"roaming=$roaming)"
}
// Convenience for iterating test data and creating new cases
@@ -158,6 +162,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
inflateStrength: Boolean? = null,
@Annotation.DataActivityType activity: Int? = null,
carrierNetworkChange: Boolean? = null,
roaming: Boolean? = null,
): TestCase =
TestCase(
level = level ?: this.level,
@@ -166,7 +171,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierId = carrierId ?: this.carrierId,
inflateStrength = inflateStrength ?: this.inflateStrength,
activity = activity ?: this.activity,
carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange
carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
roaming = roaming ?: this.roaming,
)
}
@@ -193,6 +199,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
TelephonyManager.DATA_ACTIVITY_INOUT
)
private val carrierNetworkChange = booleanList
// false first so the base case doesn't have roaming set (more common)
private val roaming = listOf(false, true)
@Parameters(name = "{0}") @JvmStatic fun data() = testData()
@@ -226,7 +234,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierIds.first(),
inflateStrength.first(),
activity.first(),
carrierNetworkChange.first()
carrierNetworkChange.first(),
roaming.first(),
)
val tail =
@@ -237,6 +246,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
inflateStrength.map { baseCase.modifiedBy(inflateStrength = it) },
activity.map { baseCase.modifiedBy(activity = it) },
carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
roaming.map { baseCase.modifiedBy(roaming = it) }
)
.flatten()

View File

@@ -292,6 +292,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
assertThat(connectionInfo.carrierNetworkChangeActive)
.isEqualTo(model.carrierNetworkChange)
assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
// TODO(b/261029387) check these once we start handling them
assertThat(connectionInfo.isEmergencyOnly).isFalse()
@@ -313,6 +314,7 @@ fun validMobileEvent(
inflateStrength: Boolean? = false,
activity: Int? = null,
carrierNetworkChange: Boolean = false,
roaming: Boolean = false,
): FakeNetworkEventModel =
FakeNetworkEventModel.Mobile(
level = level,
@@ -322,4 +324,5 @@ fun validMobileEvent(
inflateStrength = inflateStrength,
activity = activity,
carrierNetworkChange = carrierNetworkChange,
roaming = roaming,
)

View File

@@ -32,6 +32,8 @@ import android.telephony.TelephonyManager.DATA_CONNECTING
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.ERI_OFF
import android.telephony.TelephonyManager.ERI_ON
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
@@ -402,6 +404,61 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun `roaming - cdma - queries telephony manager`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
// Start the telephony collection job so that cdmaRoaming starts updating
val telephonyJob = underTest.connectionInfo.launchIn(this)
val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
val cb = getTelephonyCallbackForType<ServiceStateListener>()
val serviceState = ServiceState()
serviceState.roaming = false
// CDMA roaming is off, GSM roaming is off
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
cb.onServiceStateChanged(serviceState)
assertThat(latest).isFalse()
// CDMA roaming is off, GSM roaming is on
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_ON)
cb.onServiceStateChanged(serviceState)
assertThat(latest).isTrue()
telephonyJob.cancel()
job.cancel()
}
@Test
fun `roaming - gsm - queries service state`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.connectionInfo.onEach { latest = it.isRoaming }.launchIn(this)
val serviceState = ServiceState()
serviceState.roaming = false
val cb = getTelephonyCallbackForType<ServiceStateListener>()
// CDMA roaming is off, GSM roaming is off
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
cb.onServiceStateChanged(serviceState)
assertThat(latest).isFalse()
// CDMA roaming is off, GSM roaming is on
serviceState.roaming = true
cb.onServiceStateChanged(serviceState)
assertThat(latest).isTrue()
job.cancel()
}
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>()
Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())

View File

@@ -30,6 +30,8 @@ class FakeMobileIconInteractor : MobileIconInteractor {
private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly = _isEmergencyOnly
override val isRoaming = MutableStateFlow(false)
private val _isFailedConnection = MutableStateFlow(false)
override val isDefaultConnectionFailed = _isFailedConnection

View File

@@ -298,6 +298,100 @@ class MobileIconInteractorTest : SysuiTestCase() {
job.cancel()
}
@Test
fun `roaming - is gsm - uses connection model`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = true
connectionRepository.setConnectionInfo(
MobileConnectionModel(
isGsm = true,
isRoaming = false,
)
)
yield()
assertThat(latest).isFalse()
connectionRepository.setConnectionInfo(
MobileConnectionModel(
isGsm = true,
isRoaming = true,
)
)
yield()
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun `roaming - is cdma - uses cdma roaming bit`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = false
connectionRepository.setConnectionInfo(
MobileConnectionModel(
isGsm = false,
isRoaming = true,
)
)
yield()
assertThat(latest).isFalse()
connectionRepository.cdmaRoaming.value = true
connectionRepository.setConnectionInfo(
MobileConnectionModel(
isGsm = false,
isRoaming = false,
)
)
yield()
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun `roaming - false while carrierNetworkChangeActive`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = true
connectionRepository.setConnectionInfo(
MobileConnectionModel(
isGsm = false,
isRoaming = true,
carrierNetworkChangeActive = true,
)
)
yield()
assertThat(latest).isFalse()
connectionRepository.cdmaRoaming.value = true
connectionRepository.setConnectionInfo(
MobileConnectionModel(
isGsm = true,
isRoaming = true,
carrierNetworkChangeActive = true,
)
)
yield()
assertThat(latest).isFalse()
job.cancel()
}
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate

View File

@@ -234,6 +234,22 @@ class MobileIconViewModelTest : SysuiTestCase() {
job.cancel()
}
@Test
fun roaming() =
runBlocking(IMMEDIATE) {
interactor.isRoaming.value = true
var latest: Boolean? = null
val job = underTest.roaming.onEach { latest = it }.launchIn(this)
assertThat(latest).isTrue()
interactor.isRoaming.value = false
assertThat(latest).isFalse()
job.cancel()
}
/** Convenience constructor for these tests */
private fun defaultSignal(
level: Int = 1,