Loading src/com/android/settings/network/telephony/SubscriptionRepository.kt +56 −14 Original line number Diff line number Diff line Loading @@ -23,11 +23,13 @@ import android.util.Log import androidx.lifecycle.LifecycleOwner import com.android.settings.network.SubscriptionUtil import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.distinctUntilChanged Loading @@ -36,6 +38,8 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn private const val TAG = "SubscriptionRepository" Loading Loading @@ -132,20 +136,7 @@ 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) fun subscriptionsChangedFlow() = getSharedSubscriptionsChangedFlow(context) /** Flow of active subscription ids. */ fun activeSubscriptionIdListFlow(): Flow<List<Int>> = Loading @@ -172,6 +163,57 @@ class SubscriptionRepository(private val context: Context) { flowOf(null) } } companion object { private lateinit var SharedSubscriptionsChangedFlow: Flow<Unit> private fun getSharedSubscriptionsChangedFlow(context: Context): Flow<Unit> { if (!this::SharedSubscriptionsChangedFlow.isInitialized) { SharedSubscriptionsChangedFlow = context.applicationContext .requireSubscriptionManager() .subscriptionsChangedFlow() .shareIn( scope = CoroutineScope(Dispatchers.Default), started = SharingStarted.WhileSubscribed(), replay = 1, ) } return SharedSubscriptionsChangedFlow } /** * Flow for subscriptions changes. * * Note: Even the SubscriptionManager.addOnSubscriptionsChangedListener's doc says the * SubscriptionManager.OnSubscriptionsChangedListener.onSubscriptionsChanged() method will * also be invoked once initially when calling it, there still case that the * onSubscriptionsChanged() method is not invoked initially. For example, when the * onSubscriptionsChanged event never happens before, on a device never ever has any * subscriptions. */ private fun SubscriptionManager.subscriptionsChangedFlow() = callbackFlow { val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() { override fun onSubscriptionsChanged() { trySend(Unit) } override fun onAddListenerFailed() { close() } } addOnSubscriptionsChangedListener(Dispatchers.Default.asExecutor(), listener) awaitClose { removeOnSubscriptionsChangedListener(listener) } } .onStart { emit(Unit) } // Ensure this flow is never empty .conflate() .onEach { Log.d(TAG, "subscriptions changed") } .flowOn(Dispatchers.Default) } } val Context.subscriptionManager: SubscriptionManager? Loading tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt +5 −0 Original line number Diff line number Diff line Loading @@ -62,10 +62,15 @@ class MobileNetworkSwitchControllerTest { on { isSubscriptionEnabledFlow(SUB_ID) } doReturn flowOf(false) } private val mockSubscriptionActivationRepository = mock<SubscriptionActivationRepository> { on { isActivationChangeableFlow() } doReturn flowOf(true) } private val controller = MobileNetworkSwitchController( context = context, preferenceKey = TEST_KEY, subscriptionRepository = mockSubscriptionRepository, subscriptionActivationRepository = mockSubscriptionActivationRepository, ).apply { init(SUB_ID) } @Test Loading tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt +17 −1 Original line number Diff line number Diff line Loading @@ -91,7 +91,23 @@ class SubscriptionRepositoryTest { subInfoListener?.onSubscriptionsChanged() assertThat(listDeferred.await()).hasSize(2) assertThat(listDeferred.await().size).isAtLeast(2) } @Test fun subscriptionsChangedFlow_managerNotCallOnSubscriptionsChangedInitially() = runBlocking { mockSubscriptionManager.stub { on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer { subInfoListener = it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener // not call onSubscriptionsChanged here } } val initialValue = repository.subscriptionsChangedFlow().firstWithTimeoutOrNull() assertThat(initialValue).isSameInstanceAs(Unit) } @Test Loading Loading
src/com/android/settings/network/telephony/SubscriptionRepository.kt +56 −14 Original line number Diff line number Diff line Loading @@ -23,11 +23,13 @@ import android.util.Log import androidx.lifecycle.LifecycleOwner import com.android.settings.network.SubscriptionUtil import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.distinctUntilChanged Loading @@ -36,6 +38,8 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn private const val TAG = "SubscriptionRepository" Loading Loading @@ -132,20 +136,7 @@ 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) fun subscriptionsChangedFlow() = getSharedSubscriptionsChangedFlow(context) /** Flow of active subscription ids. */ fun activeSubscriptionIdListFlow(): Flow<List<Int>> = Loading @@ -172,6 +163,57 @@ class SubscriptionRepository(private val context: Context) { flowOf(null) } } companion object { private lateinit var SharedSubscriptionsChangedFlow: Flow<Unit> private fun getSharedSubscriptionsChangedFlow(context: Context): Flow<Unit> { if (!this::SharedSubscriptionsChangedFlow.isInitialized) { SharedSubscriptionsChangedFlow = context.applicationContext .requireSubscriptionManager() .subscriptionsChangedFlow() .shareIn( scope = CoroutineScope(Dispatchers.Default), started = SharingStarted.WhileSubscribed(), replay = 1, ) } return SharedSubscriptionsChangedFlow } /** * Flow for subscriptions changes. * * Note: Even the SubscriptionManager.addOnSubscriptionsChangedListener's doc says the * SubscriptionManager.OnSubscriptionsChangedListener.onSubscriptionsChanged() method will * also be invoked once initially when calling it, there still case that the * onSubscriptionsChanged() method is not invoked initially. For example, when the * onSubscriptionsChanged event never happens before, on a device never ever has any * subscriptions. */ private fun SubscriptionManager.subscriptionsChangedFlow() = callbackFlow { val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() { override fun onSubscriptionsChanged() { trySend(Unit) } override fun onAddListenerFailed() { close() } } addOnSubscriptionsChangedListener(Dispatchers.Default.asExecutor(), listener) awaitClose { removeOnSubscriptionsChangedListener(listener) } } .onStart { emit(Unit) } // Ensure this flow is never empty .conflate() .onEach { Log.d(TAG, "subscriptions changed") } .flowOn(Dispatchers.Default) } } val Context.subscriptionManager: SubscriptionManager? Loading
tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.kt +5 −0 Original line number Diff line number Diff line Loading @@ -62,10 +62,15 @@ class MobileNetworkSwitchControllerTest { on { isSubscriptionEnabledFlow(SUB_ID) } doReturn flowOf(false) } private val mockSubscriptionActivationRepository = mock<SubscriptionActivationRepository> { on { isActivationChangeableFlow() } doReturn flowOf(true) } private val controller = MobileNetworkSwitchController( context = context, preferenceKey = TEST_KEY, subscriptionRepository = mockSubscriptionRepository, subscriptionActivationRepository = mockSubscriptionActivationRepository, ).apply { init(SUB_ID) } @Test Loading
tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt +17 −1 Original line number Diff line number Diff line Loading @@ -91,7 +91,23 @@ class SubscriptionRepositoryTest { subInfoListener?.onSubscriptionsChanged() assertThat(listDeferred.await()).hasSize(2) assertThat(listDeferred.await().size).isAtLeast(2) } @Test fun subscriptionsChangedFlow_managerNotCallOnSubscriptionsChangedInitially() = runBlocking { mockSubscriptionManager.stub { on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer { subInfoListener = it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener // not call onSubscriptionsChanged here } } val initialValue = repository.subscriptionsChangedFlow().firstWithTimeoutOrNull() assertThat(initialValue).isSameInstanceAs(Unit) } @Test Loading