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
This commit is contained in:
Chaohui Wang
2024-07-25 15:46:03 +08:00
parent 8a4a5f207e
commit cf13fe7776
5 changed files with 100 additions and 141 deletions

View File

@@ -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 @@ 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 {

View File

@@ -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 =

View File

@@ -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<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 @@ class SubscriptionRepository(private val context: Context) {
}.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 @@ 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<String?> =
subscriptionsChangedFlow()
.map { SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo) }
.distinctUntilChanged()
.conflate()
.flowOn(Dispatchers.Default)
fun Context.subscriptionsChangedFlow(): Flow<Unit> =
SubscriptionRepository(this).subscriptionsChangedFlow()

View File

@@ -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<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 @@ 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"
}
}

View File

@@ -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