Skip to content

Commit

Permalink
fix: Don't do network calls when apps are in background (#730)
Browse files Browse the repository at this point in the history
Addresses #724

The other task needed to close this ticket is to have endpoint apps bind and unbind from the Gateway when they are in the foreground/background.
  • Loading branch information
sdsantos committed Feb 22, 2024
1 parent 2f16b32 commit afbadd8
Show file tree
Hide file tree
Showing 9 changed files with 52 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import android.os.Message
import android.os.Messenger
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.rule.ServiceTestRule
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
Expand Down Expand Up @@ -45,7 +44,7 @@ class EndpointPreRegistrationServiceTest {
lateinit var internetGatewayPreferences: InternetGatewayPreferences

private val coroutineContext
get() = app.backgroundScope.coroutineContext + UnconfinedTestDispatcher()
get() = (getApplicationContext() as App).backgroundContext

@Before
fun setUp() {
Expand Down Expand Up @@ -117,7 +116,7 @@ class EndpointPreRegistrationServiceTest {
internetGatewayPreferences.setRegistrationState(RegistrationState.ToDo)

val serviceIntent = Intent(
getApplicationContext<Context>(),
getApplicationContext(),
EndpointPreRegistrationService::class.java,
)
val binder = serviceRule.bindService(serviceIntent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import androidx.test.rule.ServiceTestRule
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import tech.relaycorp.gateway.App
import tech.relaycorp.gateway.data.model.RecipientLocation
import tech.relaycorp.gateway.domain.StoreParcel
import tech.relaycorp.gateway.pdc.local.PDCServer
Expand Down Expand Up @@ -45,7 +45,7 @@ class GatewaySyncServiceParcelCollectionTest {
lateinit var storeParcel: StoreParcel

private val coroutineContext
get() = UnconfinedTestDispatcher()
get() = (getApplicationContext() as App).backgroundContext

@Before
fun setUp() {
Expand All @@ -55,6 +55,7 @@ class GatewaySyncServiceParcelCollectionTest {

@After
fun tearDown() {
serviceRule.unbindService()
Thread.sleep(3000) // Wait for netty to properly stop, to avoid a RejectedExecutionException
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import tech.relaycorp.gateway.App
import tech.relaycorp.gateway.data.database.StoredParcelDao
import tech.relaycorp.gateway.data.disk.FileStore
import tech.relaycorp.gateway.data.model.MessageAddress
Expand Down Expand Up @@ -41,6 +42,9 @@ class GatewaySyncServiceParcelDeliveryTest {
@Inject
lateinit var storedParcelDao: StoredParcelDao

private val coroutineContext
get() = (getApplicationContext() as App).backgroundContext

@Before
fun setUp() {
AppTestProvider.component.inject(this)
Expand All @@ -49,11 +53,12 @@ class GatewaySyncServiceParcelDeliveryTest {

@After
fun tearDown() {
serviceRule.unbindService()
Thread.sleep(3000) // Wait for netty to properly stop, to avoid a RejectedExecutionException
}

@Test
fun parcelDelivery_validParcel() = runTest {
fun parcelDelivery_validParcel() = runTest(coroutineContext) {
setGatewayCertificate(PDACertPath.PRIVATE_GW)
val recipientId = "0deadbeef"
val recipientInternetAddress = "example.org"
Expand All @@ -76,7 +81,7 @@ class GatewaySyncServiceParcelDeliveryTest {
}

@Test(expected = RejectedParcelException::class)
fun parcelDelivery_invalidParcel() = runTest {
fun parcelDelivery_invalidParcel() = runTest(coroutineContext) {
val fiveMinutesAgo = ZonedDateTime.now().minusMinutes(5)
val recipientId = "0deadbeef"
val recipientInternetAddress = "example.org"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package tech.relaycorp.gateway.test

import kotlinx.coroutines.test.UnconfinedTestDispatcher
import tech.relaycorp.gateway.App
import tech.relaycorp.gateway.TestAppModule

Expand All @@ -8,6 +9,8 @@ open class TestApp : App() {
.testAppModule(TestAppModule(this))
.build() as AppTestComponent

override val backgroundContext = UnconfinedTestDispatcher()

override fun onCreate() {
super.onCreate()
component.inject(this)
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/tech/relaycorp/gateway/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ open class App : Application() {
}
}

open val backgroundContext = Dispatchers.IO

@VisibleForTesting
val backgroundScope = CoroutineScope(Dispatchers.IO)
val backgroundScope get() = CoroutineScope(backgroundContext)

@Inject
lateinit var foregroundAppMonitor: ForegroundAppMonitor
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/tech/relaycorp/gateway/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import dagger.Module
import dagger.Provides
import kotlin.coroutines.CoroutineContext

@Module
open class AppModule(
Expand Down Expand Up @@ -34,4 +35,7 @@ open class AppModule(

@Provides
fun wifiManager() = app.getSystemService(Context.WIFI_SERVICE) as WifiManager

@Provides
fun backgroundCoroutineContext(): CoroutineContext = app.backgroundContext
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import tech.relaycorp.gateway.background.ConnectionState
import tech.relaycorp.gateway.background.ConnectionStateObserver
Expand Down Expand Up @@ -45,22 +47,26 @@ class PublicSync
combine(
foregroundAppMonitor.observe(),
pdcServerStateManager.observe(),
connectionStateObserver.observe(),
// Retry registration and sync every minute in case there's a failure
interval(1.minutes),
) { foregroundState, pdcState, connectionState, _ ->
if (
connectionState is ConnectionState.InternetWithGateway && (
) { foregroundState, pdcState, _ -> Pair(foregroundState, pdcState) }
.flatMapLatest { (foregroundState, pdcState) ->
if (
foregroundState == ForegroundAppMonitor.State.Foreground ||
pdcState == PDCServer.State.Started
)
) {
startSync()
} else {
stopSync()
pdcState == PDCServer.State.Started
) {
connectionStateObserver.observe()
.map { it is ConnectionState.InternetWithGateway }
} else {
flowOf(false)
}
}.collect { syncShouldBeRunning ->
if (syncShouldBeRunning) {
startSync()
} else {
stopSync()
}
}
}
.collect()
}

suspend fun syncOneOff() {
Expand Down
12 changes: 7 additions & 5 deletions app/src/main/java/tech/relaycorp/gateway/pdc/local/PDCServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,22 @@ import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.ktor.server.routing.routing
import io.ktor.server.websocket.WebSockets
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import tech.relaycorp.gateway.pdc.local.routes.EndpointRegistrationRoute
import tech.relaycorp.gateway.pdc.local.routes.PDCServerRoute
import tech.relaycorp.gateway.pdc.local.routes.ParcelCollectionRoute
import tech.relaycorp.gateway.pdc.local.routes.ParcelDeliveryRoute
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration.Companion.seconds

class PDCServer
@Inject constructor(
private val stateManager: PDCServerStateManager,
endpointRegistrationRoute: EndpointRegistrationRoute,
parcelCollectionRoute: ParcelCollectionRoute,
private val parcelCollectionRoute: ParcelCollectionRoute,
parcelDeliveryRoute: ParcelDeliveryRoute,
private val backgroundContext: CoroutineContext,
) {

private val server by lazy {
Expand All @@ -37,15 +38,16 @@ class PDCServer
}

suspend fun start() {
withContext(Dispatchers.IO) {
withContext(backgroundContext) {
server.start(false)
}
stateManager.set(State.Started)
}

suspend fun stop() {
withContext(Dispatchers.IO) {
server.stop(0, CALL_DEADLINE.inWholeMilliseconds)
withContext(backgroundContext) {
parcelCollectionRoute.stop()
server.stop(1000, CALL_DEADLINE.inWholeMilliseconds)
}
stateManager.set(State.Stopped)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ class ParcelCollectionRoute
}
}

fun stop() {
asyncJob.cancel()
}

private suspend fun DefaultWebSocketServerSession.handle() {
if (call.request.header(HEADER_ORIGIN) != null) {
// The client is most likely a (malicious) web page
Expand Down

0 comments on commit afbadd8

Please sign in to comment.