diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7338c2f7abc..66169e1b773 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3912,6 +3912,18 @@
Bluetooth tethering
+
+
+ IP address
+
+ MAC address
+
+ Transit link speed
+
+ Receive link speed
+
+ Network usage
+
Ethernet tethering
diff --git a/res/xml/ethernet_interface_details.xml b/res/xml/ethernet_interface_details.xml
index f2d106f4d2a..237b4b8b8cd 100644
--- a/res/xml/ethernet_interface_details.xml
+++ b/res/xml/ethernet_interface_details.xml
@@ -24,4 +24,36 @@
android:selectable="false"
android:order="-10000"/>
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/android/settings/network/ethernet/EthernetInterface.kt b/src/com/android/settings/network/ethernet/EthernetInterface.kt
index 1e2dc74752e..ddb138938a7 100644
--- a/src/com/android/settings/network/ethernet/EthernetInterface.kt
+++ b/src/com/android/settings/network/ethernet/EthernetInterface.kt
@@ -18,34 +18,68 @@ package com.android.settings.network.ethernet
import android.content.Context
import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
import android.net.EthernetManager
import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetNetworkManagementException
import android.net.EthernetNetworkUpdateRequest
import android.net.IpConfiguration
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
import android.os.OutcomeReceiver
import android.util.Log
import androidx.core.content.ContextCompat
+import com.google.common.annotations.VisibleForTesting
class EthernetInterface(private val context: Context, private val id: String) :
EthernetManager.InterfaceStateListener {
+ interface EthernetInterfaceStateListener {
+ fun interfaceUpdated()
+ }
+
private val ethernetManager: EthernetManager? =
context.getSystemService(EthernetManager::class.java)
private val connectivityManager: ConnectivityManager? =
context.getSystemService(ConnectivityManager::class.java)
private val executor = ContextCompat.getMainExecutor(context)
+ private val interfaceListeners = mutableListOf()
private val TAG = "EthernetInterface"
+ private val networkRequest: NetworkRequest =
+ NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .build()
+
private var interfaceState = STATE_ABSENT
private var ipConfiguration = IpConfiguration()
+ private var linkProperties = LinkProperties()
fun getInterfaceState() = interfaceState
fun getId() = id
- fun getConfiguration(): IpConfiguration {
- return ipConfiguration
+ fun getConfiguration() = ipConfiguration
+
+ fun getLinkProperties() = linkProperties
+
+ fun registerListener(listener: EthernetInterfaceStateListener) {
+ if (interfaceListeners.isEmpty()) {
+ ethernetManager?.addInterfaceStateListener(ContextCompat.getMainExecutor(context), this)
+ connectivityManager?.registerNetworkCallback(networkRequest, networkCallback)
+ }
+ interfaceListeners.add(listener)
+ }
+
+ fun unregisterListener(listener: EthernetInterfaceStateListener) {
+ interfaceListeners.remove(listener)
+ if (interfaceListeners.isEmpty()) {
+ connectivityManager?.unregisterNetworkCallback(networkCallback)
+ ethernetManager?.removeInterfaceStateListener(this)
+ }
}
fun setConfiguration(ipConfiguration: IpConfiguration) {
@@ -67,10 +101,28 @@ class EthernetInterface(private val context: Context, private val id: String) :
)
}
+ private fun notifyListeners() {
+ for (listener in interfaceListeners) {
+ listener.interfaceUpdated()
+ }
+ }
+
override fun onInterfaceStateChanged(id: String, state: Int, role: Int, cfg: IpConfiguration?) {
if (id == this.id) {
ipConfiguration = cfg ?: IpConfiguration()
interfaceState = state
+ notifyListeners()
}
}
+
+ @VisibleForTesting
+ val networkCallback =
+ object : NetworkCallback() {
+ override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) {
+ if (lp.getInterfaceName().equals(id)) {
+ linkProperties = lp
+ notifyListeners()
+ }
+ }
+ }
}
diff --git a/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsController.kt b/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsController.kt
index cb3ad1db8ae..174fdbdf25d 100644
--- a/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsController.kt
+++ b/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsController.kt
@@ -18,7 +18,14 @@ package com.android.settings.network.ethernet
import android.content.Context
import android.net.EthernetManager
+import android.net.IpConfiguration
+import android.net.LinkProperties
+import android.net.StaticIpConfiguration
import android.widget.ImageView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import com.android.settings.R
@@ -30,13 +37,25 @@ class EthernetInterfaceDetailsController(
context: Context,
private val fragment: PreferenceFragmentCompat,
private val preferenceId: String,
-) : AbstractPreferenceController(context) {
+ private val lifecycle: Lifecycle,
+) :
+ AbstractPreferenceController(context),
+ EthernetInterface.EthernetInterfaceStateListener,
+ LifecycleEventObserver {
private val KEY_HEADER = "ethernet_details"
private val ethernetManager = context.getSystemService(EthernetManager::class.java)
private val ethernetInterface =
EthernetTrackerImpl.getInstance(context).getInterface(preferenceId)
+ private lateinit var entityHeaderController: EntityHeaderController
+
+ private var ipAddressPref: Preference? = null
+
+ init {
+ lifecycle.addObserver(this)
+ }
+
override fun isAvailable(): Boolean {
return true
}
@@ -45,10 +64,24 @@ class EthernetInterfaceDetailsController(
return KEY_HEADER
}
+ override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+ when (event) {
+ Lifecycle.Event.ON_START -> {
+ ethernetInterface?.registerListener(this)
+ }
+
+ Lifecycle.Event.ON_STOP -> {
+ ethernetInterface?.unregisterListener(this)
+ }
+
+ else -> {}
+ }
+ }
+
override fun displayPreference(screen: PreferenceScreen) {
val headerPref: LayoutPreference? = screen.findPreference(KEY_HEADER)
- val mEntityHeaderController =
+ entityHeaderController =
EntityHeaderController.newInstance(
fragment.getActivity(),
fragment,
@@ -59,17 +92,49 @@ class EthernetInterfaceDetailsController(
iconView?.setScaleType(ImageView.ScaleType.CENTER_INSIDE)
- mEntityHeaderController
- .setLabel("Ethernet")
- .setSummary(
- if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
- mContext.getString(R.string.network_connected)
- } else {
- mContext.getString(R.string.network_disconnected)
- }
- )
- .setSecondSummary("")
- .setIcon(mContext.getDrawable(R.drawable.ic_settings_ethernet))
- .done(true /* rebind */)
+ if (entityHeaderController != null) {
+ entityHeaderController
+ .setLabel("Ethernet")
+ .setSummary(
+ if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
+ mContext.getString(R.string.network_connected)
+ } else {
+ mContext.getString(R.string.network_disconnected)
+ }
+ )
+ .setSecondSummary("")
+ .setIcon(mContext.getDrawable(R.drawable.ic_settings_ethernet))
+ .done(true /* rebind */)
+ }
+
+ ipAddressPref = screen.findPreference("ethernet_ip_address")
+
+ if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
+ initializeIpDetails()
+ }
+ }
+
+ override fun interfaceUpdated() {
+ entityHeaderController?.setSummary(
+ if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
+ mContext.getString(R.string.network_connected)
+ } else {
+ mContext.getString(R.string.network_disconnected)
+ }
+ )
+ initializeIpDetails()
+ }
+
+ private fun initializeIpDetails() {
+ val ipConfiguration: IpConfiguration? = ethernetInterface?.getConfiguration()
+ val linkProperties: LinkProperties? = ethernetInterface?.getLinkProperties()
+
+ if (ipConfiguration?.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
+ val staticIp: StaticIpConfiguration? = ipConfiguration?.getStaticIpConfiguration()
+ ipAddressPref?.setSummary(staticIp?.getIpAddress().toString())
+ } else {
+ val addresses = linkProperties?.getAddresses()
+ ipAddressPref?.setSummary(addresses?.first().toString())
+ }
}
}
diff --git a/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsFragment.kt b/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsFragment.kt
index a6cf90fed6b..46ed78f5aa5 100644
--- a/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsFragment.kt
+++ b/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsFragment.kt
@@ -50,6 +50,13 @@ class EthernetInterfaceDetailsFragment : DashboardFragment() {
override public fun createPreferenceControllers(
context: Context
): List {
- return listOf(EthernetInterfaceDetailsController(context, this, preferenceId ?: ""))
+ return listOf(
+ EthernetInterfaceDetailsController(
+ context,
+ this,
+ preferenceId ?: "",
+ getSettingsLifecycle(),
+ )
+ )
}
}
diff --git a/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsControllerTest.kt b/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsControllerTest.kt
index fb8e4762bbc..5060bd65b33 100644
--- a/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsControllerTest.kt
+++ b/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsControllerTest.kt
@@ -18,22 +18,30 @@ package com.android.settings.network.ethernet
import android.content.Context
import android.content.ContextWrapper
+import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@RunWith(AndroidJUnit4::class)
class EthernetInterfaceDetailsControllerTest {
private val ethernetInterfaceDetailsFragment = EthernetInterfaceDetailsFragment()
+ private val lifecycle = mock()
private val context: Context =
object : ContextWrapper(ApplicationProvider.getApplicationContext()) {}
private val ethernetInterfaceDetailsController =
- EthernetInterfaceDetailsController(context, ethernetInterfaceDetailsFragment, "eth0")
+ EthernetInterfaceDetailsController(
+ context,
+ ethernetInterfaceDetailsFragment,
+ "eth0",
+ lifecycle,
+ )
@Test
fun isAvailable_ShouldReturnTrue() {
diff --git a/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTest.kt b/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTest.kt
index 94487be2372..473a4d94635 100644
--- a/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTest.kt
+++ b/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTest.kt
@@ -18,14 +18,19 @@ package com.android.settings.network.ethernet
import android.content.Context
import android.content.ContextWrapper
+import android.net.ConnectivityManager
import android.net.EthernetManager
import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetManager.STATE_LINK_DOWN
import android.net.EthernetManager.STATE_LINK_UP
import android.net.IpConfiguration
+import android.net.LinkProperties
+import android.net.Network
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
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.mockito.kotlin.mock
@@ -34,12 +39,15 @@ import org.mockito.kotlin.mock
class EthernetInterfaceTest {
private val mockEthernetManager = mock()
+ private val mockConnectivityManager = mock()
+ private val mockNetwork = mock()
private val context: Context =
object : ContextWrapper(ApplicationProvider.getApplicationContext()) {
override fun getSystemService(name: String): Any? =
when (name) {
Context.ETHERNET_SERVICE -> mockEthernetManager
+ Context.CONNECTIVITY_SERVICE -> mockConnectivityManager
else -> super.getSystemService(name)
}
}
@@ -85,4 +93,27 @@ class EthernetInterfaceTest {
IpConfiguration.IpAssignment.UNASSIGNED,
)
}
+
+ @Test
+ fun linkPropertiesChanged_shouldUpdate() {
+ val linkProperties = LinkProperties()
+ linkProperties.setInterfaceName("eth0")
+ linkProperties.setUsePrivateDns(true)
+
+ ethernetInterface.networkCallback.onLinkPropertiesChanged(mockNetwork, linkProperties)
+
+ assertEquals(ethernetInterface.getLinkProperties().getInterfaceName(), "eth0")
+ assertTrue(ethernetInterface.getLinkProperties().isPrivateDnsActive())
+ }
+
+ @Test
+ fun linkPropertiesChanged_iddoesnotmatch_shouldNotUpdate() {
+ val linkProperties = LinkProperties()
+ linkProperties.setInterfaceName("eth1")
+ linkProperties.setUsePrivateDns(true)
+
+ ethernetInterface.networkCallback.onLinkPropertiesChanged(mockNetwork, linkProperties)
+
+ assertFalse(ethernetInterface.getLinkProperties().isPrivateDnsActive())
+ }
}