Merge changes Ie0da0143,I9541a914,I21f3259e,I40a6d5aa,I9c6b9ba3 into tm-qpr-dev am: 933c12efa1

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

Change-Id: I0b0476828bdfd8b4a5d2d478dfba0f8f826e0123
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
TreeHugger Robot
2022-12-28 16:57:26 +00:00
committed by Automerger Merge Worker
16 changed files with 455 additions and 302 deletions

View File

@@ -460,7 +460,7 @@
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -752,7 +752,7 @@
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/ShadeExpansionStateManagerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt

View File

@@ -51,6 +51,16 @@ data class MobileConnectionModel(
*/
val operatorAlphaShort: String? = null,
/**
* TODO (b/263167683): Clarify this field
*
* This check comes from [com.android.settingslib.Utils.isInService]. It is intended to be a
* mapping from a ServiceState to a notion of connectivity. Notably, it will consider a
* connection to be in-service if either the voice registration state is IN_SERVICE or the data
* registration state is IN_SERVICE and NOT IWLAN.
*/
val isInService: Boolean = false,
/** Fields below from [SignalStrengthsListener.onSignalStrengthsChanged] */
val isGsm: Boolean = false,
@IntRange(from = 0, to = 4)
@@ -99,6 +109,10 @@ data class MobileConnectionModel(
row.logChange(COL_OPERATOR, operatorAlphaShort)
}
if (prevVal.isInService != isInService) {
row.logChange(COL_IS_IN_SERVICE, isInService)
}
if (prevVal.isGsm != isGsm) {
row.logChange(COL_IS_GSM, isGsm)
}
@@ -129,6 +143,7 @@ data class MobileConnectionModel(
row.logChange(COL_EMERGENCY, isEmergencyOnly)
row.logChange(COL_ROAMING, isRoaming)
row.logChange(COL_OPERATOR, operatorAlphaShort)
row.logChange(COL_IS_IN_SERVICE, isInService)
row.logChange(COL_IS_GSM, isGsm)
row.logChange(COL_CDMA_LEVEL, cdmaLevel)
row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
@@ -141,6 +156,7 @@ data class MobileConnectionModel(
const val COL_EMERGENCY = "EmergencyOnly"
const val COL_ROAMING = "Roaming"
const val COL_OPERATOR = "OperatorName"
const val COL_IS_IN_SERVICE = "IsInService"
const val COL_IS_GSM = "IsGsm"
const val COL_CDMA_LEVEL = "CdmaLevel"
const val COL_PRIMARY_LEVEL = "PrimaryLevel"

View File

@@ -247,6 +247,7 @@ constructor(
return MobileConnectionModel(
isEmergencyOnly = false, // TODO(b/261029387): not yet supported
isRoaming = roaming,
isInService = (level ?: 0) > 0,
isGsm = false, // TODO(b/261029387): not yet supported
cdmaLevel = level ?: 0,
primaryLevel = level ?: 0,

View File

@@ -32,6 +32,7 @@ import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.ERI_OFF
import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
@@ -117,6 +118,7 @@ class MobileConnectionRepositoryImpl(
isEmergencyOnly = serviceState.isEmergencyOnly,
isRoaming = serviceState.roaming,
operatorAlphaShort = serviceState.operatorAlphaShort,
isInService = Utils.isInService(serviceState),
)
trySend(state)
}

View File

@@ -48,6 +48,9 @@ interface MobileIconInteractor {
/** True when telephony tells us that the data state is CONNECTED */
val isDataConnected: StateFlow<Boolean>
/** True if we consider this connection to be in service, i.e. can make calls */
val isInService: StateFlow<Boolean>
// TODO(b/256839546): clarify naming of default vs active
/** True if we want to consider the data connection enabled */
val isDefaultDataEnabled: StateFlow<Boolean>
@@ -175,4 +178,9 @@ class MobileIconInteractorImpl(
connectionInfo
.mapLatest { connection -> connection.dataConnectionState == Connected }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isInService =
connectionRepository.connectionInfo
.mapLatest { it.isInService }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
}

View File

@@ -80,11 +80,17 @@ constructor(
override val iconId: Flow<Int> = run {
val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value)
combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
level,
numberOfLevels,
showExclamationMark ->
SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
combine(
iconInteractor.level,
iconInteractor.numberOfLevels,
showExclamationMark,
iconInteractor.isInService,
) { level, numberOfLevels, showExclamationMark, isInService ->
if (!isInService) {
SignalDrawable.getEmptyState(numberOfLevels)
} else {
SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
}
}
.distinctUntilChanged()
.logDiffsForTable(

View File

@@ -16,51 +16,9 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
import android.annotation.SuppressLint
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
import android.util.Log
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
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.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
/** Provides data related to the wifi state. */
interface WifiRepository {
@@ -76,250 +34,3 @@ interface WifiRepository {
/** Observable for the current wifi network activity. */
val wifiActivity: StateFlow<DataActivityModel>
}
/** Real implementation of [WifiRepository]. */
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@SuppressLint("MissingPermission")
class WifiRepositoryImpl @Inject constructor(
broadcastDispatcher: BroadcastDispatcher,
connectivityManager: ConnectivityManager,
logger: ConnectivityPipelineLogger,
@WifiTableLog wifiTableLogBuffer: TableLogBuffer,
@Main mainExecutor: Executor,
@Application scope: CoroutineScope,
wifiManager: WifiManager?,
) : WifiRepository {
private val wifiStateChangeEvents: Flow<Unit> = broadcastDispatcher.broadcastFlow(
IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)
)
.logInputChange(logger, "WIFI_STATE_CHANGED_ACTION intent")
private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
MutableSharedFlow(extraBufferCapacity = 1)
override val isWifiEnabled: StateFlow<Boolean> =
if (wifiManager == null) {
MutableStateFlow(false).asStateFlow()
} else {
// Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
// internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
// have changed.
merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
.mapLatest { wifiManager.isWifiEnabled }
.distinctUntilChanged()
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
columnName = "isWifiEnabled",
initialValue = wifiManager.isWifiEnabled,
)
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
initialValue = wifiManager.isWifiEnabled
)
}
override val isWifiDefault: StateFlow<Boolean> = conflatedCallbackFlow {
// Note: This callback doesn't do any logging because we already log every network change
// in the [wifiNetwork] callback.
val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
// This method will always be called immediately after the network becomes the
// default, in addition to any time the capabilities change while the network is
// the default.
// If this network contains valid wifi info, then wifi is the default network.
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
trySend(wifiInfo != null)
}
override fun onLost(network: Network) {
// The system no longer has a default network, so wifi is definitely not default.
trySend(false)
}
}
connectivityManager.registerDefaultNetworkCallback(callback)
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
.distinctUntilChanged()
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
columnName = "isWifiDefault",
initialValue = false,
)
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
initialValue = false
)
override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
logger.logOnCapabilitiesChanged(network, networkCapabilities)
wifiNetworkChangeEvents.tryEmit(Unit)
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
if (wifiInfo?.isPrimary == true) {
val wifiNetworkModel = createWifiNetworkModel(
wifiInfo,
network,
networkCapabilities,
wifiManager,
)
logger.logTransformation(
WIFI_NETWORK_CALLBACK_NAME,
oldValue = currentWifi,
newValue = wifiNetworkModel
)
currentWifi = wifiNetworkModel
trySend(wifiNetworkModel)
}
}
override fun onLost(network: Network) {
logger.logOnLost(network)
wifiNetworkChangeEvents.tryEmit(Unit)
val wifi = currentWifi
if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
val newNetworkModel = WifiNetworkModel.Inactive
logger.logTransformation(
WIFI_NETWORK_CALLBACK_NAME,
oldValue = wifi,
newValue = newNetworkModel
)
currentWifi = newNetworkModel
trySend(newNetworkModel)
}
}
}
connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
.distinctUntilChanged()
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "wifiNetwork",
initialValue = WIFI_NETWORK_DEFAULT,
)
// There will be multiple wifi icons in different places that will frequently
// subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
// new subscribes will get the latest value immediately upon subscription. Otherwise, the
// views could show stale data. See b/244173280.
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
initialValue = WIFI_NETWORK_DEFAULT
)
override val wifiActivity: StateFlow<DataActivityModel> =
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(state.toWifiDataActivityModel())
}
wifiManager.registerTrafficStateCallback(mainExecutor, callback)
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
}
}
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = ACTIVITY_PREFIX,
initialValue = ACTIVITY_DEFAULT,
)
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
initialValue = ACTIVITY_DEFAULT
)
companion object {
private const val ACTIVITY_PREFIX = "wifiActivity"
val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
// Start out with no known wifi network.
// Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
// initial fetch to get a starting wifi network. But, it uses a deprecated API
// [WifiManager.getConnectionInfo()], and the deprecation doc indicates to just use
// [ConnectivityManager.NetworkCallback] results instead. So, for now we'll just rely on the
// NetworkCallback inside [wifiNetwork] for our wifi network information.
val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
private fun networkCapabilitiesToWifiInfo(
networkCapabilities: NetworkCapabilities
): WifiInfo? {
return when {
networkCapabilities.hasTransport(TRANSPORT_WIFI) ->
networkCapabilities.transportInfo as WifiInfo?
networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ->
// Sometimes, cellular networks can act as wifi networks (known as VCN --
// virtual carrier network). So, see if this cellular network has wifi info.
Utils.tryGetWifiInfoForVcn(networkCapabilities)
else -> null
}
}
private fun createWifiNetworkModel(
wifiInfo: WifiInfo,
network: Network,
networkCapabilities: NetworkCapabilities,
wifiManager: WifiManager?,
): WifiNetworkModel {
return if (wifiInfo.isCarrierMerged) {
WifiNetworkModel.CarrierMerged
} else {
WifiNetworkModel.Active(
network.getNetId(),
isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
level = wifiManager?.calculateSignalLevel(wifiInfo.rssi),
wifiInfo.ssid,
wifiInfo.isPasspointAp,
wifiInfo.isOsuAp,
wifiInfo.passpointProviderFriendlyName
)
}
}
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"
}
}
private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest =
NetworkRequest.Builder()
.clearCapabilities()
.addCapability(NET_CAPABILITY_NOT_VPN)
.addTransportType(TRANSPORT_WIFI)
.addTransportType(TRANSPORT_CELLULAR)
.build()
private const val WIFI_NETWORK_CALLBACK_NAME = "wifiNetworkModel"
}
}

View File

@@ -25,6 +25,7 @@ import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi

View File

@@ -0,0 +1,311 @@
/*
* 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.prod
import android.annotation.SuppressLint
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
import android.util.Log
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
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.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
/** Real implementation of [WifiRepository]. */
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@SuppressLint("MissingPermission")
class WifiRepositoryImpl @Inject constructor(
broadcastDispatcher: BroadcastDispatcher,
connectivityManager: ConnectivityManager,
logger: ConnectivityPipelineLogger,
@WifiTableLog wifiTableLogBuffer: TableLogBuffer,
@Main mainExecutor: Executor,
@Application scope: CoroutineScope,
wifiManager: WifiManager?,
) : WifiRepository {
private val wifiStateChangeEvents: Flow<Unit> = broadcastDispatcher.broadcastFlow(
IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)
)
.logInputChange(logger, "WIFI_STATE_CHANGED_ACTION intent")
private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
MutableSharedFlow(extraBufferCapacity = 1)
override val isWifiEnabled: StateFlow<Boolean> =
if (wifiManager == null) {
MutableStateFlow(false).asStateFlow()
} else {
// Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
// internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
// have changed.
merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
.mapLatest { wifiManager.isWifiEnabled }
.distinctUntilChanged()
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
columnName = "isWifiEnabled",
initialValue = wifiManager.isWifiEnabled,
)
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
initialValue = wifiManager.isWifiEnabled
)
}
override val isWifiDefault: StateFlow<Boolean> = conflatedCallbackFlow {
// Note: This callback doesn't do any logging because we already log every network change
// in the [wifiNetwork] callback.
val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
// This method will always be called immediately after the network becomes the
// default, in addition to any time the capabilities change while the network is
// the default.
// If this network contains valid wifi info, then wifi is the default network.
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
trySend(wifiInfo != null)
}
override fun onLost(network: Network) {
// The system no longer has a default network, so wifi is definitely not default.
trySend(false)
}
}
connectivityManager.registerDefaultNetworkCallback(callback)
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
.distinctUntilChanged()
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
columnName = "isWifiDefault",
initialValue = false,
)
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
initialValue = false
)
override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
logger.logOnCapabilitiesChanged(network, networkCapabilities)
wifiNetworkChangeEvents.tryEmit(Unit)
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
if (wifiInfo?.isPrimary == true) {
val wifiNetworkModel = createWifiNetworkModel(
wifiInfo,
network,
networkCapabilities,
wifiManager,
)
logger.logTransformation(
WIFI_NETWORK_CALLBACK_NAME,
oldValue = currentWifi,
newValue = wifiNetworkModel
)
currentWifi = wifiNetworkModel
trySend(wifiNetworkModel)
}
}
override fun onLost(network: Network) {
logger.logOnLost(network)
wifiNetworkChangeEvents.tryEmit(Unit)
val wifi = currentWifi
if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
val newNetworkModel = WifiNetworkModel.Inactive
logger.logTransformation(
WIFI_NETWORK_CALLBACK_NAME,
oldValue = wifi,
newValue = newNetworkModel
)
currentWifi = newNetworkModel
trySend(newNetworkModel)
}
}
}
connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
.distinctUntilChanged()
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "wifiNetwork",
initialValue = WIFI_NETWORK_DEFAULT,
)
// There will be multiple wifi icons in different places that will frequently
// subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
// new subscribes will get the latest value immediately upon subscription. Otherwise, the
// views could show stale data. See b/244173280.
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
initialValue = WIFI_NETWORK_DEFAULT
)
override val wifiActivity: StateFlow<DataActivityModel> =
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(state.toWifiDataActivityModel())
}
wifiManager.registerTrafficStateCallback(mainExecutor, callback)
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
}
}
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = ACTIVITY_PREFIX,
initialValue = ACTIVITY_DEFAULT,
)
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
initialValue = ACTIVITY_DEFAULT
)
companion object {
private const val ACTIVITY_PREFIX = "wifiActivity"
val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
// Start out with no known wifi network.
// Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
// initial fetch to get a starting wifi network. But, it uses a deprecated API
// [WifiManager.getConnectionInfo()], and the deprecation doc indicates to just use
// [ConnectivityManager.NetworkCallback] results instead. So, for now we'll just rely on the
// NetworkCallback inside [wifiNetwork] for our wifi network information.
val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
private fun networkCapabilitiesToWifiInfo(
networkCapabilities: NetworkCapabilities
): WifiInfo? {
return when {
networkCapabilities.hasTransport(TRANSPORT_WIFI) ->
networkCapabilities.transportInfo as WifiInfo?
networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ->
// Sometimes, cellular networks can act as wifi networks (known as VCN --
// virtual carrier network). So, see if this cellular network has wifi info.
Utils.tryGetWifiInfoForVcn(networkCapabilities)
else -> null
}
}
private fun createWifiNetworkModel(
wifiInfo: WifiInfo,
network: Network,
networkCapabilities: NetworkCapabilities,
wifiManager: WifiManager?,
): WifiNetworkModel {
return if (wifiInfo.isCarrierMerged) {
WifiNetworkModel.CarrierMerged
} else {
WifiNetworkModel.Active(
network.getNetId(),
isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
level = wifiManager?.calculateSignalLevel(wifiInfo.rssi),
wifiInfo.ssid,
wifiInfo.isPasspointAp,
wifiInfo.isOsuAp,
wifiInfo.passpointProviderFriendlyName
)
}
}
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"
}
}
private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest =
NetworkRequest.Builder()
.clearCapabilities()
.addCapability(NET_CAPABILITY_NOT_VPN)
.addTransportType(TRANSPORT_WIFI)
.addTransportType(TRANSPORT_CELLULAR)
.build()
private const val WIFI_NETWORK_CALLBACK_NAME = "wifiNetworkModel"
}
}

View File

@@ -20,7 +20,10 @@ import android.content.Intent
import android.os.UserHandle
import android.provider.Settings
import android.telephony.CellSignalStrengthCdma
import android.telephony.NetworkRegistrationInfo
import android.telephony.ServiceState
import android.telephony.ServiceState.STATE_IN_SERVICE
import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
import android.telephony.TelephonyCallback
@@ -47,7 +50,6 @@ import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
import android.telephony.TelephonyManager.EXTRA_SPN
import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
@@ -302,7 +304,6 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
var latest: MobileConnectionModel? = null
val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
val type = NETWORK_TYPE_UNKNOWN
val expected = UnknownNetworkType
assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
@@ -590,6 +591,56 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
job.cancel()
}
@Test
fun `connection model - isInService - not iwlan`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.connectionInfo.onEach { latest = it.isInService }.launchIn(this)
val serviceState = ServiceState()
serviceState.voiceRegState = STATE_IN_SERVICE
serviceState.dataRegState = STATE_IN_SERVICE
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
assertThat(latest).isTrue()
serviceState.voiceRegState = STATE_OUT_OF_SERVICE
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
assertThat(latest).isTrue()
serviceState.dataRegState = STATE_OUT_OF_SERVICE
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun `connection model - isInService - is iwlan - voice out of service - data in service`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.connectionInfo.onEach { latest = it.isInService }.launchIn(this)
// Mock the service state here so we can make it specifically IWLAN
val serviceState: ServiceState = mock()
whenever(serviceState.state).thenReturn(STATE_OUT_OF_SERVICE)
whenever(serviceState.dataRegistrationState).thenReturn(STATE_IN_SERVICE)
// See [com.android.settingslib.Utils.isInService] for more info. This is one way to
// make the network look like IWLAN
val networkRegWlan: NetworkRegistrationInfo = mock()
whenever(serviceState.getNetworkRegistrationInfo(any(), any()))
.thenReturn(networkRegWlan)
whenever(networkRegWlan.registrationState)
.thenReturn(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
assertThat(latest).isFalse()
job.cancel()
}
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>()
Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())

View File

@@ -52,6 +52,8 @@ class FakeMobileIconInteractor(
override val isDataConnected = MutableStateFlow(true)
override val isInService = MutableStateFlow(true)
private val _isDataEnabled = MutableStateFlow(true)
override val isDataEnabled = _isDataEnabled

View File

@@ -299,6 +299,23 @@ class MobileIconInteractorTest : SysuiTestCase() {
job.cancel()
}
@Test
fun `isInService - uses repository value`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.isInService.onEach { latest = it }.launchIn(this)
connectionRepository.setConnectionInfo(MobileConnectionModel(isInService = true))
assertThat(latest).isTrue()
connectionRepository.setConnectionInfo(MobileConnectionModel(isInService = false))
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun `roaming - is gsm - uses connection model`() =
runBlocking(IMMEDIATE) {

View File

@@ -97,6 +97,30 @@ class MobileIconViewModelTest : SysuiTestCase() {
job.cancel()
}
@Test
fun `icon - uses empty state - when not in service`() =
testScope.runTest {
var latest: Int? = null
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
interactor.isInService.value = false
var expected = emptySignal()
assertThat(latest).isEqualTo(expected)
// Changing the level doesn't overwrite the disabled state
interactor.level.value = 2
assertThat(latest).isEqualTo(expected)
// Once back in service, the regular icon appears
interactor.isInService.value = true
expected = defaultSignal(level = 2)
assertThat(latest).isEqualTo(expected)
job.cancel()
}
@Test
fun networkType_dataEnabled_groupIsRepresented() =
testScope.runTest {
@@ -375,5 +399,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
): Int {
return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
}
fun emptySignal(): Int = SignalDrawable.getEmptyState(4)
}
}

View File

@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

View File

@@ -27,6 +27,7 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.systemui.statusbar.pipeline.wifi.data.repository
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
import android.net.ConnectivityManager
import android.net.Network
@@ -33,8 +33,8 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor