Merge changes Ie14c36f0,I73fc9334,Ic06b0e34 into main

* changes:
  InternetPreferenceController V2 (6/n)
  InternetPreferenceController V2 (5/n)
  InternetPreferenceController V2 (4/n)
This commit is contained in:
Chaohui Wang
2024-06-13 08:15:22 +00:00
committed by Android (Google) Code Review
3 changed files with 168 additions and 34 deletions

View File

@@ -22,11 +22,13 @@ import androidx.preference.Preference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.android.settings.R import com.android.settings.R
import com.android.settings.core.BasePreferenceController import com.android.settings.core.BasePreferenceController
import com.android.settingslib.Utils
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
class InternetPreferenceControllerV2(context: Context, preferenceKey: String) : class InternetPreferenceControllerV2(context: Context, preferenceKey: String) :
BasePreferenceController(context, preferenceKey) { BasePreferenceController(context, preferenceKey) {
private val repository = InternetPreferenceRepository(mContext)
private var preference: Preference? = null private var preference: Preference? = null
override fun getAvailabilityStatus() = override fun getAvailabilityStatus() =
@@ -39,9 +41,14 @@ class InternetPreferenceControllerV2(context: Context, preferenceKey: String) :
} }
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
InternetPreferenceRepository(mContext).summaryFlow() repository.displayInfoFlow().collectLatestWithLifecycle(viewLifecycleOwner) { displayInfo ->
.collectLatestWithLifecycle(viewLifecycleOwner) { preference?.apply {
preference?.summary = it summary = displayInfo.summary
icon =
mContext.getDrawable(displayInfo.iconResId)?.apply {
setTintList(Utils.getColorAttr(mContext, android.R.attr.colorControlNormal))
}
} }
}
} }
} }

View File

@@ -18,9 +18,11 @@ package com.android.settings.network
import android.content.Context import android.content.Context
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.provider.Settings import android.provider.Settings
import android.util.Log import android.util.Log
import androidx.annotation.DrawableRes
import com.android.settings.R import com.android.settings.R
import com.android.settings.network.telephony.DataSubscriptionRepository import com.android.settings.network.telephony.DataSubscriptionRepository
import com.android.settings.wifi.WifiSummaryRepository import com.android.settings.wifi.WifiSummaryRepository
@@ -32,7 +34,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@@ -47,42 +51,80 @@ class InternetPreferenceRepository(
context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON), context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON),
) { ) {
fun summaryFlow(): Flow<String> = data class DisplayInfo(
val summary: String,
@DrawableRes val iconResId: Int,
)
fun displayInfoFlow(): Flow<DisplayInfo> =
connectivityRepository connectivityRepository
.networkCapabilitiesFlow() .networkCapabilitiesFlow()
.flatMapLatest { capabilities -> capabilities.summaryFlow() } .flatMapLatest { capabilities -> capabilities.displayInfoFlow() }
.onEach { Log.d(TAG, "summaryFlow: $it") } .onEach { Log.d(TAG, "displayInfoFlow: $it") }
.conflate() .conflate()
.flowOn(Dispatchers.Default) .flowOn(Dispatchers.Default)
private fun NetworkCapabilities.summaryFlow(): Flow<String> { private fun NetworkCapabilities.displayInfoFlow(): Flow<DisplayInfo> {
if ( if (
hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) 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) { for (transportType in transportTypes) {
when (transportType) { when (transportType) {
NetworkCapabilities.TRANSPORT_WIFI -> return wifiSummaryRepository.summaryFlow() NetworkCapabilities.TRANSPORT_WIFI -> return wifiDisplayInfoFlow()
NetworkCapabilities.TRANSPORT_CELLULAR -> NetworkCapabilities.TRANSPORT_CELLULAR -> return cellularDisplayInfoFlow()
return dataSubscriptionRepository.dataSummaryFlow() NetworkCapabilities.TRANSPORT_ETHERNET -> return ethernetDisplayInfoFlow()
} }
} }
} }
return defaultSummaryFlow() return defaultDisplayInfoFlow()
} }
private fun defaultSummaryFlow(): Flow<String> = 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<DisplayInfo> =
combine( combine(
airplaneModeOnFlow, airplaneModeOnFlow,
wifiRepository.wifiStateFlow(), wifiRepository.wifiStateFlow(),
) { airplaneModeOn: Boolean, wifiState: Int -> ) { airplaneModeOn: Boolean, wifiState: Int ->
context.getString( if (airplaneModeOn && wifiState != WifiManager.WIFI_STATE_ENABLED) {
if (airplaneModeOn && wifiState != WifiManager.WIFI_STATE_ENABLED) { DisplayInfo(
R.string.condition_airplane_title summary = context.getString(R.string.condition_airplane_title),
} else { iconResId = R.drawable.ic_no_internet_unavailable,
R.string.networks_available )
} } else {
) DisplayInfo(
summary = context.getString(R.string.networks_available),
iconResId = R.drawable.ic_no_internet_available,
)
}
} }
private companion object { private companion object {

View File

@@ -18,6 +18,7 @@ package com.android.settings.network
import android.content.Context import android.content.Context
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
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
@@ -32,6 +33,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.mockito.kotlin.stub import org.mockito.kotlin.stub
@@ -58,7 +60,7 @@ class InternetPreferenceRepositoryTest {
) )
@Test @Test
fun summaryFlow_wifi() = runBlocking { fun displayInfoFlow_wifi() = runBlocking {
val wifiNetworkCapabilities = val wifiNetworkCapabilities =
NetworkCapabilities.Builder() NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
@@ -70,13 +72,49 @@ class InternetPreferenceRepositoryTest {
} }
mockWifiSummaryRepository.stub { on { summaryFlow() } doReturn flowOf(SUMMARY) } 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 @Test
fun summaryFlow_cellular() = runBlocking { fun displayInfoFlow_carrierMergedWifi_asCellular() = runBlocking {
val wifiInfo =
mock<WifiInfo> {
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 = val wifiNetworkCapabilities =
NetworkCapabilities.Builder() NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
@@ -88,13 +126,42 @@ class InternetPreferenceRepositoryTest {
} }
mockDataSubscriptionRepository.stub { on { dataSummaryFlow() } doReturn flowOf(SUMMARY) } 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 @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 { mockConnectivityRepository.stub {
on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities()) on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities())
} }
@@ -103,13 +170,19 @@ class InternetPreferenceRepositoryTest {
on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_ENABLED) 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 @Test
fun summaryFlow_airplaneModeOnAndWifiOff() = runBlocking { fun displayInfoFlow_airplaneModeOnAndWifiOff() = runBlocking {
mockConnectivityRepository.stub { mockConnectivityRepository.stub {
on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities()) on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities())
} }
@@ -118,13 +191,19 @@ class InternetPreferenceRepositoryTest {
on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_DISABLED) 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 @Test
fun summaryFlow_airplaneModeOff() = runBlocking { fun displayInfoFlow_airplaneModeOff() = runBlocking {
mockConnectivityRepository.stub { mockConnectivityRepository.stub {
on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities()) on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities())
} }
@@ -133,9 +212,15 @@ class InternetPreferenceRepositoryTest {
on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_DISABLED) 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 { private companion object {