diff --git a/android/src/main/java/com/amplitude/android/Amplitude.kt b/android/src/main/java/com/amplitude/android/Amplitude.kt index b030e25b..07303bba 100644 --- a/android/src/main/java/com/amplitude/android/Amplitude.kt +++ b/android/src/main/java/com/amplitude/android/Amplitude.kt @@ -63,7 +63,7 @@ open class Amplitude( add(AndroidLifecyclePlugin()) add(AnalyticsConnectorIdentityPlugin()) add(AnalyticsConnectorPlugin()) - add(AmplitudeDestination(AndroidNetworkConnectivityChecker(this.configuration.context))) + add(AmplitudeDestination(AndroidNetworkConnectivityChecker(this.configuration.context, this.logger))) (timeline as Timeline).start() } diff --git a/android/src/main/java/com/amplitude/android/utilities/AndroidNetworkConnectivityChecker.kt b/android/src/main/java/com/amplitude/android/utilities/AndroidNetworkConnectivityChecker.kt index f73b320f..9caf41ce 100644 --- a/android/src/main/java/com/amplitude/android/utilities/AndroidNetworkConnectivityChecker.kt +++ b/android/src/main/java/com/amplitude/android/utilities/AndroidNetworkConnectivityChecker.kt @@ -6,10 +6,10 @@ import android.content.pm.PackageManager import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.os.Build +import com.amplitude.common.Logger import com.amplitude.core.platform.NetworkConnectivityChecker -class AndroidNetworkConnectivityChecker(private val context: Context) : NetworkConnectivityChecker { - +class AndroidNetworkConnectivityChecker(private val context: Context, private val logger: Logger) : NetworkConnectivityChecker { companion object { private const val ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE" } @@ -19,6 +19,7 @@ class AndroidNetworkConnectivityChecker(private val context: Context) : NetworkC // Events will be treated like online // regardless network connectivity if (!hasPermission(context, ACCESS_NETWORK_STATE)) { + logger.warn("No ACCESS_NETWORK_STATE permission, offline mode is not supported.") return true } @@ -26,7 +27,9 @@ class AndroidNetworkConnectivityChecker(private val context: Context) : NetworkC if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val network = cm.activeNetwork ?: return false val capabilities = cm.getNetworkCapabilities(network) ?: return false - return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + + return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) } else { @SuppressLint("MissingPermission") val networkInfo = cm.activeNetworkInfo @@ -34,7 +37,10 @@ class AndroidNetworkConnectivityChecker(private val context: Context) : NetworkC } } - private fun hasPermission(context: Context, permission: String): Boolean { + private fun hasPermission( + context: Context, + permission: String, + ): Boolean { return context.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED } } diff --git a/core/src/test/kotlin/com/amplitude/core/platform/EventPipelineTest.kt b/core/src/test/kotlin/com/amplitude/core/platform/EventPipelineTest.kt new file mode 100644 index 00000000..683200a3 --- /dev/null +++ b/core/src/test/kotlin/com/amplitude/core/platform/EventPipelineTest.kt @@ -0,0 +1,74 @@ +package com.amplitude.core.platform + +import com.amplitude.core.Amplitude +import com.amplitude.core.Configuration +import com.amplitude.core.events.BaseEvent +import io.mockk.coEvery +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +@ExperimentalCoroutinesApi +class EventPipelineTest { + private lateinit var amplitude: Amplitude + private lateinit var networkConnectivityChecker: NetworkConnectivityChecker + private val config = Configuration(apiKey = "API_KEY", flushIntervalMillis = 5) + + @BeforeEach + fun setup() { + amplitude = Amplitude(config) + networkConnectivityChecker = mockk(relaxed = true) + } + + @Test + fun `should not flush when put and offline`() = + runBlocking { + coEvery { networkConnectivityChecker.isConnected() } returns false + val eventPipeline = spyk(EventPipeline(amplitude, networkConnectivityChecker)) + val event = BaseEvent().apply { eventType = "test_event" } + + eventPipeline.start() + eventPipeline.put(event) + delay(6) + + verify(exactly = 0) { eventPipeline.flush() } + } + + @Test + fun `should flush when put and online`() = + runBlocking { + coEvery { networkConnectivityChecker.isConnected() } returns true + val eventPipeline = spyk(EventPipeline(amplitude, networkConnectivityChecker)) + val event = BaseEvent().apply { eventType = "test_event" } + + eventPipeline.start() + eventPipeline.put(event) + delay(6) + + verify(exactly = 1) { eventPipeline.flush() } + } + + @Test + fun `should flush when back to online and an event is tracked`() = + runBlocking { + coEvery { networkConnectivityChecker.isConnected() } returns false + val eventPipeline = spyk(EventPipeline(amplitude, networkConnectivityChecker)) + val event1 = BaseEvent().apply { eventType = "test_event1" } + val event2 = BaseEvent().apply { eventType = "test_event2" } + + eventPipeline.start() + eventPipeline.put(event1) + delay(6) + + coEvery { networkConnectivityChecker.isConnected() } returns true + eventPipeline.put(event2) + delay(6) + + verify(exactly = 1) { eventPipeline.flush() } + } +}