Merge "[Sb Refactor] Define an initial data/domain/ui layer for the status bar connectivity architecture for wifi." into tm-qpr-dev am: 9dce321437

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19558692

Change-Id: If9479d126a49ad7432e0aa5ef7c1b6732593b77c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Caitlin Shkuratov
2022-08-22 12:40:31 +00:00
committed by Automerger Merge Worker
21 changed files with 950 additions and 74 deletions

View File

@@ -270,7 +270,7 @@ public class LogModule {
@SysUISingleton
@StatusBarConnectivityLog
public static LogBuffer provideStatusBarConnectivityBuffer(LogBufferFactory factory) {
return factory.create("StatusBarConnectivityLog", 64);
return factory.create("SbConnectivity", 64);
}
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */

View File

@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.pipeline
import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
import kotlinx.coroutines.flow.StateFlow
/**

View File

@@ -18,9 +18,9 @@ package com.android.systemui.statusbar.pipeline
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilitiesRepo
import kotlinx.coroutines.CoroutineScope
import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilitiesRepo
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map

View File

@@ -20,14 +20,18 @@ import android.content.Context
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/**
* A processor that transforms raw connectivity information that we get from callbacks and turns it
@@ -42,12 +46,16 @@ import kotlinx.coroutines.flow.stateIn
class ConnectivityInfoProcessor @Inject constructor(
connectivityInfoCollector: ConnectivityInfoCollector,
context: Context,
// TODO(b/238425913): Don't use the application scope; instead, use the status bar view's
// scope so we only do work when there's UI that cares about it.
@Application private val scope: CoroutineScope,
statusBarPipelineFlags: StatusBarPipelineFlags,
private val statusBarPipelineFlags: StatusBarPipelineFlags,
private val wifiViewModelProvider: Provider<WifiViewModel>,
) : CoreStartable(context) {
// Note: This flow will not start running until a client calls `collect` on it, which means that
// [connectivityInfoCollector]'s flow will also not start anything until that `collect` call
// happens.
// TODO(b/238425913): Delete this.
val processedInfoFlow: Flow<ProcessedConnectivityInfo> =
if (!statusBarPipelineFlags.isNewPipelineEnabled())
emptyFlow()
@@ -60,6 +68,14 @@ class ConnectivityInfoProcessor @Inject constructor(
)
override fun start() {
if (!statusBarPipelineFlags.isNewPipelineEnabled()) {
return
}
// TODO(b/238425913): The view binder should do this instead. For now, do it here so we can
// see the logs.
scope.launch {
wifiViewModelProvider.get().isActivityInVisible.collect { }
}
}
private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo {

View File

@@ -1,59 +0,0 @@
/*
* Copyright (C) 2022 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
import android.net.Network
import android.net.NetworkCapabilities
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.StatusBarConnectivityLog
import javax.inject.Inject
@SysUISingleton
class ConnectivityPipelineLogger @Inject constructor(
@StatusBarConnectivityLog private val buffer: LogBuffer,
) {
fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
buffer.log(
TAG,
LogLevel.INFO,
{
int1 = network.getNetId()
str1 = networkCapabilities.toString()
},
{
"onCapabilitiesChanged: net=$int1 capabilities=$str1"
}
)
}
fun logOnLost(network: Network) {
buffer.log(
TAG,
LogLevel.INFO,
{
int1 = network.getNetId()
},
{
"onLost: net=$int1"
}
)
}
}
private const val TAG = "SbConnectivityPipeline"

View File

@@ -20,6 +20,8 @@ import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollector
import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollectorImpl
import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -37,4 +39,7 @@ abstract class StatusBarPipelineModule {
abstract fun provideConnectivityInfoCollector(
impl: ConnectivityInfoCollectorImpl
): ConnectivityInfoCollector
@Binds
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2022 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.shared
import android.net.Network
import android.net.NetworkCapabilities
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.StatusBarConnectivityLog
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach
@SysUISingleton
class ConnectivityPipelineLogger @Inject constructor(
@StatusBarConnectivityLog private val buffer: LogBuffer,
) {
fun logInputChange(callbackName: String, changeInfo: String) {
buffer.log(
SB_LOGGING_TAG,
LogLevel.INFO,
{
str1 = callbackName
str2 = changeInfo
},
{
"Input: $str1: $str2"
}
)
}
fun logOutputChange(outputParamName: String, changeInfo: String) {
buffer.log(
SB_LOGGING_TAG,
LogLevel.INFO,
{
str1 = outputParamName
str2 = changeInfo
},
{
"Output: $str1: $str2"
}
)
}
fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
buffer.log(
SB_LOGGING_TAG,
LogLevel.INFO,
{
int1 = network.getNetId()
str1 = networkCapabilities.toString()
},
{
"onCapabilitiesChanged: net=$int1 capabilities=$str1"
}
)
}
fun logOnLost(network: Network) {
buffer.log(
SB_LOGGING_TAG,
LogLevel.INFO,
{
int1 = network.getNetId()
},
{
"onLost: net=$int1"
}
)
}
companion object {
const val SB_LOGGING_TAG = "SbConnectivity"
/**
* Log a change in one of the **outputs** to the connectivity pipeline.
*
* @param prettyPrint an optional function to transform the value into a readable string.
* [toString] is used if no custom function is provided.
*/
fun <T : Any> Flow<T>.logOutputChange(
logger: ConnectivityPipelineLogger,
outputParamName: String,
prettyPrint: (T) -> String = { it.toString() }
): Flow<T> {
return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) }
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2022 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.model
/**
* Provides information on the current wifi activity.
*/
data class WifiActivityModel(
/** True if the wifi has activity in (download). */
val hasActivityIn: Boolean,
/** True if the wifi has activity out (upload). */
val hasActivityOut: Boolean,
)

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2022 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.model
/** Provides information about the current wifi state. */
data class WifiModel(
/** See [android.net.wifi.WifiInfo.ssid]. */
val ssid: String? = null,
/** See [android.net.wifi.WifiInfo.isPasspointAp]. */
val isPasspointAccessPoint: Boolean = false,
/** See [android.net.wifi.WifiInfo.isOsuAp]. */
val isOnlineSignUpForPasspointAccessPoint: Boolean = false,
/** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */
val passpointProviderFriendlyName: String? = null,
)

View File

@@ -16,7 +16,7 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.statusbar.pipeline.repository
package com.android.systemui.statusbar.pipeline.wifi.data.repository
import android.annotation.SuppressLint
import android.net.ConnectivityManager
@@ -25,7 +25,7 @@ import android.net.NetworkCapabilities
import android.net.NetworkRequest
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,7 +35,11 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.stateIn
/** Repository that contains all relevant [NetworkCapabilites] for the current networks */
/**
* Repository that contains all relevant [NetworkCapabilities] for the current networks.
*
* TODO(b/238425913): Figure out how to merge this with [WifiRepository].
*/
@SysUISingleton
class NetworkCapabilitiesRepo @Inject constructor(
connectivityManager: ConnectivityManager,
@@ -88,5 +92,3 @@ data class NetworkCapabilityInfo(
val network: Network,
val capabilities: NetworkCapabilities,
)
private const val TAG = "ConnectivityRepository"

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2022 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
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
import android.util.Log
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
/**
* Provides data related to the wifi state.
*/
interface WifiRepository {
/**
* Observable for the current state of wifi; `null` when there is no active wifi.
*/
val wifiModel: Flow<WifiModel?>
/**
* Observable for the current wifi network activity.
*/
val wifiActivity: Flow<WifiActivityModel>
}
/** Real implementation of [WifiRepository]. */
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class WifiRepositoryImpl @Inject constructor(
wifiManager: WifiManager?,
@Main mainExecutor: Executor,
logger: ConnectivityPipelineLogger,
) : WifiRepository {
// TODO(b/238425913): Actually implement the wifiModel flow.
override val wifiModel: Flow<WifiModel?> = flowOf(WifiModel(ssid = "AB"))
override val wifiActivity: Flow<WifiActivityModel> =
if (wifiManager == null) {
Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
flowOf(ACTIVITY_DEFAULT)
} else {
conflatedCallbackFlow {
val callback = TrafficStateCallback { state ->
logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
trySend(trafficStateToWifiActivityModel(state))
}
wifiManager.registerTrafficStateCallback(mainExecutor, callback)
trySend(ACTIVITY_DEFAULT)
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
}
}
companion object {
val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
private fun trafficStateToWifiActivityModel(state: Int): WifiActivityModel {
return WifiActivityModel(
hasActivityIn = state == TrafficStateCallback.DATA_ACTIVITY_IN ||
state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
hasActivityOut = state == TrafficStateCallback.DATA_ACTIVITY_OUT ||
state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
)
}
private fun prettyPrintActivity(activity: Int): String {
return when (activity) {
TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
else -> "INVALID"
}
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2022 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.domain.interactor
import android.net.wifi.WifiManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
/**
* The business logic layer for the wifi icon.
*
* This interactor processes information from our data layer into information that the UI layer can
* use.
*/
@SysUISingleton
class WifiInteractor @Inject constructor(
repository: WifiRepository,
) {
private val ssid: Flow<String?> = repository.wifiModel.map { info ->
when {
info == null -> null
info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint ->
info.passpointProviderFriendlyName
info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid
else -> null
}
}
val hasActivityIn: Flow<Boolean> = combine(repository.wifiActivity, ssid) { activity, ssid ->
activity.hasActivityIn && ssid != null
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2022 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.shared
import android.content.Context
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import java.io.PrintWriter
import javax.inject.Inject
/**
* An object storing constants that we use for calculating the wifi icon. Stored in a class for
* logging purposes.
*/
@SysUISingleton
class WifiConstants @Inject constructor(
context: Context,
dumpManager: DumpManager,
) : Dumpable {
init {
dumpManager.registerDumpable("$SB_LOGGING_TAG:WifiConstants", this)
}
/** True if we should show the activityIn/activityOut icons and false otherwise. */
val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.apply {
println("shouldShowActivityConfig=$shouldShowActivityConfig")
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2022 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.ui.viewmodel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
/**
* Models the UI state for the status bar wifi icon.
*
* TODO(b/238425913): Hook this up to the real status bar wifi view using a view binder.
*/
class WifiViewModel @Inject constructor(
private val constants: WifiConstants,
private val logger: ConnectivityPipelineLogger,
private val interactor: WifiInteractor,
) {
val isActivityInVisible: Flow<Boolean>
get() =
if (!constants.shouldShowActivityConfig) {
flowOf(false)
} else {
interactor.hasActivityIn
}
.logOutputChange(logger, "activityInVisible")
}

View File

@@ -20,7 +20,7 @@ import android.net.NetworkCapabilities
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -59,6 +59,7 @@ class ConnectivityInfoProcessorTest : SysuiTestCase() {
context,
scope,
statusBarPipelineFlags,
mock(),
)
var mostRecentValue: ProcessedConnectivityInfo? = null

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.systemui.statusbar.pipeline
package com.android.systemui.statusbar.pipeline.shared
import android.net.Network
import android.net.NetworkCapabilities

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2022 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
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
/** Fake implementation of [WifiRepository] exposing set methods for all the flows. */
class FakeWifiRepository : WifiRepository {
private val _wifiModel: MutableStateFlow<WifiModel?> = MutableStateFlow(null)
override val wifiModel: Flow<WifiModel?> = _wifiModel
private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
override val wifiActivity: Flow<WifiActivityModel> = _wifiActivity
fun setWifiModel(wifiModel: WifiModel?) {
_wifiModel.value = wifiModel
}
fun setWifiActivity(activity: WifiActivityModel) {
_wifiActivity.value = activity
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.systemui.statusbar.pipeline.repository
package com.android.systemui.statusbar.pipeline.wifi.data.repository
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
@@ -28,7 +28,7 @@ import android.net.NetworkRequest
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat

View File

@@ -0,0 +1,175 @@
/*
* Copyright (C) 2022 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
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
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.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class WifiRepositoryImplTest : SysuiTestCase() {
private lateinit var underTest: WifiRepositoryImpl
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var wifiManager: WifiManager
private lateinit var executor: Executor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
executor = FakeExecutor(FakeSystemClock())
}
@Test
fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
underTest = WifiRepositoryImpl(
wifiManager = null,
executor,
logger,
)
var latest: WifiActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isEqualTo(ACTIVITY_DEFAULT)
job.cancel()
}
@Test
fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) {
underTest = WifiRepositoryImpl(
wifiManager,
executor,
logger,
)
var latest: WifiActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
.launchIn(this)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
assertThat(latest).isEqualTo(
WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
)
job.cancel()
}
@Test
fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) {
underTest = WifiRepositoryImpl(
wifiManager,
executor,
logger,
)
var latest: WifiActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
.launchIn(this)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
assertThat(latest).isEqualTo(
WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
)
job.cancel()
}
@Test
fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) {
underTest = WifiRepositoryImpl(
wifiManager,
executor,
logger,
)
var latest: WifiActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
.launchIn(this)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
assertThat(latest).isEqualTo(
WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
)
job.cancel()
}
@Test
fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) {
underTest = WifiRepositoryImpl(
wifiManager,
executor,
logger,
)
var latest: WifiActivityModel? = null
val job = underTest
.wifiActivity
.onEach { latest = it }
.launchIn(this)
getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
assertThat(latest).isEqualTo(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
job.cancel()
}
private fun getTrafficStateCallback(): TrafficStateCallback {
val callbackCaptor = argumentCaptor<TrafficStateCallback>()
verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
return callbackCaptor.value!!
}
}
private val IMMEDIATE = Dispatchers.Main.immediate

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2022 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.domain.interactor
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class WifiInteractorTest : SysuiTestCase() {
private lateinit var underTest: WifiInteractor
private lateinit var repository: FakeWifiRepository
@Before
fun setUp() {
repository = FakeWifiRepository()
underTest = WifiInteractor(repository)
}
@Test
fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
repository.setWifiModel(WifiModel(ssid = "AB"))
repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false))
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) {
repository.setWifiModel(WifiModel(ssid = "AB"))
repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true))
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) {
repository.setWifiModel(WifiModel(ssid = "AB"))
repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) {
repository.setWifiModel(WifiModel(ssid = "AB"))
repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) {
repository.setWifiModel(WifiModel(ssid = null))
repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
repository.setWifiModel(WifiModel(ssid = "AB"))
var latest: Boolean? = null
val job = underTest
.hasActivityIn
.onEach { latest = it }
.launchIn(this)
// Conduct a series of changes and verify we catch each of them in succession
repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
yield()
assertThat(latest).isTrue()
repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true))
yield()
assertThat(latest).isFalse()
repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
yield()
assertThat(latest).isTrue()
repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
yield()
assertThat(latest).isTrue()
repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false))
yield()
assertThat(latest).isFalse()
job.cancel()
}
}
private val IMMEDIATE = Dispatchers.Main.immediate

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2022 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.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class WifiViewModelTest : SysuiTestCase() {
private lateinit var underTest: WifiViewModel
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: WifiConstants
private lateinit var repository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
repository = FakeWifiRepository()
interactor = WifiInteractor(repository)
underTest = WifiViewModel(
constants,
logger,
interactor
)
// Set up with a valid SSID
repository.setWifiModel(WifiModel(ssid = "AB"))
}
@Test
fun activityInVisible_showActivityConfigFalse_receivesFalse() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(false)
var latest: Boolean? = null
val job = underTest
.isActivityInVisible
.onEach { latest = it }
.launchIn(this)
// Verify that on launch, we receive a false.
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(false)
var latest: Boolean? = null
val job = underTest
.isActivityInVisible
.onEach { latest = it }
.launchIn(this)
// Update the repo to have activityIn
repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
yield()
// Verify that we didn't update to activityIn=true (because our config is false)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun activityInVisible_showActivityConfigTrue_receivesUpdate() = runBlocking(IMMEDIATE) {
whenever(constants.shouldShowActivityConfig).thenReturn(true)
var latest: Boolean? = null
val job = underTest
.isActivityInVisible
.onEach { latest = it }
.launchIn(this)
// Update the repo to have activityIn
repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
yield()
// Verify that we updated to activityIn=true
assertThat(latest).isTrue()
job.cancel()
}
}
private val IMMEDIATE = Dispatchers.Main.immediate