Merge "Edits ethernet interface details settings subpage with more info" into main

This commit is contained in:
Nikhil Nayunigari
2025-03-11 15:57:38 -07:00
committed by Android (Google) Code Review
7 changed files with 225 additions and 18 deletions

View File

@@ -3912,6 +3912,18 @@
<!-- Label for bluetooth tether checkbox [CHAR LIMIT=25]--> <!-- Label for bluetooth tether checkbox [CHAR LIMIT=25]-->
<string name="bluetooth_tether_checkbox_text">Bluetooth tethering</string> <string name="bluetooth_tether_checkbox_text">Bluetooth tethering</string>
<!-- Ethernet settings-->
<!-- Label for ethernet IP address [CHAR LIMIT=NONE]-->
<string name="ethernet_ip_address">IP address</string>
<!-- Label for ethernet MAC address [CHAR LIMIT=NONE]-->
<string name="ethernet_mac_address_title">MAC address</string>
<!-- Label for ethernet transfer speed [CHAR LIMIT=NONE]-->
<string name="tx_ethernet_speed">Transit link speed</string>
<!-- Label for ethernet receive speed [CHAR LIMIT=NONE]-->
<string name="rx_ethernet_speed">Receive link speed</string>
<!-- Label for ethernet network usage [CHAR LIMIT=NONE]-->
<string name="ethernet_network_usage">Network usage</string>
<!-- Ethernet Tethering settings--> <!-- Ethernet Tethering settings-->
<!-- Label for ethernet tether checkbox [CHAR LIMIT=NONE]--> <!-- Label for ethernet tether checkbox [CHAR LIMIT=NONE]-->
<string name="ethernet_tether_checkbox_text">Ethernet tethering</string> <string name="ethernet_tether_checkbox_text">Ethernet tethering</string>

View File

@@ -24,4 +24,36 @@
android:selectable="false" android:selectable="false"
android:order="-10000"/> android:order="-10000"/>
<ListPreference
android:key="metered"
android:icon="@drawable/ic_attach_money_black_24dp"
android:title="@string/wifi_metered_title"
android:entries="@array/wifi_metered_entries"
android:entryValues="@array/wifi_metered_values"/>
<!-- Network Details -->
<PreferenceCategory
android:key="ip_details_category">
<Preference
android:key="ethernet_ip_address"
android:title="@string/ethernet_ip_address"
android:selectable="false"
settings:enableCopying="true"/>
<Preference
android:key="ethernet_mac_address"
android:title="@string/ethernet_mac_address_title"
android:selectable="false"
settings:enableCopying="true"/>
<Preference
android:key="ethernet_tx_link_speed"
android:title="@string/tx_ethernet_speed"
android:selectable="false"
settings:enableCopying="true"/>
<Preference
android:key="ethernet_rx_link_speed"
android:title="@string/rx_ethernet_speed"
android:selectable="false"
settings:enableCopying="true"/>
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -18,34 +18,68 @@ package com.android.settings.network.ethernet
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.EthernetManager import android.net.EthernetManager
import android.net.EthernetManager.STATE_ABSENT import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetNetworkManagementException import android.net.EthernetNetworkManagementException
import android.net.EthernetNetworkUpdateRequest import android.net.EthernetNetworkUpdateRequest
import android.net.IpConfiguration 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.os.OutcomeReceiver
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.google.common.annotations.VisibleForTesting
class EthernetInterface(private val context: Context, private val id: String) : class EthernetInterface(private val context: Context, private val id: String) :
EthernetManager.InterfaceStateListener { EthernetManager.InterfaceStateListener {
interface EthernetInterfaceStateListener {
fun interfaceUpdated()
}
private val ethernetManager: EthernetManager? = private val ethernetManager: EthernetManager? =
context.getSystemService(EthernetManager::class.java) context.getSystemService(EthernetManager::class.java)
private val connectivityManager: ConnectivityManager? = private val connectivityManager: ConnectivityManager? =
context.getSystemService(ConnectivityManager::class.java) context.getSystemService(ConnectivityManager::class.java)
private val executor = ContextCompat.getMainExecutor(context) private val executor = ContextCompat.getMainExecutor(context)
private val interfaceListeners = mutableListOf<EthernetInterfaceStateListener>()
private val TAG = "EthernetInterface" private val TAG = "EthernetInterface"
private val networkRequest: NetworkRequest =
NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.build()
private var interfaceState = STATE_ABSENT private var interfaceState = STATE_ABSENT
private var ipConfiguration = IpConfiguration() private var ipConfiguration = IpConfiguration()
private var linkProperties = LinkProperties()
fun getInterfaceState() = interfaceState fun getInterfaceState() = interfaceState
fun getId() = id fun getId() = id
fun getConfiguration(): IpConfiguration { fun getConfiguration() = ipConfiguration
return 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) { 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?) { override fun onInterfaceStateChanged(id: String, state: Int, role: Int, cfg: IpConfiguration?) {
if (id == this.id) { if (id == this.id) {
ipConfiguration = cfg ?: IpConfiguration() ipConfiguration = cfg ?: IpConfiguration()
interfaceState = state interfaceState = state
notifyListeners()
} }
} }
@VisibleForTesting
val networkCallback =
object : NetworkCallback() {
override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) {
if (lp.getInterfaceName().equals(id)) {
linkProperties = lp
notifyListeners()
}
}
}
} }

View File

@@ -18,7 +18,14 @@ package com.android.settings.network.ethernet
import android.content.Context import android.content.Context
import android.net.EthernetManager import android.net.EthernetManager
import android.net.IpConfiguration
import android.net.LinkProperties
import android.net.StaticIpConfiguration
import android.widget.ImageView 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.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.android.settings.R import com.android.settings.R
@@ -30,13 +37,25 @@ class EthernetInterfaceDetailsController(
context: Context, context: Context,
private val fragment: PreferenceFragmentCompat, private val fragment: PreferenceFragmentCompat,
private val preferenceId: String, private val preferenceId: String,
) : AbstractPreferenceController(context) { private val lifecycle: Lifecycle,
) :
AbstractPreferenceController(context),
EthernetInterface.EthernetInterfaceStateListener,
LifecycleEventObserver {
private val KEY_HEADER = "ethernet_details" private val KEY_HEADER = "ethernet_details"
private val ethernetManager = context.getSystemService(EthernetManager::class.java) private val ethernetManager = context.getSystemService(EthernetManager::class.java)
private val ethernetInterface = private val ethernetInterface =
EthernetTrackerImpl.getInstance(context).getInterface(preferenceId) EthernetTrackerImpl.getInstance(context).getInterface(preferenceId)
private lateinit var entityHeaderController: EntityHeaderController
private var ipAddressPref: Preference? = null
init {
lifecycle.addObserver(this)
}
override fun isAvailable(): Boolean { override fun isAvailable(): Boolean {
return true return true
} }
@@ -45,10 +64,24 @@ class EthernetInterfaceDetailsController(
return KEY_HEADER 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) { override fun displayPreference(screen: PreferenceScreen) {
val headerPref: LayoutPreference? = screen.findPreference(KEY_HEADER) val headerPref: LayoutPreference? = screen.findPreference(KEY_HEADER)
val mEntityHeaderController = entityHeaderController =
EntityHeaderController.newInstance( EntityHeaderController.newInstance(
fragment.getActivity(), fragment.getActivity(),
fragment, fragment,
@@ -59,17 +92,49 @@ class EthernetInterfaceDetailsController(
iconView?.setScaleType(ImageView.ScaleType.CENTER_INSIDE) iconView?.setScaleType(ImageView.ScaleType.CENTER_INSIDE)
mEntityHeaderController if (entityHeaderController != null) {
.setLabel("Ethernet") entityHeaderController
.setSummary( .setLabel("Ethernet")
if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) { .setSummary(
mContext.getString(R.string.network_connected) if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
} else { mContext.getString(R.string.network_connected)
mContext.getString(R.string.network_disconnected) } else {
} mContext.getString(R.string.network_disconnected)
) }
.setSecondSummary("") )
.setIcon(mContext.getDrawable(R.drawable.ic_settings_ethernet)) .setSecondSummary("")
.done(true /* rebind */) .setIcon(mContext.getDrawable(R.drawable.ic_settings_ethernet))
.done(true /* rebind */)
}
ipAddressPref = screen.findPreference<Preference>("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())
}
} }
} }

View File

@@ -50,6 +50,13 @@ class EthernetInterfaceDetailsFragment : DashboardFragment() {
override public fun createPreferenceControllers( override public fun createPreferenceControllers(
context: Context context: Context
): List<AbstractPreferenceController> { ): List<AbstractPreferenceController> {
return listOf(EthernetInterfaceDetailsController(context, this, preferenceId ?: "")) return listOf(
EthernetInterfaceDetailsController(
context,
this,
preferenceId ?: "",
getSettingsLifecycle(),
)
)
} }
} }

View File

@@ -18,22 +18,30 @@ package com.android.settings.network.ethernet
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class EthernetInterfaceDetailsControllerTest { class EthernetInterfaceDetailsControllerTest {
private val ethernetInterfaceDetailsFragment = EthernetInterfaceDetailsFragment() private val ethernetInterfaceDetailsFragment = EthernetInterfaceDetailsFragment()
private val lifecycle = mock<Lifecycle>()
private val context: Context = private val context: Context =
object : ContextWrapper(ApplicationProvider.getApplicationContext()) {} object : ContextWrapper(ApplicationProvider.getApplicationContext()) {}
private val ethernetInterfaceDetailsController = private val ethernetInterfaceDetailsController =
EthernetInterfaceDetailsController(context, ethernetInterfaceDetailsFragment, "eth0") EthernetInterfaceDetailsController(
context,
ethernetInterfaceDetailsFragment,
"eth0",
lifecycle,
)
@Test @Test
fun isAvailable_ShouldReturnTrue() { fun isAvailable_ShouldReturnTrue() {

View File

@@ -18,14 +18,19 @@ package com.android.settings.network.ethernet
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.net.ConnectivityManager
import android.net.EthernetManager import android.net.EthernetManager
import android.net.EthernetManager.STATE_ABSENT import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetManager.STATE_LINK_DOWN import android.net.EthernetManager.STATE_LINK_DOWN
import android.net.EthernetManager.STATE_LINK_UP import android.net.EthernetManager.STATE_LINK_UP
import android.net.IpConfiguration import android.net.IpConfiguration
import android.net.LinkProperties
import android.net.Network
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
@@ -34,12 +39,15 @@ import org.mockito.kotlin.mock
class EthernetInterfaceTest { class EthernetInterfaceTest {
private val mockEthernetManager = mock<EthernetManager>() private val mockEthernetManager = mock<EthernetManager>()
private val mockConnectivityManager = mock<ConnectivityManager>()
private val mockNetwork = mock<Network>()
private val context: Context = private val context: Context =
object : ContextWrapper(ApplicationProvider.getApplicationContext()) { object : ContextWrapper(ApplicationProvider.getApplicationContext()) {
override fun getSystemService(name: String): Any? = override fun getSystemService(name: String): Any? =
when (name) { when (name) {
Context.ETHERNET_SERVICE -> mockEthernetManager Context.ETHERNET_SERVICE -> mockEthernetManager
Context.CONNECTIVITY_SERVICE -> mockConnectivityManager
else -> super.getSystemService(name) else -> super.getSystemService(name)
} }
} }
@@ -85,4 +93,27 @@ class EthernetInterfaceTest {
IpConfiguration.IpAssignment.UNASSIGNED, 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())
}
} }