diff --git a/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt b/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt index 10a8b53e5d4..db16acdfc59 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 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 @@ class MobileNetworkPhoneNumberPreferenceController(context: Context, key: String } 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 896eac6197a..9db5af2b152 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -257,7 +257,7 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme 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 c95231041d0..cc8c8b47b2d 100644 --- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt +++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt @@ -24,13 +24,14 @@ import androidx.lifecycle.LifecycleOwner 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 @@ class SubscriptionRepository(private val context: Context) { /** Flow of whether the subscription enabled for the given [subId]. */ fun isSubscriptionEnabledFlow(subId: Int): Flow { 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 @@ class SubscriptionRepository(private val context: Context) { }.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) + fun activeSubscriptionIdListFlow(): Flow> = + subscriptionsChangedFlow() + .map { subscriptionManager.activeSubscriptionIdList.sorted() } + .distinctUntilChanged() + .conflate() + .onEach { Log.d(TAG, "activeSubscriptionIdList: $it") } + .flowOn(Dispatchers.Default) + + fun activeSubscriptionInfoFlow(subId: Int): Flow = + subscriptionsChangedFlow() + .map { subscriptionManager.getActiveSubscriptionInfo(subId) } + .distinctUntilChanged() + .conflate() + .flowOn(Dispatchers.Default) + + @OptIn(ExperimentalCoroutinesApi::class) + fun phoneNumberFlow(subId: Int): Flow = + activeSubscriptionInfoFlow(subId).flatMapLatest { subInfo -> + if (subInfo != null) { + context.phoneNumberFlow(subInfo) + } else { + flowOf(null) + } + } } val Context.subscriptionManager: SubscriptionManager? @@ -100,9 +119,12 @@ val Context.subscriptionManager: SubscriptionManager? 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 = + subscriptionsChangedFlow() + .map { SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo) } + .distinctUntilChanged() + .conflate() + .flowOn(Dispatchers.Default) fun Context.subscriptionsChangedFlow(): Flow = 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 38c47c28ccc..f56c0c4b351 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 androidx.test.ext.junit.runners.AndroidJUnit4 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 @@ import org.mockito.quality.Strictness class MobileNetworkPhoneNumberPreferenceControllerTest { private lateinit var mockSession: MockitoSession - private val mockViewModels = mock>() - private val mockFragment = mock{ - val viewmodel = mockViewModels - } - - private var mockPhoneNumber = String() private val context: Context = ApplicationProvider.getApplicationContext() - private val controller = MobileNetworkPhoneNumberPreferenceController(context, TEST_KEY) + private val mockSubscriptionRepository = mock() + + 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 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { } @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 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { 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 75c9aa14456..f75c14a6d37 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 @@ class SubscriptionRepositoryTest { 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