SubscriptionRepository.activeSubscriptionIdListFlow

Bug: 328293508
Flag: EXEMPT refactor
Test: manual - on Mobile Settings
Test: unit test
Change-Id: I63a86569f4fa3a27bd38d9853f6141890d26b881
This commit is contained in:
Chaohui Wang
2024-06-25 11:42:22 +08:00
parent 69f686828b
commit edc72a9b0f
5 changed files with 59 additions and 41 deletions

View File

@@ -25,14 +25,17 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class CallStateRepository(private val context: Context) { class CallStateRepository(
private val subscriptionManager = context.requireSubscriptionManager() private val context: Context,
private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context),
) {
/** Flow for call state of given [subId]. */ /** Flow for call state of given [subId]. */
fun callStateFlow(subId: Int): Flow<Int> = context.telephonyCallbackFlow(subId) { fun callStateFlow(subId: Int): Flow<Int> = 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. * @return true if any active subscription's call state is not idle.
*/ */
fun isInCallFlow(): Flow<Boolean> = context.subscriptionsChangedFlow() fun isInCallFlow(): Flow<Boolean> = subscriptionRepository.activeSubscriptionIdListFlow()
.flatMapLatest { .flatMapLatest { subIds ->
val subIds = subscriptionManager.activeSubscriptionIdList
if (subIds.isEmpty()) { if (subIds.isEmpty()) {
flowOf(false) flowOf(false)
} else { } else {
@@ -59,9 +61,10 @@ class CallStateRepository(private val context: Context) {
} }
} }
} }
.distinctUntilChanged()
.conflate() .conflate()
.flowOn(Dispatchers.Default)
.onEach { Log.d(TAG, "isInCallFlow: $it") } .onEach { Log.d(TAG, "isInCallFlow: $it") }
.flowOn(Dispatchers.Default)
private companion object { private companion object {
private const val TAG = "CallStateRepository" private const val TAG = "CallStateRepository"

View File

@@ -29,6 +29,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
@@ -68,20 +69,9 @@ class SubscriptionRepository(private val context: Context) {
} }
fun canDisablePhysicalSubscription() = subscriptionManager.canDisablePhysicalSubscription() fun canDisablePhysicalSubscription() = subscriptionManager.canDisablePhysicalSubscription()
}
val Context.subscriptionManager: SubscriptionManager?
get() = getSystemService(SubscriptionManager::class.java)
fun Context.requireSubscriptionManager(): SubscriptionManager = subscriptionManager!!
fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsChangedFlow().map {
SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo)
}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)
fun Context.subscriptionsChangedFlow() = callbackFlow {
val subscriptionManager = requireSubscriptionManager()
/** Flow for subscriptions changes. */
fun subscriptionsChangedFlow() = callbackFlow {
val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() { val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
override fun onSubscriptionsChanged() { override fun onSubscriptionsChanged() {
trySend(Unit) trySend(Unit)
@@ -96,6 +86,27 @@ fun Context.subscriptionsChangedFlow() = callbackFlow {
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) } awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
}.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default) }.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default)
/** Flow of active subscription ids. */
fun activeSubscriptionIdListFlow(): Flow<List<Int>> = context.subscriptionsChangedFlow()
.map { subscriptionManager.activeSubscriptionIdList.sorted() }
.distinctUntilChanged()
.conflate()
.onEach { Log.d(TAG, "activeSubscriptionIdList: $it") }
.flowOn(Dispatchers.Default)
}
val Context.subscriptionManager: SubscriptionManager?
get() = getSystemService(SubscriptionManager::class.java)
fun Context.requireSubscriptionManager(): SubscriptionManager = subscriptionManager!!
fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsChangedFlow().map {
SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo)
}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)
fun Context.subscriptionsChangedFlow(): Flow<Unit> =
SubscriptionRepository(this).subscriptionsChangedFlow()
/** /**
* Return a list of subscriptions that are available and visible to the user. * Return a list of subscriptions that are available and visible to the user.
* *

View File

@@ -25,10 +25,9 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.R import com.android.settings.R
import com.android.settings.network.telephony.MobileDataRepository 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.ims.ImsMmTelRepositoryImpl
import com.android.settings.network.telephony.requireSubscriptionManager
import com.android.settings.network.telephony.safeGetConfig 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.network.telephony.telephonyManager
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -48,7 +47,7 @@ class CrossSimCallingViewModel(
private val application: Application, private val application: Application,
) : AndroidViewModel(application) { ) : AndroidViewModel(application) {
private val subscriptionManager = application.requireSubscriptionManager() private val subscriptionRepository = SubscriptionRepository(application)
private val carrierConfigManager = private val carrierConfigManager =
application.getSystemService(CarrierConfigManager::class.java)!! application.getSystemService(CarrierConfigManager::class.java)!!
private val scope = viewModelScope + Dispatchers.Default private val scope = viewModelScope + Dispatchers.Default
@@ -59,9 +58,8 @@ class CrossSimCallingViewModel(
init { init {
val resources = application.resources val resources = application.resources
if (resources.getBoolean(R.bool.config_auto_data_switch_enables_cross_sim_calling)) { if (resources.getBoolean(R.bool.config_auto_data_switch_enables_cross_sim_calling)) {
application.subscriptionsChangedFlow() subscriptionRepository.activeSubscriptionIdListFlow()
.flatMapLatest { .flatMapLatest { activeSubIds ->
val activeSubIds = subscriptionManager.activeSubscriptionIdList.toList()
merge( merge(
activeSubIds.anyMobileDataEnableChangedFlow(), activeSubIds.anyMobileDataEnableChangedFlow(),
updateChannel.receiveAsFlow(), updateChannel.receiveAsFlow(),

View File

@@ -17,7 +17,6 @@
package com.android.settings.network.telephony package com.android.settings.network.telephony
import android.content.Context import android.content.Context
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import androidx.test.core.app.ApplicationProvider 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 com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -49,20 +49,15 @@ class CallStateRepositoryTest {
} }
} }
private val mockSubscriptionManager = mock<SubscriptionManager> { private val mockSubscriptionRepository = mock<SubscriptionRepository> {
on { activeSubscriptionIdList } doReturn intArrayOf(SUB_ID) on { activeSubscriptionIdListFlow() } doReturn flowOf(listOf(SUB_ID))
on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer {
val listener = it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener
listener.onSubscriptionsChanged()
}
} }
private val context: Context = spy(ApplicationProvider.getApplicationContext()) { private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
on { subscriptionManager } doReturn mockSubscriptionManager
} }
private val repository = CallStateRepository(context) private val repository = CallStateRepository(context, mockSubscriptionRepository)
@Test @Test
fun callStateFlow_initial_sendInitialState() = runBlocking { fun callStateFlow_initial_sendInitialState() = runBlocking {
@@ -89,8 +84,8 @@ class CallStateRepositoryTest {
@Test @Test
fun isInCallFlow_noActiveSubscription() = runBlocking { fun isInCallFlow_noActiveSubscription() = runBlocking {
mockSubscriptionManager.stub { mockSubscriptionRepository.stub {
on { activeSubscriptionIdList } doReturn intArrayOf() on { activeSubscriptionIdListFlow() } doReturn flowOf(emptyList())
} }
val isInCall = repository.isInCallFlow().firstWithTimeoutOrNull() val isInCall = repository.isInCallFlow().firstWithTimeoutOrNull()

View File

@@ -77,7 +77,7 @@ class SubscriptionRepositoryTest {
@Test @Test
fun subscriptionsChangedFlow_hasInitialValue() = runBlocking { fun subscriptionsChangedFlow_hasInitialValue() = runBlocking {
val initialValue = context.subscriptionsChangedFlow().firstWithTimeoutOrNull() val initialValue = repository.subscriptionsChangedFlow().firstWithTimeoutOrNull()
assertThat(initialValue).isSameInstanceAs(Unit) assertThat(initialValue).isSameInstanceAs(Unit)
} }
@@ -85,7 +85,7 @@ class SubscriptionRepositoryTest {
@Test @Test
fun subscriptionsChangedFlow_changed() = runBlocking { fun subscriptionsChangedFlow_changed() = runBlocking {
val listDeferred = async { val listDeferred = async {
context.subscriptionsChangedFlow().toListWithTimeout() repository.subscriptionsChangedFlow().toListWithTimeout()
} }
delay(100) delay(100)
@@ -94,6 +94,17 @@ class SubscriptionRepositoryTest {
assertThat(listDeferred.await()).hasSize(2) 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 @Test
fun getSelectableSubscriptionInfoList_sortedBySimSlotIndex() { fun getSelectableSubscriptionInfoList_sortedBySimSlotIndex() {
mockSubscriptionManager.stub { mockSubscriptionManager.stub {