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

Commit dcfc485a authored by Evan Laird's avatar Evan Laird
Browse files

[mobile] Device-based emergency call capable

This is (yet another) departure from the legacy logic to answer the
question "can the device make emergency calls". This is strictly
separate from the calculation of a per-subscription understanding of
emergency call capability.

We listen for any SERVICE_STATE broadcasts that don't refer to a known
subscription (this part is done by checking subId = -1, and assumes
that -1 in this case means "device-based" rather than "carrier-based").
When that happens, we call the new telephony APIs for getting the number
of slots, and subsequently querying each slot for its service state.

If any service state for a given slot reports that it is capable of
emergency calls, then the top-level connections repository reports it.
And we can use that in our logic downstream (e.g. for satellite)

Test: MobileConnectionsRepositoryTest
Test: MobileIconsInteractorTest
Bug: 346579914
Flag: NONE bugfix
Change-Id: Id0f74df979fcd947e0fd856add9c38a764b56b3b
parent cdda826a
Loading
Loading
Loading
Loading
+0 −31
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.ServiceState

/**
 * Simplified representation of a [ServiceState] for use in SystemUI. Add any fields that we need to
 * extract from service state here for consumption downstream
 */
data class ServiceStateModel(val isEmergencyOnly: Boolean) {
    companion object {
        fun fromServiceState(serviceState: ServiceState): ServiceStateModel {
            return ServiceStateModel(isEmergencyOnly = serviceState.isEmergencyOnly)
        }
    }
}
+6 −9
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.telephony.SubscriptionManager
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -93,17 +92,15 @@ interface MobileConnectionsRepository {
    val defaultMobileIconGroup: Flow<MobileIconGroup>

    /**
     * [deviceServiceState] is equivalent to the last [Intent.ACTION_SERVICE_STATE] broadcast with a
     * subscriptionId of -1 (aka [SubscriptionManager.INVALID_SUBSCRIPTION_ID]).
     * Can the device make emergency calls using the device-based service state? This field is only
     * useful when all known active subscriptions are OOS and not emergency call capable.
     *
     * While each [MobileConnectionsRepository] listens for the service state of each subscription,
     * there is potentially a service state associated with the device itself. This value can be
     * used to calculate e.g., the emergency calling capability of the device (as opposed to the
     * emergency calling capability of an individual mobile connection)
     * Specifically, this checks every [ServiceState] of the device, and looks for any that report
     * [ServiceState.isEmergencyOnly].
     *
     * Note: this is a [StateFlow] using an eager sharing strategy.
     * This is an eager flow, and re-evaluates whenever ACTION_SERVICE_STATE is sent for subId = -1.
     */
    val deviceServiceState: StateFlow<ServiceStateModel?>
    val isDeviceEmergencyCallCapable: StateFlow<Boolean>

    /**
     * If any active SIM on the device is in
+4 −4
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
@@ -152,16 +151,17 @@ constructor(
    override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
        activeRepo.flatMapLatest { it.defaultMobileIconGroup }

    override val deviceServiceState: StateFlow<ServiceStateModel?> =
    override val isDeviceEmergencyCallCapable: StateFlow<Boolean> =
        activeRepo
            .flatMapLatest { it.deviceServiceState }
            .flatMapLatest { it.isDeviceEmergencyCallCapable }
            .stateIn(
                scope,
                SharingStarted.WhileSubscribed(),
                realRepository.deviceServiceState.value
                realRepository.isDeviceEmergencyCallCapable.value
            )

    override val isAnySimSecure: Flow<Boolean> = activeRepo.flatMapLatest { it.isAnySimSecure }

    override fun getIsAnySimSecure(): Boolean = activeRepo.value.getIsAnySimSecure()

    override val defaultDataSubId: StateFlow<Int> =
+3 −3
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
@@ -137,10 +136,11 @@ constructor(

    override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)

    // TODO(b/339023069): demo command for device-based connectivity state
    override val deviceServiceState: StateFlow<ServiceStateModel?> = MutableStateFlow(null)
    // TODO(b/339023069): demo command for device-based emergency calls state
    override val isDeviceEmergencyCallCapable: StateFlow<Boolean> = MutableStateFlow(false)

    override val isAnySimSecure: Flow<Boolean> = flowOf(getIsAnySimSecure())

    override fun getIsAnySimSecure(): Boolean = false

    override val defaultMobileIconMapping = MutableStateFlow(TelephonyIcons.ICON_NAME_TO_ICON)
+27 −20
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.telephony.CarrierConfigManager
import android.telephony.ServiceState
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -49,7 +48,6 @@ import com.android.systemui.statusbar.pipeline.airplane.data.repository.Airplane
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
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
@@ -72,7 +70,6 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
@@ -175,8 +172,8 @@ constructor(
            }
            .flowOn(bgDispatcher)

    /** Note that this flow is eager, so we don't miss any state */
    override val deviceServiceState: StateFlow<ServiceStateModel?> =
    /** Turn ACTION_SERVICE_STATE (for subId = -1) into an event */
    private val serviceStateChangedEvent: Flow<Unit> =
        broadcastDispatcher
            .broadcastFlow(IntentFilter(Intent.ACTION_SERVICE_STATE)) { intent, _ ->
                val subId =
@@ -185,24 +182,34 @@ constructor(
                        INVALID_SUBSCRIPTION_ID
                    )

                val extras = intent.extras
                if (extras == null) {
                    logger.logTopLevelServiceStateBroadcastMissingExtras(subId)
                    return@broadcastFlow null
                }

                val serviceState = ServiceState.newFromBundle(extras)
                logger.logTopLevelServiceStateBroadcastEmergencyOnly(subId, serviceState)
                // Only emit if the subId is not associated with an active subscription
                if (subId == INVALID_SUBSCRIPTION_ID) {
                    // Assume that -1 here is the device's service state. We don't care about
                    // other ones.
                    ServiceStateModel.fromServiceState(serviceState)
                } else {
                    null
                    Unit
                }
            }
            .filterNotNull()
            .stateIn(scope, SharingStarted.Eagerly, null)
            // Emit on start so that we always check the state at least once
            .onStart { emit(Unit) }

    /** Eager flow to determine the device-based emergency calls only state */
    override val isDeviceEmergencyCallCapable: StateFlow<Boolean> =
        serviceStateChangedEvent
            .mapLatest {
                val modems = telephonyManager.activeModemCount
                // Check the service state for every modem. If any state reports emergency calling
                // capable, then consider the device to have emergency call capabilities
                (0..<modems)
                    .map { telephonyManager.getServiceStateForSlot(it) }
                    .any { it?.isEmergencyOnly == true }
            }
            .flowOn(bgDispatcher)
            .distinctUntilChanged()
            .logDiffsForTable(
                tableLogger,
                columnPrefix = LOGGING_PREFIX,
                columnName = "deviceEmergencyOnly",
                initialValue = false,
            )
            .stateIn(scope, SharingStarted.Eagerly, false)

    /**
     * State flow that emits the set of mobile data subscriptions, each represented by its own
Loading