diff options
7 files changed, 172 insertions, 2 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 6f20cbc004e1..1400032ea0d3 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 @@ -36,6 +36,7 @@ import com.android.systemui.dagger.qualifiers.Application 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.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 @@ -43,13 +44,21 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn /** Provides data related to the wifi state. */ interface WifiRepository { + /** Observable for the current wifi enabled status. */ + val isWifiEnabled: StateFlow<Boolean> + /** Observable for the current wifi network. */ val wifiNetwork: StateFlow<WifiNetworkModel> @@ -68,6 +77,34 @@ class WifiRepositoryImpl @Inject constructor( @Application scope: CoroutineScope, wifiManager: WifiManager?, ) : WifiRepository { + + /** + * A flow that emits [Unit] whenever the wifi state may have changed. + * + * Because [WifiManager] doesn't expose a wifi state change listener, we do it internally by + * emitting to this flow whenever we think the state may have changed. + * + * TODO(b/238425913): We also need to emit to this flow whenever the WIFI_STATE_CHANGED_ACTION + * intent is triggered. + */ + private val _wifiStateChangeEvents: MutableSharedFlow<Unit> = + MutableSharedFlow(extraBufferCapacity = 1) + + override val isWifiEnabled: StateFlow<Boolean> = + if (wifiManager == null) { + MutableStateFlow(false).asStateFlow() + } else { + _wifiStateChangeEvents + .mapLatest { wifiManager.isWifiEnabled } + .distinctUntilChanged() + .logOutputChange(logger, "enabled") + .stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(), + initialValue = wifiManager.isWifiEnabled + ) + } + override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow { var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT @@ -78,6 +115,8 @@ class WifiRepositoryImpl @Inject constructor( ) { logger.logOnCapabilitiesChanged(network, networkCapabilities) + _wifiStateChangeEvents.tryEmit(Unit) + val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities) if (wifiInfo?.isPrimary == true) { val wifiNetworkModel = createWifiNetworkModel( @@ -98,6 +137,9 @@ class WifiRepositoryImpl @Inject constructor( override fun onLost(network: Network) { logger.logOnLost(network) + + _wifiStateChangeEvents.tryEmit(Unit) + val wifi = currentWifi if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) { val newNetworkModel = WifiNetworkModel.Inactive 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 ce6003f0abd9..04b17ed2924a 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 @@ -56,6 +56,9 @@ class WifiInteractor @Inject constructor( } } + /** Our current enabled status. */ + val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled + /** Our current wifi network. See [WifiNetworkModel]. */ val wifiNetwork: Flow<WifiNetworkModel> = 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 dae701436be2..13fd922720f6 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 @@ -122,12 +122,14 @@ constructor( /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */ private val wifiIcon: Flow<Icon?> = combine( + interactor.isEnabled, interactor.isForceHidden, iconResId, contentDescription, - ) { isForceHidden, iconResId, contentDescription -> + ) { isEnabled, isForceHidden, iconResId, contentDescription -> when { - isForceHidden || + !isEnabled || + isForceHidden || iconResId == null || iconResId <= 0 -> null else -> Icon.Resource(iconResId, contentDescription) 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 cd0f27a25b7e..f751afc195b2 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 @@ -24,6 +24,9 @@ import kotlinx.coroutines.flow.StateFlow /** Fake implementation of [WifiRepository] exposing set methods for all the flows. */ class FakeWifiRepository : WifiRepository { + private val _isWifiEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled + private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> = MutableStateFlow(WifiNetworkModel.Inactive) override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork @@ -31,6 +34,10 @@ class FakeWifiRepository : WifiRepository { private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT) override val wifiActivity: StateFlow<WifiActivityModel> = _wifiActivity + fun setIsWifiEnabled(enabled: Boolean) { + _isWifiEnabled.value = enabled + } + 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 1878ce5b7e17..c962315adb51 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 @@ -88,6 +88,78 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun isWifiEnabled_nullWifiManager_getsFalse() = runBlocking(IMMEDIATE) { + underTest = WifiRepositoryImpl( + connectivityManager, + logger, + executor, + scope, + wifiManager = null, + ) + + assertThat(underTest.isWifiEnabled.value).isFalse() + } + + @Test + fun isWifiEnabled_initiallyGetsWifiManagerValue() = runBlocking(IMMEDIATE) { + whenever(wifiManager.isWifiEnabled).thenReturn(true) + + underTest = WifiRepositoryImpl( + connectivityManager, + logger, + executor, + scope, + wifiManager + ) + + assertThat(underTest.isWifiEnabled.value).isTrue() + } + + @Test + fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() = runBlocking(IMMEDIATE) { + // We need to call launch on the flows so that they start updating + val networkJob = underTest.wifiNetwork.launchIn(this) + val enabledJob = underTest.isWifiEnabled.launchIn(this) + + whenever(wifiManager.isWifiEnabled).thenReturn(true) + getNetworkCallback().onCapabilitiesChanged( + NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO) + ) + + assertThat(underTest.isWifiEnabled.value).isTrue() + + whenever(wifiManager.isWifiEnabled).thenReturn(false) + getNetworkCallback().onCapabilitiesChanged( + NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO) + ) + + assertThat(underTest.isWifiEnabled.value).isFalse() + + networkJob.cancel() + enabledJob.cancel() + } + + @Test + fun isWifiEnabled_networkLost_valueUpdated() = runBlocking(IMMEDIATE) { + // We need to call launch on the flows so that they start updating + val networkJob = underTest.wifiNetwork.launchIn(this) + val enabledJob = underTest.isWifiEnabled.launchIn(this) + + whenever(wifiManager.isWifiEnabled).thenReturn(true) + getNetworkCallback().onLost(NETWORK) + + assertThat(underTest.isWifiEnabled.value).isTrue() + + whenever(wifiManager.isWifiEnabled).thenReturn(false) + getNetworkCallback().onLost(NETWORK) + + assertThat(underTest.isWifiEnabled.value).isFalse() + + networkJob.cancel() + enabledJob.cancel() + } + + @Test fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) { var latest: WifiNetworkModel? = null val job = underTest 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 622f20769f0d..39b886af1cb8 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 @@ -155,6 +155,29 @@ class WifiInteractorTest : SysuiTestCase() { } @Test + fun isEnabled_matchesRepoIsEnabled() = runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest + .isEnabled + .onEach { latest = it } + .launchIn(this) + + wifiRepository.setIsWifiEnabled(true) + yield() + assertThat(latest).isTrue() + + wifiRepository.setIsWifiEnabled(false) + yield() + assertThat(latest).isFalse() + + wifiRepository.setIsWifiEnabled(true) + yield() + assertThat(latest).isTrue() + + job.cancel() + } + + @Test fun wifiNetwork_matchesRepoWifiNetwork() = runBlocking(IMMEDIATE) { val wifiNetwork = WifiNetworkModel.Active( networkId = 45, 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 ca30285338c3..9b6aef76d579 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 @@ -70,6 +70,7 @@ class WifiViewModelTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) connectivityRepository = FakeConnectivityRepository() wifiRepository = FakeWifiRepository() + wifiRepository.setIsWifiEnabled(true) interactor = WifiInteractor(connectivityRepository, wifiRepository) scope = CoroutineScope(IMMEDIATE) createAndSetViewModel() @@ -86,6 +87,26 @@ class WifiViewModelTest : SysuiTestCase() { // instances. There are also some tests that verify all 3 instances received the same data. @Test + fun wifiIcon_notEnabled_outputsNull() = runBlocking(IMMEDIATE) { + wifiRepository.setIsWifiEnabled(false) + + // Start as non-null so we can verify we got the update + var latest: Icon? = Icon.Resource(0, null) + val job = underTest + .home + .wifiIcon + .onEach { latest = it } + .launchIn(this) + + wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2)) + yield() + + assertThat(latest).isNull() + + job.cancel() + } + + @Test fun wifiIcon_forceHidden_outputsNull() = runBlocking(IMMEDIATE) { connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI)) |