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

Commit 09c154b2 authored by Evan Laird's avatar Evan Laird
Browse files

[SB refactor] Add connectivity tracking to the mobile pipeline

This CL tracks 2 bits from ConnectivityManager for mobile networks:
isConnected and isValidated. These bits are used to know whether or not
cellular networks are the default transport, and whether or not
connectivitymanager has validated that transport.

Test: manual
Test: atest packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/*
Bug: 240492102
Change-Id: I13890cdc59c198c1b5290ae92e7ec4ba491999e3
parent 93b945ce
Loading
Loading
Loading
Loading
+27 −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.net.NetworkCapabilities

/** Provides information about a mobile network connection */
data class MobileConnectivityModel(
    /** Whether mobile is the connected transport see [NetworkCapabilities.TRANSPORT_CELLULAR] */
    val isConnected: Boolean = false,
    /** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
    val isValidated: Boolean = false,
)
+42 −0
Original line number Diff line number Diff line
@@ -16,9 +16,16 @@

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

import android.annotation.SuppressLint
import android.content.Context
import android.content.IntentFilter
import android.database.ContentObserver
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.provider.Settings
import android.provider.Settings.Global.MOBILE_DATA
import android.telephony.CarrierConfigManager
@@ -37,6 +44,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
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.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
@@ -73,6 +81,9 @@ interface MobileConnectionsRepository {
    /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
    val defaultDataSubId: StateFlow<Int>

    /** The current connectivity status for the default mobile network connection */
    val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>

    /** Get or create a repository for the line of service for the given subscription ID */
    fun getRepoForSubId(subId: Int): MobileConnectionRepository

@@ -86,6 +97,7 @@ interface MobileConnectionsRepository {
class MobileConnectionsRepositoryImpl
@Inject
constructor(
    private val connectivityManager: ConnectivityManager,
    private val subscriptionManager: SubscriptionManager,
    private val telephonyManager: TelephonyManager,
    private val logger: ConnectivityPipelineLogger,
@@ -212,6 +224,36 @@ constructor(
        awaitClose { context.contentResolver.unregisterContentObserver(observer) }
    }

    @SuppressLint("MissingPermission")
    override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
        conflatedCallbackFlow {
                val callback =
                    object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
                        override fun onLost(network: Network) {
                            // Send a disconnected model when lost. Maybe should create a sealed
                            // type or null here?
                            trySend(MobileConnectivityModel())
                        }

                        override fun onCapabilitiesChanged(
                            network: Network,
                            caps: NetworkCapabilities
                        ) {
                            trySend(
                                MobileConnectivityModel(
                                    isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
                                    isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
                                )
                            )
                        }
                    }

                connectivityManager.registerDefaultNetworkCallback(callback)

                awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())

    private fun isValidSubId(subId: Int): Boolean {
        subscriptionsFlow.value.forEach {
            if (it.subscriptionId == subId) {
+4 −1
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
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
@@ -35,6 +34,9 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn

interface MobileIconInteractor {
    /** Only true if mobile is the default transport but is not validated, otherwise false */
    val isDefaultConnectionFailed: 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>
@@ -63,6 +65,7 @@ class MobileIconInteractorImpl(
    defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
    defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
    defaultMobileIconGroup: StateFlow<MobileIconGroup>,
    override val isDefaultConnectionFailed: StateFlow<Boolean>,
    mobileMappingsProxy: MobileMappingsProxy,
    connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
+18 −0
Original line number Diff line number Diff line
@@ -60,6 +60,8 @@ interface MobileIconsInteractor {
    val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
    /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
    val defaultMobileIconGroup: StateFlow<MobileIconGroup>
    /** True only if the default network is mobile, and validation also failed */
    val isDefaultConnectionFailed: StateFlow<Boolean>
    /** True once the user has been set up */
    val isUserSetup: StateFlow<Boolean>
    /**
@@ -162,6 +164,21 @@ constructor(
            .mapLatest { mobileMappingsProxy.getDefaultIcons(it) }
            .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)

    /**
     * We want to show an error state when cellular has actually failed to validate, but not if some
     * other transport type is active, because then we expect there not to be validation.
     */
    override val isDefaultConnectionFailed: StateFlow<Boolean> =
        mobileConnectionsRepo.defaultMobileNetworkConnectivity
            .mapLatest { connectivityModel ->
                if (!connectivityModel.isConnected) {
                    false
                } else {
                    !connectivityModel.isValidated
                }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), false)

    override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow

    /** Vends out new [MobileIconInteractor] for a particular subId */
@@ -171,6 +188,7 @@ constructor(
            activeDataConnectionHasDataEnabled,
            defaultMobileIconMapping,
            defaultMobileIconGroup,
            isDefaultConnectionFailed,
            mobileMappingsProxy,
            mobileConnectionsRepo.getRepoForSubId(subId),
        )
+6 −4
Original line number Diff line number Diff line
@@ -66,10 +66,12 @@ constructor(

    /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
    val networkTypeIcon: Flow<Icon?> =
        combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) {
            networkTypeIconGroup,
            isDataEnabled ->
            if (!isDataEnabled) {
        combine(
            iconInteractor.networkTypeIconGroup,
            iconInteractor.isDataEnabled,
            iconInteractor.isDefaultConnectionFailed
        ) { networkTypeIconGroup, isDataEnabled, isFailedConnection ->
            if (!isDataEnabled || isFailedConnection) {
                null
            } else {
                val desc =
Loading