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
This commit is contained in:
Nikhil Nayunigari
2025-01-20 02:56:29 +00:00
parent 63242bc3ff
commit bfd3722e06
8 changed files with 257 additions and 23 deletions

View File

@@ -114,8 +114,8 @@ android_library {
"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: [

View File

@@ -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"

View File

@@ -69,6 +69,7 @@ import com.android.settings.dashboard.RestrictedDashboardFragment;
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.settings.wifi.dpp.WifiDppUtils;
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 @@ public class NetworkProviderSettings extends RestrictedDashboardFragment
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 @@ public class NetworkProviderSettings extends RestrictedDashboardFragment
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 @@ public class NetworkProviderSettings extends RestrictedDashboardFragment
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 @@ public class NetworkProviderSettings extends RestrictedDashboardFragment
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,

View File

@@ -20,41 +20,40 @@ import android.content.Context
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,7 +67,20 @@ class EthernetInterfaceTracker(private val context: Context) :
}
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
}
}
}

View File

@@ -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"
}
}

View File

@@ -47,6 +47,7 @@ import android.content.Context;
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 @@ public class NetworkProviderSettingsTest {
PreferenceCategory mFirstWifiEntryPreferenceCategory;
@Mock
NetworkProviderSettings.WifiRestriction mWifiRestriction;
@Mock
EthernetManager mEtherentManager;
private NetworkProviderSettings mNetworkProviderSettings;
@@ -178,6 +181,7 @@ public class NetworkProviderSettingsTest {
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);

View File

@@ -42,7 +42,7 @@ class EthernetInterfaceTrackerTest {
}
}
private val ethernetInterfaceTracker = EthernetInterfaceTracker(context)
private val ethernetInterfaceTracker = EthernetInterfaceTracker.getInstance(context)
@Test
fun getInterface_shouldReturnEmpty() {
@@ -51,7 +51,7 @@ class EthernetInterfaceTrackerTest {
@Test
fun getAvailableInterfaces_shouldReturnEmpty() {
assertEquals(ethernetInterfaceTracker.getAvailableInterfaces().size, 0)
assertEquals(ethernetInterfaceTracker.availableInterfaces.size, 0)
}
@Test
@@ -64,7 +64,7 @@ class EthernetInterfaceTrackerTest {
)
assertNotNull(ethernetInterfaceTracker.getInterface("id0"))
assertEquals(ethernetInterfaceTracker.getAvailableInterfaces().size, 1)
assertEquals(ethernetInterfaceTracker.availableInterfaces.size, 1)
ethernetInterfaceTracker.onInterfaceStateChanged(
"id0",
@@ -74,6 +74,6 @@ class EthernetInterfaceTrackerTest {
)
assertNull(ethernetInterfaceTracker.getInterface("id0"))
assertEquals(ethernetInterfaceTracker.getAvailableInterfaces().size, 0)
assertEquals(ethernetInterfaceTracker.availableInterfaces.size, 0)
}
}

View File

@@ -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)
}
}