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

Commit 4c52a3c3 authored by Evan Laird's avatar Evan Laird
Browse files

[Status bar refactor] Implement the lookup from network type to RAT icon

This CL adds the ability to map from network type to icon used to
display the RAT indicator (LTE, 3G, etc.). This process starts by
consuming TelephonyDisplayInfo and using the SettingsLib
MobileMappings.java utilities to map all of the telephony Network Types
to TelephonyIcons.java.

1. Add an injectable MobileMappings proxy
2. Add support for generating the MobileMappings.Config class under the
   same conditions that NetworkController does.
3. Thread all of the mobile mappings to the MobileIconGroup in
   MobileIconViewModel
4. A bunch of tests

Test: atest MobileIconInteractorTest
Bug: 240492102
Change-Id: I38e917cfa1c98bd86258061defa89cc8705816e0
parent 731095e5
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -22,6 +22,10 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubs
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
@@ -47,4 +51,10 @@ abstract class StatusBarPipelineModule {

    @Binds
    abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository

    @Binds
    abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy

    @Binds
    abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
}
+8 −2
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN

/**
 * Data class containing all of the relevant information for a particular line of service, known as
@@ -57,6 +58,11 @@ data class MobileSubscriptionModel(
    /** From [CarrierNetworkListener.onCarrierNetworkChange] */
    val carrierNetworkChangeActive: Boolean? = null,

    /** From [DisplayInfoListener.onDisplayInfoChanged] */
    val displayInfo: TelephonyDisplayInfo? = null
    /**
     * From [DisplayInfoListener.onDisplayInfoChanged].
     *
     * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
     * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
     */
    val resolvedNetworkType: ResolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
)
+33 −0
Original line number Diff line number Diff line
/*
 * 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.mobile.data.model

import android.telephony.Annotation.NetworkType
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy

/**
 * A SysUI type to represent the [NetworkType] that we pull out of [TelephonyDisplayInfo]. Depending
 * on whether or not the display info contains an override type, we may have to call different
 * methods on [MobileMappingsProxy] to generate an icon lookup key.
 */
sealed interface ResolvedNetworkType {
    @NetworkType val type: Int
}

data class DefaultNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType

data class OverrideNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
+67 −1
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.systemui.statusbar.pipeline.mobile.data.repository

import android.content.Context
import android.content.IntentFilter
import android.telephony.CarrierConfigManager
import android.telephony.CellSignalStrength
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
@@ -31,13 +34,21 @@ import android.telephony.TelephonyCallback.DisplayInfoListener
import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
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.Background
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -47,7 +58,9 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext

@@ -62,6 +75,9 @@ interface MobileSubscriptionRepository {
    /** Observable for the subscriptionId of the current mobile data connection */
    val activeMobileDataSubscriptionId: Flow<Int>

    /** Observable for [MobileMappings.Config] tracking the defaults */
    val defaultDataSubRatConfig: StateFlow<Config>

    /** Get or create an observable for the given subscription ID */
    fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
}
@@ -74,6 +90,10 @@ class MobileSubscriptionRepositoryImpl
constructor(
    private val subscriptionManager: SubscriptionManager,
    private val telephonyManager: TelephonyManager,
    private val logger: ConnectivityPipelineLogger,
    broadcastDispatcher: BroadcastDispatcher,
    private val context: Context,
    private val mobileMappings: MobileMappingsProxy,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Application private val scope: CoroutineScope,
) : MobileSubscriptionRepository {
@@ -122,6 +142,36 @@ constructor(
                SubscriptionManager.INVALID_SUBSCRIPTION_ID
            )

    private val defaultDataSubChangedEvent =
        broadcastDispatcher.broadcastFlow(
            IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
        )

    private val carrierConfigChangedEvent =
        broadcastDispatcher.broadcastFlow(
            IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
        )

    /**
     * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
     * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
     * config, so this will apply to every icon that we care about.
     *
     * Relevant bits in the config are things like
     * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
     *
     * This flow will produce whenever the default data subscription or the carrier config changes.
     */
    override val defaultDataSubRatConfig: StateFlow<Config> =
        combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ ->
                Config.readConfig(context)
            }
            .stateIn(
                scope,
                SharingStarted.WhileSubscribed(),
                initialValue = Config.readConfig(context)
            )

    /**
     * Each mobile subscription needs its own flow, which comes from registering listeners on the
     * system. Use this method to create those flows and cache them for reuse
@@ -151,6 +201,7 @@ constructor(
                            state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
                            trySend(state)
                        }

                        override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
                            val cdmaLevel =
                                signalStrength
@@ -173,6 +224,7 @@ constructor(
                                )
                            trySend(state)
                        }

                        override fun onDataConnectionStateChanged(
                            dataState: Int,
                            networkType: Int
@@ -180,18 +232,31 @@ constructor(
                            state = state.copy(dataConnectionState = dataState)
                            trySend(state)
                        }

                        override fun onDataActivity(direction: Int) {
                            state = state.copy(dataActivityDirection = direction)
                            trySend(state)
                        }

                        override fun onCarrierNetworkChange(active: Boolean) {
                            state = state.copy(carrierNetworkChangeActive = active)
                            trySend(state)
                        }

                        override fun onDisplayInfoChanged(
                            telephonyDisplayInfo: TelephonyDisplayInfo
                        ) {
                            state = state.copy(displayInfo = telephonyDisplayInfo)
                            val networkType =
                                if (
                                    telephonyDisplayInfo.overrideNetworkType ==
                                        OVERRIDE_NETWORK_TYPE_NONE
                                ) {
                                    DefaultNetworkType(telephonyDisplayInfo.networkType)
                                } else {
                                    OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
                                }

                            state = state.copy(resolvedNetworkType = networkType)
                            trySend(state)
                        }
                    }
@@ -202,6 +267,7 @@ constructor(
                    subIdFlowCache.remove(subId)
                }
            }
            .onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) }
            .stateIn(scope, SharingStarted.WhileSubscribed(), state)
    }

+29 −5
Original line number Diff line number Diff line
@@ -17,32 +17,56 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor

import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map

interface MobileIconInteractor {
    /** Identifier for RAT type indicator */
    val iconGroup: Flow<SignalIcon.MobileIconGroup>
    /** Observable for RAT type (network type) indicator */
    val networkTypeIconGroup: Flow<MobileIconGroup>

    /** True if this line of service is emergency-only */
    val isEmergencyOnly: Flow<Boolean>

    /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
    val level: Flow<Int>

    /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
    val numberOfLevels: Flow<Int>

    /** True when we want to draw an icon that makes room for the exclamation mark */
    val cutOut: Flow<Boolean>
}

/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
class MobileIconInteractorImpl(
    defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
    defaultMobileIconGroup: Flow<MobileIconGroup>,
    mobileMappingsProxy: MobileMappingsProxy,
    mobileStatusInfo: Flow<MobileSubscriptionModel>,
) : MobileIconInteractor {
    override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
    /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
    override val networkTypeIconGroup: Flow<MobileIconGroup> =
        combine(
            mobileStatusInfo,
            defaultMobileIconMapping,
            defaultMobileIconGroup,
        ) { info, mapping, defaultGroup ->
            val lookupKey =
                when (val resolved = info.resolvedNetworkType) {
                    is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
                    is OverrideNetworkType -> mobileMappingsProxy.toIconKeyOverride(resolved.type)
                }
            mapping[lookupKey] ?: defaultGroup
        }

    override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }

    override val level: Flow<Int> =
Loading