Merge "Create SubscriptionRepository.phoneNumberFlow" into main
diff --git a/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt b/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt
index 10a8b53..db16acd 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt
+++ b/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt
@@ -17,49 +17,37 @@
package com.android.settings.network.telephony
import android.content.Context
-import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
-import android.util.Log
-import androidx.annotation.VisibleForTesting
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.android.settings.R
import com.android.settings.flags.Flags
-import com.android.settings.network.SubscriptionInfoListViewModel
import com.android.settings.network.SubscriptionUtil
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-/**
- * Preference controller for "Phone number"
- */
-class MobileNetworkPhoneNumberPreferenceController(context: Context, key: String) :
- TelephonyBasePreferenceController(context, key) {
+/** Preference controller for "Phone number" */
+class MobileNetworkPhoneNumberPreferenceController
+@JvmOverloads
+constructor(
+ context: Context,
+ key: String,
+ private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context),
+) : TelephonyBasePreferenceController(context, key) {
- private lateinit var lazyViewModel: Lazy<SubscriptionInfoListViewModel>
private lateinit var preference: Preference
- private var phoneNumber = String()
-
- fun init(fragment: Fragment, subId: Int) {
- lazyViewModel = fragment.viewModels()
+ fun init(subId: Int) {
mSubId = subId
}
- override fun getAvailabilityStatus(subId: Int): Int = when {
- !Flags.isDualSimOnboardingEnabled() -> CONDITIONALLY_UNAVAILABLE
- SubscriptionManager.isValidSubscriptionId(subId)
- && SubscriptionUtil.isSimHardwareVisible(mContext) -> AVAILABLE
- else -> CONDITIONALLY_UNAVAILABLE
- }
+ override fun getAvailabilityStatus(subId: Int): Int =
+ when {
+ !Flags.isDualSimOnboardingEnabled() -> CONDITIONALLY_UNAVAILABLE
+ SubscriptionManager.isValidSubscriptionId(subId) &&
+ SubscriptionUtil.isSimHardwareVisible(mContext) -> AVAILABLE
+ else -> CONDITIONALLY_UNAVAILABLE
+ }
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
@@ -67,51 +55,10 @@
}
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
- if (!this::lazyViewModel.isInitialized) {
- Log.e(
- this.javaClass.simpleName,
- "lateinit property lazyViewModel has not been initialized"
- )
- return
- }
- val viewModel by lazyViewModel
- val coroutineScope = viewLifecycleOwner.lifecycleScope
-
- viewModel.subscriptionInfoListFlow
- .map { subscriptionInfoList ->
- subscriptionInfoList
- .firstOrNull { subInfo -> subInfo.subscriptionId == mSubId }
+ subscriptionRepository.phoneNumberFlow(mSubId).collectLatestWithLifecycle(
+ viewLifecycleOwner) { phoneNumber ->
+ preference.summary = phoneNumber ?: getStringUnknown()
}
- .flowOn(Dispatchers.Default)
- .collectLatestWithLifecycle(viewLifecycleOwner) {
- it?.let {
- coroutineScope.launch {
- refreshData(it)
- }
- }
- }
- }
-
- @VisibleForTesting
- suspend fun refreshData(subscriptionInfo: SubscriptionInfo){
- withContext(Dispatchers.Default) {
- phoneNumber = getFormattedPhoneNumber(subscriptionInfo)
- }
- refreshUi()
- }
-
- private fun refreshUi(){
- preference.summary = phoneNumber
- }
-
- private fun getFormattedPhoneNumber(subscriptionInfo: SubscriptionInfo?): String {
- val phoneNumber = SubscriptionUtil.getBidiFormattedPhoneNumber(
- mContext,
- subscriptionInfo
- )
- return phoneNumber
- ?.let { return it.ifEmpty { getStringUnknown() } }
- ?: getStringUnknown()
}
private fun getStringUnknown(): String {
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index 896eac6..9db5af2 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -257,7 +257,7 @@
use(NrDisabledInDsdsFooterPreferenceController.class).init(mSubId);
use(MobileNetworkSpnPreferenceController.class).init(this, mSubId);
- use(MobileNetworkPhoneNumberPreferenceController.class).init(this, mSubId);
+ use(MobileNetworkPhoneNumberPreferenceController.class).init(mSubId);
use(MobileNetworkImeiPreferenceController.class).init(this, mSubId);
final MobileDataPreferenceController mobileDataPreferenceController =
diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
index c952310..cc8c8b4 100644
--- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt
+++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
@@ -24,13 +24,14 @@
import com.android.settings.network.SubscriptionUtil
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
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.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -52,7 +53,7 @@
/** Flow of whether the subscription enabled for the given [subId]. */
fun isSubscriptionEnabledFlow(subId: Int): Flow<Boolean> {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
- return context.subscriptionsChangedFlow()
+ return subscriptionsChangedFlow()
.map { subscriptionManager.isSubscriptionEnabled(subId) }
.conflate()
.onEach { Log.d(TAG, "[$subId] isSubscriptionEnabledFlow: $it") }
@@ -87,12 +88,30 @@
}.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)
+ fun activeSubscriptionIdListFlow(): Flow<List<Int>> =
+ subscriptionsChangedFlow()
+ .map { subscriptionManager.activeSubscriptionIdList.sorted() }
+ .distinctUntilChanged()
+ .conflate()
+ .onEach { Log.d(TAG, "activeSubscriptionIdList: $it") }
+ .flowOn(Dispatchers.Default)
+
+ fun activeSubscriptionInfoFlow(subId: Int): Flow<SubscriptionInfo?> =
+ subscriptionsChangedFlow()
+ .map { subscriptionManager.getActiveSubscriptionInfo(subId) }
+ .distinctUntilChanged()
+ .conflate()
+ .flowOn(Dispatchers.Default)
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ fun phoneNumberFlow(subId: Int): Flow<String?> =
+ activeSubscriptionInfoFlow(subId).flatMapLatest { subInfo ->
+ if (subInfo != null) {
+ context.phoneNumberFlow(subInfo)
+ } else {
+ flowOf(null)
+ }
+ }
}
val Context.subscriptionManager: SubscriptionManager?
@@ -100,9 +119,12 @@
fun Context.requireSubscriptionManager(): SubscriptionManager = subscriptionManager!!
-fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsChangedFlow().map {
- SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo)
-}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)
+fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo): Flow<String?> =
+ subscriptionsChangedFlow()
+ .map { SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo) }
+ .distinctUntilChanged()
+ .conflate()
+ .flowOn(Dispatchers.Default)
fun Context.subscriptionsChangedFlow(): Flow<Unit> =
SubscriptionRepository(this).subscriptionsChangedFlow()
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt
index 38c47c2..f56c0c4 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt
@@ -17,8 +17,7 @@
package com.android.settings.network.telephony
import android.content.Context
-import android.telephony.SubscriptionInfo
-import androidx.fragment.app.Fragment
+import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import androidx.test.core.app.ApplicationProvider
@@ -26,17 +25,19 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.settings.R
import com.android.settings.core.BasePreferenceController
-import com.android.settings.network.SubscriptionInfoListViewModel
import com.android.settings.network.SubscriptionUtil
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoSession
-import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -44,29 +45,25 @@
class MobileNetworkPhoneNumberPreferenceControllerTest {
private lateinit var mockSession: MockitoSession
- private val mockViewModels = mock<Lazy<SubscriptionInfoListViewModel>>()
- private val mockFragment = mock<Fragment>{
- val viewmodel = mockViewModels
- }
-
- private var mockPhoneNumber = String()
private val context: Context = ApplicationProvider.getApplicationContext()
- private val controller = MobileNetworkPhoneNumberPreferenceController(context, TEST_KEY)
+ private val mockSubscriptionRepository = mock<SubscriptionRepository>()
+
+ private val controller =
+ MobileNetworkPhoneNumberPreferenceController(context, TEST_KEY, mockSubscriptionRepository)
private val preference = Preference(context).apply { key = TEST_KEY }
private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
@Before
fun setUp() {
- mockSession = ExtendedMockito.mockitoSession()
- .initMocks(this)
- .mockStatic(SubscriptionUtil::class.java)
- .strictness(Strictness.LENIENT)
- .startMocking()
+ mockSession =
+ ExtendedMockito.mockitoSession()
+ .mockStatic(SubscriptionUtil::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
preferenceScreen.addPreference(preference)
+ controller.init(SUB_ID)
controller.displayPreference(preferenceScreen)
-
- whenever(SubscriptionUtil.getBidiFormattedPhoneNumber(any(),any())).thenReturn(mockPhoneNumber)
}
@After
@@ -75,41 +72,29 @@
}
@Test
- fun refreshData_getEmptyPhoneNumber_preferenceIsNotVisible() = runBlocking {
+ fun onViewCreated_cannotGetPhoneNumber_displayUnknown() = runBlocking {
whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true)
- whenever(SubscriptionUtil.getActiveSubscriptions(any())).thenReturn(
- listOf(
- SUB_INFO_1,
- SUB_INFO_2
- )
- )
- var mockSubId = 2
- controller.init(mockFragment, mockSubId)
- mockPhoneNumber = String()
+ mockSubscriptionRepository.stub {
+ on { phoneNumberFlow(SUB_ID) } doReturn flowOf(null)
+ }
- controller.refreshData(SUB_INFO_2)
+ controller.onViewCreated(TestLifecycleOwner())
+ delay(100)
- assertThat(preference.summary).isEqualTo(
- context.getString(R.string.device_info_default))
+ assertThat(preference.summary).isEqualTo(context.getString(R.string.device_info_default))
}
@Test
- fun refreshData_getPhoneNumber_preferenceSummaryIsExpected() = runBlocking {
+ fun onViewCreated_canGetPhoneNumber_displayPhoneNumber() = runBlocking {
whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true)
- whenever(SubscriptionUtil.getActiveSubscriptions(any())).thenReturn(
- listOf(
- SUB_INFO_1,
- SUB_INFO_2
- )
- )
- var mockSubId = 2
- controller.init(mockFragment, mockSubId)
- mockPhoneNumber = "test phone number"
- whenever(SubscriptionUtil.getBidiFormattedPhoneNumber(any(),any())).thenReturn(mockPhoneNumber)
+ mockSubscriptionRepository.stub {
+ on { phoneNumberFlow(SUB_ID) } doReturn flowOf(PHONE_NUMBER)
+ }
- controller.refreshData(SUB_INFO_2)
+ controller.onViewCreated(TestLifecycleOwner())
+ delay(100)
- assertThat(preference.summary).isEqualTo(mockPhoneNumber)
+ assertThat(preference.summary).isEqualTo(PHONE_NUMBER)
}
@Test
@@ -123,18 +108,7 @@
private companion object {
const val TEST_KEY = "test_key"
- const val DISPLAY_NAME_1 = "Sub 1"
- const val DISPLAY_NAME_2 = "Sub 2"
-
- val SUB_INFO_1: SubscriptionInfo = SubscriptionInfo.Builder().apply {
- setId(1)
- setDisplayName(DISPLAY_NAME_1)
- }.build()
-
- val SUB_INFO_2: SubscriptionInfo = SubscriptionInfo.Builder().apply {
- setId(2)
- setDisplayName(DISPLAY_NAME_2)
- }.build()
-
+ const val SUB_ID = 10
+ const val PHONE_NUMBER = "1234567890"
}
}
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 75c9aa1..f75c14a 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
@@ -204,6 +204,22 @@
assertThat(phoneNumber).isEqualTo(NUMBER_1)
}
+ @Test
+ fun phoneNumberFlow_withSubId() = runBlocking {
+ val subInfo = SubscriptionInfo.Builder().apply {
+ setId(SUB_ID_IN_SLOT_1)
+ setMcc(MCC)
+ }.build()
+ mockSubscriptionManager.stub {
+ on { getActiveSubscriptionInfo(SUB_ID_IN_SLOT_1) } doReturn subInfo
+ on { getPhoneNumber(SUB_ID_IN_SLOT_1) } doReturn NUMBER_1
+ }
+
+ val phoneNumber = repository.phoneNumberFlow(SUB_ID_IN_SLOT_1).firstWithTimeoutOrNull()
+
+ assertThat(phoneNumber).isEqualTo(NUMBER_1)
+ }
+
private companion object {
const val SIM_SLOT_INDEX_0 = 0
const val SUB_ID_IN_SLOT_0 = 2