diff --git a/res/drawable/ic_calls_sms.xml b/res/drawable/ic_calls_sms.xml deleted file mode 100644 index 2033e8fc0dc..00000000000 --- a/res/drawable/ic_calls_sms.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml index ef6ed16f73f..b055ea37d4d 100644 --- a/res/xml/network_provider_internet.xml +++ b/res/xml/network_provider_internet.xml @@ -31,16 +31,11 @@ settings:keywords="@string/keywords_internet" settings:useAdminDisabledSummary="true" /> - + settings:controller="com.android.settings.network.NetworkProviderCallsSmsController" /> mSubInfoEntityList; - private int mDefaultVoiceSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private int mDefaultSmsSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private DefaultSubscriptionReceiver mDataSubscriptionChangedReceiver; - - /** - * The summary text and click behavior of the "Calls & SMS" item on the - * Network & internet page. - */ - public NetworkProviderCallsSmsController(Context context, Lifecycle lifecycle, - LifecycleOwner lifecycleOwner) { - super(context); - - mUserManager = context.getSystemService(UserManager.class); - mTelephonyManager = mContext.getSystemService(TelephonyManager.class); - mIsRtlMode = context.getResources().getConfiguration().getLayoutDirection() - == View.LAYOUT_DIRECTION_RTL; - mLifecycleOwner = lifecycleOwner; - mMobileNetworkRepository = MobileNetworkRepository.getInstance(context); - mDataSubscriptionChangedReceiver = new DefaultSubscriptionReceiver(context, this); - if (lifecycle != null) { - lifecycle.addObserver(this); - } - } - - @OnLifecycleEvent(Event.ON_RESUME) - public void onResume() { - mMobileNetworkRepository.addRegister(mLifecycleOwner, this, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - mMobileNetworkRepository.updateEntity(); - mDataSubscriptionChangedReceiver.registerReceiver(); - mDefaultVoiceSubId = SubscriptionManager.getDefaultVoiceSubscriptionId(); - mDefaultSmsSubId = SubscriptionManager.getDefaultSmsSubscriptionId(); - } - - @OnLifecycleEvent(Event.ON_PAUSE) - public void onPause() { - mMobileNetworkRepository.removeRegister(this); - mDataSubscriptionChangedReceiver.unRegisterReceiver(); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mPreference = screen.findPreference(getPreferenceKey()); - } - - @Override - public CharSequence getSummary() { - List list = getSubscriptionInfoList(); - if (list == null || list.isEmpty()) { - return setSummaryResId(R.string.calls_sms_no_sim); - } else { - final StringBuilder summary = new StringBuilder(); - SubscriptionInfoEntity[] entityArray = list.toArray( - new SubscriptionInfoEntity[0]); - for (SubscriptionInfoEntity subInfo : entityArray) { - int subsSize = list.size(); - int subId = Integer.parseInt(subInfo.subId); - final CharSequence displayName = subInfo.uniqueName; - - // Set displayName as summary if there is only one valid SIM. - if (subsSize == 1 - && list.get(0).isValidSubscription - && isInService(subId)) { - return displayName; - } - - CharSequence status = getPreferredStatus(subInfo, subsSize, subId); - if (status.toString().isEmpty()) { - // If there are 2 or more SIMs and one of these has no preferred status, - // set only its displayName as summary. - summary.append(displayName); - } else { - summary.append(displayName) - .append(" (") - .append(status) - .append(")"); - } - // Do not add ", " for the last subscription. - if (list.size() > 0 && !subInfo.equals(list.get(list.size() - 1))) { - summary.append(", "); - } - - if (mIsRtlMode) { - summary.insert(0, RTL_MARK).insert(summary.length(), RTL_MARK); - } - } - return summary; - } - } - - @VisibleForTesting - protected CharSequence getPreferredStatus(SubscriptionInfoEntity subInfo, int subsSize, - int subId) { - String status = ""; - boolean isCallPreferred = subInfo.getSubId() == getDefaultVoiceSubscriptionId(); - boolean isSmsPreferred = subInfo.getSubId() == getDefaultSmsSubscriptionId(); - - if (!subInfo.isValidSubscription || !isInService(subId)) { - status = setSummaryResId(subsSize > 1 ? R.string.calls_sms_unavailable : - R.string.calls_sms_temp_unavailable); - } else { - if (isCallPreferred && isSmsPreferred) { - status = setSummaryResId(R.string.calls_sms_preferred); - } else if (isCallPreferred) { - status = setSummaryResId(R.string.calls_sms_calls_preferred); - } else if (isSmsPreferred) { - status = setSummaryResId(R.string.calls_sms_sms_preferred); - } - } - return status; - } - - private String setSummaryResId(int resId) { - return mContext.getResources().getString(resId); - } - - @VisibleForTesting - protected List getSubscriptionInfoList() { - return mSubInfoEntityList; - } - - private void update() { - if (mPreference == null || mPreference.isDisabledByAdmin()) { - return; - } - refreshSummary(mPreference); - mPreference.setOnPreferenceClickListener(null); - mPreference.setFragment(null); - - if (mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) { - mPreference.setEnabled(false); - } else { - mPreference.setEnabled(true); - mPreference.setFragment(NetworkProviderCallsSmsFragment.class.getCanonicalName()); - } - } - - @Override - public boolean isAvailable() { - return SubscriptionUtil.isSimHardwareVisible(mContext) && - mUserManager.isAdminUser(); - } - - @Override - public String getPreferenceKey() { - return KEY; - } - - @Override - public void onAirplaneModeChanged(boolean airplaneModeEnabled) { - update(); - } - - @Override - public void updateState(Preference preference) { - super.updateState(preference); - if (preference == null) { - return; - } - refreshSummary(mPreference); - update(); - } - - @VisibleForTesting - protected boolean isInService(int subId) { - ServiceState serviceState = - mTelephonyManager.createForSubscriptionId(subId).getServiceState(); - return Utils.isInService(serviceState); - } - - @Override - public void onActiveSubInfoChanged(List activeSubInfoList) { - mSubInfoEntityList = activeSubInfoList; - update(); - } - - @VisibleForTesting - protected int getDefaultVoiceSubscriptionId() { - return mDefaultVoiceSubId; - } - - @VisibleForTesting - protected int getDefaultSmsSubscriptionId() { - return mDefaultSmsSubId; - } - - @Override - public void onDefaultVoiceChanged(int defaultVoiceSubId) { - mDefaultVoiceSubId = defaultVoiceSubId; - update(); - } - - @Override - public void onDefaultSmsChanged(int defaultSmsSubId) { - mDefaultSmsSubId = defaultSmsSubId; - update(); - } -} diff --git a/src/com/android/settings/network/NetworkProviderCallsSmsController.kt b/src/com/android/settings/network/NetworkProviderCallsSmsController.kt new file mode 100644 index 00000000000..a265041944c --- /dev/null +++ b/src/com/android/settings/network/NetworkProviderCallsSmsController.kt @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2023 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 + +import android.app.settings.SettingsEnums +import android.content.Context +import android.content.IntentFilter +import android.os.UserManager +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import androidx.annotation.VisibleForTesting +import androidx.compose.foundation.layout.Column +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.PermPhoneMsg +import androidx.compose.material3.HorizontalDivider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import com.android.settings.R +import com.android.settings.core.SubSettingLauncher +import com.android.settings.spa.preference.ComposePreferenceController +import com.android.settingslib.Utils +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.ui.SettingsIcon +import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow +import com.android.settingslib.spaprivileged.framework.common.userManager +import com.android.settingslib.spaprivileged.framework.compose.placeholder +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** + * The summary text and click behavior of the "Calls & SMS" item on the Network & internet page. + */ +open class NetworkProviderCallsSmsController @JvmOverloads constructor( + context: Context, + preferenceKey: String, + private val getDisplayName: (SubscriptionInfo) -> CharSequence = { subInfo -> + SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, context) + }, + private val isInService: (Int) -> Boolean = IsInServiceImpl(context)::isInService, +) : ComposePreferenceController(context, preferenceKey) { + + override fun getAvailabilityStatus() = when { + !SubscriptionUtil.isSimHardwareVisible(mContext) -> UNSUPPORTED_ON_DEVICE + !mContext.userManager.isAdminUser -> DISABLED_FOR_USER + else -> AVAILABLE + } + + @Composable + override fun Content() { + Column { + CallsAndSms() + HorizontalDivider() + } + } + + @Composable + private fun CallsAndSms() { + val viewModel: SubscriptionInfoListViewModel = viewModel() + val subscriptionInfos by viewModel.subscriptionInfoListFlow.collectAsStateWithLifecycle() + val summary by remember { summaryFlow(viewModel.subscriptionInfoListFlow) } + .collectAsStateWithLifecycle(initialValue = placeholder()) + RestrictedPreference( + model = object : PreferenceModel { + override val title = stringResource(R.string.calls_and_sms) + override val icon = @Composable { SettingsIcon(Icons.Outlined.PermPhoneMsg) } + override val summary = { summary } + override val enabled = { subscriptionInfos.isNotEmpty() } + override val onClick = { + SubSettingLauncher(mContext).apply { + setDestination(NetworkProviderCallsSmsFragment::class.qualifiedName) + setSourceMetricsCategory(SettingsEnums.SETTINGS_NETWORK_CATEGORY) + }.launch() + } + }, + restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)), + ) + } + + private fun summaryFlow(subscriptionInfoListFlow: Flow>) = combine( + subscriptionInfoListFlow, + mContext.defaultVoiceSubscriptionFlow(), + mContext.defaultSmsSubscriptionFlow(), + ::getSummary, + ).flowOn(Dispatchers.Default) + + @VisibleForTesting + fun getSummary( + activeSubscriptionInfoList: List, + defaultVoiceSubscriptionId: Int, + defaultSmsSubscriptionId: Int, + ): String { + if (activeSubscriptionInfoList.isEmpty()) { + return mContext.getString(R.string.calls_sms_no_sim) + } + + activeSubscriptionInfoList.singleOrNull()?.let { + // Set displayName as summary if there is only one valid SIM. + if (isInService(it.subscriptionId)) return it.displayName.toString() + } + + return activeSubscriptionInfoList.joinToString { subInfo -> + val displayName = getDisplayName(subInfo) + + val subId = subInfo.subscriptionId + val statusResId = getPreferredStatus( + subId = subId, + subsSize = activeSubscriptionInfoList.size, + isCallPreferred = subId == defaultVoiceSubscriptionId, + isSmsPreferred = subId == defaultSmsSubscriptionId, + ) + if (statusResId == null) { + // If there are 2 or more SIMs and one of these has no preferred status, + // set only its displayName as summary. + displayName + } else { + "$displayName (${mContext.getString(statusResId)})" + } + } + } + + private fun getPreferredStatus( + subId: Int, + subsSize: Int, + isCallPreferred: Boolean, + isSmsPreferred: Boolean, + ): Int? = when { + !isInService(subId) -> { + if (subsSize > 1) { + R.string.calls_sms_unavailable + } else { + R.string.calls_sms_temp_unavailable + } + } + + isCallPreferred && isSmsPreferred -> R.string.calls_sms_preferred + isCallPreferred -> R.string.calls_sms_calls_preferred + isSmsPreferred -> R.string.calls_sms_sms_preferred + else -> null + } +} + +private fun Context.defaultVoiceSubscriptionFlow(): Flow = + merge( + flowOf(null), // kick an initial value + broadcastReceiverFlow( + IntentFilter(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED) + ), + ).map { SubscriptionManager.getDefaultVoiceSubscriptionId() } + .conflate().flowOn(Dispatchers.Default) + +private fun Context.defaultSmsSubscriptionFlow(): Flow = + merge( + flowOf(null), // kick an initial value + broadcastReceiverFlow( + IntentFilter(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED) + ), + ).map { SubscriptionManager.getDefaultSmsSubscriptionId() } + .conflate().flowOn(Dispatchers.Default) + +private class IsInServiceImpl(context: Context) { + private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!! + + fun isInService(subId: Int): Boolean { + if (!SubscriptionManager.isValidSubscriptionId(subId)) return false + + val serviceState = telephonyManager.createForSubscriptionId(subId).serviceState + return Utils.isInService(serviceState) + } +} diff --git a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt new file mode 100644 index 00000000000..d30b21d6f03 --- /dev/null +++ b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 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 + +import android.app.Application +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.plus + +class SubscriptionInfoListViewModel(application: Application) : AndroidViewModel(application) { + private val scope = viewModelScope + Dispatchers.Default + + val subscriptionInfoListFlow = callbackFlow> { + val subscriptionManager = application.getSystemService(SubscriptionManager::class.java)!! + + val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() { + override fun onSubscriptionsChanged() { + trySend(subscriptionManager.activeSubscriptionInfoList ?: emptyList()) + } + } + + subscriptionManager.addOnSubscriptionsChangedListener( + Dispatchers.Default.asExecutor(), + listener, + ) + + awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) } + }.conflate().stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList()) +} diff --git a/tests/spa_unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.kt new file mode 100644 index 00000000000..110fd5eb976 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2023 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 + +import android.content.Context +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class NetworkProviderCallsSmsControllerTest { + private val context: Context = ApplicationProvider.getApplicationContext() + + private var isInService: (Int) -> Boolean = { true } + + private val controller = NetworkProviderCallsSmsController( + context = context, + preferenceKey = TEST_KEY, + getDisplayName = { subInfo -> subInfo.displayName }, + isInService = { isInService(it) }, + ) + + @Test + fun getSummary_noSim_returnNoSim() { + val summary = controller.getSummary( + activeSubscriptionInfoList = emptyList(), + defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + ) + + assertThat(summary).isEqualTo(context.getString(R.string.calls_sms_no_sim)) + } + + @Test + fun getSummary_invalidSubId_returnUnavailable() { + isInService = { false } + + val summary = controller.getSummary( + activeSubscriptionInfoList = listOf(SUB_INFO_1), + defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + ) + + assertThat(summary).isEqualTo("Sub 1 (Temporarily unavailable)") + } + + @Test + fun getSummary_oneIsInvalidSubIdTwoIsValidSubId_returnOneIsUnavailable() { + isInService = { it == SUB_INFO_2.subscriptionId } + + val summary = controller.getSummary( + activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2), + defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + ) + + assertThat(summary).isEqualTo("Sub 1 (unavailable), Sub 2") + } + + @Test + fun getSummary_oneSubscription_returnDisplayName() { + val summary = controller.getSummary( + activeSubscriptionInfoList = listOf(SUB_INFO_1), + defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + ) + + assertThat(summary).isEqualTo(DISPLAY_NAME_1) + } + + @Test + fun getSummary_allSubscriptionsHaveNoPreferredStatus_returnDisplayName() { + val summary = controller.getSummary( + activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2), + defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, + ) + + assertThat(summary).isEqualTo("Sub 1, Sub 2") + } + + @Test + fun getSummary_oneSubscriptionsIsCallPreferredTwoIsSmsPreferred_returnStatus() { + val summary = controller.getSummary( + activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2), + defaultVoiceSubscriptionId = SUB_INFO_1.subscriptionId, + defaultSmsSubscriptionId = SUB_INFO_2.subscriptionId, + ) + + assertThat(summary).isEqualTo("Sub 1 (preferred for calls), Sub 2 (preferred for SMS)") + } + + @Test + fun getSummary_oneSubscriptionsIsSmsPreferredTwoIsCallPreferred_returnStatus() { + val summary = controller.getSummary( + activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2), + defaultVoiceSubscriptionId = SUB_INFO_2.subscriptionId, + defaultSmsSubscriptionId = SUB_INFO_1.subscriptionId, + ) + + assertThat(summary).isEqualTo("Sub 1 (preferred for SMS), Sub 2 (preferred for calls)") + } + + @Test + fun getSummary_oneSubscriptionsIsSmsPreferredAndIsCallPreferred_returnStatus() { + val summary = controller.getSummary( + activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2), + defaultVoiceSubscriptionId = SUB_INFO_1.subscriptionId, + defaultSmsSubscriptionId = SUB_INFO_1.subscriptionId, + ) + + assertThat(summary).isEqualTo("Sub 1 (preferred), Sub 2") + } + + 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/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java b/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java deleted file mode 100644 index 51aecc51000..00000000000 --- a/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (C) 2020 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; - -import static androidx.lifecycle.Lifecycle.Event; - -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.os.Looper; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; - -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.LifecycleRegistry; -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceScreen; -import androidx.test.annotation.UiThreadTest; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.android.settings.testutils.ResourcesUtils; -import com.android.settingslib.RestrictedPreference; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.List; - - -@RunWith(AndroidJUnit4.class) -public class NetworkProviderCallsSmsControllerTest { - - private static final String SUB_ID_1 = "1"; - private static final String SUB_ID_2 = "2"; - private static final String INVALID_SUB_ID = "-1"; - private static final String KEY_PREFERENCE_CALLS_SMS = "calls_and_sms"; - private static final String DISPLAY_NAME_1 = "Sub 1"; - private static final String DISPLAY_NAME_2 = "Sub 2"; - private static final String SUB_MCC_1 = "123"; - private static final String SUB_MNC_1 = "456"; - private static final String SUB_MCC_2 = "223"; - private static final String SUB_MNC_2 = "456"; - private static final String SUB_COUNTRY_ISO_1 = "Sub 1"; - private static final String SUB_COUNTRY_ISO_2 = "Sub 2"; - - @Mock - private SubscriptionInfoEntity mSubInfo1; - @Mock - private SubscriptionInfoEntity mSubInfo2; - @Mock - private Lifecycle mLifecycle; - @Mock - private LifecycleOwner mLifecycleOwner; - - private LifecycleRegistry mLifecycleRegistry; - private MockNetworkProviderCallsSmsController mController; - private PreferenceManager mPreferenceManager; - private PreferenceScreen mPreferenceScreen; - private RestrictedPreference mPreference; - private Context mContext; - private List mSubscriptionInfoEntityList = new ArrayList<>(); - - /** - * Mock the NetworkProviderCallsSmsController that allows one to set a default voice - * and SMS subscription ID. - */ - private class MockNetworkProviderCallsSmsController extends - com.android.settings.network.NetworkProviderCallsSmsController { - public MockNetworkProviderCallsSmsController(Context context, Lifecycle lifecycle, - LifecycleOwner lifecycleOwner) { - super(context, lifecycle, lifecycleOwner); - } - - private List mSubscriptionInfoEntity; - private boolean mIsInService; - private int mDefaultVoiceSubscriptionId; - private int mDefaultSmsSubscriptionId; - - @Override - protected List getSubscriptionInfoList() { - return mSubscriptionInfoEntity; - } - - public void setSubscriptionInfoList(List list) { - mSubscriptionInfoEntity = list; - } - - @Override - protected boolean isInService(int subId) { - return mIsInService; - } - - public void setInService(boolean inService) { - mIsInService = inService; - } - - @Override - protected int getDefaultVoiceSubscriptionId() { - return mDefaultVoiceSubscriptionId; - } - - @Override - protected int getDefaultSmsSubscriptionId() { - return mDefaultSmsSubscriptionId; - } - - public void setDefaultVoiceSubscriptionId(int subscriptionId) { - mDefaultVoiceSubscriptionId = subscriptionId; - } - - public void setDefaultSmsSubscriptionId(int subscriptionId) { - mDefaultSmsSubscriptionId = subscriptionId; - } - } - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(ApplicationProvider.getApplicationContext()); - - if (Looper.myLooper() == null) { - Looper.prepare(); - } - - mPreferenceManager = new PreferenceManager(mContext); - mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext); - mPreference = new RestrictedPreference(mContext); - mPreference.setKey(KEY_PREFERENCE_CALLS_SMS); - mController = new MockNetworkProviderCallsSmsController(mContext, mLifecycle, - mLifecycleOwner); - mController.setInService(true); - mLifecycleRegistry = new LifecycleRegistry(mLifecycleOwner); - when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry); - } - - private void displayPreferenceWithLifecycle() { - mLifecycleRegistry.addObserver(mController); - mPreferenceScreen.addPreference(mPreference); - mController.displayPreference(mPreferenceScreen); - mLifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME); - } - - private String setSummaryResId(String resName) { - return ResourcesUtils.getResourcesString(mContext, resName); - } - - @Test - @UiThreadTest - public void getSummary_noSim_returnNoSim() { - mController.setSubscriptionInfoList(mSubscriptionInfoEntityList); - displayPreferenceWithLifecycle(); - - assertTrue(TextUtils.equals(mController.getSummary(), - setSummaryResId("calls_sms_no_sim"))); - } - - private SubscriptionInfoEntity setupSubscriptionInfoEntity(String subId, int slotId, - int carrierId, String displayName, String mcc, String mnc, String countryIso, - int cardId, boolean isValid, boolean isActive, boolean isAvailable) { - return new SubscriptionInfoEntity(subId, slotId, carrierId, - displayName, displayName, 0, mcc, mnc, countryIso, false, cardId, - TelephonyManager.DEFAULT_PORT_INDEX, false, null, - SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, displayName, false, - "1234567890", true, false, isValid, - true, isActive, isAvailable, false); - } - - @Test - @UiThreadTest - public void getSummary_invalidSubId_returnUnavailable() { - - mSubInfo1 = setupSubscriptionInfoEntity(INVALID_SUB_ID, - SubscriptionManager.INVALID_SIM_SLOT_INDEX, TelephonyManager.UNKNOWN_CARRIER_ID, - DISPLAY_NAME_1, SUB_MCC_1, SUB_MNC_1, SUB_COUNTRY_ISO_1, - TelephonyManager.UNINITIALIZED_CARD_ID, false, true, true); - mSubscriptionInfoEntityList.add(mSubInfo1); - mController.setSubscriptionInfoList(mSubscriptionInfoEntityList); - displayPreferenceWithLifecycle(); - - final StringBuilder summary = new StringBuilder(); - summary.append(DISPLAY_NAME_1) - .append(" (") - .append(setSummaryResId("calls_sms_temp_unavailable")) - .append(")"); - - assertTrue(TextUtils.equals(mController.getSummary(), summary)); - } - - @Test - @UiThreadTest - public void getSummary_oneIsInvalidSubIdTwoIsValidSubId_returnOneIsUnavailable() { - - mSubInfo1 = setupSubscriptionInfoEntity(INVALID_SUB_ID, - SubscriptionManager.INVALID_SIM_SLOT_INDEX, TelephonyManager.UNKNOWN_CARRIER_ID, - DISPLAY_NAME_1, SUB_MCC_1, SUB_MNC_1, SUB_COUNTRY_ISO_1, - TelephonyManager.UNINITIALIZED_CARD_ID, false, true, true); - mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2, - SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true); - mSubscriptionInfoEntityList.add(mSubInfo1); - mSubscriptionInfoEntityList.add(mSubInfo2); - mController.setSubscriptionInfoList(mSubscriptionInfoEntityList); - displayPreferenceWithLifecycle(); - - final StringBuilder summary = new StringBuilder(); - summary.append(DISPLAY_NAME_1) - .append(" (") - .append(setSummaryResId("calls_sms_unavailable")) - .append(")") - .append(", ") - .append(DISPLAY_NAME_2); - - assertTrue(TextUtils.equals(mController.getSummary(), summary)); - } - - @Test - @UiThreadTest - public void getSummary_oneSubscription_returnDisplayName() { - - mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1, - SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true); - mSubscriptionInfoEntityList.add(mSubInfo1); - mController.setSubscriptionInfoList(mSubscriptionInfoEntityList); - displayPreferenceWithLifecycle(); - - assertThat(mPreference.getSummary()).isEqualTo(DISPLAY_NAME_1); - } - - @Test - @UiThreadTest - public void getSummary_allSubscriptionsHaveNoPreferredStatus_returnDisplayName() { - - mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1, - SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true); - mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2, - SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true); - mSubscriptionInfoEntityList.add(mSubInfo1); - mSubscriptionInfoEntityList.add(mSubInfo2); - mController.setSubscriptionInfoList(mSubscriptionInfoEntityList); - displayPreferenceWithLifecycle(); - - final StringBuilder summary = new StringBuilder(); - summary.append(DISPLAY_NAME_1).append(", ").append(DISPLAY_NAME_2); - - assertTrue(TextUtils.equals(mController.getSummary(), summary)); - } - - @Test - @UiThreadTest - public void getSummary_oneSubscriptionsIsCallPreferredTwoIsSmsPreferred_returnStatus() { - - mController.setDefaultVoiceSubscriptionId(Integer.parseInt(SUB_ID_1)); - mController.setDefaultSmsSubscriptionId(Integer.parseInt(SUB_ID_2)); - - mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1, - SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true); - mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2, - SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true); - mSubscriptionInfoEntityList.add(mSubInfo1); - mSubscriptionInfoEntityList.add(mSubInfo2); - mController.setSubscriptionInfoList(mSubscriptionInfoEntityList); - displayPreferenceWithLifecycle(); - - final StringBuilder summary = new StringBuilder(); - summary.append(DISPLAY_NAME_1) - .append(" (") - .append(setSummaryResId("calls_sms_calls_preferred")) - .append(")") - .append(", ") - .append(DISPLAY_NAME_2) - .append(" (") - .append(setSummaryResId("calls_sms_sms_preferred")) - .append(")"); - - assertTrue(TextUtils.equals(mController.getSummary(), summary)); - } - - @Test - @UiThreadTest - public void getSummary_oneSubscriptionsIsSmsPreferredTwoIsCallPreferred_returnStatus() { - - mController.setDefaultSmsSubscriptionId(Integer.parseInt(SUB_ID_1)); - mController.setDefaultVoiceSubscriptionId(Integer.parseInt(SUB_ID_2)); - - mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1, - SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true); - mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 2, 2, DISPLAY_NAME_2, SUB_MCC_2, - SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true); - mSubscriptionInfoEntityList.add(mSubInfo1); - mSubscriptionInfoEntityList.add(mSubInfo2); - mController.setSubscriptionInfoList(mSubscriptionInfoEntityList); - displayPreferenceWithLifecycle(); - - final StringBuilder summary = new StringBuilder(); - summary.append(DISPLAY_NAME_1) - .append(" (") - .append(setSummaryResId("calls_sms_sms_preferred")) - .append(")") - .append(", ") - .append(DISPLAY_NAME_2) - .append(" (") - .append(setSummaryResId("calls_sms_calls_preferred")) - .append(")"); - - assertTrue(TextUtils.equals(mController.getSummary(), summary)); - } - - @Test - @UiThreadTest - public void getSummary_oneSubscriptionsIsSmsPreferredAndIsCallPreferred_returnStatus() { - - mController.setDefaultSmsSubscriptionId(Integer.parseInt(SUB_ID_1)); - mController.setDefaultVoiceSubscriptionId(Integer.parseInt(SUB_ID_1)); - - mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1, - SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true); - mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2, - SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true); - mSubscriptionInfoEntityList.add(mSubInfo1); - mSubscriptionInfoEntityList.add(mSubInfo2); - mController.setSubscriptionInfoList(mSubscriptionInfoEntityList); - displayPreferenceWithLifecycle(); - - final StringBuilder summary = new StringBuilder(); - summary.append(DISPLAY_NAME_1) - .append(" (") - .append(setSummaryResId("calls_sms_preferred")) - .append(")") - .append(", ") - .append(DISPLAY_NAME_2); - - assertTrue(TextUtils.equals(mController.getSummary(), summary)); - } -}