[SB Refactor] Add tracking for wifi enabled state and pipe it through to
the UI.
Bug: 238425913
Test: manual: Turn on airplane mode and verify wifi icon disappears
(airplane mode => wifiEnabled = false)
Test: manual: Turn off airplane mode and verify icon reappears
Test: statusbar.pipeline tests
Change-Id: Idee192ffc6a26a180f2ff04213ffd3fd8a2f0ee4
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 6f20cbc..1400032 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.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 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 @@
@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 @@
) {
logger.logOnCapabilitiesChanged(network, networkCapabilities)
+ _wifiStateChangeEvents.tryEmit(Unit)
+
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
if (wifiInfo?.isPrimary == true) {
val wifiNetworkModel = createWifiNetworkModel(
@@ -98,6 +137,9 @@
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 ce6003f..04b17ed 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 @@
}
}
+ /** 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 dae7014..13fd922 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 @@
/** 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 cd0f27a2..f751afc 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 @@
/** 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 @@
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 1878ce5..c962315 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 @@
}
@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 622f207..39b886a 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 @@
}
@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 ca30285..9b6aef7 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 @@
MockitoAnnotations.initMocks(this)
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
+ wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
createAndSetViewModel()
@@ -86,6 +87,26 @@
// 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))