Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c46f4339 authored by Evan Laird's avatar Evan Laird
Browse files

[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
parent 7fe757fd
Loading
Loading
Loading
Loading
+54 −13
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@ package com.android.systemui.statusbar.pipeline.satellite.domain.interactor
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 @@ constructor(
    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 @@ constructor(
    val isWifiActive: Flow<Boolean> =
        wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active }

    /** When all connections are considered OOS, satellite connectivity is potentially valid */
    val areAllConnectionsOutOfService =
        if (Flags.oemEnabledSatelliteFlag()) {
    private val allConnectionsOos =
        iconsInteractor.icons.aggregateOver(
            selector = { intr ->
                combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) {
                    isInService,
                    isEmergencyOnly,
                    isNtn ->
                            !isInService && !(isEmergencyOnly || isNtn)
                    !isInService && !isEmergencyOnly && !isNtn
                }
            },
            defaultValue = true, // no connections == everything is OOS
        ) { isOosAndNotEmergencyAndNotSatellite ->
            isOosAndNotEmergencyAndNotSatellite.all { it }
        }
                ) { isOosAndNotEmergencyOnlyOrSatellite ->
                    isOosAndNotEmergencyOnlyOrSatellite.all { it }

    /** When all connections are considered OOS, satellite connectivity is potentially valid */
    val areAllConnectionsOutOfService =
        if (Flags.oemEnabledSatelliteFlag()) {
                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 @@ constructor(
 *
 * 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) }
            }
        }
}
+131 −5
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest
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 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {
                deviceProvisioningInteractor,
                wifiInteractor,
                testScope.backgroundScope,
                FakeLogBuffer.Factory.create(),
            )
    }

@@ -114,6 +116,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {
                    deviceProvisioningInteractor,
                    wifiInteractor,
                    testScope.backgroundScope,
                    FakeLogBuffer.Factory.create(),
                )

            val latest by collectLastValue(underTest.isSatelliteAllowed)
@@ -162,6 +165,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {
                    deviceProvisioningInteractor,
                    wifiInteractor,
                    testScope.backgroundScope,
                    FakeLogBuffer.Factory.create(),
                )

            val latest by collectLastValue(underTest.connectionState)
@@ -218,6 +222,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {
                    deviceProvisioningInteractor,
                    wifiInteractor,
                    testScope.backgroundScope,
                    FakeLogBuffer.Factory.create(),
                )

            val latest by collectLastValue(underTest.signalStrength)
@@ -238,25 +243,97 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {

    @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 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {

    @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 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {

    @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 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {

    @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 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {
                    deviceProvisioningInteractor,
                    wifiInteractor,
                    testScope.backgroundScope,
                    FakeLogBuffer.Factory.create(),
                )

            val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+1 −0
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
                deviceProvisioningInteractor,
                wifiInteractor,
                testScope.backgroundScope,
                FakeLogBuffer.Factory.create(),
            )

        underTest =