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

Commit a7790acc authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB Refactor] Display carrier merged wifi networks as mobile.

This CL:
1) Makes WifiRepository an input to MobileConnectionsRepository so that
   the mobile repo will know when there's a carrier merged connection.
2) Creates CarrierMergedConnectionRepository, which fills in the correct
   mobile values based on the carried merged wifi connection.
3) Creates FullMobileConnectionRepository, which switches between a
   typical mobile repo and a carrier merged repo based on the wifi
   network status.

Bug: 238425913
Bug: 264684296
Test: `adb shell am broadcast -a com.android.systemui.demo -e command
network -e wifi carriermerged -e level 2` -> displays a mobile triangle
with level 2 and "W+" as its RAT type (note: this will only work if the
next CL is also patched in)
Test: See additional test steps in second bug
Test: atest CarrierMergedConnectionRepositoryTest
Test: atest MobileConnectionsRepositoryTest
Test: atest FullMobileConnectionRepositoryTest
Test: all tests in statusbar.pipeline

Change-Id: I649b91984bf249d783e3117b30820289d78d82af
parent 52388f92
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model

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

/**
@@ -38,4 +40,12 @@ sealed interface ResolvedNetworkType {
    data class OverrideNetworkType(
        override val lookupKey: String,
    ) : ResolvedNetworkType

    /** Represents the carrier merged network. See [CarrierMergedConnectionRepository]. */
    object CarrierMergedNetworkType : ResolvedNetworkType {
        // Effectively unused since [iconGroupOverride] is used instead.
        override val lookupKey: String = "cwf"

        val iconGroupOverride: SignalIcon.MobileIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI
    }
}
+1 −2
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import android.telephony.TelephonyManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

/**
@@ -50,7 +49,7 @@ interface MobileConnectionRepository {
     * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
     * listener + model.
     */
    val connectionInfo: Flow<MobileConnectionModel>
    val connectionInfo: StateFlow<MobileConnectionModel>

    /** The total number of levels. Used with [SignalDrawable]. */
    val numberOfLevels: StateFlow<Int>
+181 −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.prod

import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
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.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/**
 * A repository implementation for a carrier merged (aka VCN) network. A carrier merged network is
 * delivered to SysUI as a wifi network (see [WifiNetworkModel.CarrierMerged], but is visually
 * displayed as a mobile network triangle.
 *
 * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
 *
 * See [MobileConnectionRepositoryImpl] for a repository implementation of a typical mobile
 * connection.
 */
class CarrierMergedConnectionRepository(
    override val subId: Int,
    override val tableLogBuffer: TableLogBuffer,
    defaultNetworkName: NetworkNameModel,
    @Application private val scope: CoroutineScope,
    val wifiRepository: WifiRepository,
) : MobileConnectionRepository {

    /**
     * Outputs the carrier merged network to use, or null if we don't have a valid carrier merged
     * network.
     */
    private val network: Flow<WifiNetworkModel.CarrierMerged?> =
        combine(
            wifiRepository.isWifiEnabled,
            wifiRepository.isWifiDefault,
            wifiRepository.wifiNetwork,
        ) { isEnabled, isDefault, network ->
            when {
                !isEnabled -> null
                !isDefault -> null
                network !is WifiNetworkModel.CarrierMerged -> null
                network.subscriptionId != subId -> {
                    Log.w(
                        TAG,
                        "Connection repo subId=$subId " +
                            "does not equal wifi repo subId=${network.subscriptionId}; " +
                            "not showing carrier merged"
                    )
                    null
                }
                else -> network
            }
        }

    override val connectionInfo: StateFlow<MobileConnectionModel> =
        network
            .map { it.toMobileConnectionModel() }
            .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel())

    // TODO(b/238425913): Add logging to this class.
    // TODO(b/238425913): Make sure SignalStrength.getEmptyState is used when appropriate.

    // Carrier merged is never roaming.
    override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()

    // TODO(b/238425913): Fetch the carrier merged network name.
    override val networkName: StateFlow<NetworkNameModel> =
        flowOf(defaultNetworkName)
            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)

    override val numberOfLevels: StateFlow<Int> =
        wifiRepository.wifiNetwork
            .map {
                if (it is WifiNetworkModel.CarrierMerged) {
                    it.numberOfLevels
                } else {
                    DEFAULT_NUM_LEVELS
                }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)

    override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled

    private fun WifiNetworkModel.CarrierMerged?.toMobileConnectionModel(): MobileConnectionModel {
        if (this == null) {
            return MobileConnectionModel()
        }

        return createCarrierMergedConnectionModel(level)
    }

    companion object {
        /**
         * Creates an instance of [MobileConnectionModel] that represents a carrier merged network
         * with the given [level].
         */
        fun createCarrierMergedConnectionModel(level: Int): MobileConnectionModel {
            return MobileConnectionModel(
                primaryLevel = level,
                cdmaLevel = level,
                // A [WifiNetworkModel.CarrierMerged] instance is always connected.
                // (A [WifiNetworkModel.Inactive] represents a disconnected network.)
                dataConnectionState = DataConnectionState.Connected,
                // TODO(b/238425913): This should come from [WifiRepository.wifiActivity].
                dataActivityDirection =
                    DataActivityModel(
                        hasActivityIn = false,
                        hasActivityOut = false,
                    ),
                resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
                // Carrier merged is never roaming
                isRoaming = false,

                // TODO(b/238425913): Verify that these fields never change for carrier merged.
                isEmergencyOnly = false,
                operatorAlphaShort = null,
                isInService = true,
                isGsm = false,
                carrierNetworkChangeActive = false,
            )
        }
    }

    @SysUISingleton
    class Factory
    @Inject
    constructor(
        @Application private val scope: CoroutineScope,
        private val wifiRepository: WifiRepository,
    ) {
        fun build(
            subId: Int,
            mobileLogger: TableLogBuffer,
            defaultNetworkName: NetworkNameModel,
        ): MobileConnectionRepository {
            return CarrierMergedConnectionRepository(
                subId,
                mobileLogger,
                defaultNetworkName,
                scope,
                wifiRepository,
            )
        }
    }
}

private const val TAG = "CarrierMergedConnectionRepository"
+179 −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.prod

import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import javax.inject.Inject
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.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn

/**
 * A repository that fully implements a mobile connection.
 *
 * This connection could either be a typical mobile connection (see [MobileConnectionRepositoryImpl]
 * or a carrier merged connection (see [CarrierMergedConnectionRepository]). This repository
 * switches between the two types of connections based on whether the connection is currently
 * carrier merged (see [setIsCarrierMerged]).
 */
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class FullMobileConnectionRepository(
    override val subId: Int,
    startingIsCarrierMerged: Boolean,
    override val tableLogBuffer: TableLogBuffer,
    private val defaultNetworkName: NetworkNameModel,
    private val networkNameSeparator: String,
    private val globalMobileDataSettingChangedEvent: Flow<Unit>,
    @Application scope: CoroutineScope,
    private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory,
    private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory,
) : MobileConnectionRepository {
    /**
     * Sets whether this connection is a typical mobile connection or a carrier merged connection.
     */
    fun setIsCarrierMerged(isCarrierMerged: Boolean) {
        _isCarrierMerged.value = isCarrierMerged
    }

    /**
     * Returns true if this repo is currently for a carrier merged connection and false otherwise.
     */
    @VisibleForTesting fun getIsCarrierMerged() = _isCarrierMerged.value

    private val _isCarrierMerged = MutableStateFlow(startingIsCarrierMerged)
    private val isCarrierMerged: StateFlow<Boolean> =
        _isCarrierMerged
            .logDiffsForTable(
                tableLogBuffer,
                columnPrefix = "",
                columnName = "isCarrierMerged",
                initialValue = startingIsCarrierMerged,
            )
            .stateIn(scope, SharingStarted.WhileSubscribed(), startingIsCarrierMerged)

    private val mobileRepo: MobileConnectionRepository by lazy {
        mobileRepoFactory.build(
            subId,
            tableLogBuffer,
            defaultNetworkName,
            networkNameSeparator,
            globalMobileDataSettingChangedEvent,
        )
    }

    private val carrierMergedRepo: MobileConnectionRepository by lazy {
        carrierMergedRepoFactory.build(subId, tableLogBuffer, defaultNetworkName)
    }

    @VisibleForTesting
    internal val activeRepo: StateFlow<MobileConnectionRepository> = run {
        val initial =
            if (startingIsCarrierMerged) {
                carrierMergedRepo
            } else {
                mobileRepo
            }

        this.isCarrierMerged
            .mapLatest { isCarrierMerged ->
                if (isCarrierMerged) {
                    carrierMergedRepo
                } else {
                    mobileRepo
                }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
    }

    override val cdmaRoaming =
        activeRepo
            .flatMapLatest { it.cdmaRoaming }
            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaRoaming.value)

    override val connectionInfo =
        activeRepo
            .flatMapLatest { it.connectionInfo }
            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)

    override val dataEnabled =
        activeRepo
            .flatMapLatest { it.dataEnabled }
            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value)

    override val numberOfLevels =
        activeRepo
            .flatMapLatest { it.numberOfLevels }
            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.numberOfLevels.value)

    override val networkName =
        activeRepo
            .flatMapLatest { it.networkName }
            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value)

    class Factory
    @Inject
    constructor(
        @Application private val scope: CoroutineScope,
        private val logFactory: TableLogBufferFactory,
        private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory,
        private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory,
    ) {
        fun build(
            subId: Int,
            startingIsCarrierMerged: Boolean,
            defaultNetworkName: NetworkNameModel,
            networkNameSeparator: String,
            globalMobileDataSettingChangedEvent: Flow<Unit>,
        ): FullMobileConnectionRepository {
            val mobileLogger =
                logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE)

            return FullMobileConnectionRepository(
                subId,
                startingIsCarrierMerged,
                mobileLogger,
                defaultNetworkName,
                networkNameSeparator,
                globalMobileDataSettingChangedEvent,
                scope,
                mobileRepoFactory,
                carrierMergedRepoFactory,
            )
        }

        companion object {
            /** The buffer size to use for logging. */
            const val MOBILE_CONNECTION_BUFFER_SIZE = 100

            /** Returns a log buffer name for a mobile connection with the given [subId]. */
            fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
        }
    }
}
+5 −8
Original line number Diff line number Diff line
@@ -38,7 +38,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -70,6 +69,10 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn

/**
 * A repository implementation for a typical mobile connection (as opposed to a carrier merged
 * connection -- see [CarrierMergedConnectionRepository]).
 */
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl(
@@ -298,18 +301,16 @@ class MobileConnectionRepositoryImpl(
        private val logger: ConnectivityPipelineLogger,
        private val globalSettings: GlobalSettings,
        private val mobileMappingsProxy: MobileMappingsProxy,
        private val logFactory: TableLogBufferFactory,
        @Background private val bgDispatcher: CoroutineDispatcher,
        @Application private val scope: CoroutineScope,
    ) {
        fun build(
            subId: Int,
            mobileLogger: TableLogBuffer,
            defaultNetworkName: NetworkNameModel,
            networkNameSeparator: String,
            globalMobileDataSettingChangedEvent: Flow<Unit>,
        ): MobileConnectionRepository {
            val mobileLogger = logFactory.getOrCreate(tableBufferLogName(subId), 100)

            return MobileConnectionRepositoryImpl(
                context,
                subId,
@@ -327,8 +328,4 @@ class MobileConnectionRepositoryImpl(
            )
        }
    }

    companion object {
        fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
    }
}
Loading