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

Commit aa972051 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov Committed by Android (Google) Code Review
Browse files

Merge changes I9414c0b2,I649b9198 into tm-qpr-dev

* changes:
  [SB Refactor] Implement demo mode for carrier merged wifi.
  [SB Refactor] Display carrier merged wifi networks as mobile.
parents d2787bc5 6746a755
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>
+107 −20
Original line number Diff line number Diff line
@@ -39,7 +39,11 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConn
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.CarrierMergedConnectionRepository.Companion.createCarrierMergedConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,15 +64,19 @@ import kotlinx.coroutines.launch
class DemoMobileConnectionsRepository
@Inject
constructor(
    private val dataSource: DemoModeMobileConnectionDataSource,
    private val mobileDataSource: DemoModeMobileConnectionDataSource,
    private val wifiDataSource: DemoModeWifiDataSource,
    @Application private val scope: CoroutineScope,
    context: Context,
    private val logFactory: TableLogBufferFactory,
) : MobileConnectionsRepository {

    private var demoCommandJob: Job? = null
    private var mobileDemoCommandJob: Job? = null
    private var wifiDemoCommandJob: Job? = null

    private var connectionRepoCache = mutableMapOf<Int, DemoMobileConnectionRepository>()
    private var carrierMergedSubId: Int? = null

    private var connectionRepoCache = mutableMapOf<Int, CacheContainer>()
    private val subscriptionInfoCache = mutableMapOf<Int, SubscriptionModel>()
    val demoModeFinishedEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)

@@ -144,52 +152,83 @@ constructor(
    override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())

    override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
        return connectionRepoCache[subId]
            ?: createDemoMobileConnectionRepo(subId).also { connectionRepoCache[subId] = it }
        val current = connectionRepoCache[subId]?.repo
        if (current != null) {
            return current
        }

        val new = createDemoMobileConnectionRepo(subId)
        connectionRepoCache[subId] = new
        return new.repo
    }

    private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository {
        val tableLogBuffer = logFactory.getOrCreate("DemoMobileConnectionLog [$subId]", 100)
    private fun createDemoMobileConnectionRepo(subId: Int): CacheContainer {
        val tableLogBuffer =
            logFactory.getOrCreate(
                "DemoMobileConnectionLog [$subId]",
                MOBILE_CONNECTION_BUFFER_SIZE,
            )

        return DemoMobileConnectionRepository(
        val repo =
            DemoMobileConnectionRepository(
                subId,
                tableLogBuffer,
            )
        return CacheContainer(repo, lastMobileState = null)
    }

    override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)

    fun startProcessingCommands() {
        demoCommandJob =
        mobileDemoCommandJob =
            scope.launch {
                dataSource.mobileEvents.filterNotNull().collect { event -> processEvent(event) }
                mobileDataSource.mobileEvents.filterNotNull().collect { event ->
                    processMobileEvent(event)
                }
            }
        wifiDemoCommandJob =
            scope.launch {
                wifiDataSource.wifiEvents.filterNotNull().collect { event ->
                    processWifiEvent(event)
                }
            }
    }

    fun stopProcessingCommands() {
        demoCommandJob?.cancel()
        mobileDemoCommandJob?.cancel()
        wifiDemoCommandJob?.cancel()
        _subscriptions.value = listOf()
        connectionRepoCache.clear()
        subscriptionInfoCache.clear()
    }

    private fun processEvent(event: FakeNetworkEventModel) {
    private fun processMobileEvent(event: FakeNetworkEventModel) {
        when (event) {
            is Mobile -> {
                processEnabledMobileState(event)
            }
            is MobileDisabled -> {
                processDisabledMobileState(event)
                maybeRemoveSubscription(event.subId)
            }
        }
    }

    private fun processWifiEvent(event: FakeWifiEventModel) {
        when (event) {
            is FakeWifiEventModel.WifiDisabled -> disableCarrierMerged()
            is FakeWifiEventModel.Wifi -> disableCarrierMerged()
            is FakeWifiEventModel.CarrierMerged -> processCarrierMergedWifiState(event)
        }
    }

    private fun processEnabledMobileState(state: Mobile) {
        // get or create the connection repo, and set its values
        val subId = state.subId ?: DEFAULT_SUB_ID
        maybeCreateSubscription(subId)

        val connection = getRepoForSubId(subId)
        connectionRepoCache[subId]?.lastMobileState = state

        // This is always true here, because we split out disabled states at the data-source level
        connection.dataEnabled.value = true
        connection.networkName.value = NetworkNameModel.Derived(state.name)
@@ -198,14 +237,36 @@ constructor(
        connection.connectionInfo.value = state.toMobileConnectionModel()
    }

    private fun processDisabledMobileState(state: MobileDisabled) {
    private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) {
        // The new carrier merged connection is for a different sub ID, so disable carrier merged
        // for the current (now old) sub
        if (carrierMergedSubId != event.subscriptionId) {
            disableCarrierMerged()
        }

        // get or create the connection repo, and set its values
        val subId = event.subscriptionId
        maybeCreateSubscription(subId)
        carrierMergedSubId = subId

        val connection = getRepoForSubId(subId)
        // This is always true here, because we split out disabled states at the data-source level
        connection.dataEnabled.value = true
        connection.networkName.value = NetworkNameModel.Derived(CARRIER_MERGED_NAME)
        connection.numberOfLevels.value = event.numberOfLevels
        connection.cdmaRoaming.value = false
        connection.connectionInfo.value = event.toMobileConnectionModel()
        Log.e("CCS", "output connection info = ${connection.connectionInfo.value}")
    }

    private fun maybeRemoveSubscription(subId: Int?) {
        if (_subscriptions.value.isEmpty()) {
            // Nothing to do here
            return
        }

        val subId =
            state.subId
        val finalSubId =
            subId
                ?: run {
                    // For sake of usability, we can allow for no subId arg if there is only one
                    // subscription
@@ -223,7 +284,21 @@ constructor(
                    _subscriptions.value[0].subscriptionId
                }

        removeSubscription(subId)
        removeSubscription(finalSubId)
    }

    private fun disableCarrierMerged() {
        val currentCarrierMergedSubId = carrierMergedSubId ?: return

        // If this sub ID was previously not carrier merged, we should reset it to its previous
        // connection.
        val lastMobileState = connectionRepoCache[carrierMergedSubId]?.lastMobileState
        if (lastMobileState != null) {
            processEnabledMobileState(lastMobileState)
        } else {
            // Otherwise, just remove the subscription entirely
            removeSubscription(currentCarrierMergedSubId)
        }
    }

    private fun removeSubscription(subId: Int) {
@@ -251,6 +326,10 @@ constructor(
        )
    }

    private fun FakeWifiEventModel.CarrierMerged.toMobileConnectionModel(): MobileConnectionModel {
        return createCarrierMergedConnectionModel(this.level)
    }

    private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
        val key = mobileMappingsReverseLookup.value[this] ?: "dis"
        return DefaultNetworkType(key)
@@ -260,9 +339,17 @@ constructor(
        private const val TAG = "DemoMobileConnectionsRepo"

        private const val DEFAULT_SUB_ID = 1

        private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
    }
}

class CacheContainer(
    var repo: DemoMobileConnectionRepository,
    /** The last received [Mobile] event. Used when switching from carrier merged back to mobile. */
    var lastMobileState: Mobile?,
)

class DemoMobileConnectionRepository(
    override val subId: Int,
    override val tableLogBuffer: TableLogBuffer,
+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]"
        }
    }
}
Loading