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

Commit 0202e4a3 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB Refactor] Define a `defaultConnections` flow in the shared repo.

This CL:
(1) Updates the wifi and mobile repos to use this shared flow instead
of defining their own default network callbacks.

(2) Updates `MobileConnectionsRepoImpl.defaultMobileNetworkConnectivity`
to have `isDefault = true` when mobile **or** carrier merged is the
default connection. This is needed to fix b/272586234: since carrier
merged is displayed as a mobile network, but we only display mobile
network information if mobile is default, we need `isDefault` to be true
even in the carrier merged case.

Bug: 272586234
Test: all tests in statusbar.pipeline
Test: manual: verify wifi icon still works and updates with normal wifi
Test: unfortunately, it's prohibitively difficult to set up a valid
carrier merged connection to test this, so I was not able to manually
test carrier merged. I wrote extensive unit tests instead

Change-Id: I7922b47547c3a4d186e78b76f7fae9cf3460d4d6
parent ae05dfe4
Loading
Loading
Loading
Loading
+0 −21
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

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

import android.net.Network
import android.net.NetworkCapabilities
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyDisplayInfo
@@ -27,7 +25,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.pipeline.dagger.MobileInputLog
import com.android.systemui.statusbar.pipeline.shared.LoggerHelper
import javax.inject.Inject

/** Logs for inputs into the mobile pipeline. */
@@ -37,24 +34,6 @@ class MobileInputLogger
constructor(
    @MobileInputLog private val buffer: LogBuffer,
) {
    fun logOnCapabilitiesChanged(
        network: Network,
        networkCapabilities: NetworkCapabilities,
        isDefaultNetworkCallback: Boolean,
    ) {
        LoggerHelper.logOnCapabilitiesChanged(
            buffer,
            TAG,
            network,
            networkCapabilities,
            isDefaultNetworkCallback,
        )
    }

    fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
        LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
    }

    fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
        buffer.log(
            TAG,
+8 −57
Original line number Diff line number Diff line
@@ -19,12 +19,6 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.annotation.SuppressLint
import android.content.Context
import android.content.IntentFilter
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.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
@@ -50,6 +44,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameMode
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.util.kotlin.pairwise
@@ -79,7 +74,7 @@ import kotlinx.coroutines.withContext
class MobileConnectionsRepositoryImpl
@Inject
constructor(
    private val connectivityManager: ConnectivityManager,
    connectivityRepository: ConnectivityRepository,
    private val subscriptionManager: SubscriptionManager,
    private val telephonyManager: TelephonyManager,
    private val logger: MobileInputLogger,
@@ -260,50 +255,11 @@ constructor(
        subIdRepositoryCache[subId]
            ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }

    @SuppressLint("MissingPermission")
    private val defaultMobileNetworkConnectivity: StateFlow<DefaultConnectionModel> =
        conflatedCallbackFlow {
                val callback =
                    object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
                        override fun onLost(network: Network) {
                            logger.logOnLost(network, isDefaultNetworkCallback = true)
                            trySend(
                                DefaultConnectionModel(
                                    mobileIsDefault = false,
                                    defaultConnectionIsValidated = false,
                                )
                            )
                        }

                        override fun onCapabilitiesChanged(
                            network: Network,
                            caps: NetworkCapabilities
                        ) {
                            logger.logOnCapabilitiesChanged(
                                network,
                                caps,
                                isDefaultNetworkCallback = true,
                            )
                            trySend(
                                DefaultConnectionModel(
                                    mobileIsDefault = caps.hasTransport(TRANSPORT_CELLULAR),
                                    defaultConnectionIsValidated =
                                    caps.hasCapability(NET_CAPABILITY_VALIDATED),
                                )
                            )
                        }
                    }

                connectivityManager.registerDefaultNetworkCallback(callback)

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

    override val mobileIsDefault: StateFlow<Boolean> =
        defaultMobileNetworkConnectivity
            .map { it.mobileIsDefault }
        connectivityRepository.defaultConnections
            // Because carrier merged networks are displayed as mobile networks, they're
            // part of the `isDefault` calculation. See b/272586234.
            .map { it.mobile.isDefault || it.carrierMerged.isDefault }
            .distinctUntilChanged()
            .logDiffsForTable(
                tableLogger,
@@ -314,8 +270,8 @@ constructor(
            .stateIn(scope, SharingStarted.WhileSubscribed(), false)

    override val defaultConnectionIsValidated: StateFlow<Boolean> =
        defaultMobileNetworkConnectivity
            .map { it.defaultConnectionIsValidated }
        connectivityRepository.defaultConnections
            .map { it.isValidated }
            .distinctUntilChanged()
            .logDiffsForTable(
                tableLogger,
@@ -407,11 +363,6 @@ constructor(
            groupUuid = groupUuid,
        )

    private data class DefaultConnectionModel(
        val mobileIsDefault: Boolean = false,
        val defaultConnectionIsValidated: Boolean = false,
    )

    companion object {
        private const val LOGGING_PREFIX = "Repo"
    }
+29 −0
Original line number Diff line number Diff line
@@ -16,10 +16,13 @@

package com.android.systemui.statusbar.pipeline.shared

import android.net.Network
import android.net.NetworkCapabilities
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.pipeline.dagger.SharedConnectivityInputLog
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
import javax.inject.Inject

/** Logs for connectivity-related inputs that are shared across wifi, mobile, etc. */
@@ -32,6 +35,32 @@ constructor(
    fun logTuningChanged(tuningList: String?) {
        buffer.log(TAG, LogLevel.DEBUG, { str1 = tuningList }, { "onTuningChanged: $str1" })
    }

    fun logOnDefaultCapabilitiesChanged(
        network: Network,
        networkCapabilities: NetworkCapabilities,
    ) {
        LoggerHelper.logOnCapabilitiesChanged(
            buffer,
            TAG,
            network,
            networkCapabilities,
            isDefaultNetworkCallback = true,
        )
    }

    fun logOnDefaultLost(network: Network) {
        LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback = true)
    }

    fun logDefaultConnectionsChanged(model: DefaultConnectionModel) {
        buffer.log(
            TAG,
            LogLevel.DEBUG,
            model::messageInitializer,
            model::messagePrinter,
        )
    }
}

private const val TAG = "ConnectivityInputLogger"
+87 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.shared.data.model

import android.net.NetworkCapabilities
import com.android.systemui.plugins.log.LogMessage

/**
 * A model for all of the current default connections(s).
 *
 * Uses different classes for each connection type to ensure type safety when setting the values.
 *
 * Important: We generally expect there to be only *one* default network at a time (with the
 * exception of carrier merged). Specifically, we don't expect to ever have both wifi *and* cellular
 * as default at the same time. However, the framework network callbacks don't provide any
 * guarantees about why types of network could be default at the same time, so we don't enforce any
 * guarantees on this class.
 */
data class DefaultConnectionModel(
    /** Wifi's status as default or not. */
    val wifi: Wifi = Wifi(isDefault = false),

    /** Mobile's status as default or not. */
    val mobile: Mobile = Mobile(isDefault = false),

    /**
     * True if the current default network represents a carrier merged network, and false otherwise.
     * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
     *
     * Important: A carrier merged network can come in as either a
     * [NetworkCapabilities.TRANSPORT_CELLULAR] *or* as a [NetworkCapabilities.TRANSPORT_WIFI]. This
     * means that when carrier merged is in effect, either:
     * - [wifi] *and* [carrierMerged] will be marked as default; or
     * - [mobile] *and* [carrierMerged] will be marked as default
     *
     * Specifically, [carrierMerged] will never be the *only* default connection.
     */
    val carrierMerged: CarrierMerged = CarrierMerged(isDefault = false),

    /** Ethernet's status as default or not. */
    val ethernet: Ethernet = Ethernet(isDefault = false),

    /** True if the default connection is currently validated and false otherwise. */
    val isValidated: Boolean = false,
) {
    data class Wifi(val isDefault: Boolean)
    data class Mobile(val isDefault: Boolean)
    data class CarrierMerged(val isDefault: Boolean)
    data class Ethernet(val isDefault: Boolean)

    /**
     * Used in conjunction with [ConnectivityInputLogger] to log this class without calling
     * [toString] on it.
     *
     * Be sure to change [messagePrinter] whenever this method is changed.
     */
    fun messageInitializer(message: LogMessage) {
        message.bool1 = wifi.isDefault
        message.bool2 = mobile.isDefault
        message.bool3 = carrierMerged.isDefault
        message.bool4 = ethernet.isDefault
        message.int1 = if (isValidated) 1 else 0
    }

    fun messagePrinter(message: LogMessage): String {
        return "DefaultConnectionModel(" +
            "wifi.isDefault=${message.bool1}, " +
            "mobile.isDefault=${message.bool2}, " +
            "carrierMerged.isDefault=${message.bool3}, " +
            "ethernet.isDefault=${message.bool4}, " +
            "isValidated=${if (message.int1 == 1) "true" else "false"})"
    }
}
+113 −1
Original line number Diff line number Diff line
@@ -16,7 +16,17 @@

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

import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
import androidx.annotation.ArrayRes
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
@@ -29,6 +39,11 @@ import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.pipeline.shared.ConnectivityInputLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.CarrierMerged
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Ethernet
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Mobile
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi
import com.android.systemui.tuner.TunerService
import java.io.PrintWriter
import javax.inject.Inject
@@ -37,6 +52,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn

/**
@@ -46,6 +63,9 @@ import kotlinx.coroutines.flow.stateIn
interface ConnectivityRepository {
    /** Observable for the current set of connectivity icons that should be force-hidden. */
    val forceHiddenSlots: StateFlow<Set<ConnectivitySlot>>

    /** Observable for which connection(s) are currently default. */
    val defaultConnections: StateFlow<DefaultConnectionModel>
}

@OptIn(ExperimentalCoroutinesApi::class)
@@ -53,6 +73,7 @@ interface ConnectivityRepository {
class ConnectivityRepositoryImpl
@Inject
constructor(
    connectivityManager: ConnectivityManager,
    private val connectivitySlots: ConnectivitySlots,
    context: Context,
    dumpManager: DumpManager,
@@ -61,7 +82,7 @@ constructor(
    tunerService: TunerService,
) : ConnectivityRepository, Dumpable {
    init {
        dumpManager.registerDumpable("ConnectivityRepository", this)
        dumpManager.registerNormalDumpable("ConnectivityRepository", this)
    }

    // The default set of hidden icons to use if we don't get any from [TunerService].
@@ -97,6 +118,67 @@ constructor(
                initialValue = defaultHiddenIcons
            )

    @SuppressLint("MissingPermission")
    override val defaultConnections: StateFlow<DefaultConnectionModel> =
        conflatedCallbackFlow {
                val callback =
                    object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
                        override fun onLost(network: Network) {
                            logger.logOnDefaultLost(network)
                            // The system no longer has a default network, so everything is
                            // non-default.
                            trySend(
                                DefaultConnectionModel(
                                    Wifi(isDefault = false),
                                    Mobile(isDefault = false),
                                    CarrierMerged(isDefault = false),
                                    Ethernet(isDefault = false),
                                    isValidated = false,
                                )
                            )
                        }

                        override fun onCapabilitiesChanged(
                            network: Network,
                            networkCapabilities: NetworkCapabilities,
                        ) {
                            logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities)

                            val isWifiDefault =
                                networkCapabilities.hasTransport(TRANSPORT_WIFI) ||
                                    networkCapabilities.getMainOrUnderlyingWifiInfo() != null
                            val isMobileDefault =
                                networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
                            val isCarrierMergedDefault =
                                networkCapabilities
                                    .getMainOrUnderlyingWifiInfo()
                                    ?.isCarrierMerged == true
                            val isEthernetDefault =
                                networkCapabilities.hasTransport(TRANSPORT_ETHERNET)

                            val isValidated =
                                networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)

                            trySend(
                                DefaultConnectionModel(
                                    Wifi(isWifiDefault),
                                    Mobile(isMobileDefault),
                                    CarrierMerged(isCarrierMergedDefault),
                                    Ethernet(isEthernetDefault),
                                    isValidated,
                                )
                            )
                        }
                    }

                connectivityManager.registerDefaultNetworkCallback(callback)

                awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
            }
            .distinctUntilChanged()
            .onEach { logger.logDefaultConnectionsChanged(it) }
            .stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel())

    override fun dump(pw: PrintWriter, args: Array<out String>) {
        pw.apply { println("defaultHiddenIcons=$defaultHiddenIcons") }
    }
@@ -116,5 +198,35 @@ constructor(
                .mapNotNull { connectivitySlots.getSlotFromName(it) }
                .toSet()
        }

        /**
         * Returns a [WifiInfo] object from the capabilities if it has one, or null if there is no
         * underlying wifi network.
         *
         * This will return a valid [WifiInfo] object if wifi is the main transport **or** wifi is
         * an underlying transport. This is important for carrier merged networks, where the main
         * transport info is *not* wifi, but the underlying transport info *is* wifi. We want to
         * always use [WifiInfo] if it's available, so we need to check the underlying transport
         * info.
         */
        fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(): WifiInfo? {
            // Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for
            // virtual networks like VCN.
            val canHaveWifiInfo =
                this.hasTransport(TRANSPORT_CELLULAR) || this.hasTransport(TRANSPORT_WIFI)
            if (!canHaveWifiInfo) {
                return null
            }

            return when (val currentTransportInfo = transportInfo) {
                // This VcnTransportInfo logic is copied from
                // [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of
                // re-used because it makes the logic here clearer, and because the method will be
                // removed once this pipeline is fully launched.
                is VcnTransportInfo -> currentTransportInfo.wifiInfo
                is WifiInfo -> currentTransportInfo
                else -> null
            }
        }
    }
}
Loading