Create SubscriptionRepository.phoneNumberFlow

And use it in the MobileNetworkSettings.

Fix: 341318273
Flag: EXEMPT bug fix
Test: manual - on MobileNetworkSettings
Test: unit test
Change-Id: I886373c1ed5129ebd8bcceedd513e9d1776106c8
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