diff --git a/src/com/android/settings/network/telephony/ims/ImsFeatureRepository.kt b/src/com/android/settings/network/telephony/ims/ImsFeatureRepository.kt new file mode 100644 index 00000000000..ba332574819 --- /dev/null +++ b/src/com/android/settings/network/telephony/ims/ImsFeatureRepository.kt @@ -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 = + context.subscriptionsChangedFlow().flatMapLatest { + combine( + provisioningRepository.imsFeatureProvisionedFlow(subId, capability, tech), + imsMmTelRepository.isSupportedFlow(capability, transportType), + ) { imsFeatureProvisioned, isSupported -> + imsFeatureProvisioned && isSupported + } + } +} diff --git a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt index 9bc10e555e1..c5d1200a1d4 100644 --- a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt +++ b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt @@ -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 + fun isSupportedFlow( + @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int, + @AccessNetworkConstants.TransportType transportType: Int, + ): Flow + 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 = + imsReadyFlow().map { imsReady -> imsReady && isSupported(capability, transportType) } + override suspend fun isSupported( @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int, @AccessNetworkConstants.TransportType transportType: Int, diff --git a/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt index 04e687c2d7f..6af0559adb1 100644 --- a/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt +++ b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt @@ -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 { - if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false) - return context.subscriptionsChangedFlow().flatMapLatest { - combine( - provisioningRepository.imsFeatureProvisionedFlow( - subId = subId, - capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE, - tech = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, - ), - isWifiCallingSupportedFlow(), - ) { imsFeatureProvisioned, isWifiCallingSupported -> - imsFeatureProvisioned && isWifiCallingSupported - } - } - } - - private fun isWifiCallingSupportedFlow(): Flow { - return imsMmTelRepository.imsReadyFlow().map { imsReady -> - imsReady && isWifiCallingSupported() - } - } + fun wifiCallingReadyFlow(): Flow = + imsFeatureRepository.isReadyFlow( + capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE, + tech = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, + transportType = AccessNetworkConstants.TRANSPORT_TYPE_WLAN, + ) suspend fun isWifiCallingSupported(): Boolean = withContext(Dispatchers.Default) { imsMmTelRepository.isSupported( diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureRepositoryTest.kt new file mode 100644 index 00000000000..3f72b2c0dc4 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureRepositoryTest.kt @@ -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() + private val mockImsMmTelRepository = mock() + + @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 + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt index 0144f6669cb..f0a23eb92c9 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt @@ -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() {