Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 41db632a authored by Evan Laird's avatar Evan Laird Committed by Android (Google) Code Review
Browse files

Merge "[Sat] Carrier-based view model + signal" into main

parents 9bbc5ba4 7b7b46bc
Loading
Loading
Loading
Loading
+50 −16
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor

import android.content.Context
import com.android.internal.telephony.flags.Flags
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.graph.SignalDrawable
import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
@@ -32,14 +33,18 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIc
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

@@ -79,6 +84,9 @@ interface MobileIconInteractor {
    /** Whether or not to show the slice attribution */
    val showSliceAttribution: StateFlow<Boolean>

    /** True if this connection is satellite-based */
    val isNonTerrestrial: StateFlow<Boolean>

    /**
     * Provider name for this network connection. The name can be one of 3 values:
     * 1. The default network name, if one is configured
@@ -244,6 +252,13 @@ class MobileIconInteractorImpl(
    override val showSliceAttribution: StateFlow<Boolean> =
        connectionRepository.hasPrioritizedNetworkCapabilities

    override val isNonTerrestrial: StateFlow<Boolean> =
        if (Flags.carrierEnabledSatelliteFlag()) {
            connectionRepository.isNonTerrestrial
        } else {
            MutableStateFlow(false).asStateFlow()
        }

    override val isRoaming: StateFlow<Boolean> =
        combine(
                connectionRepository.carrierNetworkChangeActive,
@@ -313,27 +328,46 @@ class MobileIconInteractorImpl(
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), 0)

    override val signalLevelIcon: StateFlow<SignalIconModel> = run {
        val initial =
            SignalIconModel(
                level = shownLevel.value,
                numberOfLevels = numberOfLevels.value,
                showExclamationMark = showExclamationMark.value,
                carrierNetworkChange = carrierNetworkChangeActive.value,
            )
    private val cellularIcon: Flow<SignalIconModel.Cellular> =
        combine(
            shownLevel,
            numberOfLevels,
            showExclamationMark,
            carrierNetworkChangeActive,
        ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
                SignalIconModel(
            SignalIconModel.Cellular(
                shownLevel,
                numberOfLevels,
                showExclamationMark,
                carrierNetworkChange,
            )
        }

    private val satelliteIcon: Flow<SignalIconModel.Satellite> =
        shownLevel.map {
            SignalIconModel.Satellite(
                level = it,
                icon = SatelliteIconModel.fromSignalStrength(it)
                        ?: SatelliteIconModel.fromSignalStrength(0)!!
            )
        }

    override val signalLevelIcon: StateFlow<SignalIconModel> = run {
        val initial =
            SignalIconModel.Cellular(
                shownLevel.value,
                numberOfLevels.value,
                showExclamationMark.value,
                carrierNetworkChangeActive.value,
            )
        isNonTerrestrial
            .flatMapLatest { ntn ->
                if (ntn) {
                    satelliteIcon
                } else {
                    cellularIcon
                }
            }
            .distinctUntilChanged()
            .logDiffsForTable(
                tableLogBuffer,
+70 −27
Original line number Diff line number Diff line
@@ -17,18 +17,34 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.model

import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger

sealed interface SignalIconModel : Diffable<SignalIconModel> {
    val level: Int

    override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) {
        logPartial(prevVal, row)
    }

    override fun logFull(row: TableRowLogger) = logFully(row)

    fun logFully(row: TableRowLogger)

    fun logPartial(prevVal: SignalIconModel, row: TableRowLogger)

    /** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */
data class SignalIconModel(
    val level: Int,
    data class Cellular(
        override val level: Int,
        val numberOfLevels: Int,
        val showExclamationMark: Boolean,
        val carrierNetworkChange: Boolean,
) : Diffable<SignalIconModel> {
    // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
    override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) {
    ) : SignalIconModel {
        override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) {
            if (prevVal !is Cellular) {
                logFull(row)
            } else {
                if (prevVal.level != level) {
                    row.logChange(COL_LEVEL, level)
                }
@@ -42,8 +58,10 @@ data class SignalIconModel(
                    row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
                }
            }
        }

    override fun logFull(row: TableRowLogger) {
        override fun logFully(row: TableRowLogger) {
            row.logChange(COL_TYPE, "c")
            row.logChange(COL_LEVEL, level)
            row.logChange(COL_NUM_LEVELS, numberOfLevels)
            row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
@@ -57,11 +75,36 @@ data class SignalIconModel(
            } else {
                SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
            }
    }

    /**
     * For non-terrestrial networks, we can use a resource-backed icon instead of the
     * [SignalDrawable]-backed version above
     */
    data class Satellite(
        override val level: Int,
        val icon: Icon.Resource,
    ) : SignalIconModel {
        override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) {
            if (prevVal !is Satellite) {
                logFull(row)
            } else {
                if (prevVal.level != level) row.logChange(COL_LEVEL, level)
            }
        }

        override fun logFully(row: TableRowLogger) {
            row.logChange("numLevels", "HELLO")
            row.logChange(COL_TYPE, "s")
            row.logChange(COL_LEVEL, level)
        }
    }

    companion object {
        private const val COL_LEVEL = "level"
        private const val COL_NUM_LEVELS = "numLevels"
        private const val COL_SHOW_EXCLAMATION = "showExclamation"
        private const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChange"
        private const val COL_TYPE = "type"
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -59,7 +59,7 @@ constructor(
                str1 = parentView.getIdForLogging()
                int1 = subId
                int2 = icon.level
                bool1 = icon.showExclamationMark
                bool1 = if (icon is SignalIconModel.Cellular) icon.showExclamationMark else false
            },
            {
                "Binder[subId=$int1, viewId=$str1] received new signal icon: " +
+8 −2
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
@@ -70,7 +71,7 @@ object MobileIconBinder {
        val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
        val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container)
        val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
        val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
        val mobileDrawable = SignalDrawable(view.context)
        val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
        val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
        val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
@@ -138,7 +139,12 @@ object MobileIconBinder {
                                viewModel.subscriptionId,
                                icon,
                            )
                            if (icon is SignalIconModel.Cellular) {
                                iconView.setImageDrawable(mobileDrawable)
                                mobileDrawable.level = icon.toSignalDrawableState()
                            } else if (icon is SignalIconModel.Satellite) {
                                IconViewBinder.bind(icon.icon, iconView)
                            }
                        }
                    }

+97 −1
Original line number Diff line number Diff line
@@ -33,12 +33,15 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn

/** Common interface for all of the location-based mobile icon view models. */
@@ -71,7 +74,6 @@ interface MobileIconViewModelCommon {
 * model gets the exact same information, as well as allows us to log that unified state only once
 * per icon.
 */
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileIconViewModel(
    override val subscriptionId: Int,
@@ -80,6 +82,100 @@ class MobileIconViewModel(
    constants: ConnectivityConstants,
    flags: FeatureFlagsClassic,
    scope: CoroutineScope,
) : MobileIconViewModelCommon {
    private val cellProvider by lazy {
        CellularIconViewModel(
            subscriptionId,
            iconInteractor,
            airplaneModeInteractor,
            constants,
            flags,
            scope,
        )
    }

    private val satelliteProvider by lazy {
        CarrierBasedSatelliteViewModelImpl(
            subscriptionId,
            iconInteractor,
        )
    }

    /**
     * Similar to repository switching, this allows us to split up the logic of satellite/cellular
     * states, since they are different by nature
     */
    private val vmProvider: Flow<MobileIconViewModelCommon> =
        iconInteractor.isNonTerrestrial
            .mapLatest { nonTerrestrial ->
                if (nonTerrestrial) {
                    satelliteProvider
                } else {
                    cellProvider
                }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), cellProvider)

    override val isVisible: StateFlow<Boolean> =
        vmProvider
            .flatMapLatest { it.isVisible }
            .stateIn(scope, SharingStarted.WhileSubscribed(), false)

    override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon }

    override val contentDescription: Flow<ContentDescription> =
        vmProvider.flatMapLatest { it.contentDescription }

    override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming }

    override val networkTypeIcon: Flow<Icon.Resource?> =
        vmProvider.flatMapLatest { it.networkTypeIcon }

    override val networkTypeBackground: StateFlow<Icon.Resource?> =
        vmProvider
            .flatMapLatest { it.networkTypeBackground }
            .stateIn(scope, SharingStarted.WhileSubscribed(), null)

    override val activityInVisible: Flow<Boolean> =
        vmProvider.flatMapLatest { it.activityInVisible }

    override val activityOutVisible: Flow<Boolean> =
        vmProvider.flatMapLatest { it.activityOutVisible }

    override val activityContainerVisible: Flow<Boolean> =
        vmProvider.flatMapLatest { it.activityContainerVisible }
}

/** Representation of this network when it is non-terrestrial (e.g., satellite) */
private class CarrierBasedSatelliteViewModelImpl(
    override val subscriptionId: Int,
    interactor: MobileIconInteractor,
) : MobileIconViewModelCommon {
    override val isVisible: StateFlow<Boolean> = MutableStateFlow(true)
    override val icon: Flow<SignalIconModel> = interactor.signalLevelIcon

    override val contentDescription: Flow<ContentDescription> =
        MutableStateFlow(ContentDescription.Loaded(""))

    /** These fields are not used for satellite icons currently */
    override val roaming: Flow<Boolean> = flowOf(false)
    override val networkTypeIcon: Flow<Icon.Resource?> = flowOf(null)
    override val networkTypeBackground: StateFlow<Icon.Resource?> = MutableStateFlow(null)
    override val activityInVisible: Flow<Boolean> = flowOf(false)
    override val activityOutVisible: Flow<Boolean> = flowOf(false)
    override val activityContainerVisible: Flow<Boolean> = flowOf(false)
}

/** Terrestrial (cellular) icon. */
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
private class CellularIconViewModel(
    override val subscriptionId: Int,
    iconInteractor: MobileIconInteractor,
    airplaneModeInteractor: AirplaneModeInteractor,
    constants: ConnectivityConstants,
    flags: FeatureFlagsClassic,
    scope: CoroutineScope,
) : MobileIconViewModelCommon {
    override val isVisible: StateFlow<Boolean> =
        if (!constants.hasDataCapabilities) {
Loading