Skip to content

Commit

Permalink
Add additional logs, and check for validated capability before saying…
Browse files Browse the repository at this point in the history
… we have the internet property
  • Loading branch information
jsoberg committed Apr 28, 2024
1 parent 1d19dce commit 25d6633
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.soberg.netinfo.android.app.di

import com.soberg.netinfo.data.ipconfig.IpConfigKtorClient
import com.soberg.netinfo.data.ipconfig.IpConfigWanInfoRepository
import com.soberg.netinfo.data.ipconfig.IpConfigWanInfoRepository.HttpClientProvider
import com.soberg.netinfo.data.ipconfig.IpConfigWanInfoRepository.KtorHttpClientProvider
import com.soberg.netinfo.domain.wan.WanInfoRepository
import dagger.Binds
import dagger.Module
Expand All @@ -17,10 +17,7 @@ internal abstract class IpConfigModule {
companion object {
@Provides
@Singleton
internal fun provideHttpClientProvider(): HttpClientProvider =
HttpClientProvider {
IpConfigKtorClient.create()
}
internal fun provideHttpClientProvider(): HttpClientProvider = KtorHttpClientProvider()
}

@Binds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ data class NetworkInterface(
val isConnectedToVpn: Boolean
get() = properties.contains(Properties.VPN)

val canConnectToInternet: Boolean
get() = properties.contains(Properties.Internet)

enum class Properties {
/** Indicates that this network interface is connected through a VPN (Virtual Private Network). */
VPN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,20 @@ internal class NetworkInterfaceTest {
assertThat(iface.isConnectedToVpn).isFalse()
}

@Test
fun `return true for canConnectToInternet when Internet property present`() {
val iface = NetworkInterfaceTestFixtures.get(
properties = listOf(Properties.Internet)
)
assertThat(iface.canConnectToInternet).isTrue()
}

@Test
fun `return false for canConnectToInternet when Internet property not present`() {
val iface = NetworkInterfaceTestFixtures.get(
properties = listOf(Properties.VPN)
)
assertThat(iface.canConnectToInternet).isFalse()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.soberg.netinfo.data.ipconfig

import com.soberg.netinfo.base.annotation.IODispatcher
import com.soberg.netinfo.base.logging.Logger
import com.soberg.netinfo.base.logging.logErrorIfException
import com.soberg.netinfo.data.ipconfig.model.WanInfoNetworkModel
import com.soberg.netinfo.data.ipconfig.model.toDomain
import com.soberg.netinfo.domain.wan.WanInfoRepository
Expand Down Expand Up @@ -31,24 +30,31 @@ class IpConfigWanInfoRepository @Inject constructor(
runQuery(client)
}
}.fold(
onSuccess = WanInfoRepository.Result::Success,
onFailure = { WanInfoRepository.Result.Error },
onSuccess = { info ->
Logger.debug(Tag, "Query to ipconfig.io succeeded, IP ${info.ip}")
WanInfoRepository.Result.Success(info)
},
onFailure = { error ->
Logger.warn(Tag, "Query to ipconfig.io failed", error)
WanInfoRepository.Result.Error
},
)
}

private suspend fun runQuery(client: HttpClient): WanInfo {
val response = client.get(IpConfigUrl.Main)
return if (response.status == HttpStatusCode.OK) {
Logger.debug(Tag, "Query to ipconfig.io succeeded")
response.body<WanInfoNetworkModel>()
.toDomain()
.logErrorIfException(Tag)
.getOrThrow()
} else {
Logger.debug(Tag, "Query to ipconfig.io failed")
error("Received status code ${response.status}")
}
}

fun interface HttpClientProvider : () -> HttpClient

class KtorHttpClientProvider : HttpClientProvider {
override fun invoke(): HttpClient = IpConfigKtorClient.create()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.headersOf
import io.ktor.utils.io.ByteReadChannel
import io.mockk.mockk
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
Expand Down Expand Up @@ -151,6 +150,6 @@ internal class IpConfigWanInfoRepositoryTest {
)
}
val client = IpConfigKtorClient.create(mockEngine)
return IpConfigWanInfoRepository(mockk(relaxed = true), ioDispatcher) { client }
return IpConfigWanInfoRepository(ioDispatcher) { client }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import android.net.LinkProperties
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import com.soberg.netinfo.android.data.netconnectivity.local.FindLocalIpAddressUseCase
import com.soberg.netinfo.base.logging.Logger
import com.soberg.netinfo.base.type.network.NetworkInterface
import com.soberg.netinfo.domain.lan.NetworkConnectionRepository
import kotlinx.coroutines.channels.ProducerScope
Expand All @@ -20,6 +22,10 @@ internal class AndroidActiveNetworkCallback(
private val findLocalIpAddress: FindLocalIpAddressUseCase,
) : ConnectivityManager.NetworkCallback() {

companion object {
private const val Tag = "AndroidActiveNetworkCallback"
}

override fun onAvailable(network: Network) {
emitActiveNetworkInformation()
}
Expand All @@ -46,16 +52,19 @@ internal class AndroidActiveNetworkCallback(
private fun emitActiveNetworkInformation() = with(scope) {
val activeNetwork = connectivityManager.activeNetwork
if (activeNetwork == null) {
Logger.debug(Tag, "Active network update to None")
trySend(NetworkConnectionRepository.State.NoActiveConnection)
} else {
val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork)
val type = capabilities?.let(::interfaceType) ?: NetworkInterface.Type.Unknown
val linkProperties = connectivityManager.getLinkProperties(activeNetwork)

val interfaceName = linkProperties?.interfaceName
Logger.debug(Tag, "Active network update to $interfaceName, type $type")
val netInterface = NetworkInterface(
name = interfaceName,
ipAddress = interfaceName?.let(findLocalIpAddress::invoke),
type = capabilities?.let(::interfaceType) ?: NetworkInterface.Type.Unknown,
type = type,
properties = capabilities?.let(::buildPropertiesList) ?: emptyList()
)
trySend(NetworkConnectionRepository.State.Connected(netInterface))
Expand All @@ -65,7 +74,9 @@ internal class AndroidActiveNetworkCallback(
private fun buildPropertiesList(
capabilities: NetworkCapabilities
) = buildList {
if (capabilities.hasCapability(NET_CAPABILITY_INTERNET)) {
if (capabilities.hasCapability(NET_CAPABILITY_INTERNET) &&
capabilities.hasCapability(NET_CAPABILITY_VALIDATED)
) {
add(NetworkInterface.Properties.Internet)
}
if (capabilities.hasTransport(TRANSPORT_VPN)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.LinkProperties
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import androidx.test.core.app.ApplicationProvider
import app.cash.turbine.test
import assertk.assertThat
Expand Down Expand Up @@ -97,7 +99,12 @@ internal class AndroidNetworkConnectionRepositoryTest {
assertThat(awaitItem()).isEqualTo(State.NoActiveConnection)

shadowOf(connectivityManager).setDefaultNetworkActive(true)
mockActiveNetwork(capabilities = listOf(NetworkCapabilities.NET_CAPABILITY_INTERNET))
mockActiveNetwork(
capabilities = listOf(
NET_CAPABILITY_INTERNET,
NET_CAPABILITY_VALIDATED,
)
)
callback.onAvailable(mockk())
val second = awaitItem() as State.Connected
assertThat(second.netInterface.properties).containsExactly(Internet)
Expand All @@ -106,7 +113,7 @@ internal class AndroidNetworkConnectionRepositoryTest {

@Test
fun `emit network changes from active network for onCapabilitiesChanged`() = runTest {
mockActiveNetwork(capabilities = listOf(NetworkCapabilities.NET_CAPABILITY_INTERNET))
mockActiveNetwork(capabilities = listOf(NET_CAPABILITY_INTERNET, NET_CAPABILITY_VALIDATED))
connectionRepository.activeConnectionStateFlow.test {
val first = awaitItem() as State.Connected
assertThat(first.netInterface.properties).containsExactly(Internet)
Expand All @@ -120,7 +127,7 @@ internal class AndroidNetworkConnectionRepositoryTest {

@Test
fun `emit network changes from active network for onLinkPropertiesChanged`() = runTest {
mockActiveNetwork(capabilities = listOf(NetworkCapabilities.NET_CAPABILITY_INTERNET))
mockActiveNetwork(capabilities = listOf(NET_CAPABILITY_INTERNET, NET_CAPABILITY_VALIDATED))
connectionRepository.activeConnectionStateFlow.test {
val first = awaitItem() as State.Connected
assertThat(first.netInterface.properties).containsExactly(Internet)
Expand All @@ -130,14 +137,23 @@ internal class AndroidNetworkConnectionRepositoryTest {
NetworkCapabilities.TRANSPORT_WIFI,
NetworkCapabilities.TRANSPORT_VPN
),
capabilities = listOf(NetworkCapabilities.NET_CAPABILITY_INTERNET),
capabilities = listOf(NET_CAPABILITY_INTERNET, NET_CAPABILITY_VALIDATED),
)
callback.onLinkPropertiesChanged(mockk(), mockk())
val second = awaitItem() as State.Connected
assertThat(second.netInterface.properties).containsExactly(Internet, VPN)
}
}

@Test
fun `emit without Internet property when validated capability not found`() = runTest {
mockActiveNetwork(capabilities = listOf(NET_CAPABILITY_INTERNET))
connectionRepository.activeConnectionStateFlow.test {
val first = awaitItem() as State.Connected
assertThat(first.netInterface.properties).isEmpty()
}
}

@Test
fun `emit network changes from active network for onLost`() = runTest {
mockActiveNetwork(transportTypes = listOf(NetworkCapabilities.TRANSPORT_WIFI))
Expand Down Expand Up @@ -190,7 +206,7 @@ internal class AndroidNetworkConnectionRepositoryTest {
private fun mockActiveNetwork(
interfaceName: String? = "wlan0",
transportTypes: List<Int> = listOf(NetworkCapabilities.TRANSPORT_WIFI),
capabilities: List<Int> = listOf(NetworkCapabilities.NET_CAPABILITY_INTERNET),
capabilities: List<Int> = listOf(NET_CAPABILITY_INTERNET),
) {
val shadowCapabilities = ShadowNetworkCapabilities.newInstance()
transportTypes.forEach { transport ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,15 @@ class NetworkInfoViewModel @Inject internal constructor(

is NetworkConnectionRepository.State.Connected -> {
dataCache.updateLan(state.netInterface)
val result = wanInfoRepository.loadWanInfo()
val wanResult = if (state.netInterface.canConnectToInternet) {
toWanState(wanResult = wanInfoRepository.loadWanInfo())
} else {
NetworkInfoViewState.Ready.Wan.CannotConnect
}

NetworkInfoViewState.Ready(
lan = toLanState(state.netInterface),
wan = toWanState(
wanResult = result,
),
wan = wanResult,
)
}
}
Expand Down

0 comments on commit 25d6633

Please sign in to comment.