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

Commit 857340c0 authored by Evan Laird's avatar Evan Laird
Browse files

[Sat] Add retrySignal to the device-based satellite repository

In the event that a telephony process crashes, SystemUI needs to
potentially re-register our callbacks with the SatelliteManager service.
To achieve this, we define a new [retrySignal] option in the wrapper for
flows that query [SatelliteManager] APIs.

Test: DeviceBasedSatelliteRepositoryImplTest
Bug: 337258696
Flag: com.android.internal.telephony.flags.oem_enabled_satellite_flag
Change-Id: I22176b104adfbfa5b9b792114d98a05d5e6ce75e
parent 939bd52b
Loading
Loading
Loading
Loading
+77 −6
Original line number Original line Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.satellite.data.prod
package com.android.systemui.statusbar.pipeline.satellite.data.prod


import android.os.OutcomeReceiver
import android.os.OutcomeReceiver
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import android.telephony.satellite.NtnSignalStrengthCallback
import android.telephony.satellite.NtnSignalStrengthCallback
import android.telephony.satellite.SatelliteManager
import android.telephony.satellite.SatelliteManager
import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS
import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS
@@ -38,6 +40,7 @@ import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupp
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Unknown
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Unknown
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.time.SystemClock
import java.util.Optional
import java.util.Optional
import javax.inject.Inject
import javax.inject.Inject
@@ -51,12 +54,15 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -92,13 +98,19 @@ sealed interface SatelliteSupport {


    @OptIn(ExperimentalCoroutinesApi::class)
    @OptIn(ExperimentalCoroutinesApi::class)
    companion object {
    companion object {
        /** Convenience function to switch to the supported flow */
        /**
         * Convenience function to switch to the supported flow. [retrySignal] is a flow that emits
         * [Unit] whenever the [supported] flow needs to be restarted
         */
        fun <T> Flow<SatelliteSupport>.whenSupported(
        fun <T> Flow<SatelliteSupport>.whenSupported(
            supported: (SatelliteManager) -> Flow<T>,
            supported: (SatelliteManager) -> Flow<T>,
            orElse: Flow<T>,
            orElse: Flow<T>,
        ): Flow<T> = flatMapLatest {
            retrySignal: Flow<Unit>,
            when (it) {
        ): Flow<T> = flatMapLatest { satelliteSupport ->
                is Supported -> supported(it.satelliteManager)
            when (satelliteSupport) {
                is Supported -> {
                    retrySignal.flatMapLatest { supported(satelliteSupport.satelliteManager) }
                }
                else -> orElse
                else -> orElse
            }
            }
        }
        }
@@ -132,6 +144,7 @@ class DeviceBasedSatelliteRepositoryImpl
@Inject
@Inject
constructor(
constructor(
    satelliteManagerOpt: Optional<SatelliteManager>,
    satelliteManagerOpt: Optional<SatelliteManager>,
    telephonyManager: TelephonyManager,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Application private val scope: CoroutineScope,
    @Application private val scope: CoroutineScope,
    @OemSatelliteInputLog private val logBuffer: LogBuffer,
    @OemSatelliteInputLog private val logBuffer: LogBuffer,
@@ -201,11 +214,65 @@ constructor(
        }
        }
    }
    }


    /**
     * Note that we are given an "unbound" [TelephonyManager] (meaning it was not created with a
     * specific `subscriptionId`). Therefore this is the radio power state of the
     * DEFAULT_SUBSCRIPTION_ID subscription. This subscription, I am led to believe, is the one that
     * would be used for the SatelliteManager subscription.
     *
     * By watching power state changes, we can detect if the telephony process crashes.
     *
     * See b/337258696 for details
     */
    private val radioPowerState: StateFlow<Int> =
        conflatedCallbackFlow {
                val cb =
                    object : TelephonyCallback(), TelephonyCallback.RadioPowerStateListener {
                        override fun onRadioPowerStateChanged(powerState: Int) {
                            trySend(powerState)
                        }
                    }

                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), cb)

                awaitClose { telephonyManager.unregisterTelephonyCallback(cb) }
            }
            .flowOn(bgDispatcher)
            .stateIn(
                scope,
                SharingStarted.WhileSubscribed(),
                TelephonyManager.RADIO_POWER_UNAVAILABLE
            )

    /**
     * In the event that a telephony phone process has crashed, we expect to see a radio power state
     * change from ON to something else. This trigger can be used to re-start a flow via
     * [whenSupported]
     *
     * This flow emits [Unit] when started so that newly-started collectors always run, and only
     * restart when the state goes from ON -> !ON
     */
    private val telephonyProcessCrashedEvent: Flow<Unit> =
        radioPowerState
            .pairwise()
            .mapNotNull { (prev: Int, new: Int) ->
                if (
                    prev == TelephonyManager.RADIO_POWER_ON &&
                        new != TelephonyManager.RADIO_POWER_ON
                ) {
                    Unit
                } else {
                    null
                }
            }
            .onStart { emit(Unit) }

    override val connectionState =
    override val connectionState =
        satelliteSupport
        satelliteSupport
            .whenSupported(
            .whenSupported(
                supported = ::connectionStateFlow,
                supported = ::connectionStateFlow,
                orElse = flowOf(SatelliteConnectionState.Off)
                orElse = flowOf(SatelliteConnectionState.Off),
                retrySignal = telephonyProcessCrashedEvent,
            )
            )
            .stateIn(scope, SharingStarted.Eagerly, SatelliteConnectionState.Off)
            .stateIn(scope, SharingStarted.Eagerly, SatelliteConnectionState.Off)


@@ -232,7 +299,11 @@ constructor(


    override val signalStrength =
    override val signalStrength =
        satelliteSupport
        satelliteSupport
            .whenSupported(supported = ::signalStrengthFlow, orElse = flowOf(0))
            .whenSupported(
                supported = ::signalStrengthFlow,
                orElse = flowOf(0),
                retrySignal = telephonyProcessCrashedEvent,
            )
            .stateIn(scope, SharingStarted.Eagerly, 0)
            .stateIn(scope, SharingStarted.Eagerly, 0)


    // By using the SupportedSatelliteManager here, we expect registration never to fail
    // By using the SupportedSatelliteManager here, we expect registration never to fail
+3 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.systemui.statusbar.pipeline.satellite.data
package com.android.systemui.statusbar.pipeline.satellite.data


import android.telephony.TelephonyManager
import android.telephony.satellite.SatelliteManager
import android.telephony.satellite.SatelliteManager
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
@@ -50,11 +51,13 @@ class DeviceBasedSatelliteRepositorySwitcherTest : SysuiTestCase() {
    private val demoModeController =
    private val demoModeController =
        mock<DemoModeController>().apply { whenever(this.isInDemoMode).thenReturn(false) }
        mock<DemoModeController>().apply { whenever(this.isInDemoMode).thenReturn(false) }
    private val satelliteManager = mock<SatelliteManager>()
    private val satelliteManager = mock<SatelliteManager>()
    private val telephonyManager = mock<TelephonyManager>()
    private val systemClock = FakeSystemClock()
    private val systemClock = FakeSystemClock()


    private val realImpl =
    private val realImpl =
        DeviceBasedSatelliteRepositoryImpl(
        DeviceBasedSatelliteRepositoryImpl(
            Optional.of(satelliteManager),
            Optional.of(satelliteManager),
            telephonyManager,
            testDispatcher,
            testDispatcher,
            testScope.backgroundScope,
            testScope.backgroundScope,
            FakeLogBuffer.Factory.create(),
            FakeLogBuffer.Factory.create(),
+69 −0
Original line number Original line Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.pipeline.satellite.data.prod


import android.os.OutcomeReceiver
import android.os.OutcomeReceiver
import android.os.Process
import android.os.Process
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import android.telephony.satellite.NtnSignalStrength
import android.telephony.satellite.NtnSignalStrength
import android.telephony.satellite.NtnSignalStrengthCallback
import android.telephony.satellite.NtnSignalStrengthCallback
import android.telephony.satellite.SatelliteManager
import android.telephony.satellite.SatelliteManager
@@ -36,6 +38,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.MIN_UPTIME
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.MIN_UPTIME
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -59,6 +62,7 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.never
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.MockitoAnnotations


@@ -69,6 +73,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
    private lateinit var underTest: DeviceBasedSatelliteRepositoryImpl
    private lateinit var underTest: DeviceBasedSatelliteRepositoryImpl


    @Mock private lateinit var satelliteManager: SatelliteManager
    @Mock private lateinit var satelliteManager: SatelliteManager
    @Mock private lateinit var telephonyManager: TelephonyManager


    private val systemClock = FakeSystemClock()
    private val systemClock = FakeSystemClock()
    private val dispatcher = StandardTestDispatcher()
    private val dispatcher = StandardTestDispatcher()
@@ -86,6 +91,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
            underTest =
            underTest =
                DeviceBasedSatelliteRepositoryImpl(
                DeviceBasedSatelliteRepositoryImpl(
                    Optional.empty(),
                    Optional.empty(),
                    telephonyManager,
                    dispatcher,
                    dispatcher,
                    testScope.backgroundScope,
                    testScope.backgroundScope,
                    FakeLogBuffer.Factory.create(),
                    FakeLogBuffer.Factory.create(),
@@ -362,6 +368,68 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
            verify(satelliteManager).registerForModemStateChanged(any(), any())
            verify(satelliteManager).registerForModemStateChanged(any(), any())
        }
        }


    @Test
    fun telephonyCrash_repoReregistersConnectionStateListener() =
        testScope.runTest {
            setupDefaultRepo()

            // GIVEN connection state is requested
            val connectionState by collectLastValue(underTest.connectionState)

            runCurrent()

            val telephonyCallback =
                MobileTelephonyHelpers.getTelephonyCallbackForType<
                    TelephonyCallback.RadioPowerStateListener
                >(
                    telephonyManager
                )

            // THEN listener is registered once
            verify(satelliteManager, times(1)).registerForModemStateChanged(any(), any())

            // WHEN a crash event happens (detected by radio state change)
            telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_ON)
            runCurrent()
            telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_OFF)
            runCurrent()

            // THEN listeners are unregistered and re-registered
            verify(satelliteManager, times(1)).unregisterForModemStateChanged(any())
            verify(satelliteManager, times(2)).registerForModemStateChanged(any(), any())
        }

    @Test
    fun telephonyCrash_repoReregistersSignalStrengthListener() =
        testScope.runTest {
            setupDefaultRepo()

            // GIVEN signal strength is requested
            val signalStrength by collectLastValue(underTest.signalStrength)

            runCurrent()

            val telephonyCallback =
                MobileTelephonyHelpers.getTelephonyCallbackForType<
                    TelephonyCallback.RadioPowerStateListener
                >(
                    telephonyManager
                )

            // THEN listeners are registered the first time
            verify(satelliteManager, times(1)).registerForNtnSignalStrengthChanged(any(), any())

            // WHEN a crash event happens (detected by radio state change)
            telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_ON)
            runCurrent()
            telephonyCallback.onRadioPowerStateChanged(TelephonyManager.RADIO_POWER_OFF)
            runCurrent()

            // THEN listeners are unregistered and re-registered
            verify(satelliteManager, times(1)).unregisterForNtnSignalStrengthChanged(any())
            verify(satelliteManager, times(2)).registerForNtnSignalStrengthChanged(any(), any())
        }

    private fun setUpRepo(
    private fun setUpRepo(
        uptime: Long = MIN_UPTIME,
        uptime: Long = MIN_UPTIME,
        satMan: SatelliteManager? = satelliteManager,
        satMan: SatelliteManager? = satelliteManager,
@@ -380,6 +448,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
        underTest =
        underTest =
            DeviceBasedSatelliteRepositoryImpl(
            DeviceBasedSatelliteRepositoryImpl(
                if (satMan != null) Optional.of(satMan) else Optional.empty(),
                if (satMan != null) Optional.of(satMan) else Optional.empty(),
                telephonyManager,
                dispatcher,
                dispatcher,
                testScope.backgroundScope,
                testScope.backgroundScope,
                FakeLogBuffer.Factory.create(),
                FakeLogBuffer.Factory.create(),