diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml index b5d0c5939ed..a29e12385ba 100644 --- a/res/xml/mobile_network_settings.xml +++ b/res/xml/mobile_network_settings.xml @@ -47,6 +47,23 @@ android:enabled="false" settings:controller="com.android.settings.network.telephony.SmsDefaultSubscriptionController"/> + + + + + + + + + + private lateinit var preference: CustomDialogPreferenceCompat + private lateinit var fragment: Fragment + private var coroutineScope: CoroutineScope? = null + private var title = String() + private var eid = String() + + fun init(fragment: Fragment, subId: Int) { + this.fragment = fragment + lazyViewModel = fragment.viewModels() + mSubId = subId + } + + override fun getAvailabilityStatus(subId: Int): Int = when { + !Flags.isDualSimOnboardingEnabled() -> CONDITIONALLY_UNAVAILABLE + SubscriptionManager.isValidSubscriptionId(subId) + && eid.isNotEmpty() + && mContext.userManager.isAdminUser -> AVAILABLE + + else -> CONDITIONALLY_UNAVAILABLE + } + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey)!! + } + + override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { + preference.isVisible = false + + val viewModel by lazyViewModel + coroutineScope = viewLifecycleOwner.lifecycleScope + viewModel.subscriptionInfoListFlow + .map { subscriptionInfoList -> + subscriptionInfoList + .firstOrNull { subInfo -> + subInfo.subscriptionId == mSubId && subInfo.isEmbedded + } + } + .collectLatestWithLifecycle(viewLifecycleOwner) { subscriptionInfo -> + subscriptionInfo?.let { + coroutineScope?.launch { + refreshData(it) + } + } + } + } + + @VisibleForTesting + suspend fun refreshData(subscriptionInfo: SubscriptionInfo) { + withContext(Dispatchers.Default) { + eid = getEid(subscriptionInfo) + if (eid.isEmpty()) { + Log.d(TAG, "EID is empty.") + } + title = getTitle() + } + refreshUi() + } + + fun refreshUi() { + preference.title = title + preference.dialogTitle = title + preference.summary = eid + preference.isVisible = eid.isNotEmpty() + } + + override fun handlePreferenceTreeClick(preference: Preference): Boolean { + if (preference.key != preferenceKey) return false + this.preference.setOnShowListener { + coroutineScope?.launch { updateDialog() } + } + return true + } + + private fun getTitle(): String { + return mContext.getString(R.string.status_eid) + } + + private suspend fun updateDialog() { + val dialog = preference.dialog ?: return + dialog.window?.setFlags( + WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE + ) + dialog.setCanceledOnTouchOutside(false) + val textView = dialog.requireViewById(R.id.esim_id_value) + textView.text = PhoneNumberUtil.expandByTts(eid) + + val qrCodeView = dialog.requireViewById(R.id.esim_id_qrcode) + + qrCodeView.setImageBitmap(getEidQrCode(eid)) + } + + protected fun getTelephonyManager(context: Context): TelephonyManager? { + return context.getSystemService(TelephonyManager::class.java) + } + + protected fun getEuiccManager(context: Context): EuiccManager? { + return context.getSystemService(EuiccManager::class.java) + } + + @VisibleForTesting + fun getEid(subscriptionInfo: SubscriptionInfo): String { + val euiccMgr = getEuiccManager(mContext) + val telMgr = getTelephonyManager(mContext) + if(euiccMgr==null || telMgr==null) return String() + + var eid = getEidPerSlot(telMgr, euiccMgr, subscriptionInfo) + return eid.ifEmpty { + getDefaultEid(euiccMgr) + } + } + + private fun getEidPerSlot( + telMgr: TelephonyManager, + euiccMgr: EuiccManager, + subscriptionInfo: SubscriptionInfo + ): String { + val uiccCardInfoList = telMgr.uiccCardsInfo + val cardId = subscriptionInfo.cardId + + /** + * Find EID from first slot which contains an eSIM and with card ID within + * the eSIM card ID provided by SubscriptionManager. + */ + return uiccCardInfoList.firstOrNull { cardInfo -> cardInfo.isEuicc && cardInfo.cardId == cardId } + ?.let { cardInfo -> + var eid = cardInfo.getEid() + if (TextUtils.isEmpty(eid)) { + eid = euiccMgr.createForCardId(cardInfo.cardId).getEid() + } + eid + } ?: String() + } + + private fun getDefaultEid(euiccMgr: EuiccManager?): String { + return if (euiccMgr == null || !euiccMgr.isEnabled) { + String() + } else euiccMgr.getEid() ?: String() + } + + companion object { + private const val TAG = "MobileNetworkEidPreferenceController" + private const val QR_CODE_SIZE = 600 + + /** + * Gets the QR code for EID + * @param eid is the EID string + * @return a Bitmap of QR code + */ + private suspend fun getEidQrCode(eid: String): Bitmap? = withContext(Dispatchers.Default) { + try { + Log.d(TAG, "updateDialog. getEidQrCode $eid") + QrCodeGenerator.encodeQrCode(contents = eid, size = QR_CODE_SIZE) + } catch (exception: Exception) { + Log.w(TAG, "Error when creating QR code width $QR_CODE_SIZE", exception) + null + } + } + } +} diff --git a/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceController.kt b/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceController.kt new file mode 100644 index 00000000000..8ec313b0483 --- /dev/null +++ b/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceController.kt @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony + +import android.content.Context +import android.os.UserManager +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +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.deviceinfo.imei.ImeiInfoDialogFragment +import com.android.settings.flags.Flags +import com.android.settings.network.SubscriptionInfoListViewModel +import com.android.settings.network.SubscriptionUtil +import com.android.settingslib.Utils +import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle +import com.android.settingslib.spaprivileged.framework.common.userManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Preference controller for "IMEI" + */ +class MobileNetworkImeiPreferenceController(context: Context, key: String) : + TelephonyBasePreferenceController(context, key) { + + private lateinit var lazyViewModel: Lazy + private lateinit var preference: Preference + private lateinit var fragment: Fragment + private lateinit var mTelephonyManager: TelephonyManager + private var simSlot = -1 + private var imei = String() + private var title = String() + + fun init(fragment: Fragment, subId: Int) { + this.fragment = fragment + lazyViewModel = fragment.viewModels() + mSubId = subId + mTelephonyManager = mContext.getSystemService(TelephonyManager::class.java) + ?.createForSubscriptionId(mSubId)!! + } + + override fun getAvailabilityStatus(subId: Int): Int = when { + !Flags.isDualSimOnboardingEnabled() -> CONDITIONALLY_UNAVAILABLE + SubscriptionManager.isValidSubscriptionId(subId) + && SubscriptionUtil.isSimHardwareVisible(mContext) + && mContext.userManager.isAdminUser + && !Utils.isWifiOnly(mContext) -> AVAILABLE + else -> CONDITIONALLY_UNAVAILABLE + } + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey)!! + } + + override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { + val viewModel by lazyViewModel + val coroutineScope = viewLifecycleOwner.lifecycleScope + + viewModel.subscriptionInfoListFlow + .collectLatestWithLifecycle(viewLifecycleOwner) { subscriptionInfoList -> + subscriptionInfoList + .firstOrNull { subInfo -> subInfo.subscriptionId == mSubId } + ?.let { + coroutineScope.launch { + refreshData(it) + } + } + } + } + + @VisibleForTesting + suspend fun refreshData(subscription:SubscriptionInfo){ + withContext(Dispatchers.Default) { + title = getTitle() + imei = getImei() + simSlot = subscription.simSlotIndex + } + refreshUi() + } + + private fun refreshUi(){ + preference.title = title + preference.summary = imei + preference.isVisible = true + } + + override fun handlePreferenceTreeClick(preference: Preference): Boolean { + if (preference.key != preferenceKey) return false + + Log.d(TAG, "handlePreferenceTreeClick:") + ImeiInfoDialogFragment.show(fragment, simSlot, preference.title.toString()) + return true + } + private fun getImei(): String { + val phoneType = getPhoneType() + return if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) mTelephonyManager.meid?: String() + else mTelephonyManager.imei?: String() + } + private fun getTitleForGsmPhone(): String { + return mContext.getString(R.string.status_imei) + } + + private fun getTitleForCdmaPhone(): String { + return mContext.getString(R.string.status_meid_number) + } + + private fun getTitle(): String { + val phoneType = getPhoneType() + return if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) getTitleForCdmaPhone() + else getTitleForGsmPhone() + } + + fun getPhoneType(): Int { + return mTelephonyManager.currentPhoneType + } + + companion object { + private const val TAG = "MobileNetworkImeiPreferenceController" + } +} diff --git a/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt b/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt new file mode 100644 index 00000000000..65a4b7e6dce --- /dev/null +++ b/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony + +import android.content.Context +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +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) { + + private lateinit var lazyViewModel: Lazy + private lateinit var preference: Preference + + private var phoneNumber = String() + + fun init(fragment: Fragment, subId: Int) { + lazyViewModel = fragment.viewModels() + 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 displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey)!! + } + + override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { + val viewModel by lazyViewModel + val coroutineScope = viewLifecycleOwner.lifecycleScope + + viewModel.subscriptionInfoListFlow + .map { subscriptionInfoList -> + subscriptionInfoList + .firstOrNull { subInfo -> subInfo.subscriptionId == mSubId } + } + .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 { + return mContext.getString(R.string.device_info_default) + } +} diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index 4188f8d1ad7..2f2b20e1699 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -85,6 +85,7 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme private static final String KEY_SMS_PREF = "sms_preference"; private static final String KEY_MOBILE_DATA_PREF = "mobile_data_enable"; private static final String KEY_CONVERT_TO_ESIM_PREF = "convert_to_esim"; + private static final String KEY_EID_KEY = "network_mode_eid_info"; //String keys for preference lookup private static final String BUTTON_CDMA_SYSTEM_SELECT_KEY = "cdma_system_select_key"; @@ -171,6 +172,10 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme String.valueOf(mSubId)); }); + MobileNetworkEidPreferenceController eid = new MobileNetworkEidPreferenceController(context, + KEY_EID_KEY); + eid.init(this, mSubId); + return Arrays.asList( new DataUsageSummaryPreferenceController(context, mSubId), new RoamingPreferenceController(context, KEY_ROAMING_PREF, getSettingsLifecycle(), @@ -182,7 +187,7 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme new MobileDataPreferenceController(context, KEY_MOBILE_DATA_PREF, getSettingsLifecycle(), this, mSubId), new ConvertToEsimPreferenceController(context, KEY_CONVERT_TO_ESIM_PREF, - getSettingsLifecycle(), this, mSubId)); + getSettingsLifecycle(), this, mSubId), eid); } @Override @@ -239,6 +244,10 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme use(DisableSimFooterPreferenceController.class).init(mSubId); use(NrDisabledInDsdsFooterPreferenceController.class).init(mSubId); + use(MobileNetworkSpnPreferenceController.class).init(this, mSubId); + use(MobileNetworkPhoneNumberPreferenceController.class).init(this, mSubId); + use(MobileNetworkImeiPreferenceController.class).init(this, mSubId); + final MobileDataPreferenceController mobileDataPreferenceController = use(MobileDataPreferenceController.class); if (mobileDataPreferenceController != null) { diff --git a/src/com/android/settings/network/telephony/MobileNetworkSpnPreferenceController.kt b/src/com/android/settings/network/telephony/MobileNetworkSpnPreferenceController.kt new file mode 100644 index 00000000000..ac055b02f06 --- /dev/null +++ b/src/com/android/settings/network/telephony/MobileNetworkSpnPreferenceController.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony + +import android.content.Context +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import androidx.annotation.VisibleForTesting +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.LifecycleOwner +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import com.android.settings.flags.Flags +import com.android.settings.network.SubscriptionInfoListViewModel +import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle + +/** + * Preference controller for "Mobile network" and showing the SPN. + */ +class MobileNetworkSpnPreferenceController(context: Context, key: String) : + TelephonyBasePreferenceController(context, key) { + + private lateinit var lazyViewModel: Lazy + private lateinit var preference: Preference + + private var spn = String() + + fun init(fragment: Fragment, subId: Int) { + lazyViewModel = fragment.viewModels() + mSubId = subId + } + + override fun getAvailabilityStatus(subId: Int): Int = when { + !Flags.isDualSimOnboardingEnabled() -> CONDITIONALLY_UNAVAILABLE + SubscriptionManager.isValidSubscriptionId(subId)-> AVAILABLE + else -> CONDITIONALLY_UNAVAILABLE + } + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey)!! + } + + override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { + val viewModel by lazyViewModel + + viewModel.subscriptionInfoListFlow + .collectLatestWithLifecycle(viewLifecycleOwner) { subscriptionInfoList -> + refreshData(subscriptionInfoList) + } + } + + @VisibleForTesting + fun refreshData(subscriptionInfoList: List){ + spn = subscriptionInfoList + .firstOrNull { subInfo -> subInfo.subscriptionId == mSubId } + ?.let { info -> info.carrierName.toString() } + ?: String() + + refreshUi() + } + + private fun refreshUi(){ + preference.summary = spn + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkEidPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkEidPreferenceControllerTest.kt new file mode 100644 index 00000000000..b035af519c4 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkEidPreferenceControllerTest.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony + +import android.content.Context +import android.platform.test.flag.junit.SetFlagsRule +import android.telephony.SubscriptionInfo +import android.telephony.TelephonyManager +import android.telephony.euicc.EuiccManager +import androidx.fragment.app.Fragment +import androidx.preference.PreferenceManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.settings.core.BasePreferenceController +import com.android.settings.flags.Flags +import com.android.settings.network.SubscriptionInfoListViewModel +import com.android.settings.network.SubscriptionUtil +import com.android.settingslib.CustomDialogPreferenceCompat +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Before +import org.junit.Rule +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.spy +import org.mockito.kotlin.stub +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@RunWith(AndroidJUnit4::class) +class MobileNetworkEidPreferenceControllerTest { + @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule() + + private lateinit var mockSession: MockitoSession + + private val mockViewModels = mock>() + private val mockFragment = mock{ + val viewmodel = mockViewModels + } + + private var mockEid = String() + private val mockTelephonyManager = mock { + on {uiccCardsInfo} doReturn listOf() + on { createForSubscriptionId(any()) } doReturn mock + } + private val mockEuiccManager = mock { + on {isEnabled} doReturn true + on {eid} doReturn mockEid + } + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + on { getSystemService(EuiccManager::class.java) } doReturn mockEuiccManager + } + + private val controller = MobileNetworkEidPreferenceController(context, TEST_KEY) + private val preference = CustomDialogPreferenceCompat(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() + + preferenceScreen.addPreference(preference) + controller.displayPreference(preferenceScreen) + } + + @After + fun tearDown() { + mockSession.finishMocking() + } + + @Test + fun refreshData_getEmptyEid_preferenceIsNotVisible() = 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) + mockEid = String() + + controller.refreshData(SUB_INFO_2) + + assertThat(preference.isVisible).isEqualTo(false) + } + + @Test + fun refreshData_getEmptyEid_preferenceSummaryIsExpected() = 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) + mockEid = "test eid" + mockEuiccManager.stub { + on {eid} doReturn mockEid + } + + controller.refreshData(SUB_INFO_2) + + assertThat(preference.summary).isEqualTo(mockEid) + } + + @Test + fun getAvailabilityStatus_notSimHardwareVisible() { + whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(false) + + val availabilityStatus = controller.availabilityStatus + + assertThat(availabilityStatus).isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE) + } + + 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() + + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceControllerTest.kt new file mode 100644 index 00000000000..2f678464935 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkImeiPreferenceControllerTest.kt @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony + +import android.content.Context +import android.telephony.SubscriptionInfo +import android.telephony.TelephonyManager +import android.telephony.euicc.EuiccManager +import androidx.fragment.app.Fragment +import androidx.preference.Preference +import androidx.preference.PreferenceManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.internal.telephony.PhoneConstants +import com.android.settings.core.BasePreferenceController +import com.android.settings.network.SubscriptionInfoListViewModel +import com.android.settings.network.SubscriptionUtil +import com.android.settingslib.CustomDialogPreferenceCompat +import com.google.common.truth.Truth.assertThat +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.spy +import org.mockito.kotlin.stub +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@RunWith(AndroidJUnit4::class) +class MobileNetworkImeiPreferenceControllerTest { + private lateinit var mockSession: MockitoSession + + private val mockViewModels = mock>() + private val mockFragment = mock{ + val viewmodel = mockViewModels + } + + private var mockImei = String() + private val mockTelephonyManager = mock { + on { uiccCardsInfo } doReturn listOf() + on { createForSubscriptionId(any()) } doReturn mock + on { currentPhoneType } doReturn TelephonyManager.PHONE_TYPE_GSM + on { imei } doReturn mockImei + on { meid } doReturn mockImei + } + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } + + private val controller = MobileNetworkImeiPreferenceController(context, TEST_KEY) + 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() + + preferenceScreen.addPreference(preference) + controller.displayPreference(preferenceScreen) + } + + @After + fun tearDown() { + mockSession.finishMocking() + } + + @Test + fun refreshData_getPhoneNumber_preferenceSummaryIsExpected() = 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) + mockImei = "test imei" + mockTelephonyManager.stub { + on { imei } doReturn mockImei + } + + controller.refreshData(SUB_INFO_2) + + assertThat(preference.summary).isEqualTo(mockImei) + } + + @Test + fun getAvailabilityStatus_notSimHardwareVisible() { + whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(false) + + val availabilityStatus = controller.availabilityStatus + + assertThat(availabilityStatus).isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE) + } + + 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() + + } +} 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 new file mode 100644 index 00000000000..38c47c28ccc --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony + +import android.content.Context +import android.telephony.SubscriptionInfo +import androidx.fragment.app.Fragment +import androidx.preference.Preference +import androidx.preference.PreferenceManager +import androidx.test.core.app.ApplicationProvider +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.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.mock +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@RunWith(AndroidJUnit4::class) +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 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() + + preferenceScreen.addPreference(preference) + controller.displayPreference(preferenceScreen) + + whenever(SubscriptionUtil.getBidiFormattedPhoneNumber(any(),any())).thenReturn(mockPhoneNumber) + } + + @After + fun tearDown() { + mockSession.finishMocking() + } + + @Test + fun refreshData_getEmptyPhoneNumber_preferenceIsNotVisible() = 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() + + controller.refreshData(SUB_INFO_2) + + assertThat(preference.summary).isEqualTo( + context.getString(R.string.device_info_default)) + } + + @Test + fun refreshData_getPhoneNumber_preferenceSummaryIsExpected() = 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) + + controller.refreshData(SUB_INFO_2) + + assertThat(preference.summary).isEqualTo(mockPhoneNumber) + } + + @Test + fun getAvailabilityStatus_notSimHardwareVisible() { + whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(false) + + val availabilityStatus = controller.availabilityStatus + + assertThat(availabilityStatus).isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE) + } + + 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() + + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSpnPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSpnPreferenceControllerTest.kt new file mode 100644 index 00000000000..f5592c1bc8d --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSpnPreferenceControllerTest.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony + +import android.content.Context +import android.telephony.SubscriptionInfo +import android.telephony.TelephonyManager +import android.telephony.euicc.EuiccManager +import androidx.fragment.app.Fragment +import androidx.preference.Preference +import androidx.preference.PreferenceManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.dx.mockito.inline.extended.ExtendedMockito +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.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.spy +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@RunWith(AndroidJUnit4::class) +class MobileNetworkSpnPreferenceControllerTest { + private lateinit var mockSession: MockitoSession + + private val mockViewModels = mock>() + private val mockFragment = mock{ + val viewmodel = mockViewModels + } + + private val context: Context = ApplicationProvider.getApplicationContext() + private val controller = MobileNetworkSpnPreferenceController(context, TEST_KEY) + 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() + + preferenceScreen.addPreference(preference) + controller.displayPreference(preferenceScreen) + } + + @After + fun tearDown() { + mockSession.finishMocking() + } + + @Test + fun refreshData_getCarrierName_preferenceSummaryIsExpected() = runBlocking { + var testList = listOf( + SUB_INFO_1, + SUB_INFO_2 + ) + whenever(SubscriptionUtil.getActiveSubscriptions(any())).thenReturn(testList) + var mockSubId = 2 + controller.init(mockFragment, mockSubId) + + controller.refreshData(testList) + + assertThat(preference.summary).isEqualTo(CARRIER_NAME_2) + } + + private companion object { + const val TEST_KEY = "test_key" + const val CARRIER_NAME_1 = "Sub 1" + const val CARRIER_NAME_2 = "Sub 2" + + val SUB_INFO_1: SubscriptionInfo = SubscriptionInfo.Builder().apply { + setId(1) + setCarrierName(CARRIER_NAME_1) + }.build() + + val SUB_INFO_2: SubscriptionInfo = SubscriptionInfo.Builder().apply { + setId(2) + setCarrierName(CARRIER_NAME_2) + }.build() + + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/MobileDataPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/MobileDataPreferenceControllerTest.java index 4d480253306..11a490ef76e 100644 --- a/tests/unit/src/com/android/settings/network/telephony/MobileDataPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/network/telephony/MobileDataPreferenceControllerTest.java @@ -98,6 +98,7 @@ public class MobileDataPreferenceControllerTest { doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE); when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); + when(mSubscriptionManager.createForAllUserProfiles()).thenReturn(mSubscriptionManager); doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID); doReturn(mInvalidTelephonyManager).when(mTelephonyManager).createForSubscriptionId( SubscriptionManager.INVALID_SUBSCRIPTION_ID); @@ -182,7 +183,8 @@ public class MobileDataPreferenceControllerTest { mController.onPreferenceChange(mPreference, true); - verify(mTelephonyManager).setDataEnabled(true); + verify(mTelephonyManager).setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER + ,true); } @Test @@ -195,7 +197,8 @@ public class MobileDataPreferenceControllerTest { mController.onPreferenceChange(mPreference, true); - verify(mTelephonyManager).setDataEnabled(true); + verify(mTelephonyManager).setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER + ,true); } @Test