diff --git a/res/values/strings.xml b/res/values/strings.xml
index dc54e50f7b5..95b789da33b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -10993,6 +10993,9 @@
Ethernet
+
+ Ethernet %1$s
+
^1 mobile data
diff --git a/res/xml/network_provider_settings.xml b/res/xml/network_provider_settings.xml
index 73bed545aa5..62e1559b762 100644
--- a/res/xml/network_provider_settings.xml
+++ b/res/xml/network_provider_settings.xml
@@ -42,6 +42,10 @@
settings:restrictedSwitchSummary="@string/not_allowed_by_ent"
settings:allowDividerAbove="true"/>
+
+
ethernetInterfaces) {
+ updateEthernetInterfaces(ethernetInterfaces);
+ }
+
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
Preference preference = (Preference) view.getTag();
@@ -1081,6 +1110,28 @@ public class NetworkProviderSettings extends RestrictedDashboardFragment
setAdditionalSettingsSummaries();
}
+ void updateEthernetInterfaces(Collection interfaces) {
+ int index = 0;
+ mEthernetPreferenceCategory.removeAll();
+ if (interfaces.size() > 0) {
+ for (EthernetInterface ethernetInterface : interfaces) {
+ Preference pref = new Preference(getPrefContext());
+ pref.setSelectable(false);
+ pref.setOrder(index++);
+ pref.setKey(ethernetInterface.getId());
+ pref.setTitle(getContext().getString(R.string.ethernet_interface_title, index));
+ pref.setSummary(
+ (ethernetInterface.getInterfaceState() == EthernetManager.STATE_LINK_UP)
+ ? getContext().getString(R.string.network_connected) :
+ getContext().getString(R.string.network_disconnected));
+ mEthernetPreferenceCategory.addPreference(pref);
+ }
+ mEthernetPreferenceCategory.setVisible(true);
+ } else {
+ mEthernetPreferenceCategory.setVisible(false);
+ }
+ }
+
@VisibleForTesting
PreferenceCategory getConnectedWifiPreferenceCategory() {
if (mInternetUpdater.getInternetType() == InternetUpdater.INTERNET_WIFI) {
diff --git a/src/com/android/settings/network/ethernet/EthernetInterface.kt b/src/com/android/settings/network/ethernet/EthernetInterface.kt
index 7c7cf8dca0b..1e2dc74752e 100644
--- a/src/com/android/settings/network/ethernet/EthernetInterface.kt
+++ b/src/com/android/settings/network/ethernet/EthernetInterface.kt
@@ -26,13 +26,12 @@ import android.net.IpConfiguration
import android.os.OutcomeReceiver
import android.util.Log
import androidx.core.content.ContextCompat
-import java.util.concurrent.Executor
class EthernetInterface(private val context: Context, private val id: String) :
EthernetManager.InterfaceStateListener {
- private val ethernetManager =
+ private val ethernetManager: EthernetManager? =
context.getSystemService(EthernetManager::class.java)
- private val connectivityManager =
+ private val connectivityManager: ConnectivityManager? =
context.getSystemService(ConnectivityManager::class.java)
private val executor = ContextCompat.getMainExecutor(context)
@@ -43,6 +42,8 @@ class EthernetInterface(private val context: Context, private val id: String) :
fun getInterfaceState() = interfaceState
+ fun getId() = id
+
fun getConfiguration(): IpConfiguration {
return ipConfiguration
}
@@ -50,7 +51,7 @@ class EthernetInterface(private val context: Context, private val id: String) :
fun setConfiguration(ipConfiguration: IpConfiguration) {
val request =
EthernetNetworkUpdateRequest.Builder().setIpConfiguration(ipConfiguration).build()
- ethernetManager.updateConfiguration(
+ ethernetManager?.updateConfiguration(
id,
request,
executor,
diff --git a/src/com/android/settings/network/ethernet/EthernetSwitchPreferenceController.kt b/src/com/android/settings/network/ethernet/EthernetSwitchPreferenceController.kt
index 5836f554bf0..662c6bb1b3e 100644
--- a/src/com/android/settings/network/ethernet/EthernetSwitchPreferenceController.kt
+++ b/src/com/android/settings/network/ethernet/EthernetSwitchPreferenceController.kt
@@ -33,13 +33,14 @@ import java.util.concurrent.Executor
class EthernetSwitchPreferenceController(context: Context, private val lifecycle: Lifecycle) :
AbstractPreferenceController(context),
LifecycleEventObserver,
- EthernetInterfaceTracker.EthernetInterfaceTrackerListener {
+ EthernetTracker.EthernetInterfaceTrackerListener {
private val ethernetManager: EthernetManager? =
context.getSystemService(EthernetManager::class.java)
private var preference: RestrictedSwitchPreference? = null
private val executor = ContextCompat.getMainExecutor(context)
- private val ethernetInterfaceTracker = EthernetInterfaceTracker.getInstance(context)
+ private val ethernetTracker =
+ EthernetTrackerImpl.getInstance(context)
init {
lifecycle.addObserver(this)
@@ -50,7 +51,7 @@ class EthernetSwitchPreferenceController(context: Context, private val lifecycle
}
override fun isAvailable(): Boolean {
- return (Flags.ethernetSettings() && ethernetInterfaceTracker.availableInterfaces.size > 0)
+ return (Flags.ethernetSettings() && ethernetTracker.availableInterfaces.size > 0)
}
override fun displayPreference(screen: PreferenceScreen) {
@@ -63,12 +64,12 @@ class EthernetSwitchPreferenceController(context: Context, private val lifecycle
when (event) {
Lifecycle.Event.ON_START -> {
ethernetManager?.addEthernetStateListener(executor, this::onEthernetStateChanged)
- ethernetInterfaceTracker.registerInterfaceListener(this)
+ ethernetTracker.registerInterfaceListener(this)
}
Lifecycle.Event.ON_STOP -> {
ethernetManager?.removeEthernetStateListener(this::onEthernetStateChanged)
- ethernetInterfaceTracker.unregisterInterfaceListener(this)
+ ethernetTracker.unregisterInterfaceListener(this)
}
else -> {}
diff --git a/src/com/android/settings/network/ethernet/EthernetTracker.kt b/src/com/android/settings/network/ethernet/EthernetTracker.kt
new file mode 100644
index 00000000000..5f54badd12d
--- /dev/null
+++ b/src/com/android/settings/network/ethernet/EthernetTracker.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.ethernet
+
+interface EthernetTracker {
+ interface EthernetInterfaceTrackerListener {
+ fun onInterfaceListChanged(ethernetInterfaces: List)
+ }
+
+ val availableInterfaces: Collection
+
+ interface EthernetInterfaceListListener {
+ fun onInterfaceListChanged()
+ }
+
+ fun getInterface(id: String): EthernetInterface?
+
+ fun registerInterfaceListener(listener: EthernetInterfaceTrackerListener)
+
+ fun unregisterInterfaceListener(listener: EthernetInterfaceTrackerListener)
+}
diff --git a/src/com/android/settings/network/ethernet/EthernetInterfaceTracker.kt b/src/com/android/settings/network/ethernet/EthernetTrackerImpl.kt
similarity index 68%
rename from src/com/android/settings/network/ethernet/EthernetInterfaceTracker.kt
rename to src/com/android/settings/network/ethernet/EthernetTrackerImpl.kt
index 7981f7ffbf8..10ae8795a72 100644
--- a/src/com/android/settings/network/ethernet/EthernetInterfaceTracker.kt
+++ b/src/com/android/settings/network/ethernet/EthernetTrackerImpl.kt
@@ -21,35 +21,40 @@ import android.net.EthernetManager
import android.net.IpConfiguration
import androidx.core.content.ContextCompat
-class EthernetInterfaceTracker private constructor(private val context: Context) :
- EthernetManager.InterfaceStateListener {
- interface EthernetInterfaceTrackerListener {
- fun onInterfaceListChanged(ethernetInterfaces: List)
- }
+class EthernetTrackerImpl
+private constructor(private val context: Context) :
+ EthernetManager.InterfaceStateListener, EthernetTracker {
+
+ private val TAG = "EthernetTracker"
private val ethernetManager: EthernetManager? =
- context.applicationContext.getSystemService(EthernetManager::class.java)
- private val TAG = "EthernetInterfaceTracker"
+ context.getSystemService(EthernetManager::class.java)
// Maps ethernet interface identifier to EthernetInterface object
private val ethernetInterfaces = mutableMapOf()
- private val interfaceListeners = mutableListOf()
+ private val interfaceListeners =
+ mutableListOf()
- fun getInterface(id: String): EthernetInterface? {
+ override fun getInterface(id: String): EthernetInterface? {
return ethernetInterfaces.get(id)
}
- val availableInterfaces: Collection
+ override val availableInterfaces: Collection
get() = ethernetInterfaces.values
- fun registerInterfaceListener(listener: EthernetInterfaceTrackerListener) {
+ override fun registerInterfaceListener(
+ listener: EthernetTracker.EthernetInterfaceTrackerListener
+ ) {
if (interfaceListeners.isEmpty()) {
ethernetManager?.addInterfaceStateListener(ContextCompat.getMainExecutor(context), this)
}
interfaceListeners.add(listener)
+ listener.onInterfaceListChanged(ethernetInterfaces.values.toList())
}
- fun unregisterInterfaceListener(listener: EthernetInterfaceTrackerListener) {
+ override fun unregisterInterfaceListener(
+ listener: EthernetTracker.EthernetInterfaceTrackerListener
+ ) {
interfaceListeners.remove(listener)
if (interfaceListeners.isEmpty()) {
ethernetManager?.removeInterfaceStateListener(this)
@@ -73,12 +78,16 @@ class EthernetInterfaceTracker private constructor(private val context: Context)
}
companion object {
- @Volatile private var INSTANCE: EthernetInterfaceTracker? = null
+ @Volatile private var INSTANCE: EthernetTrackerImpl? = null
- fun getInstance(context: Context): EthernetInterfaceTracker {
+ @JvmStatic
+ fun getInstance(
+ context: Context
+ ): EthernetTrackerImpl {
return INSTANCE
?: synchronized(this) {
- val instance = EthernetInterfaceTracker(context.applicationContext)
+ val instance =
+ EthernetTrackerImpl(context.applicationContext)
INSTANCE = instance
instance
}
diff --git a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
index 0d251c79580..0849ff2caab 100644
--- a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
@@ -48,6 +48,7 @@ import android.content.Intent;
import android.content.res.Resources;
import android.location.LocationManager;
import android.net.EthernetManager;
+import android.net.IpConfiguration;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
@@ -73,6 +74,8 @@ import com.android.settings.AirplaneModeEnabler;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.datausage.DataUsagePreference;
+import com.android.settings.network.ethernet.EthernetInterface;
+import com.android.settings.network.ethernet.EthernetTracker;
import com.android.settings.testutils.shadow.ShadowDataUsageUtils;
import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settings.wifi.AddWifiNetworkPreference;
@@ -90,6 +93,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
@@ -102,6 +106,7 @@ import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowToast;
import org.robolectric.util.ReflectionHelpers;
+import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@@ -160,6 +165,10 @@ public class NetworkProviderSettingsTest {
NetworkProviderSettings.WifiRestriction mWifiRestriction;
@Mock
EthernetManager mEtherentManager;
+ @Mock
+ EthernetTracker mEthernetTracker;
+ @Mock
+ PreferenceCategory mEthernetPreferenceCategory;
private NetworkProviderSettings mNetworkProviderSettings;
@@ -194,6 +203,7 @@ public class NetworkProviderSettingsTest {
mNetworkProviderSettings.mAirplaneModeMsgPreference = mAirplaneModeMsgPreference;
mNetworkProviderSettings.mAirplaneModeEnabler = mAirplaneModeEnabler;
mNetworkProviderSettings.mInternetUpdater = mInternetUpdater;
+ mNetworkProviderSettings.mEthernetTracker = mEthernetTracker;
mNetworkProviderSettings.mWifiStatusMessagePreference = new FooterPreference(mContext);
doReturn(NetworkProviderSettings.PREF_KEY_CONNECTED_ACCESS_POINTS)
.when(mConnectedWifiEntryPreferenceCategory).getKey();
@@ -203,6 +213,7 @@ public class NetworkProviderSettingsTest {
.when(mFirstWifiEntryPreferenceCategory).getKey();
mNetworkProviderSettings.mFirstWifiEntryPreferenceCategory =
mFirstWifiEntryPreferenceCategory;
+ mNetworkProviderSettings.mEthernetPreferenceCategory = mEthernetPreferenceCategory;
ReflectionHelpers.setField(mNetworkProviderSettings, "mDashboardFeatureProvider",
mock(DashboardFeatureProvider.class));
@@ -749,6 +760,8 @@ public class NetworkProviderSettingsTest {
public void onStop_shouldRemoveCallbacks() {
View fragmentView = mock(View.class);
when(mNetworkProviderSettings.getView()).thenReturn(fragmentView);
+ doNothing().when(mEthernetTracker)
+ .unregisterInterfaceListener(any());
mNetworkProviderSettings.onStop();
@@ -925,6 +938,57 @@ public class NetworkProviderSettingsTest {
verify(mContext).startActivity(any());
}
+ @Test
+ public void updateEthernetInterfaces_withEmptyInterfaces() {
+ doNothing().when(mEthernetPreferenceCategory).removeAll();
+
+ mNetworkProviderSettings.updateEthernetInterfaces(new ArrayList());
+
+ verify(mEthernetPreferenceCategory).removeAll();
+ verify(mEthernetPreferenceCategory).setVisible(false);
+ }
+
+ @Test
+ public void updateEthernetInterfaces_withConnectedInterface() {
+ List interfaces = new ArrayList<>();
+ EthernetInterface ethernetInterface = new EthernetInterface(mContext, "eth0");
+ ethernetInterface.onInterfaceStateChanged(
+ "eth0", EthernetManager.STATE_LINK_UP, 0, new IpConfiguration());
+
+ interfaces.add(ethernetInterface);
+
+ mNetworkProviderSettings.updateEthernetInterfaces(interfaces);
+
+ ArgumentCaptor arg = ArgumentCaptor.forClass(Preference.class);
+
+ verify(mEthernetPreferenceCategory).removeAll();
+ verify(mEthernetPreferenceCategory).setVisible(true);
+ verify(mEthernetPreferenceCategory).addPreference(arg.capture());
+
+ Preference pref = arg.getValue();
+ assertThat(pref.getKey()).isEqualTo("eth0");
+ assertThat(pref.getSummary()).isEqualTo("Connected");
+ }
+
+ @Test
+ public void updateEthernetInterfaces_withDisconnectedInterface() {
+ List interfaces = new ArrayList<>();
+ EthernetInterface ethernetInterface = new EthernetInterface(mContext, "eth0");
+ ethernetInterface.onInterfaceStateChanged(
+ "eth0", EthernetManager.STATE_LINK_DOWN, 0, new IpConfiguration());
+
+ interfaces.add(ethernetInterface);
+
+ mNetworkProviderSettings.updateEthernetInterfaces(interfaces);
+
+ ArgumentCaptor arg = ArgumentCaptor.forClass(Preference.class);
+
+ verify(mEthernetPreferenceCategory).addPreference(arg.capture());
+
+ Preference pref = arg.getValue();
+ assertThat(pref.getSummary()).isEqualTo("Disconnected");
+ }
+
@Implements(PreferenceFragmentCompat.class)
public static class ShadowPreferenceFragmentCompat {
diff --git a/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTrackerTest.kt b/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTrackerTest.kt
index b1516d17964..f063b422f70 100644
--- a/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTrackerTest.kt
+++ b/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTrackerTest.kt
@@ -30,7 +30,7 @@ import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@RunWith(AndroidJUnit4::class)
-class EthernetInterfaceTrackerTest {
+class EthernetTrackerImplTest {
private val mockEthernetManager = mock()
private val context: Context =
@@ -42,38 +42,38 @@ class EthernetInterfaceTrackerTest {
}
}
- private val ethernetInterfaceTracker = EthernetInterfaceTracker.getInstance(context)
+ private val ethernetTrackerImpl = EthernetTrackerImpl.getInstance(context)
@Test
fun getInterface_shouldReturnEmpty() {
- assertNull(ethernetInterfaceTracker.getInterface("id0"))
+ assertNull(ethernetTrackerImpl.getInterface("id0"))
}
@Test
fun getAvailableInterfaces_shouldReturnEmpty() {
- assertEquals(ethernetInterfaceTracker.availableInterfaces.size, 0)
+ assertEquals(ethernetTrackerImpl.availableInterfaces.size, 0)
}
@Test
fun interfacesChanged_shouldUpdateInterfaces() {
- ethernetInterfaceTracker.onInterfaceStateChanged(
+ ethernetTrackerImpl.onInterfaceStateChanged(
"id0",
EthernetManager.STATE_LINK_DOWN,
EthernetManager.ROLE_NONE,
IpConfiguration(),
)
- assertNotNull(ethernetInterfaceTracker.getInterface("id0"))
- assertEquals(ethernetInterfaceTracker.availableInterfaces.size, 1)
+ assertNotNull(ethernetTrackerImpl.getInterface("id0"))
+ assertEquals(ethernetTrackerImpl.availableInterfaces.size, 1)
- ethernetInterfaceTracker.onInterfaceStateChanged(
+ ethernetTrackerImpl.onInterfaceStateChanged(
"id0",
EthernetManager.STATE_ABSENT,
EthernetManager.ROLE_NONE,
IpConfiguration(),
)
- assertNull(ethernetInterfaceTracker.getInterface("id0"))
- assertEquals(ethernetInterfaceTracker.availableInterfaces.size, 0)
+ assertNull(ethernetTrackerImpl.getInterface("id0"))
+ assertEquals(ethernetTrackerImpl.availableInterfaces.size, 0)
}
}