[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
This commit is contained in:
Caitlin Shkuratov
2023-07-12 17:39:19 +00:00
parent e50d7b5dcb
commit ae06864816
10 changed files with 1115 additions and 9 deletions

View File

@@ -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")

View File

@@ -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) {
@@ -131,6 +141,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)
@@ -149,6 +189,14 @@ abstract class StatusBarPipelineModule {
return factory.create("WifiInputLog", 50)
}
@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
@@ -156,6 +204,13 @@ abstract class StatusBarPipelineModule {
return factory.create("WifiTableLog", 100)
}
@Provides
@SysUISingleton
@WifiTrackerLibTableLog
fun provideWifiTrackerLibTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
return factory.create("WifiTrackerLibTableLog", 100)
}
@Provides
@SysUISingleton
@AirplaneTableLog

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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,

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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"
}
}