Skip to content

Commit

Permalink
feat: Switch to bindAutomatically for the GatewayClient
Browse files Browse the repository at this point in the history
  • Loading branch information
sdsantos committed Feb 21, 2024
1 parent 24ffa42 commit 6811d0b
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 2 deletions.
7 changes: 5 additions & 2 deletions lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,13 @@ dependencies {
// Kotlin
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion"

// Android
implementation 'androidx.lifecycle:lifecycle-process:2.7.0'

// Awala
implementation 'tech.relaycorp:awala:1.68.5'
implementation 'tech.relaycorp:awala:1.68.6'
implementation 'tech.relaycorp:awala-keystore-file:1.6.42'
implementation 'tech.relaycorp:poweb:1.5.68'
implementation 'tech.relaycorp:poweb:1.5.80'
testImplementation 'tech.relaycorp:awala-testing:1.5.27'

// Security
Expand Down
55 changes: 55 additions & 0 deletions lib/src/main/java/tech/relaycorp/awaladroid/GatewayClientImpl.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package tech.relaycorp.awaladroid

import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import tech.relaycorp.awaladroid.background.ServiceInteractor
import tech.relaycorp.awaladroid.common.Logging.logger
Expand Down Expand Up @@ -41,6 +46,9 @@ public class GatewayClientImpl
{ PoWebClient.initLocal(port = Awala.POWEB_PORT) },
private val sendMessage: SendMessage = SendMessage(),
private val receiveMessages: ReceiveMessages = ReceiveMessages(),
private val addProcessLifecycleObserver: (DefaultLifecycleObserver) -> Unit = {
ProcessLifecycleOwner.get().lifecycle.addObserver(it)
},
) {
// Gateway

Expand All @@ -50,6 +58,10 @@ public class GatewayClientImpl
/**
* Bind to the gateway to be able to communicate with it.
*/
@Deprecated(
message = "Use bindAutomatically to bind/unbind when the app is in the foreground/background",
replaceWith = ReplaceWith("bindAutomatically()"),
)
@Throws(GatewayBindingException::class)
public suspend fun bind() {
withContext(coroutineContext) {
Expand Down Expand Up @@ -79,11 +91,52 @@ public class GatewayClientImpl
*
* Make sure to call this when you no longer need to communicate with the gateway.
*/
@Deprecated(
message = "Use bindAutomatically to bind/unbind when the app is in the foreground/background",
replaceWith = ReplaceWith(""),
)
public fun unbind() {
gwServiceInteractor?.unbind()
gwServiceInteractor = null
}

/**
* The app will automatically bind when the application is in the foreground, and unbind when
* the app is moved to the background. Only call this method once.
*
* @param onBindSuccessful called on every successful bind to the gateway
* @param onUnbind called on every unbind to the gateway
* @param onBindFailure called if the binding failed, usually because the Gateway app is
* not installed on the device
*/
public fun bindAutomatically(
onBindSuccessful: () -> Unit = {},
onUnbind: () -> Unit = {},
onBindFailure: (GatewayBindingException) -> Unit,
) {
addProcessLifecycleObserver(
object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
CoroutineScope(coroutineContext).launch {
try {
@Suppress("deprecated")
bind()
onBindSuccessful()
} catch (exp: GatewayBindingException) {
onBindFailure(exp)
}
}
}

override fun onStop(owner: LifecycleOwner) {
@Suppress("deprecated")
unbind()
onUnbind()
}
},
)
}

// First-Party Endpoints

@Throws(
Expand Down Expand Up @@ -140,11 +193,13 @@ public class GatewayClientImpl
REGISTRATION_AUTHORIZATION -> {
cont.resume(replyMessage.data.getByteArray("auth")!!)
}

GATEWAY_NOT_REGISTERED -> {
cont.resumeWithException(
GatewayUnregisteredException("Gateway not registered"),
)
}

else -> {
cont.resumeWithException(
GatewayProtocolException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package tech.relaycorp.awaladroid

import android.os.Bundle
import android.os.Message
import androidx.lifecycle.DefaultLifecycleObserver
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import junit.framework.Assert.assertNull
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.emptyFlow
Expand All @@ -19,6 +21,8 @@ import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
Expand Down Expand Up @@ -50,6 +54,7 @@ internal class GatewayClientImplTest : MockContextTestCase() {
private val serviceInteractor = mock<ServiceInteractor>()
private val sendMessage = mock<SendMessage>()
private val receiveMessages = mock<ReceiveMessages>()
private var lifecycleObserver: DefaultLifecycleObserver? = null

override val gatewayClient =
GatewayClientImpl(
Expand All @@ -58,6 +63,7 @@ internal class GatewayClientImplTest : MockContextTestCase() {
{ pdcClient },
sendMessage,
receiveMessages,
{ lifecycleObserver = it },
)

// Binding
Expand Down Expand Up @@ -112,6 +118,91 @@ internal class GatewayClientImplTest : MockContextTestCase() {
gatewayClient.bind()
}

@Test
fun bindAutomatically_bindsOnStart() =
coroutineScope.runTest {
var onBindSuccessfulCalled = false
var onUnbindCalled = false
var onBindFailureCalled = false

gatewayClient.bindAutomatically(
onBindSuccessful = { onBindSuccessfulCalled = true },
onUnbind = { onUnbindCalled = true },
onBindFailure = { onBindFailureCalled = true },
)

assertFalse(onBindSuccessfulCalled)

lifecycleObserver?.onStart(mock())
coroutineScope.testScheduler.advanceUntilIdle()

verify(serviceInteractor).bind(
Awala.GATEWAY_SYNC_ACTION,
Awala.GATEWAY_PACKAGE,
Awala.GATEWAY_SYNC_COMPONENT,
)
assertTrue(onBindSuccessfulCalled)
assertFalse(onUnbindCalled)
assertFalse(onBindFailureCalled)
}

@Test
fun bindAutomatically_unbindsOnStop() =
coroutineScope.runTest {
var onBindSuccessfulCalled = false
var onUnbindCalled = false
var onBindFailureCalled = false

gatewayClient.bindAutomatically(
onBindSuccessful = { onBindSuccessfulCalled = true },
onUnbind = { onUnbindCalled = true },
onBindFailure = { onBindFailureCalled = true },
)

assertFalse(onUnbindCalled)

lifecycleObserver?.onStart(mock())
coroutineScope.testScheduler.advanceUntilIdle()
lifecycleObserver?.onStop(mock())
coroutineScope.testScheduler.advanceUntilIdle()

verify(serviceInteractor).unbind()
assertTrue(onBindSuccessfulCalled)
assertTrue(onUnbindCalled)
assertFalse(onBindFailureCalled)
}

@Test
fun bindAutomatically_callsOnFailure() =
coroutineScope.runTest {
var onBindSuccessfulCalled = false
var onUnbindCalled = false
var onBindFailureArgument: GatewayBindingException? = null

whenever(serviceInteractor.bind(any(), any(), any()))
.thenThrow(ServiceInteractor.BindFailedException(""))

gatewayClient.bindAutomatically(
onBindSuccessful = { onBindSuccessfulCalled = true },
onUnbind = { onUnbindCalled = true },
onBindFailure = { onBindFailureArgument = it },
)

assertNull(onBindFailureArgument)

lifecycleObserver?.onStart(mock())
coroutineScope.testScheduler.advanceUntilIdle()

verify(serviceInteractor).bind(
Awala.GATEWAY_SYNC_ACTION,
Awala.GATEWAY_PACKAGE,
Awala.GATEWAY_SYNC_COMPONENT,
)
assertFalse(onBindSuccessfulCalled)
assertFalse(onUnbindCalled)
assertTrue(onBindFailureArgument is GatewayBindingException)
}

// Registration

@Test
Expand Down

0 comments on commit 6811d0b

Please sign in to comment.