diff --git a/app/src/main/java/tech/relaycorp/courier/App.kt b/app/src/main/java/tech/relaycorp/courier/App.kt index 1d36b7fd..d2ec0ff2 100644 --- a/app/src/main/java/tech/relaycorp/courier/App.kt +++ b/app/src/main/java/tech/relaycorp/courier/App.kt @@ -3,6 +3,7 @@ package tech.relaycorp.courier import android.app.Application import android.os.Build import android.os.StrictMode +import tech.relaycorp.courier.background.ForegroundAppMonitor import tech.relaycorp.courier.background.WifiHotspotStateWatcher import tech.relaycorp.courier.common.Logging import tech.relaycorp.courier.common.di.AppComponent @@ -16,6 +17,9 @@ open class App : Application() { @Inject lateinit var wifiHotspotStateWatcher: WifiHotspotStateWatcher + @Inject + lateinit var foregroundAppMonitor: ForegroundAppMonitor + open val component: AppComponent by lazy { DaggerAppComponent.builder() .appModule(AppModule(this)) @@ -36,6 +40,7 @@ open class App : Application() { component.inject(this) setupLogger() setupStrictMode() + registerActivityLifecycleCallbacks(foregroundAppMonitor) wifiHotspotStateWatcher.start() } diff --git a/app/src/main/java/tech/relaycorp/courier/background/ForegroundAppMonitor.kt b/app/src/main/java/tech/relaycorp/courier/background/ForegroundAppMonitor.kt new file mode 100644 index 00000000..b0c5dc4c --- /dev/null +++ b/app/src/main/java/tech/relaycorp/courier/background/ForegroundAppMonitor.kt @@ -0,0 +1,35 @@ +package tech.relaycorp.courier.background + +import android.app.Activity +import android.app.Application +import android.os.Bundle +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ForegroundAppMonitor +@Inject constructor() : Application.ActivityLifecycleCallbacks { + private val activityCountFlow = MutableStateFlow(0) + + fun observe() = activityCountFlow.map { if (it == 0) State.Background else State.Foreground } + + override fun onActivityStarted(activity: Activity) { + activityCountFlow.value++ + } + + override fun onActivityStopped(activity: Activity) { + activityCountFlow.value-- + } + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit + override fun onActivityResumed(activity: Activity) = Unit + override fun onActivityPaused(activity: Activity) = Unit + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit + override fun onActivityDestroyed(activity: Activity) = Unit + + enum class State { + Foreground, Background + } +} diff --git a/app/src/main/java/tech/relaycorp/courier/background/WifiHotspotStateWatcher.kt b/app/src/main/java/tech/relaycorp/courier/background/WifiHotspotStateWatcher.kt index 920c5c30..5c1b1fbd 100644 --- a/app/src/main/java/tech/relaycorp/courier/background/WifiHotspotStateWatcher.kt +++ b/app/src/main/java/tech/relaycorp/courier/background/WifiHotspotStateWatcher.kt @@ -8,15 +8,16 @@ import android.net.wifi.WifiManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.channels.trySendBlocking -import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import tech.relaycorp.cogrpc.server.GatewayIPAddressException import tech.relaycorp.cogrpc.server.Networking -import tech.relaycorp.courier.common.BehaviorChannel import tech.relaycorp.courier.common.Logging.logger import tech.relaycorp.courier.common.tickerFlow import javax.inject.Inject @@ -26,11 +27,12 @@ import kotlin.time.Duration.Companion.seconds @Singleton class WifiHotspotStateWatcher @Inject constructor( - private val context: Context + private val context: Context, + private val foregroundAppMonitor: ForegroundAppMonitor ) { - private val state = BehaviorChannel(WifiHotspotState.Disabled) - fun state() = state.asFlow().distinctUntilChanged() + private val state = MutableStateFlow(WifiHotspotState.Disabled) + fun state() = state.asStateFlow() private var pollingGatewayAddressesJob: Job? = null @@ -54,8 +56,14 @@ class WifiHotspotStateWatcher } private fun startPollingGatewayAddresses() { - pollingGatewayAddressesJob = tickerFlow(POLLING_GATEWAY_ADDRESS_INTERVAL) - .map { + pollingGatewayAddressesJob = foregroundAppMonitor.observe() + .flatMapLatest { + if (it == ForegroundAppMonitor.State.Foreground) { + tickerFlow(POLLING_GATEWAY_ADDRESS_INTERVAL) + } else { + emptyFlow() + } + }.map { try { Networking.getGatewayIpAddress() WifiHotspotState.Enabled @@ -66,13 +74,14 @@ class WifiHotspotStateWatcher .distinctUntilChanged() .onEach { logger.info("Hotspot State $it") - state.send(it) + state.value = it } .launchIn(CoroutineScope(Dispatchers.IO)) } private fun stopPollingGatewayAddresses() { pollingGatewayAddressesJob?.cancel() + pollingGatewayAddressesJob = null } private val wifiApStateChangeReceiver by lazy { @@ -81,14 +90,13 @@ class WifiHotspotStateWatcher if (intent.action != WIFI_AP_STATE_CHANGED_ACTION) return val stateFlag = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0) - logger.info("Wifi State $stateFlag") - state.trySendBlocking( + logger.info("Hotspot State $stateFlag") + state.value = if (stateFlag == WIFI_AP_STATE_ENABLED) { WifiHotspotState.Enabled } else { WifiHotspotState.Disabled } - ) } } } diff --git a/app/src/test/java/tech/relaycorp/courier/ui/main/MainViewModelTest.kt b/app/src/test/java/tech/relaycorp/courier/ui/main/MainViewModelTest.kt index b8ca887f..4eac1eba 100644 --- a/app/src/test/java/tech/relaycorp/courier/ui/main/MainViewModelTest.kt +++ b/app/src/test/java/tech/relaycorp/courier/ui/main/MainViewModelTest.kt @@ -37,7 +37,7 @@ internal class MainViewModelTest { @BeforeEach internal fun setUp() { whenever(connectionObserver.observe()).thenReturn(emptyFlow()) - whenever(hotspotStateReceiver.state()).thenReturn(emptyFlow()) + whenever(hotspotStateReceiver.state()).thenReturn(MutableStateFlow(WifiHotspotState.Disabled)) whenever(getStorageUsage.observe()).thenReturn(emptyFlow()) whenever(observeCCACount.observe()).thenReturn(emptyFlow()) } @@ -45,7 +45,7 @@ internal class MainViewModelTest { @Test internal fun syncPeopleState() = runBlockingTest { val connectionStateFlow = MutableStateFlow(InternetConnection.Offline) - whenever(hotspotStateReceiver.state()).thenReturn(flowOf(WifiHotspotState.Disabled)) + whenever(hotspotStateReceiver.state()).thenReturn(MutableStateFlow(WifiHotspotState.Disabled)) whenever(connectionObserver.observe()).thenReturn(connectionStateFlow) val viewModel = buildViewModel()