diff --git a/src/com/android/settings/network/telephony/CallStateRepository.kt b/src/com/android/settings/network/telephony/CallStateRepository.kt index 4b6cdc83975..e5a21bf2292 100644 --- a/src/com/android/settings/network/telephony/CallStateRepository.kt +++ b/src/com/android/settings/network/telephony/CallStateRepository.kt @@ -25,14 +25,17 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onEach @OptIn(ExperimentalCoroutinesApi::class) -class CallStateRepository(private val context: Context) { - private val subscriptionManager = context.requireSubscriptionManager() +class CallStateRepository( + private val context: Context, + private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context), +) { /** Flow for call state of given [subId]. */ fun callStateFlow(subId: Int): Flow = context.telephonyCallbackFlow(subId) { @@ -48,9 +51,8 @@ class CallStateRepository(private val context: Context) { * * @return true if any active subscription's call state is not idle. */ - fun isInCallFlow(): Flow = context.subscriptionsChangedFlow() - .flatMapLatest { - val subIds = subscriptionManager.activeSubscriptionIdList + fun isInCallFlow(): Flow = subscriptionRepository.activeSubscriptionIdListFlow() + .flatMapLatest { subIds -> if (subIds.isEmpty()) { flowOf(false) } else { @@ -59,9 +61,10 @@ class CallStateRepository(private val context: Context) { } } } + .distinctUntilChanged() .conflate() - .flowOn(Dispatchers.Default) .onEach { Log.d(TAG, "isInCallFlow: $it") } + .flowOn(Dispatchers.Default) private companion object { private const val TAG = "CallStateRepository" diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt index 3ee854843fc..c95231041d0 100644 --- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt +++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt @@ -29,6 +29,7 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn @@ -68,6 +69,30 @@ class SubscriptionRepository(private val context: Context) { } fun canDisablePhysicalSubscription() = subscriptionManager.canDisablePhysicalSubscription() + + /** Flow for subscriptions changes. */ + fun subscriptionsChangedFlow() = callbackFlow { + val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() { + override fun onSubscriptionsChanged() { + trySend(Unit) + } + } + + subscriptionManager.addOnSubscriptionsChangedListener( + Dispatchers.Default.asExecutor(), + listener, + ) + + awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) } + }.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default) + + /** Flow of active subscription ids. */ + fun activeSubscriptionIdListFlow(): Flow> = context.subscriptionsChangedFlow() + .map { subscriptionManager.activeSubscriptionIdList.sorted() } + .distinctUntilChanged() + .conflate() + .onEach { Log.d(TAG, "activeSubscriptionIdList: $it") } + .flowOn(Dispatchers.Default) } val Context.subscriptionManager: SubscriptionManager? @@ -79,22 +104,8 @@ fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsC SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo) }.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default) -fun Context.subscriptionsChangedFlow() = callbackFlow { - val subscriptionManager = requireSubscriptionManager() - - val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() { - override fun onSubscriptionsChanged() { - trySend(Unit) - } - } - - subscriptionManager.addOnSubscriptionsChangedListener( - Dispatchers.Default.asExecutor(), - listener, - ) - - awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) } -}.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default) +fun Context.subscriptionsChangedFlow(): Flow = + SubscriptionRepository(this).subscriptionsChangedFlow() /** * Return a list of subscriptions that are available and visible to the user. diff --git a/src/com/android/settings/network/telephony/wificalling/CrossSimCallingViewModel.kt b/src/com/android/settings/network/telephony/wificalling/CrossSimCallingViewModel.kt index fb0bd82a7d4..170af548ad1 100644 --- a/src/com/android/settings/network/telephony/wificalling/CrossSimCallingViewModel.kt +++ b/src/com/android/settings/network/telephony/wificalling/CrossSimCallingViewModel.kt @@ -25,10 +25,9 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.android.settings.R import com.android.settings.network.telephony.MobileDataRepository +import com.android.settings.network.telephony.SubscriptionRepository import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl -import com.android.settings.network.telephony.requireSubscriptionManager import com.android.settings.network.telephony.safeGetConfig -import com.android.settings.network.telephony.subscriptionsChangedFlow import com.android.settings.network.telephony.telephonyManager import com.android.settings.overlay.FeatureFactory.Companion.featureFactory import kotlinx.coroutines.Dispatchers @@ -48,7 +47,7 @@ class CrossSimCallingViewModel( private val application: Application, ) : AndroidViewModel(application) { - private val subscriptionManager = application.requireSubscriptionManager() + private val subscriptionRepository = SubscriptionRepository(application) private val carrierConfigManager = application.getSystemService(CarrierConfigManager::class.java)!! private val scope = viewModelScope + Dispatchers.Default @@ -59,9 +58,8 @@ class CrossSimCallingViewModel( init { val resources = application.resources if (resources.getBoolean(R.bool.config_auto_data_switch_enables_cross_sim_calling)) { - application.subscriptionsChangedFlow() - .flatMapLatest { - val activeSubIds = subscriptionManager.activeSubscriptionIdList.toList() + subscriptionRepository.activeSubscriptionIdListFlow() + .flatMapLatest { activeSubIds -> merge( activeSubIds.anyMobileDataEnableChangedFlow(), updateChannel.receiveAsFlow(), diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/CallStateRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/CallStateRepositoryTest.kt index 55d520fd214..d192eb490b1 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/CallStateRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/CallStateRepositoryTest.kt @@ -17,7 +17,6 @@ package com.android.settings.network.telephony import android.content.Context -import android.telephony.SubscriptionManager import android.telephony.TelephonyCallback import android.telephony.TelephonyManager import androidx.test.core.app.ApplicationProvider @@ -27,6 +26,7 @@ import com.android.settingslib.spa.testutils.toListWithTimeout import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.async import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith @@ -49,20 +49,15 @@ class CallStateRepositoryTest { } } - private val mockSubscriptionManager = mock { - on { activeSubscriptionIdList } doReturn intArrayOf(SUB_ID) - on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer { - val listener = it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener - listener.onSubscriptionsChanged() - } + private val mockSubscriptionRepository = mock { + on { activeSubscriptionIdListFlow() } doReturn flowOf(listOf(SUB_ID)) } private val context: Context = spy(ApplicationProvider.getApplicationContext()) { on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager - on { subscriptionManager } doReturn mockSubscriptionManager } - private val repository = CallStateRepository(context) + private val repository = CallStateRepository(context, mockSubscriptionRepository) @Test fun callStateFlow_initial_sendInitialState() = runBlocking { @@ -89,8 +84,8 @@ class CallStateRepositoryTest { @Test fun isInCallFlow_noActiveSubscription() = runBlocking { - mockSubscriptionManager.stub { - on { activeSubscriptionIdList } doReturn intArrayOf() + mockSubscriptionRepository.stub { + on { activeSubscriptionIdListFlow() } doReturn flowOf(emptyList()) } val isInCall = repository.isInCallFlow().firstWithTimeoutOrNull() diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt index e233fa428a5..75c9aa14456 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt @@ -77,7 +77,7 @@ class SubscriptionRepositoryTest { @Test fun subscriptionsChangedFlow_hasInitialValue() = runBlocking { - val initialValue = context.subscriptionsChangedFlow().firstWithTimeoutOrNull() + val initialValue = repository.subscriptionsChangedFlow().firstWithTimeoutOrNull() assertThat(initialValue).isSameInstanceAs(Unit) } @@ -85,7 +85,7 @@ class SubscriptionRepositoryTest { @Test fun subscriptionsChangedFlow_changed() = runBlocking { val listDeferred = async { - context.subscriptionsChangedFlow().toListWithTimeout() + repository.subscriptionsChangedFlow().toListWithTimeout() } delay(100) @@ -94,6 +94,17 @@ class SubscriptionRepositoryTest { assertThat(listDeferred.await()).hasSize(2) } + @Test + fun activeSubscriptionIdListFlow(): Unit = runBlocking { + mockSubscriptionManager.stub { + on { activeSubscriptionIdList } doReturn intArrayOf(SUB_ID_IN_SLOT_0) + } + + val activeSubIds = repository.activeSubscriptionIdListFlow().firstWithTimeoutOrNull() + + assertThat(activeSubIds).containsExactly(SUB_ID_IN_SLOT_0) + } + @Test fun getSelectableSubscriptionInfoList_sortedBySimSlotIndex() { mockSubscriptionManager.stub {