Create ImsFeatureRepository

To be shared with video calling and VoLTE features.

Bug: 233327342
Flag: EXEMPT bug fix
Test: manual - on Mobile Settings
Test: atest ImsFeatureRepositoryTest
Change-Id: Ic7bcb532c4bd32c6f7ac4af1eebdd8a70a86ff29
This commit is contained in:
Chaohui Wang
2024-08-16 12:56:36 +08:00
parent 6b25f15fe6
commit 37693f29f1
5 changed files with 195 additions and 33 deletions

View File

@@ -0,0 +1,61 @@
/*
* 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.settings.network.telephony.ims
import android.content.Context
import android.telephony.AccessNetworkConstants.TransportType
import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability
import android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech
import com.android.settings.network.telephony.subscriptionsChangedFlow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
/**
* A repository for the IMS feature.
*
* @throws IllegalArgumentException if the [subId] is invalid.
*/
@OptIn(ExperimentalCoroutinesApi::class)
class ImsFeatureRepository(
private val context: Context,
private val subId: Int,
private val provisioningRepository: ProvisioningRepository = ProvisioningRepository(context),
private val imsMmTelRepository: ImsMmTelRepository = ImsMmTelRepositoryImpl(context, subId)
) {
/**
* A cold flow that determines the provisioning status for the specified IMS MmTel capability,
* and whether or not the requested MmTel capability is supported by the carrier on the
* specified network transport.
*
* @return true if the feature is provisioned and supported, false otherwise.
*/
fun isReadyFlow(
@MmTelCapability capability: Int,
@ImsRegistrationTech tech: Int,
@TransportType transportType: Int,
): Flow<Boolean> =
context.subscriptionsChangedFlow().flatMapLatest {
combine(
provisioningRepository.imsFeatureProvisionedFlow(subId, capability, tech),
imsMmTelRepository.isSupportedFlow(capability, transportType),
) { imsFeatureProvisioned, isSupported ->
imsFeatureProvisioned && isSupported
}
}
}

View File

@@ -36,6 +36,7 @@ import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -47,6 +48,11 @@ interface ImsMmTelRepository {
fun imsReadyFlow(): Flow<Boolean>
fun isSupportedFlow(
@MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
@AccessNetworkConstants.TransportType transportType: Int,
): Flow<Boolean>
suspend fun isSupported(
@MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
@AccessNetworkConstants.TransportType transportType: Int,
@@ -55,6 +61,11 @@ interface ImsMmTelRepository {
suspend fun setCrossSimCallingEnabled(enabled: Boolean)
}
/**
* A repository for the IMS MMTel.
*
* @throws IllegalArgumentException if the [subId] is invalid.
*/
class ImsMmTelRepositoryImpl(
context: Context,
private val subId: Int,
@@ -126,8 +137,12 @@ class ImsMmTelRepositoryImpl(
awaitClose { imsMmTelManager.unregisterImsStateCallback(callback) }
}.catch { e ->
Log.w(TAG, "[$subId] error while imsReadyFlow", e)
emit(false)
}.conflate().flowOn(Dispatchers.Default)
override fun isSupportedFlow(capability: Int, transportType: Int): Flow<Boolean> =
imsReadyFlow().map { imsReady -> imsReady && isSupported(capability, transportType) }
override suspend fun isSupported(
@MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
@AccessNetworkConstants.TransportType transportType: Int,

View File

@@ -20,24 +20,17 @@ import android.content.Context
import android.telephony.AccessNetworkConstants
import android.telephony.CarrierConfigManager
import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
import android.telephony.SubscriptionManager
import android.telephony.ims.ImsMmTelManager.WiFiCallingMode
import android.telephony.ims.feature.MmTelFeature
import android.telephony.ims.stub.ImsRegistrationImplBase
import androidx.lifecycle.LifecycleOwner
import com.android.settings.network.telephony.ims.ImsFeatureRepository
import com.android.settings.network.telephony.ims.ImsMmTelRepository
import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
import com.android.settings.network.telephony.ims.ProvisioningRepository
import com.android.settings.network.telephony.subscriptionsChangedFlow
import com.android.settings.network.telephony.telephonyManager
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
interface IWifiCallingRepository {
@@ -50,11 +43,11 @@ class WifiCallingRepository
constructor(
private val context: Context,
private val subId: Int,
private val imsMmTelRepository: ImsMmTelRepository = ImsMmTelRepositoryImpl(context, subId)
private val imsFeatureRepository: ImsFeatureRepository = ImsFeatureRepository(context, subId),
private val imsMmTelRepository: ImsMmTelRepository = ImsMmTelRepositoryImpl(context, subId),
) : IWifiCallingRepository {
private val telephonyManager = context.telephonyManager(subId)
private val provisioningRepository = ProvisioningRepository(context)
private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
@WiFiCallingMode
@@ -76,28 +69,12 @@ constructor(
wifiCallingReadyFlow().collectLatestWithLifecycle(lifecycleOwner, action = action)
}
@OptIn(ExperimentalCoroutinesApi::class)
fun wifiCallingReadyFlow(): Flow<Boolean> {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
return context.subscriptionsChangedFlow().flatMapLatest {
combine(
provisioningRepository.imsFeatureProvisionedFlow(
subId = subId,
fun wifiCallingReadyFlow(): Flow<Boolean> =
imsFeatureRepository.isReadyFlow(
capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
tech = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
),
isWifiCallingSupportedFlow(),
) { imsFeatureProvisioned, isWifiCallingSupported ->
imsFeatureProvisioned && isWifiCallingSupported
}
}
}
private fun isWifiCallingSupportedFlow(): Flow<Boolean> {
return imsMmTelRepository.imsReadyFlow().map { imsReady ->
imsReady && isWifiCallingSupported()
}
}
transportType = AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
)
suspend fun isWifiCallingSupported(): Boolean = withContext(Dispatchers.Default) {
imsMmTelRepository.isSupported(

View File

@@ -0,0 +1,108 @@
/*
* 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.settings.network.telephony.ims
import android.content.Context
import android.telephony.AccessNetworkConstants
import android.telephony.ims.feature.MmTelFeature
import android.telephony.ims.stub.ImsRegistrationImplBase
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class ImsFeatureRepositoryTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val mockProvisioningRepository = mock<ProvisioningRepository>()
private val mockImsMmTelRepository = mock<ImsMmTelRepository>()
@Test
fun isReadyFlow_notProvisioned_returnFalse() = runBlocking {
mockProvisioningRepository.stub {
onBlocking { imsFeatureProvisionedFlow(SUB_ID, CAPABILITY, TECH) } doReturn
flowOf(false)
}
val repository =
ImsFeatureRepository(
context = context,
subId = SUB_ID,
provisioningRepository = mockProvisioningRepository,
)
val isReady = repository.isReadyFlow(CAPABILITY, TECH, TRANSPORT_TYPE).first()
assertThat(isReady).isFalse()
}
@Test
fun isReadyFlow_notSupported_returnFalse() = runBlocking {
mockImsMmTelRepository.stub {
onBlocking { isSupportedFlow(CAPABILITY, TRANSPORT_TYPE) } doReturn flowOf(false)
}
val repository =
ImsFeatureRepository(
context = context,
subId = SUB_ID,
imsMmTelRepository = mockImsMmTelRepository,
)
val isReady = repository.isReadyFlow(CAPABILITY, TECH, TRANSPORT_TYPE).first()
assertThat(isReady).isFalse()
}
@Test
fun isReadyFlow_provisionedAndSupported_returnFalse() = runBlocking {
mockProvisioningRepository.stub {
onBlocking { imsFeatureProvisionedFlow(SUB_ID, CAPABILITY, TECH) } doReturn flowOf(true)
}
mockImsMmTelRepository.stub {
onBlocking { isSupportedFlow(CAPABILITY, TRANSPORT_TYPE) } doReturn flowOf(true)
}
val repository =
ImsFeatureRepository(
context = context,
subId = SUB_ID,
provisioningRepository = mockProvisioningRepository,
imsMmTelRepository = mockImsMmTelRepository,
)
val isReady = repository.isReadyFlow(CAPABILITY, TECH, TRANSPORT_TYPE).first()
assertThat(isReady).isTrue()
}
private companion object {
const val SUB_ID = 10
const val CAPABILITY = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE
const val TECH = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
const val TRANSPORT_TYPE = AccessNetworkConstants.TRANSPORT_TYPE_WLAN
}
}

View File

@@ -55,7 +55,8 @@ class WifiCallingRepositoryTest {
on { getWiFiCallingMode(any()) } doReturn ImsMmTelManager.WIFI_MODE_UNKNOWN
}
private val repository = WifiCallingRepository(context, SUB_ID, mockImsMmTelRepository)
private val repository =
WifiCallingRepository(context, SUB_ID, imsMmTelRepository = mockImsMmTelRepository)
@Test
fun getWiFiCallingMode_roamingAndNotUseWfcHomeModeForRoaming_returnRoamingSetting() {