summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Caitlin Shkuratov <caitlinshk@google.com> 2023-07-12 17:39:19 +0000
committer Caitlin Shkuratov <caitlinshk@google.com> 2023-07-25 20:26:01 +0000
commitae06864816e9cd777e9de77f52e485fab8bb893c (patch)
tree1656a65fb3727d815776492fe312fad2547ef51a
parente50d7b5dcbcf990088aa0956c89c7958bc5b4793 (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
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt295
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt665
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"
+ }
+}