[Sat] Add device-based emergency calls only state to satellie icon
See the previous CL for the definition of isDeviceInEmergencyCallsOnlyMode.
This CL incorporates the device-based emergency calls only state into
the consideration of whether or not the device is completely out of
service.
Also updates the definition of `aggregateOver`, since there was an issue
that going from N subscriptions -> 0 subscriptions would never re-emit
from that flow.
Test: DeviceBasedSatelliteInteractorTest
Bug: 339023069
Bug: 341109689
Flag: com.android.internal.telephony.flags.oem_enabled_satellite_flag
Change-Id: Ifb576a5dadcd0d430f2b9c60f82031332aff1ee8
Merged-In: Ifb576a5dadcd0d430f2b9c60f82031332aff1ee8
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 51c053e..5b954b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -19,6 +19,9 @@
import com.android.internal.telephony.flags.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -45,6 +48,7 @@
deviceProvisioningInteractor: DeviceProvisioningInteractor,
wifiInteractor: WifiInteractor,
@Application scope: CoroutineScope,
+ @OemSatelliteInputLog private val logBuffer: LogBuffer,
) {
/** Must be observed by any UI showing Satellite iconography */
val isSatelliteAllowed =
@@ -79,25 +83,52 @@
val isWifiActive: Flow<Boolean> =
wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active }
+ private val allConnectionsOos =
+ iconsInteractor.icons.aggregateOver(
+ selector = { intr ->
+ combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
+ isInService,
+ isEmergencyOnly,
+ isNtn ->
+ !isInService && !isEmergencyOnly && !isNtn
+ }
+ },
+ defaultValue = true, // no connections == everything is OOS
+ ) { isOosAndNotEmergencyAndNotSatellite ->
+ isOosAndNotEmergencyAndNotSatellite.all { it }
+ }
+
/** When all connections are considered OOS, satellite connectivity is potentially valid */
val areAllConnectionsOutOfService =
if (Flags.oemEnabledSatelliteFlag()) {
- iconsInteractor.icons.aggregateOver(
- selector = { intr ->
- combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
- isInService,
- isEmergencyOnly,
- isNtn ->
- !isInService && !(isEmergencyOnly || isNtn)
- }
- }
- ) { isOosAndNotEmergencyOnlyOrSatellite ->
- isOosAndNotEmergencyOnlyOrSatellite.all { it }
+ combine(
+ allConnectionsOos,
+ iconsInteractor.isDeviceInEmergencyCallsOnlyMode,
+ ) { connectionsOos, deviceEmergencyOnly ->
+ logBuffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ bool1 = connectionsOos
+ bool2 = deviceEmergencyOnly
+ },
+ {
+ "Updating OOS status. allConnectionsOOs=$bool1 " +
+ "deviceEmergencyOnly=$bool2"
+ },
+ )
+ // If no connections exist, or all are OOS, then we look to the device-based
+ // service state to detect if any calls are possible
+ connectionsOos && !deviceEmergencyOnly
}
} else {
flowOf(false)
}
.stateIn(scope, SharingStarted.WhileSubscribed(), true)
+
+ companion object {
+ const val TAG = "DeviceBasedSatelliteInteractor"
+ }
}
/**
@@ -106,12 +137,22 @@
*
* Provides a way to connect the reactivity of the top-level flow with the reactivity of an
* arbitrarily-defined relationship ([selector]) from R to the flow that R exposes.
+ *
+ * [defaultValue] allows for a default value to be used if there are no leaf nodes after applying
+ * [selector]. E.g., if there are no mobile connections, assume that there is no service.
*/
@OptIn(ExperimentalCoroutinesApi::class)
private inline fun <R, reified S, T> Flow<List<R>>.aggregateOver(
crossinline selector: (R) -> Flow<S>,
- crossinline transform: (Array<S>) -> T
+ defaultValue: T,
+ crossinline transform: (Array<S>) -> T,
): Flow<T> {
return map { list -> list.map { selector(it) } }
- .flatMapLatest { newFlows -> combine(newFlows) { newVals -> transform(newVals) } }
+ .flatMapLatest { newFlows ->
+ if (newFlows.isEmpty()) {
+ flowOf(defaultValue)
+ } else {
+ combine(newFlows) { newVals -> transform(newVals) }
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index 405e3ed..d303976 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -22,6 +22,7 @@
import com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
@@ -71,6 +72,7 @@
deviceProvisioningInteractor,
wifiInteractor,
testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
)
}
@@ -114,6 +116,7 @@
deviceProvisioningInteractor,
wifiInteractor,
testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
)
val latest by collectLastValue(underTest.isSatelliteAllowed)
@@ -162,6 +165,7 @@
deviceProvisioningInteractor,
wifiInteractor,
testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
)
val latest by collectLastValue(underTest.connectionState)
@@ -218,6 +222,7 @@
deviceProvisioningInteractor,
wifiInteractor,
testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
)
val latest by collectLastValue(underTest.signalStrength)
@@ -238,25 +243,97 @@
@Test
@EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
- fun areAllConnectionsOutOfService_noConnections_yes() =
+ fun areAllConnectionsOutOfService_noConnections_noDeviceEmergencyCalls_yes() =
testScope.runTest {
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
// GIVEN, 0 connections
+ // GIVEN, device is not in emergency calls only mode
+ iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
+
// THEN the value is propagated to this interactor
assertThat(latest).isTrue()
}
@Test
@EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
- fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_yes() =
+ fun areAllConnectionsOutOfService_noConnections_deviceEmergencyCalls_yes() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 0 connections
+
+ // GIVEN, device is in emergency calls only mode
+ iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+ // THEN the value is propagated to this interactor
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun areAllConnectionsOutOfService_oneConnectionInService_thenLost_noDeviceEmergencyCalls_yes() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 1 connections
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+ // GIVEN, no device-based emergency calls
+ iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
+
+ // WHEN connection is in service
+ i1.isInService.value = true
+ i1.isEmergencyOnly.value = false
+ i1.isNonTerrestrial.value = false
+
+ // THEN we are considered NOT to be OOS
+ assertThat(latest).isFalse()
+
+ // WHEN the connection disappears
+ iconsInteractor.icons.value = listOf()
+
+ // THEN we are back to OOS
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun areAllConnectionsOutOfService_oneConnectionInService_thenLost_deviceEmergencyCalls_no() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 1 connections
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+ // GIVEN, device-based emergency calls
+ iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+ // WHEN one connection is in service
+ i1.isInService.value = true
+ i1.isEmergencyOnly.value = false
+ i1.isNonTerrestrial.value = false
+
+ // THEN we are considered NOT to be OOS
+ assertThat(latest).isFalse()
+
+ // WHEN the connection disappears
+ iconsInteractor.icons.value = listOf()
+
+ // THEN we are still NOT in OOS, due to device-based emergency calls
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_noDeviceEmergencyCalls_yes() =
testScope.runTest {
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
// GIVEN, 2 connections
val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+ // GIVEN, no device-based emergency calls
+ iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
// WHEN all of the connections are OOS and none are NTN
i1.isInService.value = false
@@ -272,13 +349,39 @@
@Test
@EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
- fun areAllConnectionsOutOfService_twoConnectionsOos_oneNtn_no() =
+ fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_deviceEmergencyCalls_no() =
testScope.runTest {
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
// GIVEN, 2 connections
val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+ // GIVEN, device-based emergency calls
+ iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+ // WHEN all of the connections are OOS and none are NTN
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+ i1.isNonTerrestrial.value = false
+ i2.isInService.value = false
+ i2.isEmergencyOnly.value = false
+ i2.isNonTerrestrial.value = false
+
+ // THEN we are not considered OOS due to device based emergency calling
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun areAllConnectionsOutOfService_twoConnectionsOos_noDeviceEmergencyCalls_oneNtn_no() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 2 connections
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+ val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+ // GIVEN, no device-based emergency calls
+ iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
// WHEN all of the connections are OOS and one is NTN
i1.isInService.value = false
@@ -296,12 +399,14 @@
@Test
@EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
- fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_yes() =
+ fun areAllConnectionsOutOfService_oneConnectionOos_noDeviceEmergencyCalls_nonNtn_yes() =
testScope.runTest {
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
// GIVEN, 1 connection
val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+ // GIVEN, no device-based emergency calls
+ iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false
// WHEN all of the connections are OOS
i1.isInService.value = false
@@ -314,7 +419,27 @@
@Test
@EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
- fun areAllConnectionsOutOfService_oneConnectionOos_ntn_yes() =
+ fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_no() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 1 connection
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+ // GIVEN, device-based emergency calls
+ iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true
+
+ // WHEN all of the connections are OOS
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+ i1.isNonTerrestrial.value = false
+
+ // THEN the value is propagated to this interactor
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun areAllConnectionsOutOfService_oneConnectionOos_ntn_no() =
testScope.runTest {
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
@@ -416,6 +541,7 @@
deviceProvisioningInteractor,
wifiInteractor,
testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
)
val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index ceaae9e..43b9568 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -75,6 +75,7 @@
deviceProvisioningInteractor,
wifiInteractor,
testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
)
underTest =