diff --git a/src/com/android/settings/network/InternetPreferenceControllerV2.kt b/src/com/android/settings/network/InternetPreferenceControllerV2.kt index 351aca83526..a181abd2ef0 100644 --- a/src/com/android/settings/network/InternetPreferenceControllerV2.kt +++ b/src/com/android/settings/network/InternetPreferenceControllerV2.kt @@ -22,11 +22,13 @@ import androidx.preference.Preference import androidx.preference.PreferenceScreen import com.android.settings.R import com.android.settings.core.BasePreferenceController +import com.android.settingslib.Utils import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle class InternetPreferenceControllerV2(context: Context, preferenceKey: String) : BasePreferenceController(context, preferenceKey) { + private val repository = InternetPreferenceRepository(mContext) private var preference: Preference? = null override fun getAvailabilityStatus() = @@ -39,9 +41,14 @@ class InternetPreferenceControllerV2(context: Context, preferenceKey: String) : } override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { - InternetPreferenceRepository(mContext).summaryFlow() - .collectLatestWithLifecycle(viewLifecycleOwner) { - preference?.summary = it + repository.displayInfoFlow().collectLatestWithLifecycle(viewLifecycleOwner) { displayInfo -> + preference?.apply { + summary = displayInfo.summary + icon = + mContext.getDrawable(displayInfo.iconResId)?.apply { + setTintList(Utils.getColorAttr(mContext, android.R.attr.colorControlNormal)) + } } + } } } diff --git a/src/com/android/settings/network/InternetPreferenceRepository.kt b/src/com/android/settings/network/InternetPreferenceRepository.kt index 6aa8db2c308..41a2fccea5a 100644 --- a/src/com/android/settings/network/InternetPreferenceRepository.kt +++ b/src/com/android/settings/network/InternetPreferenceRepository.kt @@ -18,9 +18,11 @@ package com.android.settings.network import android.content.Context import android.net.NetworkCapabilities +import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.provider.Settings import android.util.Log +import androidx.annotation.DrawableRes import com.android.settings.R import com.android.settings.network.telephony.DataSubscriptionRepository import com.android.settings.wifi.WifiSummaryRepository @@ -32,7 +34,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @OptIn(ExperimentalCoroutinesApi::class) @@ -47,42 +51,80 @@ class InternetPreferenceRepository( context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON), ) { - fun summaryFlow(): Flow = + data class DisplayInfo( + val summary: String, + @DrawableRes val iconResId: Int, + ) + + fun displayInfoFlow(): Flow = connectivityRepository .networkCapabilitiesFlow() - .flatMapLatest { capabilities -> capabilities.summaryFlow() } - .onEach { Log.d(TAG, "summaryFlow: $it") } + .flatMapLatest { capabilities -> capabilities.displayInfoFlow() } + .onEach { Log.d(TAG, "displayInfoFlow: $it") } .conflate() .flowOn(Dispatchers.Default) - private fun NetworkCapabilities.summaryFlow(): Flow { + private fun NetworkCapabilities.displayInfoFlow(): Flow { if ( hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) ) { + val transportInfo = transportInfo + if (transportInfo is WifiInfo && transportInfo.isCarrierMerged) { + Log.i(TAG, "Detect a merged carrier Wi-Fi connected.") + return cellularDisplayInfoFlow() + } for (transportType in transportTypes) { when (transportType) { - NetworkCapabilities.TRANSPORT_WIFI -> return wifiSummaryRepository.summaryFlow() - NetworkCapabilities.TRANSPORT_CELLULAR -> - return dataSubscriptionRepository.dataSummaryFlow() + NetworkCapabilities.TRANSPORT_WIFI -> return wifiDisplayInfoFlow() + NetworkCapabilities.TRANSPORT_CELLULAR -> return cellularDisplayInfoFlow() + NetworkCapabilities.TRANSPORT_ETHERNET -> return ethernetDisplayInfoFlow() } } } - return defaultSummaryFlow() + return defaultDisplayInfoFlow() } - private fun defaultSummaryFlow(): Flow = + private fun wifiDisplayInfoFlow() = + wifiSummaryRepository.summaryFlow().map { summary -> + DisplayInfo( + summary = summary, + iconResId = R.drawable.ic_wifi_signal_4, + ) + } + + private fun cellularDisplayInfoFlow() = + dataSubscriptionRepository.dataSummaryFlow().map { summary -> + DisplayInfo( + summary = summary, + iconResId = R.drawable.ic_network_cell, + ) + } + + private fun ethernetDisplayInfoFlow() = + flowOf( + DisplayInfo( + summary = context.getString(R.string.to_switch_networks_disconnect_ethernet), + iconResId = R.drawable.ic_settings_ethernet, + ) + ) + + private fun defaultDisplayInfoFlow(): Flow = combine( airplaneModeOnFlow, wifiRepository.wifiStateFlow(), ) { airplaneModeOn: Boolean, wifiState: Int -> - context.getString( - if (airplaneModeOn && wifiState != WifiManager.WIFI_STATE_ENABLED) { - R.string.condition_airplane_title - } else { - R.string.networks_available - } - ) + if (airplaneModeOn && wifiState != WifiManager.WIFI_STATE_ENABLED) { + DisplayInfo( + summary = context.getString(R.string.condition_airplane_title), + iconResId = R.drawable.ic_no_internet_unavailable, + ) + } else { + DisplayInfo( + summary = context.getString(R.string.networks_available), + iconResId = R.drawable.ic_no_internet_available, + ) + } } private companion object { diff --git a/tests/spa_unit/src/com/android/settings/network/InternetPreferenceRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/InternetPreferenceRepositoryTest.kt index aafd77f1a4a..a2542b59a84 100644 --- a/tests/spa_unit/src/com/android/settings/network/InternetPreferenceRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/InternetPreferenceRepositoryTest.kt @@ -18,6 +18,7 @@ package com.android.settings.network import android.content.Context import android.net.NetworkCapabilities +import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -32,6 +33,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.stub @@ -58,7 +60,7 @@ class InternetPreferenceRepositoryTest { ) @Test - fun summaryFlow_wifi() = runBlocking { + fun displayInfoFlow_wifi() = runBlocking { val wifiNetworkCapabilities = NetworkCapabilities.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) @@ -70,13 +72,49 @@ class InternetPreferenceRepositoryTest { } mockWifiSummaryRepository.stub { on { summaryFlow() } doReturn flowOf(SUMMARY) } - val summary = repository.summaryFlow().firstWithTimeoutOrNull() + val displayInfo = repository.displayInfoFlow().firstWithTimeoutOrNull() - assertThat(summary).isEqualTo(SUMMARY) + assertThat(displayInfo) + .isEqualTo( + InternetPreferenceRepository.DisplayInfo( + summary = SUMMARY, + iconResId = R.drawable.ic_wifi_signal_4, + ) + ) } @Test - fun summaryFlow_cellular() = runBlocking { + fun displayInfoFlow_carrierMergedWifi_asCellular() = runBlocking { + val wifiInfo = + mock { + on { isCarrierMerged } doReturn true + on { makeCopy(any()) } doReturn mock + } + val wifiNetworkCapabilities = + NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + .setTransportInfo(wifiInfo) + .build() + mockConnectivityRepository.stub { + on { networkCapabilitiesFlow() } doReturn flowOf(wifiNetworkCapabilities) + } + mockDataSubscriptionRepository.stub { on { dataSummaryFlow() } doReturn flowOf(SUMMARY) } + + val displayInfo = repository.displayInfoFlow().firstWithTimeoutOrNull() + + assertThat(displayInfo) + .isEqualTo( + InternetPreferenceRepository.DisplayInfo( + summary = SUMMARY, + iconResId = R.drawable.ic_network_cell, + ) + ) + } + + @Test + fun displayInfoFlow_cellular() = runBlocking { val wifiNetworkCapabilities = NetworkCapabilities.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) @@ -88,13 +126,42 @@ class InternetPreferenceRepositoryTest { } mockDataSubscriptionRepository.stub { on { dataSummaryFlow() } doReturn flowOf(SUMMARY) } - val summary = repository.summaryFlow().firstWithTimeoutOrNull() + val displayInfo = repository.displayInfoFlow().firstWithTimeoutOrNull() - assertThat(summary).isEqualTo(SUMMARY) + assertThat(displayInfo) + .isEqualTo( + InternetPreferenceRepository.DisplayInfo( + summary = SUMMARY, + iconResId = R.drawable.ic_network_cell, + ) + ) } @Test - fun summaryFlow_airplaneModeOnAndWifiOn() = runBlocking { + fun displayInfoFlow_ethernet() = runBlocking { + val wifiNetworkCapabilities = + NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + .build() + mockConnectivityRepository.stub { + on { networkCapabilitiesFlow() } doReturn flowOf(wifiNetworkCapabilities) + } + + val displayInfo = repository.displayInfoFlow().firstWithTimeoutOrNull() + + assertThat(displayInfo) + .isEqualTo( + InternetPreferenceRepository.DisplayInfo( + summary = context.getString(R.string.to_switch_networks_disconnect_ethernet), + iconResId = R.drawable.ic_settings_ethernet, + ) + ) + } + + @Test + fun displayInfoFlow_airplaneModeOnAndWifiOn() = runBlocking { mockConnectivityRepository.stub { on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities()) } @@ -103,13 +170,19 @@ class InternetPreferenceRepositoryTest { on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_ENABLED) } - val summary = repository.summaryFlow().firstWithTimeoutOrNull() + val displayInfo = repository.displayInfoFlow().firstWithTimeoutOrNull() - assertThat(summary).isEqualTo(context.getString(R.string.networks_available)) + assertThat(displayInfo) + .isEqualTo( + InternetPreferenceRepository.DisplayInfo( + summary = context.getString(R.string.networks_available), + iconResId = R.drawable.ic_no_internet_available, + ) + ) } @Test - fun summaryFlow_airplaneModeOnAndWifiOff() = runBlocking { + fun displayInfoFlow_airplaneModeOnAndWifiOff() = runBlocking { mockConnectivityRepository.stub { on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities()) } @@ -118,13 +191,19 @@ class InternetPreferenceRepositoryTest { on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_DISABLED) } - val summary = repository.summaryFlow().firstWithTimeoutOrNull() + val displayInfo = repository.displayInfoFlow().firstWithTimeoutOrNull() - assertThat(summary).isEqualTo(context.getString(R.string.condition_airplane_title)) + assertThat(displayInfo) + .isEqualTo( + InternetPreferenceRepository.DisplayInfo( + summary = context.getString(R.string.condition_airplane_title), + iconResId = R.drawable.ic_no_internet_unavailable, + ) + ) } @Test - fun summaryFlow_airplaneModeOff() = runBlocking { + fun displayInfoFlow_airplaneModeOff() = runBlocking { mockConnectivityRepository.stub { on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities()) } @@ -133,9 +212,15 @@ class InternetPreferenceRepositoryTest { on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_DISABLED) } - val summary = repository.summaryFlow().firstWithTimeoutOrNull() + val displayInfo = repository.displayInfoFlow().firstWithTimeoutOrNull() - assertThat(summary).isEqualTo(context.getString(R.string.networks_available)) + assertThat(displayInfo) + .isEqualTo( + InternetPreferenceRepository.DisplayInfo( + summary = context.getString(R.string.networks_available), + iconResId = R.drawable.ic_no_internet_available, + ) + ) } private companion object {