blob: ec7ba653cac7848561125a4334ea1642ff39a0cc [file] [log] [blame]
/*
* 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 android.content.Context
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
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.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/**
* Models the UI state for the status bar wifi icon.
*
* This class exposes three view models, one per status bar location:
* - [home]
* - [keyguard]
* - [qs]
* In order to get the UI state for the wifi icon, you must use one of those view models (whichever
* is correct for your location).
*
* Internally, this class maintains the current state of the wifi icon and notifies those three
* view models of any changes.
*/
@SysUISingleton
class WifiViewModel
@Inject
constructor(
airplaneModeViewModel: AirplaneModeViewModel,
connectivityConstants: ConnectivityConstants,
private val context: Context,
logger: ConnectivityPipelineLogger,
@WifiTableLog wifiTableLogBuffer: TableLogBuffer,
interactor: WifiInteractor,
@Application private val scope: CoroutineScope,
statusBarPipelineFlags: StatusBarPipelineFlags,
wifiConstants: WifiConstants,
) {
/** Returns the icon to use based on the given network. */
private fun WifiNetworkModel.icon(): WifiIcon {
return when (this) {
is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
is WifiNetworkModel.Inactive -> WifiIcon.Visible(
res = WIFI_NO_NETWORK,
ContentDescription.Loaded(
"${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
)
)
is WifiNetworkModel.Active ->
when (this.level) {
null -> WifiIcon.Hidden
else -> {
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
when {
this.isValidated ->
WifiIcon.Visible(
WIFI_FULL_ICONS[this.level],
ContentDescription.Loaded(levelDesc)
)
else ->
WifiIcon.Visible(
WIFI_NO_INTERNET_ICONS[this.level],
ContentDescription.Loaded(
"$levelDesc,${context.getString(NO_INTERNET)}"
)
)
}
}
}
}
}
/** The wifi icon that should be displayed. */
private val wifiIcon: StateFlow<WifiIcon> =
combine(
interactor.isEnabled,
interactor.isDefault,
interactor.isForceHidden,
interactor.wifiNetwork,
) { isEnabled, isDefault, isForceHidden, wifiNetwork ->
if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
return@combine WifiIcon.Hidden
}
val icon = wifiNetwork.icon()
return@combine when {
isDefault -> icon
wifiConstants.alwaysShowIconIfEnabled -> icon
!connectivityConstants.hasDataCapabilities -> icon
wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
else -> WifiIcon.Hidden
}
}
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
initialValue = WifiIcon.Hidden,
)
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
initialValue = WifiIcon.Hidden
)
/** The wifi activity status. Null if we shouldn't display the activity status. */
private val activity: Flow<WifiActivityModel?> =
if (!wifiConstants.shouldShowActivityConfig) {
flowOf(null)
} else {
combine(interactor.activity, interactor.ssid) { activity, ssid ->
when (ssid) {
null -> null
else -> activity
}
}
}
.distinctUntilChanged()
.logOutputChange(logger, "activity")
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
private val isActivityInViewVisible: Flow<Boolean> =
activity
.map { it?.hasActivityIn == true }
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
private val isActivityOutViewVisible: Flow<Boolean> =
activity
.map { it?.hasActivityOut == true }
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
private val isActivityContainerVisible: Flow<Boolean> =
combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut ->
activityIn || activityOut
}
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
// TODO(b/238425913): It isn't ideal for the wifi icon to need to know about whether the
// airplane icon is visible. Instead, we should have a parent StatusBarSystemIconsViewModel
// that appropriately knows about both icons and sets the padding appropriately.
private val isAirplaneSpacerVisible: Flow<Boolean> =
airplaneModeViewModel.isAirplaneModeIconVisible
/** A view model for the status bar on the home screen. */
val home: HomeWifiViewModel =
HomeWifiViewModel(
statusBarPipelineFlags,
wifiIcon,
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
isAirplaneSpacerVisible,
)
/** A view model for the status bar on keyguard. */
val keyguard: KeyguardWifiViewModel =
KeyguardWifiViewModel(
statusBarPipelineFlags,
wifiIcon,
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
isAirplaneSpacerVisible,
)
/** A view model for the status bar in quick settings. */
val qs: QsWifiViewModel =
QsWifiViewModel(
statusBarPipelineFlags,
wifiIcon,
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
isAirplaneSpacerVisible,
)
companion object {
@StringRes
@VisibleForTesting
internal val NO_INTERNET = R.string.data_connection_no_internet
}
}