diff options
| author | 2023-07-12 17:39:19 +0000 | |
|---|---|---|
| committer | 2023-07-25 20:26:01 +0000 | |
| commit | ae06864816e9cd777e9de77f52e485fab8bb893c (patch) | |
| tree | 1656a65fb3727d815776492fe312fad2547ef51a | |
| parent | e50d7b5dcbcf990088aa0956c89c7958bc5b4793 (diff) | |
[Status Bar] Implement WifiRepository interface using WifiTrackerLib.
This CL also adds a flag to switch between using the current
WifiRepository and using the WifiRepository based on WifiTrackerLib.
Bug: 292534484
Bug: 292533677
Test: Flip WIFI_TRACKER_LIB_FOR_WIFI_ICON flag on -> verify wifi icon
generally still works
Test: dump WifiTrackerLibInputLog and WifiTrackerLibTableLog -> verify
data is included only with flag **on**
Test: dump WifiInputLog and WifiTableLog -> verify data is included when
flag is on **and** off
Test: atest WifiRepositoryViaTrackerLibTest
Change-Id: Ib74835e78399c6c5ca3821e4aed09bfc0925fc28
10 files changed, 1115 insertions, 9 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index efa5981ef7b2..511ff85cb272 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -375,6 +375,10 @@ object Flags { @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag(612, "new_bluetooth_repository") + // TODO(b/292533677): Tracking Bug + val WIFI_TRACKER_LIB_FOR_WIFI_ICON = + unreleasedFlag(613, "wifi_tracker_lib_for_wifi_icon") + // 700 - dialer/calls // TODO(b/254512734): Tracking Bug val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 0e99c67a0d80..e1fd37f558ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.pipeline.dagger import android.net.wifi.WifiManager import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.log.table.TableLogBuffer @@ -48,9 +50,12 @@ import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStat import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.DisabledWifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl +import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryViaTrackerLib import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl import dagger.Binds @@ -114,14 +119,19 @@ abstract class StatusBarPipelineModule { impl: CollapsedStatusBarViewBinderImpl ): CollapsedStatusBarViewBinder + @Binds + @IntoMap + @ClassKey(WifiRepositoryDagger::class) + abstract fun bindWifiRepositoryDagger(impl: WifiRepositoryDagger): CoreStartable + companion object { @Provides @SysUISingleton - fun provideRealWifiRepository( + fun provideWifiRepositoryDagger( wifiManager: WifiManager?, disabledWifiRepository: DisabledWifiRepository, wifiRepositoryImplFactory: WifiRepositoryImpl.Factory, - ): RealWifiRepository { + ): WifiRepositoryDagger { // If we have a null [WifiManager], then the wifi repository should be permanently // disabled. return if (wifiManager == null) { @@ -133,6 +143,36 @@ abstract class StatusBarPipelineModule { @Provides @SysUISingleton + fun provideWifiRepositoryViaTrackerLibDagger( + wifiManager: WifiManager?, + disabledWifiRepository: DisabledWifiRepository, + wifiRepositoryFromTrackerLibFactory: WifiRepositoryViaTrackerLib.Factory, + ): WifiRepositoryViaTrackerLibDagger { + // If we have a null [WifiManager], then the wifi repository should be permanently + // disabled. + return if (wifiManager == null) { + disabledWifiRepository + } else { + wifiRepositoryFromTrackerLibFactory.create(wifiManager) + } + } + + @Provides + @SysUISingleton + fun provideRealWifiRepository( + wifiRepository: WifiRepositoryDagger, + wifiRepositoryFromTrackerLib: WifiRepositoryViaTrackerLibDagger, + flags: FeatureFlags, + ): RealWifiRepository { + return if (flags.isEnabled(Flags.WIFI_TRACKER_LIB_FOR_WIFI_ICON)) { + wifiRepositoryFromTrackerLib + } else { + wifiRepository + } + } + + @Provides + @SysUISingleton @Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON) fun provideFirstMobileSubShowingNetworkTypeIconProvider( mobileIconsViewModel: MobileIconsViewModel, @@ -151,6 +191,14 @@ abstract class StatusBarPipelineModule { @Provides @SysUISingleton + @WifiTrackerLibInputLog + fun provideWifiTrackerLibInputLogBuffer(factory: LogBufferFactory): LogBuffer { + // WifiTrackerLib is pretty noisy, so give it more room than WifiInputLog. + return factory.create("WifiTrackerLibInputLog", 200) + } + + @Provides + @SysUISingleton @WifiTableLog fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer { return factory.create("WifiTableLog", 100) @@ -158,6 +206,13 @@ abstract class StatusBarPipelineModule { @Provides @SysUISingleton + @WifiTrackerLibTableLog + fun provideWifiTrackerLibTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer { + return factory.create("WifiTrackerLibTableLog", 100) + } + + @Provides + @SysUISingleton @AirplaneTableLog fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer { return factory.create("AirplaneTableLog", 30) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt new file mode 100644 index 000000000000..b84b01e66955 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.dagger + +import javax.inject.Qualifier + +/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */ +@Qualifier +@MustBeDocumented +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +annotation class WifiTrackerLibInputLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt new file mode 100644 index 000000000000..7ca70308361d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.dagger + +import javax.inject.Qualifier + +/** Wifi logs from [WifiRepositoryViaTrackerLib] in table format. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class WifiTrackerLibTableLog 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 f800cf496ca9..b11b4727c3c3 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 @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository +import com.android.systemui.CoreStartable import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import kotlinx.coroutines.flow.StateFlow @@ -42,6 +43,13 @@ interface WifiRepository { val currentNetwork = wifiNetwork.value return currentNetwork is WifiNetworkModel.Active && currentNetwork.hasValidSsid() } + + companion object { + /** Column name to use for [isWifiEnabled] for table logging. */ + const val COL_NAME_IS_ENABLED = "isEnabled" + /** Column name to use for [isWifiDefault] for table logging. */ + const val COL_NAME_IS_DEFAULT = "isDefault" + } } /** @@ -53,3 +61,8 @@ interface WifiRepository { * repository. */ interface RealWifiRepository : WifiRepository + +/** Used only by Dagger to bind [WifiRepositoryImpl]. */ +interface WifiRepositoryDagger : RealWifiRepository, CoreStartable +/** Used only by Dagger to bind [WifiRepositoryViaTrackerLib]. */ +interface WifiRepositoryViaTrackerLibDagger : RealWifiRepository diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt index 86a668a2e842..9ed7c6a83cee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt @@ -18,7 +18,8 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow @@ -32,7 +33,10 @@ import kotlinx.coroutines.flow.asStateFlow * wifi information. */ @SysUISingleton -class DisabledWifiRepository @Inject constructor() : RealWifiRepository { +class DisabledWifiRepository @Inject constructor() : + WifiRepositoryDagger, WifiRepositoryViaTrackerLibDagger { + override fun start() {} + override val isWifiEnabled: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() override val isWifiDefault: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index 7f35dfbe2700..995de6d2fc61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -43,8 +43,10 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo -import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import java.util.concurrent.Executor @@ -64,6 +66,7 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** Real implementation of [WifiRepository]. */ @@ -81,9 +84,21 @@ constructor( @WifiTableLog wifiTableLogBuffer: TableLogBuffer, @Main mainExecutor: Executor, @Background private val bgDispatcher: CoroutineDispatcher, - @Application scope: CoroutineScope, + @Application private val scope: CoroutineScope, private val wifiManager: WifiManager, -) : RealWifiRepository { +) : WifiRepositoryDagger { + + override fun start() { + // There are two possible [WifiRepository] implementations: This class (old) and + // [WifiRepositoryFromTrackerLib] (new). While we migrate to the new class, we want this old + // class to still be running in the background so that we can collect logs and compare + // discrepancies. This #start method collects on the flows to ensure that the logs are + // collected. + scope.launch { isWifiEnabled.collect {} } + scope.launch { isWifiDefault.collect {} } + scope.launch { wifiNetwork.collect {} } + scope.launch { wifiActivity.collect {} } + } private val wifiStateChangeEvents: Flow<Unit> = broadcastDispatcher @@ -104,7 +119,7 @@ constructor( .logDiffsForTable( wifiTableLogBuffer, columnPrefix = "", - columnName = "isEnabled", + columnName = COL_NAME_IS_ENABLED, initialValue = false, ) .stateIn( @@ -125,7 +140,7 @@ constructor( .logDiffsForTable( wifiTableLogBuffer, columnPrefix = "", - columnName = "isDefault", + columnName = COL_NAME_IS_DEFAULT, initialValue = false, ) .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) @@ -234,6 +249,8 @@ constructor( // NetworkCallback inside [wifiNetwork] for our wifi network information. val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive + const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED + private fun createWifiNetworkModel( wifiInfo: WifiInfo, network: Network, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt new file mode 100644 index 000000000000..127136789ba6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod + +import android.net.wifi.WifiManager +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory +import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog +import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger +import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.wifitrackerlib.MergedCarrierEntry +import com.android.wifitrackerlib.WifiEntry +import com.android.wifitrackerlib.WifiPickerTracker +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * An implementation of [WifiRepository] that uses [com.android.wifitrackerlib] as the source of + * truth for wifi information. + * + * Serves as a possible replacement for [WifiRepositoryImpl]. See b/292534484. + */ +@SysUISingleton +class WifiRepositoryViaTrackerLib +@Inject +constructor( + @Application private val scope: CoroutineScope, + @Main private val mainExecutor: Executor, + private val wifiPickerTrackerFactory: WifiPickerTrackerFactory, + private val wifiManager: WifiManager, + @WifiTrackerLibInputLog private val inputLogger: LogBuffer, + @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer, +) : WifiRepositoryViaTrackerLibDagger, LifecycleOwner { + + override val lifecycle = + LifecycleRegistry(this).also { + mainExecutor.execute { it.currentState = Lifecycle.State.CREATED } + } + + private var wifiPickerTracker: WifiPickerTracker? = null + + private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run { + var current = + WifiPickerTrackerInfo( + state = WIFI_STATE_DEFAULT, + isDefault = false, + network = WIFI_NETWORK_DEFAULT, + ) + callbackFlow { + val callback = + object : WifiPickerTracker.WifiPickerTrackerCallback { + override fun onWifiEntriesChanged() { + val connectedEntry = wifiPickerTracker?.connectedWifiEntry + logOnWifiEntriesChanged(connectedEntry) + + // [WifiPickerTracker.connectedWifiEntry] will return the same instance + // but with updated internals. For example, when its validation status + // changes from false to true, the same instance is re-used but with the + // validated field updated. + // + // Because it's the same instance, the flow won't re-emit the value + // (even though the internals have changed). So, we need to transform it + // into our internal model immediately. [toWifiNetworkModel] always + // returns a new instance, so the flow is guaranteed to emit. + send( + newNetwork = connectedEntry?.toWifiNetworkModel() + ?: WIFI_NETWORK_DEFAULT, + newIsDefault = connectedEntry?.isDefaultNetwork ?: false, + ) + } + + override fun onWifiStateChanged() { + val state = wifiPickerTracker?.wifiState + logOnWifiStateChanged(state) + send(newState = state ?: WIFI_STATE_DEFAULT) + } + + override fun onNumSavedNetworksChanged() {} + + override fun onNumSavedSubscriptionsChanged() {} + + private fun send( + newState: Int = current.state, + newIsDefault: Boolean = current.isDefault, + newNetwork: WifiNetworkModel = current.network, + ) { + val new = WifiPickerTrackerInfo(newState, newIsDefault, newNetwork) + current = new + trySend(new) + } + } + + // TODO(b/292591403): [WifiPickerTrackerFactory] currently scans to see all + // available wifi networks every 10s. Because SysUI only needs to display the + // **connected** network, we don't need scans to be running. We should disable these + // scans (ideal) or at least run them very infrequently. + wifiPickerTracker = wifiPickerTrackerFactory.create(lifecycle, callback) + // The lifecycle must be STARTED in order for the callback to receive events. + mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED } + awaitClose { + mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED } + } + } + // TODO(b/292534484): Update to Eagerly once scans are disabled. (Here and other flows) + .stateIn(scope, SharingStarted.WhileSubscribed(), current) + } + + override val isWifiEnabled: StateFlow<Boolean> = + wifiPickerTrackerInfo + .map { it.state == WifiManager.WIFI_STATE_ENABLED } + .distinctUntilChanged() + .logDiffsForTable( + wifiTrackerLibTableLogBuffer, + columnPrefix = "", + columnName = COL_NAME_IS_ENABLED, + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val wifiNetwork: StateFlow<WifiNetworkModel> = + wifiPickerTrackerInfo + .map { it.network } + .distinctUntilChanged() + .logDiffsForTable( + wifiTrackerLibTableLogBuffer, + columnPrefix = "", + initialValue = WIFI_NETWORK_DEFAULT, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), WIFI_NETWORK_DEFAULT) + + /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */ + private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel { + if (!this.isPrimaryNetwork) { + return WIFI_NETWORK_DEFAULT + } + return if (this is MergedCarrierEntry) { + WifiNetworkModel.CarrierMerged( + networkId = NETWORK_ID, + // TODO(b/292534484): Fetch the real subscription ID from [MergedCarrierEntry]. + subscriptionId = TEMP_SUB_ID, + level = this.level, + // WifiManager APIs to calculate the signal level start from 0, so + // maxSignalLevel + 1 represents the total level buckets count. + numberOfLevels = wifiManager.maxSignalLevel + 1, + ) + } else { + WifiNetworkModel.Active( + networkId = NETWORK_ID, + isValidated = this.hasInternetAccess(), + level = this.level, + ssid = this.ssid, + // TODO(b/292534484): Fetch the real values from [WifiEntry] (#getTitle might be + // appropriate). + isPasspointAccessPoint = false, + isOnlineSignUpForPasspointAccessPoint = false, + passpointProviderFriendlyName = null, + ) + } + } + + override val isWifiDefault: StateFlow<Boolean> = + wifiPickerTrackerInfo + .map { it.isDefault } + .distinctUntilChanged() + .logDiffsForTable( + wifiTrackerLibTableLogBuffer, + columnPrefix = "", + columnName = COL_NAME_IS_DEFAULT, + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + // TODO(b/292534484): Re-use WifiRepositoryImpl code to implement wifi activity since + // WifiTrackerLib doesn't expose activity details. + override val wifiActivity: StateFlow<DataActivityModel> = + MutableStateFlow(DataActivityModel(false, false)) + + private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) { + inputLogger.log( + TAG, + LogLevel.DEBUG, + { str1 = connectedEntry.toString() }, + { "onWifiEntriesChanged. ConnectedEntry=$str1" }, + ) + } + + private fun logOnWifiStateChanged(state: Int?) { + inputLogger.log( + TAG, + LogLevel.DEBUG, + { int1 = state ?: -1 }, + { "onWifiStateChanged. State=${if (int1 == -1) null else int1}" }, + ) + } + + /** + * Data class storing all the information fetched from [WifiPickerTracker]. + * + * Used so that we only register a single callback on [WifiPickerTracker]. + */ + data class WifiPickerTrackerInfo( + /** The current wifi state. See [WifiManager.getWifiState]. */ + val state: Int, + /** True if wifi is currently the default connection and false otherwise. */ + val isDefault: Boolean, + /** The currently primary wifi network. */ + val network: WifiNetworkModel, + ) + + @SysUISingleton + class Factory + @Inject + constructor( + @Application private val scope: CoroutineScope, + @Main private val mainExecutor: Executor, + private val wifiPickerTrackerFactory: WifiPickerTrackerFactory, + @WifiTrackerLibInputLog private val inputLogger: LogBuffer, + @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer, + ) { + fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib { + return WifiRepositoryViaTrackerLib( + scope, + mainExecutor, + wifiPickerTrackerFactory, + wifiManager, + inputLogger, + wifiTrackerLibTableLogBuffer, + ) + } + } + + companion object { + private const val TAG = "WifiTrackerLibInputLog" + + /** + * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by + * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework + * callbacks within the repository. + * + * Since this class does not need to manually apply framework callbacks and since the + * network ID is not used beyond the repository, it's safe to use an invalid ID in this + * repository. + * + * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated + * to [WifiRepositoryViaTrackerLib]. + */ + private const val NETWORK_ID = -1 + + /** + * A temporary subscription ID until WifiTrackerLib exposes a method to fetch the + * subscription ID. + * + * Use -2 because [SubscriptionManager.INVALID_SUBSCRIPTION_ID] is -1. + */ + private const val TEMP_SUB_ID = -2 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index e874a00865d0..6845152b5315 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -62,6 +62,9 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +/** + * Note: Any new tests added here may also need to be added to [WifiRepositoryViaTrackerLibTest]. + */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SmallTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt new file mode 100644 index 000000000000..7002cbb6ab21 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -0,0 +1,665 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod + +import android.net.wifi.WifiManager +import android.net.wifi.WifiManager.UNKNOWN_SSID +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory +import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.android.wifitrackerlib.MergedCarrierEntry +import com.android.wifitrackerlib.WifiEntry +import com.android.wifitrackerlib.WifiPickerTracker +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +/** + * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests. + * + * Any new tests added here may also need to be added to [WifiRepositoryImplTest]. + */ +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { + + private lateinit var underTest: WifiRepositoryViaTrackerLib + + private val executor = FakeExecutor(FakeSystemClock()) + private val logger = LogBuffer("name", maxSize = 100, logcatEchoTracker = mock()) + private val tableLogger = mock<TableLogBuffer>() + private val wifiManager = + mock<WifiManager>().apply { whenever(this.maxSignalLevel).thenReturn(10) } + private val wifiPickerTrackerFactory = mock<WifiPickerTrackerFactory>() + private val wifiPickerTracker = mock<WifiPickerTracker>() + + private val callbackCaptor = argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>() + + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) + + @Before + fun setUp() { + whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor))) + .thenReturn(wifiPickerTracker) + underTest = createRepo() + } + + @Test + fun isWifiEnabled_enabled_true() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiEnabled) + + whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_ENABLED) + getCallback().onWifiStateChanged() + + assertThat(latest).isTrue() + } + + @Test + fun isWifiEnabled_enabling_false() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiEnabled) + + whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_ENABLING) + getCallback().onWifiStateChanged() + + assertThat(latest).isFalse() + } + + @Test + fun isWifiEnabled_disabling_true() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiEnabled) + + whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_DISABLING) + getCallback().onWifiStateChanged() + + assertThat(latest).isFalse() + } + + @Test + fun isWifiEnabled_disabled_false() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiEnabled) + + whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_DISABLED) + getCallback().onWifiStateChanged() + + assertThat(latest).isFalse() + } + + @Test + fun isWifiEnabled_respondsToUpdates() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiEnabled) + executor.runAllReady() + + whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_ENABLED) + getCallback().onWifiStateChanged() + + assertThat(latest).isTrue() + + whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_DISABLED) + getCallback().onWifiStateChanged() + + assertThat(latest).isFalse() + } + + @Test + fun isWifiDefault_initiallyGetsDefault() = + testScope.runTest { assertThat(underTest.isWifiDefault.value).isFalse() } + + @Test + fun isWifiDefault_wifiNetwork_isTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiDefault) + + val wifiEntry = + mock<WifiEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(true) } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isTrue() + } + + @Test + fun isWifiDefault_carrierMerged_isTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiDefault) + + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isDefaultNetwork).thenReturn(true) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isTrue() + } + + @Test + fun isWifiDefault_wifiNetworkNotDefault_isFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiDefault) + + val wifiEntry = + mock<WifiEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(false) } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isFalse() + } + + @Test + fun isWifiDefault_carrierMergedNotDefault_isFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiDefault) + + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isDefaultNetwork).thenReturn(false) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isFalse() + } + + @Test + fun isWifiDefault_noWifiNetwork_isFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.isWifiDefault) + + // First, add a network + val wifiEntry = + mock<WifiEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(true) } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isTrue() + + // WHEN the network is lost + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + getCallback().onWifiEntriesChanged() + + // THEN we update to false + assertThat(latest).isFalse() + } + + @Test + fun wifiNetwork_initiallyGetsDefault() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT) + } + + @Test + fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + whenever(this.ssid).thenReturn(SSID) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.level).isEqualTo(3) + assertThat(latestActive.ssid).isEqualTo(SSID) + } + + @Test + fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + val latestMerged = latest as WifiNetworkModel.CarrierMerged + assertThat(latestMerged.level).isEqualTo(3) + // numberOfLevels = maxSignalLevel + 1 + } + + @Test + fun wifiNetwork_isCarrierMerged_getsMaxSignalLevel() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiManager.maxSignalLevel).thenReturn(5) + + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + val latestMerged = latest as WifiNetworkModel.CarrierMerged + // numberOfLevels = maxSignalLevel + 1 + assertThat(latestMerged.numberOfLevels).isEqualTo(6) + } + + /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID. + @Test + fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + } + + getNetworkCallback() + .onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(wifiInfo), + ) + + assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java) + } + + */ + + @Test + fun wifiNetwork_notValidated_networkNotValidated() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.hasInternetAccess()).thenReturn(false) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse() + } + + @Test + fun wifiNetwork_validated_networkValidated() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.hasInternetAccess()).thenReturn(true) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue() + } + + @Test + fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + } + + @Test + fun wifiNetwork_nonPrimaryCarrierMergedNetworkAdded_flowHasNoNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(false) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + } + + @Test + fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + // Start with the original network + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + whenever(this.ssid).thenReturn("AB") + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.Active).isTrue() + var latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.level).isEqualTo(3) + assertThat(latestActive.ssid).isEqualTo("AB") + + // WHEN we update to a new primary network + val newWifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(4) + whenever(this.ssid).thenReturn("CD") + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(newWifiEntry) + getCallback().onWifiEntriesChanged() + + // THEN we use the new network + assertThat(latest is WifiNetworkModel.Active).isTrue() + latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.level).isEqualTo(4) + assertThat(latestActive.ssid).isEqualTo("CD") + } + + @Test + fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + // WHEN we receive a null network without any networks beforehand + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + getCallback().onWifiEntriesChanged() + + // THEN there's no crash and we still have no network + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + } + + @Test + fun wifiNetwork_currentActiveNetworkLost_flowHasNoNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.ssid).thenReturn(SSID) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(SSID) + + // WHEN we lose our current network + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + getCallback().onWifiEntriesChanged() + + // THEN we update to no network + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + } + + /** Possible regression test for b/278618530. */ + @Test + fun wifiNetwork_currentCarrierMergedNetworkLost_flowHasNoNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + assertThat((latest as WifiNetworkModel.CarrierMerged).level).isEqualTo(3) + + // WHEN we lose our current network + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + getCallback().onWifiEntriesChanged() + + // THEN we update to no network + assertThat(latest is WifiNetworkModel.Inactive).isTrue() + } + + /** Regression test for b/244173280. */ + @Test + fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() = + testScope.runTest { + val latest1 by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(1) + whenever(this.ssid).thenReturn(SSID) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest1 is WifiNetworkModel.Active).isTrue() + val latest1Active = latest1 as WifiNetworkModel.Active + assertThat(latest1Active.level).isEqualTo(1) + assertThat(latest1Active.ssid).isEqualTo(SSID) + + // WHEN we add a second subscriber after having already emitted a value + val latest2 by collectLastValue(underTest.wifiNetwork) + + // THEN the second subscribe receives the already-emitted value + assertThat(latest2 is WifiNetworkModel.Active).isTrue() + val latest2Active = latest2 as WifiNetworkModel.Active + assertThat(latest2Active.level).isEqualTo(1) + assertThat(latest2Active.ssid).isEqualTo(SSID) + } + + @Test + fun isWifiConnectedWithValidSsid_inactiveNetwork_false() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + getCallback().onWifiEntriesChanged() + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + } + + @Test + fun isWifiConnectedWithValidSsid_nonPrimaryNetwork_false() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + testScope.runCurrent() + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + } + + @Test + fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + testScope.runCurrent() + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + } + + /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID. + @Test + fun isWifiConnectedWithValidSsid_invalidNetwork_false() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + } + + getNetworkCallback() + .onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(wifiInfo), + ) + testScope.runCurrent() + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + } + + */ + + @Test + fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.ssid).thenReturn(null) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + testScope.runCurrent() + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + } + + @Test + fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.ssid).thenReturn(UNKNOWN_SSID) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + testScope.runCurrent() + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + } + + @Test + fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.ssid).thenReturn("fakeSsid") + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + testScope.runCurrent() + + assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue() + } + + @Test + fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + + // Start with active + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.ssid).thenReturn("fakeSsid") + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + testScope.runCurrent() + + assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue() + + // WHEN the network is lost + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + getCallback().onWifiEntriesChanged() + testScope.runCurrent() + + // THEN the isWifiConnected updates + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + } + + private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback { + testScope.runCurrent() + return callbackCaptor.value + } + + private fun createRepo(): WifiRepositoryViaTrackerLib { + return WifiRepositoryViaTrackerLib( + testScope.backgroundScope, + executor, + wifiPickerTrackerFactory, + wifiManager, + logger, + tableLogger, + ) + } + + private companion object { + const val SSID = "AB" + } +} |