Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public static void updateNotification(Context context, boolean noStopButton) {
boolean secureConnection =
sharedPreferences.getBoolean(FtpService.KEY_PREFERENCE_SECURE, FtpService.DEFAULT_SECURE);

InetAddress address = NetworkUtil.getLocalInetAddress(context);
InetAddress address = NetworkUtil.getLocalInetAddress(context, false);

String address_text = "Address not found";

Expand Down
22 changes: 18 additions & 4 deletions app/src/main/java/com/amaze/filemanager/utils/NetworkUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ object NetworkUtil {
* Caveat: doesn't handle IPv6 addresses well. Forcing return IPv4 if possible.
*/
@JvmStatic
fun getLocalInetAddress(context: Context): InetAddress? {
fun getLocalInetAddress(
context: Context,
requestMulticast: Boolean = false,
): InetAddress? {
if (!isConnectedToLocalNetwork(context)) {
return null
}
Expand All @@ -112,14 +115,25 @@ object NetworkUtil {
return if (ipAddress == 0) null else intToInet(ipAddress)
}
runCatching {
NetworkInterface.getNetworkInterfaces().iterator().forEach { netinterface ->
netinterface.inetAddresses.iterator().forEach { address ->
NetworkInterface.getNetworkInterfaces().iterator().forEach { networkInterface ->
networkInterface.inetAddresses.iterator().forEach { address ->
// this is the condition that sometimes gives problems
if (!address.isLoopbackAddress &&
!address.isLinkLocalAddress &&
address is Inet4Address
) {
return address
if (requestMulticast) {
if (networkInterface.supportsMulticast()) {
return address
} else {
log.warn(
"network interface {} does not support multicast",
networkInterface.displayName,
)
}
} else {
return address
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class SameSubnetDiscoverDeviceStrategy : SmbDeviceScannerObservable.DiscoverDevi
}

private fun getNeighbourhoodHosts(): List<InetAddress> {
val deviceAddress = NetworkUtil.getLocalInetAddress(AppConfig.getInstance())
val deviceAddress = NetworkUtil.getLocalInetAddress(AppConfig.getInstance(), requestMulticast = true)
return deviceAddress?.let { addr ->
if (addr is Inet6Address) {
// IPv6 neigbourhood hosts can be very big - that should use wsdd instead; hence
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class WsddDiscoverDeviceStrategy : SmbDeviceScannerObservable.DiscoverDeviceStra

@Suppress("LabeledExpression")
private fun multicastForDevice(callback: (ComputerParcelable) -> Unit) {
NetworkUtil.getLocalInetAddress(AppConfig.getInstance())?.let { addr ->
NetworkUtil.getLocalInetAddress(AppConfig.getInstance(), requestMulticast = true)?.let { addr ->
val multicastAddressV4 = InetAddress.getByName(BROADCAST_IPV4)
val multicastAddressV6 = InetAddress.getByName(BROADCAST_IPV6_LINK_LOCAL)

Expand Down
267 changes: 267 additions & 0 deletions app/src/test/java/com/amaze/filemanager/utils/NetworkUtilTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
package com.amaze.filemanager.utils

import android.app.Service
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkInfo
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.os.Build
import android.os.Build.VERSION_CODES
import android.os.Build.VERSION_CODES.LOLLIPOP
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import java.net.Inet4Address
import java.net.NetworkInterface
import java.util.Collections

/**
* Tests for [NetworkUtil].
*/
@RunWith(AndroidJUnit4::class)
@Config(sdk = [LOLLIPOP, P, VERSION_CODES.R])
@Suppress("StringLiteralDuplication")
class NetworkUtilTest {
private lateinit var context: Context
private lateinit var connectivityManager: ConnectivityManager
private lateinit var wifiManager: WifiManager
private lateinit var wifiInfo: WifiInfo

/**
* Set up the mocks.
*/
@Before
fun setUp() {
context = mockk(relaxed = true)
connectivityManager = mockk(relaxed = true)
wifiManager = mockk(relaxed = true)
wifiInfo = mockk(relaxed = true)

every { context.applicationContext } returns context
every {
context.getSystemService(Service.CONNECTIVITY_SERVICE)
} returns connectivityManager
every {
context.getSystemService(Service.WIFI_SERVICE)
} returns wifiManager
every { wifiManager.connectionInfo } returns wifiInfo
}

/**
* Test [NetworkUtil.isConnectedToLocalNetwork] when device is not connected.
*/
@Test
fun testGetLocalInetAddressWhenNotConnected() {
mockNetworkNotConnected()
assertNull(NetworkUtil.getLocalInetAddress(context))
}

/**
* Test [NetworkUtil.getLocalInetAddress] when device is connected to Wifi.
*/
@Test
fun testGetLocalInetAddressOnWifi() {
mockWifiConnected()
every { wifiInfo.ipAddress } returns 0x0F02000A // 10.0.2.15

val result = NetworkUtil.getLocalInetAddress(context)
assertNotNull(result)
assertEquals("10.0.2.15", result?.hostAddress)
}

/**
* Test [NetworkUtil.getLocalInetAddress] when device is connected to Ethernet.
*/
@Test
fun testGetLocalInetAddressOnEthernet() {
mockEthernetConnected()
mockkStatic(NetworkInterface::class)

val inetAddress = mockk<Inet4Address>()
every { inetAddress.isLoopbackAddress } returns false
every { inetAddress.isLinkLocalAddress } returns false
every { inetAddress.hostAddress } returns "192.168.1.100"

val networkInterface = mockk<NetworkInterface>()
every { networkInterface.inetAddresses } returns
Collections.enumeration(listOf(inetAddress))
every { NetworkInterface.getNetworkInterfaces() } returns
Collections.enumeration(listOf(networkInterface))

val result = NetworkUtil.getLocalInetAddress(context)
assertNotNull(result)
assertEquals("192.168.1.100", result?.hostAddress)
}

/**
* Test [NetworkUtil.getLocalInetAddress] for network interface that supports multicast.
*/
@Test
fun testGetLocalInetAddressWithMulticastSupport() {
mockEthernetConnected()
mockkStatic(NetworkInterface::class)

val inetAddress = mockk<Inet4Address>()
every { inetAddress.isLoopbackAddress } returns false
every { inetAddress.isLinkLocalAddress } returns false
every { inetAddress.hostAddress } returns "192.168.1.1"

val networkInterface = mockk<NetworkInterface>()
every { networkInterface.supportsMulticast() } returns true
every { networkInterface.inetAddresses } returns
Collections.enumeration(listOf(inetAddress))
every { NetworkInterface.getNetworkInterfaces() } returns
Collections.enumeration(listOf(networkInterface))

val result = NetworkUtil.getLocalInetAddress(context, requestMulticast = true)
assertNotNull(result)
assertEquals("192.168.1.1", result?.hostAddress)
}

/**
*
*/
@Test
fun testGetLocalInetAddressWithoutMulticastSupport() {
mockEthernetConnected()

val inetAddress = mockk<Inet4Address>()
every { inetAddress.isLoopbackAddress } returns false
every { inetAddress.isLinkLocalAddress } returns false
every { inetAddress.hostAddress } returns "192.168.1.100"

val networkInterface = mockk<NetworkInterface>()
every { networkInterface.supportsMulticast() } returns false
every { networkInterface.displayName } returns "eth0"
every { networkInterface.inetAddresses } returns
Collections.enumeration(listOf(inetAddress))

mockkStatic(NetworkInterface::class)
every { NetworkInterface.getNetworkInterfaces() } returns
Collections.enumeration(listOf(networkInterface))

val result = NetworkUtil.getLocalInetAddress(context, requestMulticast = true)
assertNull(result)
}

/**
* Test [NetworkUtil.getLocalInetAddress] when there are multiple network interfaces, and we
* need to pick one that supports multicast.
*/
@Test
fun testGetLocalInetAddressWithMultipleInterfacesOneSupportsMulticast() {
mockEthernetConnected()
mockkStatic(NetworkInterface::class)

// Create a non-multicast interface
val nonMulticastAddress = mockk<Inet4Address>()
every { nonMulticastAddress.isLoopbackAddress } returns false
every { nonMulticastAddress.isLinkLocalAddress } returns false
every { nonMulticastAddress.hostAddress } returns "192.168.1.100"

val nonMulticastInterface = mockk<NetworkInterface>()
every { nonMulticastInterface.supportsMulticast() } returns false
every { nonMulticastInterface.displayName } returns "eth0"
every { nonMulticastInterface.inetAddresses } returns
Collections.enumeration(listOf(nonMulticastAddress))

// Create a multicast-capable interface
val multicastAddress = mockk<Inet4Address>()
every { multicastAddress.isLoopbackAddress } returns false
every { multicastAddress.isLinkLocalAddress } returns false
every { multicastAddress.hostAddress } returns "192.168.2.100"

val multicastInterface = mockk<NetworkInterface>()
every { multicastInterface.supportsMulticast() } returns true
every { multicastInterface.displayName } returns "wlan0"
every { multicastInterface.inetAddresses } returns
Collections.enumeration(listOf(multicastAddress))

// Mock NetworkInterface.getNetworkInterfaces() to return both interfaces
every { NetworkInterface.getNetworkInterfaces() } returns
Collections.enumeration(listOf(nonMulticastInterface, multicastInterface))

val result = NetworkUtil.getLocalInetAddress(context, requestMulticast = true)
assertNotNull(result)
assertEquals("192.168.2.100", result?.hostAddress)
}

private fun mockNetworkNotConnected() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val networkCapabilities = mockk<NetworkCapabilities>()
every { networkCapabilities.hasTransport(any()) } returns false
every { connectivityManager.activeNetwork } returns null
every {
connectivityManager.getNetworkCapabilities(any())
} returns networkCapabilities
} else {
every { connectivityManager.activeNetworkInfo } returns null
}
}

private fun mockWifiConnected() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = mockk<Network>()
val networkCapabilities = mockk<NetworkCapabilities>()
every {
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
} returns true
every {
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
} returns false
every { connectivityManager.activeNetwork } returns network
every {
connectivityManager.getNetworkCapabilities(network)
} returns networkCapabilities
} else {
val networkInfo = mockk<NetworkInfo>()
every { networkInfo.isConnected } returns true
every { networkInfo.type } returns ConnectivityManager.TYPE_WIFI
every { connectivityManager.activeNetworkInfo } returns networkInfo
}
}

private fun mockEthernetConnected() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = mockk<Network>()
val networkCapabilities = mockk<NetworkCapabilities>()
every {
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
} returns false
every {
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
} returns true
every { connectivityManager.activeNetwork } returns network
every {
connectivityManager.getNetworkCapabilities(network)
} returns networkCapabilities
} else {
val networkInfo = mockk<NetworkInfo>()
every { networkInfo.isConnected } returns true
every { networkInfo.type } returns ConnectivityManager.TYPE_ETHERNET
every { connectivityManager.activeNetworkInfo } returns networkInfo
}
}

/**
* Clean up the mocks.
*/
@After
fun tearDown() {
unmockkAll()
}
}
Loading