diff --git a/lib/src/main/java/tech/relaycorp/awaladroid/Awala.kt b/lib/src/main/java/tech/relaycorp/awaladroid/Awala.kt index e463e8e7..324b7566 100644 --- a/lib/src/main/java/tech/relaycorp/awaladroid/Awala.kt +++ b/lib/src/main/java/tech/relaycorp/awaladroid/Awala.kt @@ -1,8 +1,11 @@ package tech.relaycorp.awaladroid import android.content.Context +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout import tech.relaycorp.awala.keystores.file.FileCertificateStore import tech.relaycorp.awala.keystores.file.FileKeystoreRoot import tech.relaycorp.awala.keystores.file.FileSessionPublicKeystore @@ -15,6 +18,8 @@ import tech.relaycorp.awaladroid.storage.StorageImpl import tech.relaycorp.awaladroid.storage.persistence.DiskPersistence import tech.relaycorp.relaynet.nodes.EndpointManager import java.io.File +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds public object Awala { internal const val POWEB_PORT = 13276 @@ -39,19 +44,21 @@ public object Awala { val fileSessionPublicKeystore = FileSessionPublicKeystore(keystoreRoot) val fileCertificateStore = FileCertificateStore(keystoreRoot) - this.context = AwalaContext( - StorageImpl(DiskPersistence(context.filesDir.path.toString())), - GatewayClientImpl( - serviceInteractorBuilder = { ServiceInteractor(context) }, + contextDeferred.complete( + AwalaContext( + StorageImpl(DiskPersistence(context.filesDir.path.toString())), + GatewayClientImpl( + serviceInteractorBuilder = { ServiceInteractor(context) }, + ), + EndpointManager(androidPrivateKeyStore, fileSessionPublicKeystore), + ChannelManager { + context.getSharedPreferences("awaladroid-channels", Context.MODE_PRIVATE) + }, + androidPrivateKeyStore, + fileSessionPublicKeystore, + fileCertificateStore, + HandleGatewayCertificateChange(androidPrivateKeyStore), ), - EndpointManager(androidPrivateKeyStore, fileSessionPublicKeystore), - ChannelManager { - context.getSharedPreferences("awaladroid-channels", Context.MODE_PRIVATE) - }, - androidPrivateKeyStore, - fileSessionPublicKeystore, - fileCertificateStore, - HandleGatewayCertificateChange(androidPrivateKeyStore), ) coroutineScope { @@ -62,8 +69,21 @@ public object Awala { } } - internal var context: AwalaContext? = null - internal fun getContextOrThrow(): AwalaContext = context ?: throw SetupPendingException() + internal var contextDeferred: CompletableDeferred = CompletableDeferred() + + internal fun getContextOrThrow(): AwalaContext = try { + contextDeferred.getCompleted() + } catch (e: IllegalStateException) { + throw SetupPendingException() + } + + internal suspend fun awaitContextOrThrow(timeout: Duration = 3.seconds): AwalaContext = try { + withTimeout(timeout) { + contextDeferred.await() + } + } catch (e: TimeoutCancellationException) { + throw SetupPendingException() + } } /** diff --git a/lib/src/main/java/tech/relaycorp/awaladroid/background/GatewayCertificateChangeBroadcastReceiver.kt b/lib/src/main/java/tech/relaycorp/awaladroid/background/GatewayCertificateChangeBroadcastReceiver.kt index c22e3367..c1919135 100644 --- a/lib/src/main/java/tech/relaycorp/awaladroid/background/GatewayCertificateChangeBroadcastReceiver.kt +++ b/lib/src/main/java/tech/relaycorp/awaladroid/background/GatewayCertificateChangeBroadcastReceiver.kt @@ -15,7 +15,7 @@ internal class GatewayCertificateChangeBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { CoroutineScope(coroutineContext).launch { - Awala.getContextOrThrow().handleGatewayCertificateChange() + Awala.awaitContextOrThrow().handleGatewayCertificateChange() } } } diff --git a/lib/src/main/java/tech/relaycorp/awaladroid/background/IncomingParcelBroadcastReceiver.kt b/lib/src/main/java/tech/relaycorp/awaladroid/background/IncomingParcelBroadcastReceiver.kt index c56d7560..d12ddcf2 100644 --- a/lib/src/main/java/tech/relaycorp/awaladroid/background/IncomingParcelBroadcastReceiver.kt +++ b/lib/src/main/java/tech/relaycorp/awaladroid/background/IncomingParcelBroadcastReceiver.kt @@ -15,7 +15,7 @@ internal class IncomingParcelBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { CoroutineScope(coroutineContext).launch { - Awala.getContextOrThrow().gatewayClient.checkForNewMessages() + Awala.awaitContextOrThrow().gatewayClient.checkForNewMessages() } } } diff --git a/lib/src/test/java/tech/relaycorp/awaladroid/AwalaTest.kt b/lib/src/test/java/tech/relaycorp/awaladroid/AwalaTest.kt index 70c931b2..017dc116 100644 --- a/lib/src/test/java/tech/relaycorp/awaladroid/AwalaTest.kt +++ b/lib/src/test/java/tech/relaycorp/awaladroid/AwalaTest.kt @@ -3,13 +3,16 @@ package tech.relaycorp.awaladroid import android.content.Context import com.nhaarman.mockitokotlin2.spy import com.nhaarman.mockitokotlin2.verify +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull -import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -26,6 +29,7 @@ import tech.relaycorp.relaynet.wrappers.nodeId import java.io.File import java.time.Duration import java.time.ZonedDateTime +import kotlin.time.Duration.Companion.milliseconds @RunWith(RobolectricTestRunner::class) public class AwalaTest { @@ -33,9 +37,9 @@ public class AwalaTest { @After public fun tearDownAwala(): Unit = unsetAwalaContext() - @Test + @Test(expected = SetupPendingException::class) public fun useBeforeSetup() { - assertThrows(SetupPendingException::class.java) { Awala.getContextOrThrow() } + Awala.getContextOrThrow() } @Test @@ -45,6 +49,29 @@ public class AwalaTest { Awala.getContextOrThrow() } + @Test(expected = SetupPendingException::class) + public fun awaitWithoutSetup(): Unit = runTest { + Awala.awaitContextOrThrow(100.milliseconds) + } + + @Test(expected = SetupPendingException::class) + public fun awaitWithLateSetup(): Unit = runTest { + CoroutineScope(UnconfinedTestDispatcher()).launch { + delay(200.milliseconds) + Awala.setUp(RuntimeEnvironment.getApplication()) + } + Awala.awaitContextOrThrow(100.milliseconds) + } + + @Test(expected = SetupPendingException::class) + public fun awaitAfterSetup(): Unit = runTest { + CoroutineScope(UnconfinedTestDispatcher()).launch { + delay(500.milliseconds) + Awala.setUp(RuntimeEnvironment.getApplication()) + } + Awala.awaitContextOrThrow(1000.milliseconds) + } + @Test public fun keystores(): Unit = runTest { val androidContext = RuntimeEnvironment.getApplication() diff --git a/lib/src/test/java/tech/relaycorp/awaladroid/test/AwalaContextUnits.kt b/lib/src/test/java/tech/relaycorp/awaladroid/test/AwalaContextUnits.kt index 87297c2c..2aa35239 100644 --- a/lib/src/test/java/tech/relaycorp/awaladroid/test/AwalaContextUnits.kt +++ b/lib/src/test/java/tech/relaycorp/awaladroid/test/AwalaContextUnits.kt @@ -1,12 +1,13 @@ package tech.relaycorp.awaladroid.test +import kotlinx.coroutines.CompletableDeferred import tech.relaycorp.awaladroid.Awala import tech.relaycorp.awaladroid.AwalaContext internal fun setAwalaContext(context: AwalaContext) { - Awala.context = context + Awala.contextDeferred = CompletableDeferred(context) } internal fun unsetAwalaContext() { - Awala.context = null + Awala.contextDeferred = CompletableDeferred() }