[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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -87,6 +87,78 @@ class WifiRepositoryImplTest : SysuiTestCase() {
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -154,6 +154,29 @@ class WifiInteractorTest : SysuiTestCase() {
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@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(
|
||||
|
||||
@@ -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()
|
||||
@@ -85,6 +86,26 @@ class WifiViewModelTest : SysuiTestCase() {
|
||||
// same data for icon, activity, etc. flows. So, most of these tests will test just one of the
|
||||
// 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))
|
||||
|
||||
Reference in New Issue
Block a user