diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml index e4ebe788b0c..292f1824552 100644 --- a/res/xml/network_provider_internet.xml +++ b/res/xml/network_provider_internet.xml @@ -52,9 +52,8 @@ android:order="-15" settings:keywords="@string/keywords_more_mobile_networks" settings:userRestriction="no_config_mobile_networks" - settings:isPreferenceVisible="@bool/config_show_sim_info" settings:useAdminDisabledSummary="true" - settings:searchable="@bool/config_show_sim_info"/> + settings:controller="com.android.settings.network.MobileNetworkSummaryController" /> mSubInfoEntityList; - private List mUiccInfoEntityList; - private List mMobileNetworkInfoEntityList; - private boolean mIsAirplaneModeOn; - private LifecycleOwner mLifecycleOwner; - - /** - * This controls the summary text and click behavior of the "Mobile network" item on the - * Network & internet page. There are 3 separate cases depending on the number of mobile network - * subscriptions: - *
    - *
  • No subscription: click action begins a UI flow to add a network subscription, and - * the summary text indicates this
  • - * - *
  • One subscription: click action takes you to details for that one network, and - * the summary text is the network name
  • - * - *
  • More than one subscription: click action takes you to a page listing the subscriptions, - * and the summary text gives the count of SIMs
  • - *
- */ - public MobileNetworkSummaryController(Context context, Lifecycle lifecycle, - LifecycleOwner lifecycleOwner) { - super(context); - mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); - mLifecycleOwner = lifecycleOwner; - mMobileNetworkRepository = MobileNetworkRepository.getInstance(context); - mIsAirplaneModeOn = mMobileNetworkRepository.isAirplaneModeOn(); - if (lifecycle != null) { - lifecycle.addObserver(this); - } - } - - @OnLifecycleEvent(ON_RESUME) - public void onResume() { - mMobileNetworkRepository.addRegister(mLifecycleOwner, this, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - mMobileNetworkRepository.updateEntity(); - } - - @OnLifecycleEvent(ON_PAUSE) - public void onPause() { - mMobileNetworkRepository.removeRegister(this); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mPreference = screen.findPreference(getPreferenceKey()); - } - - @Override - public CharSequence getSummary() { - - if ((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) || ( - mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty()) || ( - mMobileNetworkInfoEntityList == null || mMobileNetworkInfoEntityList.isEmpty())) { - if (new EuiccRepository(mContext).showEuiccSettings()) { - return mContext.getResources().getString( - R.string.mobile_network_summary_add_a_network); - } - // set empty string to override previous text for carrier when SIM available - return ""; - } else if (mSubInfoEntityList.size() == 1) { - SubscriptionInfoEntity info = mSubInfoEntityList.get(0); - CharSequence displayName = info.uniqueName; - if (info.isEmbedded || mUiccInfoEntityList.get(0).isActive - || mMobileNetworkInfoEntityList.get(0).showToggleForPhysicalSim) { - return displayName; - } - return mContext.getString(R.string.mobile_network_tap_to_activate, displayName); - } else { - return mSubInfoEntityList.stream() - .map(SubscriptionInfoEntity::getUniqueDisplayName) - .collect(Collectors.joining(", ")); - } - } - - private void logPreferenceClick(Preference preference) { - mMetricsFeatureProvider.logClickedPreference(preference, - preference.getExtras().getInt(DashboardFragment.CATEGORY)); - } - - private void startAddSimFlow() { - final Intent intent = new Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION); - intent.setPackage(com.android.settings.Utils.PHONE_PACKAGE_NAME); - intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true); - mContext.startActivity(intent); - } - - private void initPreference() { - refreshSummary(mPreference); - mPreference.setOnPreferenceClickListener(null); - mPreference.setFragment(null); - mPreference.setEnabled(!mIsAirplaneModeOn); - } - - private void update() { - if (mPreference == null || mPreference.isDisabledByAdmin()) { - return; - } - - initPreference(); - if (((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) - || (mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty()) - || (mMobileNetworkInfoEntityList == null - || mMobileNetworkInfoEntityList.isEmpty()))) { - if (new EuiccRepository(mContext).showEuiccSettings()) { - mPreference.setOnPreferenceClickListener((Preference pref) -> { - logPreferenceClick(pref); - startAddSimFlow(); - return true; - }); - } else { - mPreference.setEnabled(false); - } - return; - } - - mPreference.setFragment(MobileNetworkListFragment.class.getCanonicalName()); - } - - @Override - public boolean isAvailable() { - return new SimRepository(mContext).showMobileNetworkPage(); - } - - @Override - public String getPreferenceKey() { - return KEY; - } - - @Override - public void onAirplaneModeChanged(boolean airplaneModeEnabled) { - if (mIsAirplaneModeOn != airplaneModeEnabled) { - mIsAirplaneModeOn = airplaneModeEnabled; - update(); - } - } - - @Override - public void onAvailableSubInfoChanged(List subInfoEntityList) { - mSubInfoEntityList = subInfoEntityList; - update(); - } - - @Override - public void onAllUiccInfoChanged(List uiccInfoEntityList) { - mUiccInfoEntityList = uiccInfoEntityList; - update(); - } - - @Override - public void onAllMobileNetworkInfoChanged( - List mobileNetworkInfoEntityList) { - mMobileNetworkInfoEntityList = mobileNetworkInfoEntityList; - update(); - } -} diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.kt b/src/com/android/settings/network/MobileNetworkSummaryController.kt new file mode 100644 index 00000000000..5980bbd7d05 --- /dev/null +++ b/src/com/android/settings/network/MobileNetworkSummaryController.kt @@ -0,0 +1,121 @@ +/* + * 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 + +import android.content.Context +import android.provider.Settings +import androidx.lifecycle.LifecycleOwner +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import com.android.settings.R +import com.android.settings.core.BasePreferenceController +import com.android.settings.dashboard.DashboardFragment +import com.android.settings.network.telephony.SimRepository +import com.android.settings.overlay.FeatureFactory.Companion.featureFactory +import com.android.settings.spa.network.startAddSimFlow +import com.android.settingslib.RestrictedPreference +import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle +import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow +import kotlinx.coroutines.flow.Flow + +/** + * This controls the summary text and click behavior of the "Mobile network" item on the Network & + * internet page. There are 2 separate cases depending on the number of mobile network + * subscriptions: + * - No subscription: click action begins a UI flow to add a network subscription, and the summary + * text indicates this + * - Has subscriptions: click action takes you to a page listing the subscriptions, and the summary + * text gives the count of SIMs + */ +class MobileNetworkSummaryController +@JvmOverloads +constructor( + private val context: Context, + preferenceKey: String, + private val repository: MobileNetworkSummaryRepository = + MobileNetworkSummaryRepository(context), + private val airplaneModeOnFlow: Flow = + context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON), +) : BasePreferenceController(context, preferenceKey) { + private val metricsFeatureProvider = featureFactory.metricsFeatureProvider + private var preference: RestrictedPreference? = null + + private var isAirplaneModeOn = false + + override fun getAvailabilityStatus() = + if (SimRepository(mContext).showMobileNetworkPage()) AVAILABLE + else CONDITIONALLY_UNAVAILABLE + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey) + } + + override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { + repository + .subscriptionsStateFlow() + .collectLatestWithLifecycle(viewLifecycleOwner, action = ::update) + airplaneModeOnFlow.collectLatestWithLifecycle(viewLifecycleOwner) { + isAirplaneModeOn = it + updateEnabled() + } + } + + private fun update(state: MobileNetworkSummaryRepository.SubscriptionsState) { + val preference = preference ?: return + preference.onPreferenceClickListener = null + preference.fragment = null + when (state) { + MobileNetworkSummaryRepository.AddNetwork -> { + preference.summary = + context.getString(R.string.mobile_network_summary_add_a_network) + preference.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + logPreferenceClick() + startAddSimFlow(context) + true + } + } + + MobileNetworkSummaryRepository.NoSubscriptions -> { + preference.summary = null + } + + is MobileNetworkSummaryRepository.HasSubscriptions -> { + preference.summary = state.displayNames.joinToString(", ") + preference.fragment = MobileNetworkListFragment::class.java.canonicalName + } + } + updateEnabled() + } + + private fun updateEnabled() { + val preference = preference ?: return + if (preference.isDisabledByAdmin) return + preference.isEnabled = + (preference.onPreferenceClickListener != null || preference.fragment != null) && + !isAirplaneModeOn + } + + private fun logPreferenceClick() { + val preference = preference ?: return + metricsFeatureProvider.logClickedPreference( + preference, + preference.extras.getInt(DashboardFragment.CATEGORY), + ) + } +} diff --git a/src/com/android/settings/network/MobileNetworkSummaryRepository.kt b/src/com/android/settings/network/MobileNetworkSummaryRepository.kt new file mode 100644 index 00000000000..edf557bb725 --- /dev/null +++ b/src/com/android/settings/network/MobileNetworkSummaryRepository.kt @@ -0,0 +1,66 @@ +/* + * 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 + +import android.content.Context +import android.telephony.SubscriptionInfo +import com.android.settings.network.telephony.SubscriptionRepository +import com.android.settings.network.telephony.euicc.EuiccRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +class MobileNetworkSummaryRepository( + private val context: Context, + private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context), + private val euiccRepository: EuiccRepository = EuiccRepository(context), + private val getDisplayName: (SubscriptionInfo) -> String = { subInfo -> + SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, context).toString() + }, +) { + sealed interface SubscriptionsState + + data object AddNetwork : SubscriptionsState + + data object NoSubscriptions : SubscriptionsState + + data class HasSubscriptions(val displayNames: List) : SubscriptionsState + + fun subscriptionsStateFlow(): Flow = + subDisplayNamesFlow() + .map { displayNames -> + if (displayNames.isEmpty()) { + if (euiccRepository.showEuiccSettings()) AddNetwork else NoSubscriptions + } else { + HasSubscriptions(displayNames) + } + } + .distinctUntilChanged() + .conflate() + .flowOn(Dispatchers.Default) + + private fun subDisplayNamesFlow(): Flow> = + subscriptionRepository + .selectableSubscriptionInfoListFlow() + .map { subInfos -> subInfos.map(getDisplayName) } + .distinctUntilChanged() + .conflate() + .flowOn(Dispatchers.Default) +} diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java index aff91308e94..ee7d440bcf3 100644 --- a/src/com/android/settings/network/NetworkDashboardFragment.java +++ b/src/com/android/settings/network/NetworkDashboardFragment.java @@ -19,7 +19,7 @@ import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; -import androidx.lifecycle.LifecycleOwner; +import androidx.annotation.Nullable; import com.android.settings.R; import com.android.settings.SettingsDumpService; @@ -69,12 +69,11 @@ public class NetworkDashboardFragment extends DashboardFragment implements @Override protected List createPreferenceControllers(Context context) { - return buildPreferenceControllers(context, getSettingsLifecycle(), - this /* LifecycleOwner */); + return buildPreferenceControllers(context, getSettingsLifecycle()); } private static List buildPreferenceControllers(Context context, - Lifecycle lifecycle, LifecycleOwner lifecycleOwner) { + @Nullable Lifecycle lifecycle) { final VpnPreferenceController vpnPreferenceController = new VpnPreferenceController(context); final PrivateDnsPreferenceController privateDnsPreferenceController = @@ -87,7 +86,6 @@ public class NetworkDashboardFragment extends DashboardFragment implements final List controllers = new ArrayList<>(); - controllers.add(new MobileNetworkSummaryController(context, lifecycle, lifecycleOwner)); controllers.add(vpnPreferenceController); controllers.add(privateDnsPreferenceController); @@ -114,8 +112,7 @@ public class NetworkDashboardFragment extends DashboardFragment implements @Override public List createPreferenceControllers(Context context) { - return buildPreferenceControllers(context, null /* lifecycle */, - null /* LifecycleOwner */); + return buildPreferenceControllers(context, null /* lifecycle */); } }; } diff --git a/src/com/android/settings/spa/network/SimsSection.kt b/src/com/android/settings/spa/network/SimsSection.kt index 276d121c24f..bd55b32a5e8 100644 --- a/src/com/android/settings/spa/network/SimsSection.kt +++ b/src/com/android/settings/spa/network/SimsSection.kt @@ -137,7 +137,7 @@ private fun AddSim() { } } -private fun startAddSimFlow(context: Context) { +fun startAddSimFlow(context: Context) { val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION) intent.setPackage(Utils.PHONE_PACKAGE_NAME) intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true) diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java deleted file mode 100644 index 1823d6d6bed..00000000000 --- a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (C) 2019 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 com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.notNull; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.Intent; -import android.provider.Settings; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.telephony.euicc.EuiccManager; -import android.text.TextUtils; - -import androidx.lifecycle.LifecycleOwner; -import androidx.preference.PreferenceScreen; - -import com.android.settings.Settings.MobileNetworkActivity; -import com.android.settings.widget.AddPreference; -import com.android.settingslib.RestrictedLockUtils; -import com.android.settingslib.core.lifecycle.Lifecycle; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -import java.util.Arrays; - -@RunWith(RobolectricTestRunner.class) -public class MobileNetworkSummaryControllerTest { - - @Mock - private TelephonyManager mTelephonyManager; - @Mock - private SubscriptionManager mSubscriptionManager; - @Mock - private EuiccManager mEuiccManager; - @Mock - private PreferenceScreen mPreferenceScreen; - @Mock - private MobileNetworkRepository mMobileNetworkRepository; - @Mock - private MobileNetworkRepository.MobileNetworkCallback mMobileNetworkCallback; - - private AddPreference mPreference; - private Context mContext; - private MobileNetworkSummaryController mController; - private LifecycleOwner mLifecycleOwner; - private Lifecycle mLifecycle; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - doReturn(mTelephonyManager).when(mContext).getSystemService(TelephonyManager.class); - doReturn(mSubscriptionManager).when(mContext).getSystemService(SubscriptionManager.class); - doReturn(mEuiccManager).when(mContext).getSystemService(EuiccManager.class); - mMobileNetworkRepository = MobileNetworkRepository.getInstance(mContext); - mLifecycleOwner = () -> mLifecycle; - mLifecycle = new Lifecycle(mLifecycleOwner); - mMobileNetworkRepository.addRegister(mLifecycleOwner, mMobileNetworkCallback, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - - when(mTelephonyManager.getNetworkCountryIso()).thenReturn(""); - when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true); - when(mEuiccManager.isEnabled()).thenReturn(true); - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1); - - mController = new MobileNetworkSummaryController(mContext, mLifecycle, mLifecycleOwner); - mPreference = spy(new AddPreference(mContext, null)); - mPreference.setKey(mController.getPreferenceKey()); - when(mPreferenceScreen.findPreference(eq(mController.getPreferenceKey()))).thenReturn( - mPreference); - } - - @After - public void tearDown() { - mMobileNetworkRepository.removeRegister(mMobileNetworkCallback); - SubscriptionUtil.setActiveSubscriptionsForTesting(null); - SubscriptionUtil.setAvailableSubscriptionsForTesting(null); - } - - @Test - public void getSummary_noSubscriptions_returnSummaryCorrectly() { - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - - assertThat(mController.getSummary()).isEqualTo("Add a network"); - } - - @Test - public void getSummary_noSubscriptionsNoEuiccMgr_correctSummaryAndClickHandler() { - when(mEuiccManager.isEnabled()).thenReturn(false); - assertThat(TextUtils.isEmpty(mController.getSummary())).isTrue(); - assertThat(mPreference.getOnPreferenceClickListener()).isNull(); - assertThat(mPreference.getFragment()).isNull(); - } - - @Test - @Ignore - public void getSummary_oneSubscription_correctSummaryAndClickHandler() { - final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - when(sub1.getSubscriptionId()).thenReturn(1); - when(sub1.getDisplayName()).thenReturn("sub1"); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); - SubscriptionUtil.setActiveSubscriptionsForTesting(Arrays.asList(sub1)); - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - assertThat(mController.getSummary()).isEqualTo("sub1"); - assertThat(mPreference.getFragment()).isNull(); - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - doNothing().when(mContext).startActivity(intentCaptor.capture()); - mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference); - Intent intent = intentCaptor.getValue(); - assertThat(intent.getComponent().getClassName()).isEqualTo( - MobileNetworkActivity.class.getName()); - assertThat(intent.getIntExtra(Settings.EXTRA_SUB_ID, - SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(sub1.getSubscriptionId()); - } - - @Test - @Ignore - public void getSummary_oneInactivePSim_cannotDisablePsim_correctSummaryAndClickHandler() { - final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - when(sub1.getSubscriptionId()).thenReturn(1); - when(sub1.getDisplayName()).thenReturn("sub1"); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); - when(mSubscriptionManager.isActiveSubscriptionId(eq(1))).thenReturn(false); - - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - - assertThat(mController.getSummary()).isEqualTo("Tap to activate sub1"); - - assertThat(mPreference.getFragment()).isNull(); - mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference); - verify(mSubscriptionManager).setSubscriptionEnabled(eq(sub1.getSubscriptionId()), eq(true)); - } - - @Test - @Ignore - public void getSummary_oneInactivePSim_canDisablePsim_correctSummaryAndClickHandler() { - final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - when(sub1.getSubscriptionId()).thenReturn(1); - when(sub1.getDisplayName()).thenReturn("sub1"); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); - SubscriptionUtil.setActiveSubscriptionsForTesting(Arrays.asList(sub1)); - when(mSubscriptionManager.isActiveSubscriptionId(eq(1))).thenReturn(false); - when(mSubscriptionManager.canDisablePhysicalSubscription()).thenReturn(true); - - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - - assertThat(mController.getSummary()).isEqualTo("sub1"); - - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - doNothing().when(mContext).startActivity(intentCaptor.capture()); - mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference); - Intent intent = intentCaptor.getValue(); - assertThat(intent.getComponent().getClassName()).isEqualTo( - MobileNetworkActivity.class.getName()); - assertThat(intent.getIntExtra(Settings.EXTRA_SUB_ID, - SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(sub1.getSubscriptionId()); - } - - @Test - public void addButton_noSubscriptionsNoEuiccMgr_noAddClickListener() { - when(mEuiccManager.isEnabled()).thenReturn(false); - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - verify(mPreference, never()).setOnAddClickListener(notNull()); - } - - @Test - public void addButton_oneSubscriptionNoEuiccMgr_noAddClickListener() { - when(mEuiccManager.isEnabled()).thenReturn(false); - final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - verify(mPreference, never()).setOnAddClickListener(notNull()); - } - - @Test - public void addButton_noSubscriptions_noAddClickListener() { - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - verify(mPreference, never()).setOnAddClickListener(notNull()); - } - - @Test - @Ignore - public void addButton_oneSubscription_hasAddClickListener() { - final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - verify(mPreference).setOnAddClickListener(notNull()); - } - - @Test - @Ignore - public void addButton_twoSubscriptions_hasAddClickListener() { - final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - final SubscriptionInfo sub2 = mock(SubscriptionInfo.class); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1, sub2)); - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - verify(mPreference).setOnAddClickListener(notNull()); - } - - @Test - @Ignore - public void addButton_oneSubscriptionAirplaneModeTurnedOn_addButtonGetsDisabled() { - final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); - mController.onAirplaneModeChanged(true); - - final ArgumentCaptor captor = ArgumentCaptor.forClass(Boolean.class); - verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture()); - assertThat(captor.getValue()).isFalse(); - } - - @Test - @Ignore - public void onResume_oneSubscriptionAirplaneMode_isDisabled() { - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); - final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - - assertThat(mPreference.isEnabled()).isFalse(); - - final ArgumentCaptor captor = ArgumentCaptor.forClass(Boolean.class); - verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture()); - assertThat(captor.getValue()).isFalse(); - } - - @Test - public void onAvailableSubInfoChanged_noSubscriptionEsimDisabled_isDisabled() { - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0); - when(mEuiccManager.isEnabled()).thenReturn(false); - mController.displayPreference(mPreferenceScreen); - - mController.onAvailableSubInfoChanged(null); - - assertThat(mPreference.isEnabled()).isFalse(); - } - - @Test - public void onAirplaneModeChanged_oneSubscriptionAirplaneModeGetsTurnedOn_isDisabled() { - final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - - assertThat(mPreference.isEnabled()).isTrue(); - - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); - mController.onAirplaneModeChanged(true); - - assertThat(mPreference.isEnabled()).isFalse(); - } - - @Test - @Ignore - public void onAirplaneModeChanged_oneSubscriptionAirplaneModeGetsTurnedOff_isEnabled() { - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); - final SubscriptionInfo sub1 = mock(SubscriptionInfo.class); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1)); - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - - assertThat(mPreference.isEnabled()).isFalse(); - - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0); - mController.onAirplaneModeChanged(false); - - assertThat(mPreference.isEnabled()).isTrue(); - - final ArgumentCaptor captor = ArgumentCaptor.forClass(Boolean.class); - verify(mPreference, atLeastOnce()).setAddWidgetEnabled(eq(false)); - verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture()); - assertThat(captor.getValue()).isTrue(); - } - - @Test - public void onResume_disabledByAdmin_prefStaysDisabled() { - mPreference.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin()); - mController.displayPreference(mPreferenceScreen); - mController.onResume(); - verify(mPreference, never()).setEnabled(eq(true)); - } -} diff --git a/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryControllerTest.kt new file mode 100644 index 00000000000..69fa9c42e6c --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryControllerTest.kt @@ -0,0 +1,151 @@ +/* + * 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 + +import android.content.Context +import androidx.lifecycle.testing.TestLifecycleOwner +import androidx.preference.PreferenceManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.android.settingslib.RestrictedPreference +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub + +@RunWith(AndroidJUnit4::class) +class MobileNetworkSummaryControllerTest { + + private val context: Context = ApplicationProvider.getApplicationContext() + + private val preference = RestrictedPreference(context).apply { key = KEY } + private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context) + + private val mockMobileNetworkSummaryRepository = mock() + private val airplaneModeOnFlow = MutableStateFlow(false) + + private val controller = + MobileNetworkSummaryController( + context = context, + preferenceKey = KEY, + repository = mockMobileNetworkSummaryRepository, + airplaneModeOnFlow = airplaneModeOnFlow, + ) + + @Before + fun setUp() { + preferenceScreen.addPreference(preference) + controller.displayPreference(preferenceScreen) + } + + @Test + fun onViewCreated_noSubscriptions(): Unit = runBlocking { + mockMobileNetworkSummaryRepository.stub { + on { subscriptionsStateFlow() } doReturn + flowOf(MobileNetworkSummaryRepository.NoSubscriptions) + } + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + + assertThat(preference.summary).isNull() + assertThat(preference.isEnabled).isFalse() + assertThat(preference.onPreferenceClickListener).isNull() + } + + @Test + fun onViewCreated_addNetwork(): Unit = runBlocking { + mockMobileNetworkSummaryRepository.stub { + on { subscriptionsStateFlow() } doReturn + flowOf(MobileNetworkSummaryRepository.AddNetwork) + } + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + + assertThat(preference.summary) + .isEqualTo(context.getString(R.string.mobile_network_summary_add_a_network)) + assertThat(preference.isEnabled).isTrue() + assertThat(preference.onPreferenceClickListener).isNotNull() + } + + @Test + fun onViewCreated_hasSubscriptions(): Unit = runBlocking { + mockMobileNetworkSummaryRepository.stub { + on { subscriptionsStateFlow() } doReturn + flowOf( + MobileNetworkSummaryRepository.HasSubscriptions( + displayNames = listOf(DISPLAY_NAME_1, DISPLAY_NAME_2) + ) + ) + } + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + + assertThat(preference.summary).isEqualTo("$DISPLAY_NAME_1, $DISPLAY_NAME_2") + assertThat(preference.isEnabled).isTrue() + assertThat(preference.fragment).isNotNull() + } + + @Test + fun onViewCreated_addNetworkAndAirplaneModeOn(): Unit = runBlocking { + mockMobileNetworkSummaryRepository.stub { + on { subscriptionsStateFlow() } doReturn + flowOf(MobileNetworkSummaryRepository.AddNetwork) + } + airplaneModeOnFlow.value = true + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + + assertThat(preference.isEnabled).isFalse() + } + + @Test + fun onViewCreated_hasSubscriptionsAndAirplaneModeOn(): Unit = runBlocking { + mockMobileNetworkSummaryRepository.stub { + on { subscriptionsStateFlow() } doReturn + flowOf( + MobileNetworkSummaryRepository.HasSubscriptions( + displayNames = listOf(DISPLAY_NAME_1, DISPLAY_NAME_2) + ) + ) + } + airplaneModeOnFlow.value = true + + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + + assertThat(preference.isEnabled).isFalse() + } + + + private companion object { + const val KEY = "test_key" + const val DISPLAY_NAME_1 = "Display Name 1" + const val DISPLAY_NAME_2 = "Display Name 2" + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryRepositoryTest.kt new file mode 100644 index 00000000000..463af96268f --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryRepositoryTest.kt @@ -0,0 +1,101 @@ +/* + * 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 + +import android.content.Context +import android.telephony.SubscriptionInfo +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.network.telephony.SubscriptionRepository +import com.android.settings.network.telephony.euicc.EuiccRepository +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub + +@RunWith(AndroidJUnit4::class) +class MobileNetworkSummaryRepositoryTest { + + private val context: Context = ApplicationProvider.getApplicationContext() + + private val mockSubscriptionRepository = mock() + private val mockEuiccRepository = mock() + + private val repository = + MobileNetworkSummaryRepository( + context = context, + subscriptionRepository = mockSubscriptionRepository, + euiccRepository = mockEuiccRepository, + getDisplayName = { it.displayName.toString() }, + ) + + @Test + fun subscriptionsStateFlow_noSubscriptionsAndShowEuicc_returnsAddNetwork() = runBlocking { + mockSubscriptionRepository.stub { + on { selectableSubscriptionInfoListFlow() } doReturn flowOf(emptyList()) + } + mockEuiccRepository.stub { on { showEuiccSettings() } doReturn true } + + val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull() + + assertThat(state).isEqualTo(MobileNetworkSummaryRepository.AddNetwork) + } + + @Test + fun subscriptionsStateFlow_noSubscriptionsAndHideEuicc_returnsNoSubscriptions() = runBlocking { + mockSubscriptionRepository.stub { + on { selectableSubscriptionInfoListFlow() } doReturn flowOf(emptyList()) + } + mockEuiccRepository.stub { on { showEuiccSettings() } doReturn false } + + val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull() + + assertThat(state).isEqualTo(MobileNetworkSummaryRepository.NoSubscriptions) + } + + @Test + fun subscriptionsStateFlow_hasSubscriptions_returnsHasSubscriptions() = runBlocking { + mockSubscriptionRepository.stub { + on { selectableSubscriptionInfoListFlow() } doReturn + flowOf( + listOf( + SubscriptionInfo.Builder().setDisplayName(DISPLAY_NAME_1).build(), + SubscriptionInfo.Builder().setDisplayName(DISPLAY_NAME_2).build(), + ) + ) + } + + val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull() + + assertThat(state) + .isEqualTo( + MobileNetworkSummaryRepository.HasSubscriptions( + listOf(DISPLAY_NAME_1, DISPLAY_NAME_2) + ) + ) + } + + private companion object { + const val DISPLAY_NAME_1 = "Sub 1" + const val DISPLAY_NAME_2 = "Sub 2" + } +}