Changes to add Ethernet settings row item
Adds EthernetSettings row item to NetworkProviderSettings and its
controller.
Flag: com.android.settings.connectivity.ethernet_settings
Test: atest
SettingsRoboTests:
com.android.settings.network.EthernetSwitchPreferenceControllerTest
Change-Id: I27965ad0c8563657b17e0aa6d3bd19b97fcf5615
diff --git a/Android.bp b/Android.bp
index ed094cf..150bdaf 100644
--- a/Android.bp
+++ b/Android.bp
@@ -114,8 +114,8 @@
"setupdesign-lottie-loading-layout",
"device_policy_aconfig_flags_lib",
"keyboard_flags_lib",
- "settings_connectivity_flags",
"com_android_systemui_flags_lib",
+ "settings_connectivity_flags_lib",
],
plugins: [
diff --git a/res/xml/network_provider_settings.xml b/res/xml/network_provider_settings.xml
index 74ec948..73bed54 100644
--- a/res/xml/network_provider_settings.xml
+++ b/res/xml/network_provider_settings.xml
@@ -36,6 +36,12 @@
android:layout="@layout/airplane_mode_message_preference"
settings:allowDividerBelow="true"/>
+ <com.android.settingslib.RestrictedSwitchPreference
+ android:key="main_toggle_ethernet"
+ android:title="@string/ethernet"
+ settings:restrictedSwitchSummary="@string/not_allowed_by_ent"
+ settings:allowDividerAbove="true"/>
+
<Preference
android:key="connected_ethernet_network"
android:title="@string/ethernet"
diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java
index 3b73846..e6ebccf 100644
--- a/src/com/android/settings/network/NetworkProviderSettings.java
+++ b/src/com/android/settings/network/NetworkProviderSettings.java
@@ -69,6 +69,7 @@
import com.android.settings.datausage.DataUsagePreference;
import com.android.settings.datausage.DataUsageUtils;
import com.android.settings.location.WifiScanningFragment;
+import com.android.settings.network.ethernet.EthernetSwitchPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.wifi.AddNetworkFragment;
import com.android.settings.wifi.AddWifiNetworkPreference;
@@ -84,6 +85,7 @@
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
+import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.search.Indexable;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.utils.StringUtil;
@@ -129,6 +131,8 @@
private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list";
@VisibleForTesting
static final String PREF_KEY_WIFI_TOGGLE = "main_toggle_wifi";
+ @VisibleForTesting
+ static final String PREF_KEY_ETHERNET_TOGGLE = "main_toggle_ethernet";
// TODO(b/70983952): Rename these to use WifiEntry instead of AccessPoint.
@VisibleForTesting
static final String PREF_KEY_CONNECTED_ACCESS_POINTS = "connected_access_point";
@@ -242,10 +246,12 @@
LayoutPreference mResetInternetPreference;
@VisibleForTesting
ConnectedEthernetNetworkController mConnectedEthernetNetworkController;
+ private EthernetSwitchPreferenceController mEthernetSwitchPreferenceController;
@VisibleForTesting
FooterPreference mWifiStatusMessagePreference;
@VisibleForTesting
MenuProvider mMenuProvider;
+ RestrictedSwitchPreference mEthernetSwitchPreference;
/**
* Mobile networks list for provider model
@@ -383,12 +389,18 @@
mDataUsagePreference.setTemplate(new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI)
.build(), SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mResetInternetPreference = findPreference(PREF_KEY_RESET_INTERNET);
+ mEthernetSwitchPreference = findPreference(PREF_KEY_ETHERNET_TOGGLE);
if (mResetInternetPreference != null) {
mResetInternetPreference.setVisible(false);
}
addNetworkMobileProviderController();
addConnectedEthernetNetworkController();
addWifiSwitchPreferenceController();
+ if (com.android.settings.connectivity.Flags.ethernetSettings()) {
+ addEthernetSwitchPreferenceController();
+ } else {
+ mEthernetSwitchPreference.setVisible(false);
+ }
mWifiStatusMessagePreference = findPreference(PREF_KEY_WIFI_STATUS_MESSAGE);
checkConnectivityRecovering();
@@ -441,6 +453,14 @@
mWifiSwitchPreferenceController.displayPreference(getPreferenceScreen());
}
+ private void addEthernetSwitchPreferenceController() {
+ if (mEthernetSwitchPreferenceController == null) {
+ mEthernetSwitchPreferenceController =
+ new EthernetSwitchPreferenceController(getContext(), getSettingsLifecycle());
+ }
+ mEthernetSwitchPreferenceController.displayPreference(getPreferenceScreen());
+ }
+
private void checkConnectivityRecovering() {
mInternetResetHelper = new InternetResetHelper(getContext(), getLifecycle(),
mNetworkMobileProviderController,
diff --git a/src/com/android/settings/network/ethernet/EthernetInterfaceTracker.kt b/src/com/android/settings/network/ethernet/EthernetInterfaceTracker.kt
index ef2ea12..7981f7f 100644
--- a/src/com/android/settings/network/ethernet/EthernetInterfaceTracker.kt
+++ b/src/com/android/settings/network/ethernet/EthernetInterfaceTracker.kt
@@ -20,41 +20,40 @@
import android.net.EthernetManager
import android.net.IpConfiguration
import androidx.core.content.ContextCompat
-import java.util.concurrent.Executor
-class EthernetInterfaceTracker(private val context: Context) :
+class EthernetInterfaceTracker private constructor(private val context: Context) :
EthernetManager.InterfaceStateListener {
- interface EthernetInterfaceListListener {
- fun onInterfaceListChanged()
+ interface EthernetInterfaceTrackerListener {
+ fun onInterfaceListChanged(ethernetInterfaces: List<EthernetInterface>)
}
- private val ethernetManager =
- context.getSystemService(Context.ETHERNET_SERVICE) as EthernetManager
+ private val ethernetManager: EthernetManager? =
+ context.applicationContext.getSystemService(EthernetManager::class.java)
private val TAG = "EthernetInterfaceTracker"
// Maps ethernet interface identifier to EthernetInterface object
private val ethernetInterfaces = mutableMapOf<String, EthernetInterface>()
- private val interfaceListeners = mutableListOf<EthernetInterfaceListListener>()
- private val mExecutor = ContextCompat.getMainExecutor(context)
-
- init {
- ethernetManager.addInterfaceStateListener(mExecutor, this)
- }
+ private val interfaceListeners = mutableListOf<EthernetInterfaceTrackerListener>()
fun getInterface(id: String): EthernetInterface? {
return ethernetInterfaces.get(id)
}
- fun getAvailableInterfaces(): Collection<EthernetInterface> {
- return ethernetInterfaces.values
- }
+ val availableInterfaces: Collection<EthernetInterface>
+ get() = ethernetInterfaces.values
- fun registerInterfaceListener(listener: EthernetInterfaceListListener) {
+ fun registerInterfaceListener(listener: EthernetInterfaceTrackerListener) {
+ if (interfaceListeners.isEmpty()) {
+ ethernetManager?.addInterfaceStateListener(ContextCompat.getMainExecutor(context), this)
+ }
interfaceListeners.add(listener)
}
- fun unregisterInterfaceListener(listener: EthernetInterfaceListListener) {
+ fun unregisterInterfaceListener(listener: EthernetInterfaceTrackerListener) {
interfaceListeners.remove(listener)
+ if (interfaceListeners.isEmpty()) {
+ ethernetManager?.removeInterfaceStateListener(this)
+ }
}
override fun onInterfaceStateChanged(id: String, state: Int, role: Int, cfg: IpConfiguration?) {
@@ -68,8 +67,21 @@
}
if (interfacesChanged) {
for (listener in interfaceListeners) {
- listener.onInterfaceListChanged()
+ listener.onInterfaceListChanged(ethernetInterfaces.values.toList())
}
}
}
+
+ companion object {
+ @Volatile private var INSTANCE: EthernetInterfaceTracker? = null
+
+ fun getInstance(context: Context): EthernetInterfaceTracker {
+ return INSTANCE
+ ?: synchronized(this) {
+ val instance = EthernetInterfaceTracker(context.applicationContext)
+ INSTANCE = instance
+ instance
+ }
+ }
+ }
}
diff --git a/src/com/android/settings/network/ethernet/EthernetSwitchPreferenceController.kt b/src/com/android/settings/network/ethernet/EthernetSwitchPreferenceController.kt
new file mode 100644
index 0000000..5836f55
--- /dev/null
+++ b/src/com/android/settings/network/ethernet/EthernetSwitchPreferenceController.kt
@@ -0,0 +1,96 @@
+/*
+ * 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
+
+import android.content.Context
+import android.net.EthernetManager
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import com.android.settings.connectivity.Flags
+import com.android.settingslib.RestrictedSwitchPreference
+import com.android.settingslib.core.AbstractPreferenceController
+import com.google.common.annotations.VisibleForTesting
+import java.util.concurrent.Executor
+
+class EthernetSwitchPreferenceController(context: Context, private val lifecycle: Lifecycle) :
+ AbstractPreferenceController(context),
+ LifecycleEventObserver,
+ EthernetInterfaceTracker.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)
+
+ init {
+ lifecycle.addObserver(this)
+ }
+
+ override fun getPreferenceKey(): String {
+ return KEY
+ }
+
+ override fun isAvailable(): Boolean {
+ return (Flags.ethernetSettings() && ethernetInterfaceTracker.availableInterfaces.size > 0)
+ }
+
+ override fun displayPreference(screen: PreferenceScreen) {
+ super.displayPreference(screen)
+ preference = screen.findPreference(KEY)
+ preference?.setOnPreferenceChangeListener(this::onPreferenceChange)
+ }
+
+ override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+ when (event) {
+ Lifecycle.Event.ON_START -> {
+ ethernetManager?.addEthernetStateListener(executor, this::onEthernetStateChanged)
+ ethernetInterfaceTracker.registerInterfaceListener(this)
+ }
+
+ Lifecycle.Event.ON_STOP -> {
+ ethernetManager?.removeEthernetStateListener(this::onEthernetStateChanged)
+ ethernetInterfaceTracker.unregisterInterfaceListener(this)
+ }
+
+ else -> {}
+ }
+ }
+
+ fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
+ val isChecked = newValue as Boolean
+ ethernetManager?.setEthernetEnabled(isChecked)
+ return true
+ }
+
+ @VisibleForTesting
+ fun onEthernetStateChanged(state: Int) {
+ preference?.setChecked(state == EthernetManager.ETHERNET_STATE_ENABLED)
+ }
+
+ override fun onInterfaceListChanged(ethernetInterfaces: List<EthernetInterface>) {
+ preference?.setVisible(ethernetInterfaces.size > 0)
+ }
+
+ companion object {
+ private val KEY = "main_toggle_ethernet"
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
index a8862cd..0d251c7 100644
--- a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
@@ -47,6 +47,7 @@
import android.content.Intent;
import android.content.res.Resources;
import android.location.LocationManager;
+import android.net.EthernetManager;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
@@ -157,6 +158,8 @@
PreferenceCategory mFirstWifiEntryPreferenceCategory;
@Mock
NetworkProviderSettings.WifiRestriction mWifiRestriction;
+ @Mock
+ EthernetManager mEtherentManager;
private NetworkProviderSettings mNetworkProviderSettings;
@@ -178,6 +181,7 @@
doReturn(mWifiManager).when(mContext).getSystemService(WifiManager.class);
doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE);
doReturn(mLocationManager).when(mContext).getSystemService(LocationManager.class);
+ doReturn(mEtherentManager).when(mContext).getSystemService(Context.ETHERNET_SERVICE);
when(mUserManager.hasBaseUserRestriction(any(), any())).thenReturn(true);
doReturn(mContext).when(mPreferenceManager).getContext();
mNetworkProviderSettings.mAddWifiNetworkPreference = new AddWifiNetworkPreference(mContext);
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 369eb1a..b1516d1 100644
--- a/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTrackerTest.kt
+++ b/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTrackerTest.kt
@@ -42,7 +42,7 @@
}
}
- private val ethernetInterfaceTracker = EthernetInterfaceTracker(context)
+ private val ethernetInterfaceTracker = EthernetInterfaceTracker.getInstance(context)
@Test
fun getInterface_shouldReturnEmpty() {
@@ -51,7 +51,7 @@
@Test
fun getAvailableInterfaces_shouldReturnEmpty() {
- assertEquals(ethernetInterfaceTracker.getAvailableInterfaces().size, 0)
+ assertEquals(ethernetInterfaceTracker.availableInterfaces.size, 0)
}
@Test
@@ -64,7 +64,7 @@
)
assertNotNull(ethernetInterfaceTracker.getInterface("id0"))
- assertEquals(ethernetInterfaceTracker.getAvailableInterfaces().size, 1)
+ assertEquals(ethernetInterfaceTracker.availableInterfaces.size, 1)
ethernetInterfaceTracker.onInterfaceStateChanged(
"id0",
@@ -74,6 +74,6 @@
)
assertNull(ethernetInterfaceTracker.getInterface("id0"))
- assertEquals(ethernetInterfaceTracker.getAvailableInterfaces().size, 0)
+ assertEquals(ethernetInterfaceTracker.availableInterfaces.size, 0)
}
}
diff --git a/tests/robotests/src/com/android/settings/network/ethernet/EthernetSwitchPreferenceControllerTest.kt b/tests/robotests/src/com/android/settings/network/ethernet/EthernetSwitchPreferenceControllerTest.kt
new file mode 100644
index 0000000..bb79296
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/network/ethernet/EthernetSwitchPreferenceControllerTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.net.EthernetManager
+import androidx.lifecycle.Lifecycle
+import androidx.preference.PreferenceScreen
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.RestrictedSwitchPreference
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class EthernetSwitchPreferenceControllerTest {
+ private val mockEthernetManager = mock<EthernetManager>()
+ private val preferenceScreen = mock<PreferenceScreen>()
+ private val switchPreference = mock<RestrictedSwitchPreference>()
+
+ private val context: Context =
+ object : ContextWrapper(ApplicationProvider.getApplicationContext()) {
+ override fun getSystemService(name: String): Any? =
+ when (name) {
+ getSystemServiceName(EthernetManager::class.java) -> mockEthernetManager
+ else -> super.getSystemService(name)
+ }
+ }
+
+ private val lifecycle = mock<Lifecycle>()
+
+ private val controller: EthernetSwitchPreferenceController =
+ EthernetSwitchPreferenceController(context, lifecycle)
+
+ @Before
+ fun setUp() {
+ preferenceScreen.stub {
+ on { findPreference<RestrictedSwitchPreference>("main_toggle_ethernet") } doReturn
+ switchPreference
+ }
+ controller.displayPreference(preferenceScreen)
+ }
+
+ @Test
+ fun getPreferenceKey_shouldReturnCorrectKey() {
+ assertEquals(controller.getPreferenceKey(), "main_toggle_ethernet")
+ }
+
+ @Test
+ fun onPreferenceChange_shouldCallEthernetManager() {
+ assertTrue(controller.onPreferenceChange(switchPreference, true))
+ verify(mockEthernetManager).setEthernetEnabled(true)
+
+ assertTrue(controller.onPreferenceChange(switchPreference, false))
+ verify(mockEthernetManager).setEthernetEnabled(false)
+ }
+
+ @Test
+ fun ethernetEnabled_shouldUpdatePreferenceState() {
+ switchPreference.stub { on { isChecked } doReturn false }
+
+ controller.onEthernetStateChanged(EthernetManager.ETHERNET_STATE_ENABLED)
+
+ verify(switchPreference).setChecked(true)
+ }
+
+ @Test
+ fun ethernetDisabled_shouldUpdatePreferenceState() {
+ switchPreference.stub { on { isChecked } doReturn true }
+
+ controller.onEthernetStateChanged(EthernetManager.ETHERNET_STATE_DISABLED)
+
+ verify(switchPreference).setChecked(false)
+ }
+}