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

Commit 690d0acc authored by Evan Laird's avatar Evan Laird Committed by Automerger Merge Worker
Browse files

Merge changes Id53733ac,I38e917cf into tm-qpr-dev am: ac6d6164

parents a60d9959 ac6d6164
Loading
Loading
Loading
Loading
+15 −5
Original line number Diff line number Diff line
@@ -18,10 +18,14 @@ package com.android.systemui.statusbar.pipeline.dagger

import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryImpl
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
@@ -41,10 +45,16 @@ abstract class StatusBarPipelineModule {
    abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository

    @Binds
    abstract fun mobileSubscriptionRepository(
        impl: MobileSubscriptionRepositoryImpl
    ): MobileSubscriptionRepository
    abstract fun mobileConnectionsRepository(
        impl: MobileConnectionsRepositoryImpl
    ): MobileConnectionsRepository

    @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
+77 −102
Original line number Diff line number Diff line
@@ -21,23 +21,18 @@ import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyCallback.CarrierNetworkListener
import android.telephony.TelephonyCallback.DataActivityListener
import android.telephony.TelephonyCallback.DataConnectionStateListener
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.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.shared.ConnectivityPipelineLogger
import java.lang.IllegalStateException
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -47,110 +42,64 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext

/**
 * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
 * on various policy
 * Every mobile line of service can be identified via a [SubscriptionInfo] object. We set up a
 * repository for each individual, tracked subscription via [MobileConnectionsRepository], and this
 * repository is responsible for setting up a [TelephonyManager] object tied to its subscriptionId
 *
 * There should only ever be one [MobileConnectionRepository] per subscription, since
 * [TelephonyManager] limits the number of callbacks that can be registered per process.
 *
 * This repository should have all of the relevant information for a single line of service, which
 * eventually becomes a single icon in the status bar.
 */
interface MobileSubscriptionRepository {
    /** Observable list of current mobile subscriptions */
    val subscriptionsFlow: Flow<List<SubscriptionInfo>>

    /** Observable for the subscriptionId of the current mobile data connection */
    val activeMobileDataSubscriptionId: Flow<Int>

    /** Get or create an observable for the given subscription ID */
    fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
}

@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class MobileSubscriptionRepositoryImpl
@Inject
constructor(
    private val subscriptionManager: SubscriptionManager,
    private val telephonyManager: TelephonyManager,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Application private val scope: CoroutineScope,
) : MobileSubscriptionRepository {
    private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()

interface MobileConnectionRepository {
    /**
     * State flow that emits the set of mobile data subscriptions, each represented by its own
     * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
     * info object, but for now we keep track of the infos themselves.
     * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
     * listener + model.
     */
    override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
        conflatedCallbackFlow {
                val callback =
                    object : SubscriptionManager.OnSubscriptionsChangedListener() {
                        override fun onSubscriptionsChanged() {
                            trySend(Unit)
                        }
    val subscriptionModelFlow: Flow<MobileSubscriptionModel>
}

                subscriptionManager.addOnSubscriptionsChangedListener(
                    bgDispatcher.asExecutor(),
                    callback,
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl(
    private val subId: Int,
    telephonyManager: TelephonyManager,
    bgDispatcher: CoroutineDispatcher,
    logger: ConnectivityPipelineLogger,
    scope: CoroutineScope,
) : MobileConnectionRepository {
    init {
        if (telephonyManager.subscriptionId != subId) {
            throw IllegalStateException(
                "TelephonyManager should be created with subId($subId). " +
                    "Found ${telephonyManager.subscriptionId} instead."
            )

                awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
            }
            .mapLatest { fetchSubscriptionsList() }
            .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())

    /** StateFlow that keeps track of the current active mobile data subscription */
    override val activeMobileDataSubscriptionId: StateFlow<Int> =
        conflatedCallbackFlow {
                val callback =
                    object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
                        override fun onActiveDataSubscriptionIdChanged(subId: Int) {
                            trySend(subId)
                        }
                    }

                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
        }
            .stateIn(
                scope,
                started = SharingStarted.WhileSubscribed(),
                SubscriptionManager.INVALID_SUBSCRIPTION_ID
            )

    /**
     * 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
     */
    override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
        return subIdFlowCache[subId]
            ?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
    }

    @VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache

    private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
    override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
        var state = MobileSubscriptionModel()
        conflatedCallbackFlow {
                val phony = telephonyManager.createForSubscriptionId(subId)
                // TODO (b/240569788): log all of these into the connectivity logger
                val callback =
                    object :
                        TelephonyCallback(),
                        ServiceStateListener,
                        SignalStrengthsListener,
                        DataConnectionStateListener,
                        DataActivityListener,
                        CarrierNetworkListener,
                        DisplayInfoListener {
                        TelephonyCallback.ServiceStateListener,
                        TelephonyCallback.SignalStrengthsListener,
                        TelephonyCallback.DataConnectionStateListener,
                        TelephonyCallback.DataActivityListener,
                        TelephonyCallback.CarrierNetworkListener,
                        TelephonyCallback.DisplayInfoListener {
                        override fun onServiceStateChanged(serviceState: ServiceState) {
                            state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
                            trySend(state)
                        }

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

                        override fun onDataConnectionStateChanged(
                            dataState: Int,
                            networkType: Int
@@ -180,31 +130,56 @@ 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)
                            trySend(state)
                            val networkType =
                                if (
                                    telephonyDisplayInfo.overrideNetworkType ==
                                        OVERRIDE_NETWORK_TYPE_NONE
                                ) {
                                    DefaultNetworkType(telephonyDisplayInfo.networkType)
                                } else {
                                    OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
                                }
                            state = state.copy(resolvedNetworkType = networkType)
                            trySend(state)
                        }
                phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                awaitClose {
                    phony.unregisterTelephonyCallback(callback)
                    // Release the cached flow
                    subIdFlowCache.remove(subId)
                    }
                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
            }
            .onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) }
            .stateIn(scope, SharingStarted.WhileSubscribed(), state)
    }

    private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
        withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
    class Factory
    @Inject
    constructor(
        private val telephonyManager: TelephonyManager,
        private val logger: ConnectivityPipelineLogger,
        @Background private val bgDispatcher: CoroutineDispatcher,
        @Application private val scope: CoroutineScope,
    ) {
        fun build(subId: Int): MobileConnectionRepository {
            return MobileConnectionRepositoryImpl(
                subId,
                telephonyManager.createForSubscriptionId(subId),
                bgDispatcher,
                logger,
                scope,
            )
        }
    }
}
+201 −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.repository

import android.content.Context
import android.content.IntentFilter
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
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.shared.ConnectivityPipelineLogger
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asExecutor
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

/**
 * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
 * on various policy
 */
interface MobileConnectionsRepository {
    /** Observable list of current mobile subscriptions */
    val subscriptionsFlow: Flow<List<SubscriptionInfo>>

    /** 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 a repository for the line of service for the given subscription ID */
    fun getRepoForSubId(subId: Int): MobileConnectionRepository
}

@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class MobileConnectionsRepositoryImpl
@Inject
constructor(
    private val subscriptionManager: SubscriptionManager,
    private val telephonyManager: TelephonyManager,
    private val logger: ConnectivityPipelineLogger,
    broadcastDispatcher: BroadcastDispatcher,
    private val context: Context,
    @Background private val bgDispatcher: CoroutineDispatcher,
    @Application private val scope: CoroutineScope,
    private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
) : MobileConnectionsRepository {
    private val subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()

    /**
     * State flow that emits the set of mobile data subscriptions, each represented by its own
     * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
     * info object, but for now we keep track of the infos themselves.
     */
    override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
        conflatedCallbackFlow {
                val callback =
                    object : SubscriptionManager.OnSubscriptionsChangedListener() {
                        override fun onSubscriptionsChanged() {
                            trySend(Unit)
                        }
                    }

                subscriptionManager.addOnSubscriptionsChangedListener(
                    bgDispatcher.asExecutor(),
                    callback,
                )

                awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
            }
            .mapLatest { fetchSubscriptionsList() }
            .onEach { infos -> dropUnusedReposFromCache(infos) }
            .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())

    /** StateFlow that keeps track of the current active mobile data subscription */
    override val activeMobileDataSubscriptionId: StateFlow<Int> =
        conflatedCallbackFlow {
                val callback =
                    object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
                        override fun onActiveDataSubscriptionIdChanged(subId: Int) {
                            trySend(subId)
                        }
                    }

                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
            }
            .stateIn(
                scope,
                started = SharingStarted.WhileSubscribed(),
                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)
            )

    override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
        if (!isValidSubId(subId)) {
            throw IllegalArgumentException(
                "subscriptionId $subId is not in the list of valid subscriptions"
            )
        }

        return subIdRepositoryCache[subId]
            ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
    }

    private fun isValidSubId(subId: Int): Boolean {
        subscriptionsFlow.value.forEach {
            if (it.subscriptionId == subId) {
                return true
            }
        }

        return false
    }

    @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache

    private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
        return mobileConnectionRepositoryFactory.build(subId)
    }

    private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
        // Remove any connection repository from the cache that isn't in the new set of IDs. They
        // will get garbage collected once their subscribers go away
        val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }

        subIdRepositoryCache.keys.forEach {
            if (!currentValidSubscriptionIds.contains(it)) {
                subIdRepositoryCache.remove(it)
            }
        }
    }

    private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
        withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
}
Loading