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

Commit 254e989e authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[Sat] Add retrySignal to the device-based satellite repository" into main

parents 55b43834 857340c0
Loading
Loading
Loading
Loading
+77 −6
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.satellite.data.prod

import android.os.OutcomeReceiver
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import android.telephony.satellite.NtnSignalStrengthCallback
import android.telephony.satellite.SatelliteManager
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.shared.model.SatelliteConnectionState
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
import java.util.Optional
import javax.inject.Inject
@@ -51,12 +54,15 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -92,13 +98,19 @@ sealed interface SatelliteSupport {

    @OptIn(ExperimentalCoroutinesApi::class)
    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(
            supported: (SatelliteManager) -> Flow<T>,
            orElse: Flow<T>,
        ): Flow<T> = flatMapLatest {
            when (it) {
                is Supported -> supported(it.satelliteManager)
            retrySignal: Flow<Unit>,
        ): Flow<T> = flatMapLatest { satelliteSupport ->
            when (satelliteSupport) {
                is Supported -> {
                    retrySignal.flatMapLatest { supported(satelliteSupport.satelliteManager) }
                }
                else -> orElse
            }
        }
@@ -132,6 +144,7 @@ class DeviceBasedSatelliteRepositoryImpl
@Inject
constructor(
    satelliteManagerOpt: Optional<SatelliteManager>,
    telephonyManager: TelephonyManager,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Application private val scope: CoroutineScope,
    @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 =
        satelliteSupport
            .whenSupported(
                supported = ::connectionStateFlow,
                orElse = flowOf(SatelliteConnectionState.Off)
                orElse = flowOf(SatelliteConnectionState.Off),
                retrySignal = telephonyProcessCrashedEvent,
            )
            .stateIn(scope, SharingStarted.Eagerly, SatelliteConnectionState.Off)

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

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

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

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

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

    private val realImpl =
        DeviceBasedSatelliteRepositoryImpl(
            Optional.of(satelliteManager),
            telephonyManager,
            testDispatcher,
            testScope.backgroundScope,
            FakeLogBuffer.Factory.create(),
+69 −0
Original line number 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.Process
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import android.telephony.satellite.NtnSignalStrength
import android.telephony.satellite.NtnSignalStrengthCallback
import android.telephony.satellite.SatelliteManager
@@ -36,6 +38,7 @@ import androidx.test.filters.SmallTest
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.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.POLLING_INTERVAL_MS
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.doAnswer
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

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

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

    private val systemClock = FakeSystemClock()
    private val dispatcher = StandardTestDispatcher()
@@ -86,6 +91,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
            underTest =
                DeviceBasedSatelliteRepositoryImpl(
                    Optional.empty(),
                    telephonyManager,
                    dispatcher,
                    testScope.backgroundScope,
                    FakeLogBuffer.Factory.create(),
@@ -362,6 +368,68 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
            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(
        uptime: Long = MIN_UPTIME,
        satMan: SatelliteManager? = satelliteManager,
@@ -380,6 +448,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
        underTest =
            DeviceBasedSatelliteRepositoryImpl(
                if (satMan != null) Optional.of(satMan) else Optional.empty(),
                telephonyManager,
                dispatcher,
                testScope.backgroundScope,
                FakeLogBuffer.Factory.create(),