From 80d67a4487cf02cc4d87e746a134334d9b69f161 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Tue, 11 Oct 2022 17:18:47 +0000 Subject: [PATCH] [SB Refactor] Migrate the isWifiDefault bit to the new pipeline. This migrates WifiStatusTracker's `isDefaultNetwork` tracking, and WifiSignalController's `wifiVisible` calculation. Bug: 238425913 Test: manual: Turn wifi off then back on, verify that wifi icon shows up as soon as wifi becomes default again. Test: statusbar.pipeline tests Change-Id: Ice2bbb217e5c834ccef9905dca0ce47388775c67 --- .../wifi/data/repository/WifiRepository.kt | 39 ++++++++- .../wifi/domain/interactor/WifiInteractor.kt | 3 + .../wifi/ui/viewmodel/WifiViewModel.kt | 4 +- .../data/repository/FakeWifiRepository.kt | 7 ++ .../data/repository/WifiRepositoryImplTest.kt | 83 +++++++++++++++++++ .../domain/interactor/WifiInteractorTest.kt | 23 +++++ .../WifiViewModelIconParameterizedTest.kt | 43 ++++++++++ .../wifi/ui/viewmodel/WifiViewModelTest.kt | 2 + 8 files changed, 201 insertions(+), 3 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index 681cf7254ae75..93448c1dee0e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -39,7 +39,6 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel import java.util.concurrent.Executor @@ -64,6 +63,9 @@ interface WifiRepository { /** Observable for the current wifi enabled status. */ val isWifiEnabled: StateFlow + /** Observable for the current wifi default status. */ + val isWifiDefault: StateFlow + /** Observable for the current wifi network. */ val wifiNetwork: StateFlow @@ -103,7 +105,7 @@ class WifiRepositoryImpl @Inject constructor( merge(wifiNetworkChangeEvents, wifiStateChangeEvents) .mapLatest { wifiManager.isWifiEnabled } .distinctUntilChanged() - .logOutputChange(logger, "enabled") + .logInputChange(logger, "enabled") .stateIn( scope = scope, started = SharingStarted.WhileSubscribed(), @@ -111,6 +113,39 @@ class WifiRepositoryImpl @Inject constructor( ) } + override val isWifiDefault: StateFlow = conflatedCallbackFlow { + // Note: This callback doesn't do any logging because we already log every network change + // in the [wifiNetwork] callback. + val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities + ) { + // This method will always be called immediately after the network becomes the + // default, in addition to any time the capabilities change while the network is + // the default. + // If this network contains valid wifi info, then wifi is the default network. + val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities) + trySend(wifiInfo != null) + } + + override fun onLost(network: Network) { + // The system no longer has a default network, so wifi is definitely not default. + trySend(false) + } + } + + connectivityManager.registerDefaultNetworkCallback(callback) + awaitClose { connectivityManager.unregisterNetworkCallback(callback) } + } + .distinctUntilChanged() + .logInputChange(logger, "isWifiDefault") + .stateIn( + scope, + started = SharingStarted.WhileSubscribed(), + initialValue = false + ) + override val wifiNetwork: StateFlow = conflatedCallbackFlow { var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt index 04b17ed2924af..3a3e611de96a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt @@ -59,6 +59,9 @@ class WifiInteractor @Inject constructor( /** Our current enabled status. */ val isEnabled: Flow = wifiRepository.isWifiEnabled + /** Our current default status. */ + val isDefault: Flow = wifiRepository.isWifiDefault + /** Our current wifi network. See [WifiNetworkModel]. */ val wifiNetwork: Flow = wifiRepository.wifiNetwork diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt index ebbd77b72014e..160c577042a46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt @@ -124,9 +124,10 @@ constructor( private val wifiIcon: StateFlow = combine( interactor.isEnabled, + interactor.isDefault, interactor.isForceHidden, interactor.wifiNetwork, - ) { isEnabled, isForceHidden, wifiNetwork -> + ) { isEnabled, isDefault, isForceHidden, wifiNetwork -> if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) { return@combine null } @@ -135,6 +136,7 @@ constructor( val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription()) return@combine when { + isDefault -> icon wifiConstants.alwaysShowIconIfEnabled -> icon !connectivityConstants.hasDataCapabilities -> icon wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt index f751afc195b2d..2f18ce31217e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt @@ -27,6 +27,9 @@ class FakeWifiRepository : WifiRepository { private val _isWifiEnabled: MutableStateFlow = MutableStateFlow(false) override val isWifiEnabled: StateFlow = _isWifiEnabled + private val _isWifiDefault: MutableStateFlow = MutableStateFlow(false) + override val isWifiDefault: StateFlow = _isWifiDefault + private val _wifiNetwork: MutableStateFlow = MutableStateFlow(WifiNetworkModel.Inactive) override val wifiNetwork: StateFlow = _wifiNetwork @@ -38,6 +41,10 @@ class FakeWifiRepository : WifiRepository { _isWifiEnabled.value = enabled } + fun setIsWifiDefault(default: Boolean) { + _isWifiDefault.value = default + } + fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) { _wifiNetwork.value = wifiNetworkModel } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt index 0ba0bd623c391..a64a4bd2e57a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt @@ -221,6 +221,83 @@ class WifiRepositoryImplTest : SysuiTestCase() { enabledJob.cancel() } + @Test + fun isWifiDefault_initiallyGetsDefault() = runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + assertThat(underTest.isWifiDefault.value).isFalse() + + job.cancel() + } + + @Test + fun isWifiDefault_wifiNetwork_isTrue() = runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + val wifiInfo = mock().apply { + whenever(this.ssid).thenReturn(SSID) + } + + getDefaultNetworkCallback().onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(wifiInfo) + ) + + assertThat(underTest.isWifiDefault.value).isTrue() + + job.cancel() + } + + @Test + fun isWifiDefault_cellularVcnNetwork_isTrue() = runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + val capabilities = mock().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(underTest.isWifiDefault.value).isTrue() + + job.cancel() + } + + @Test + fun isWifiDefault_cellularNotVcnNetwork_isFalse() = runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + val capabilities = mock().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(mock()) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(underTest.isWifiDefault.value).isFalse() + + job.cancel() + } + + @Test + fun isWifiDefault_wifiNetworkLost_isFalse() = runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + // First, add a network + getDefaultNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat(underTest.isWifiDefault.value).isTrue() + + // WHEN the network is lost + getDefaultNetworkCallback().onLost(NETWORK) + + // THEN we update to false + assertThat(underTest.isWifiDefault.value).isFalse() + + job.cancel() + } + @Test fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) { var latest: WifiNetworkModel? = null @@ -745,6 +822,12 @@ class WifiRepositoryImplTest : SysuiTestCase() { return callbackCaptor.value!! } + private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback { + val callbackCaptor = argumentCaptor() + verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture()) + return callbackCaptor.value!! + } + private fun createWifiNetworkCapabilities( wifiInfo: WifiInfo, isValidated: Boolean = true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt index 39b886af1cb8e..71b8bab87d197 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt @@ -177,6 +177,29 @@ class WifiInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun isDefault_matchesRepoIsDefault() = runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest + .isDefault + .onEach { latest = it } + .launchIn(this) + + wifiRepository.setIsWifiDefault(true) + yield() + assertThat(latest).isTrue() + + wifiRepository.setIsWifiDefault(false) + yield() + assertThat(latest).isFalse() + + wifiRepository.setIsWifiDefault(true) + yield() + assertThat(latest).isTrue() + + job.cancel() + } + @Test fun wifiNetwork_matchesRepoWifiNetwork() = runBlocking(IMMEDIATE) { val wifiNetwork = WifiNetworkModel.Active( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt index e56623f9fea28..7686071206f94 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt @@ -88,6 +88,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase fun wifiIcon() = runBlocking(IMMEDIATE) { wifiRepository.setIsWifiEnabled(testCase.enabled) + wifiRepository.setIsWifiDefault(testCase.isDefault) connectivityRepository.setForceHiddenIcons( if (testCase.forceHidden) { setOf(ConnectivitySlot.WIFI) @@ -152,6 +153,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase val forceHidden: Boolean = false, val alwaysShowIconWhenEnabled: Boolean = false, val hasDataCapabilities: Boolean = true, + val isDefault: Boolean = false, val network: WifiNetworkModel, /** The expected output. Null if we expect the output to be null. */ @@ -162,6 +164,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase "forceHidden=$forceHidden, " + "showWhenEnabled=$alwaysShowIconWhenEnabled, " + "hasDataCaps=$hasDataCapabilities, " + + "isDefault=$isDefault, " + "network=$network) then " + "EXPECTED($expected)" } @@ -296,6 +299,46 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), ), + // isDefault = true => all Inactive and Active networks shown + TestCase( + isDefault = true, + network = WifiNetworkModel.Inactive, + expected = + Expected( + iconResource = WIFI_NO_NETWORK, + contentDescription = { context -> + "${context.getString(WIFI_NO_CONNECTION)}," + + context.getString(NO_INTERNET) + }, + description = "No network icon", + ), + ), + TestCase( + isDefault = true, + network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3), + expected = + Expected( + iconResource = WIFI_NO_INTERNET_ICONS[3], + contentDescription = { context -> + "${context.getString(WIFI_CONNECTION_STRENGTH[3])}," + + context.getString(NO_INTERNET) + }, + description = "No internet level 3 icon", + ), + ), + TestCase( + isDefault = true, + network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1), + expected = + Expected( + iconResource = WIFI_FULL_ICONS[1], + contentDescription = { context -> + context.getString(WIFI_CONNECTION_STRENGTH[1]) + }, + description = "Full internet level 1 icon", + ), + ), + // network = CarrierMerged => not shown TestCase( network = WifiNetworkModel.CarrierMerged, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt index 3169eef83f07b..79633d467b7d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -76,6 +76,8 @@ class WifiViewModelTest : SysuiTestCase() { scope.cancel() } + // See [WifiViewModelIconParameterizedTest] for additional view model tests. + // Note on testing: [WifiViewModel] exposes 3 different instances of // [LocationBasedWifiViewModel]. In practice, these 3 different instances will get the exact // same data for icon, activity, etc. flows. So, most of these tests will test just one of the