Create MobileNetworkSummaryRepository

For MobileNetworkSummaryController to use, so it no longer use
MobileNetworkRepository.

Fix: 366097262
Flag: EXEMPT refactor
Test: manual - on Network & internet
Test: atest MobileNetworkSummaryRepositoryTest
Change-Id: I8a9d52af8e230fc407a4339c27f73ef79d512b24
This commit is contained in:
Chaohui Wang
2024-09-13 16:52:25 +08:00
parent 540ce288ad
commit 777a179bd6
9 changed files with 445 additions and 562 deletions

View File

@@ -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" />
<com.android.settingslib.RestrictedSwitchPreference
android:key="airplane_mode"

View File

@@ -1,219 +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 androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
import android.content.Context;
import android.content.Intent;
import android.telephony.SubscriptionManager;
import android.telephony.euicc.EuiccManager;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.network.telephony.SimRepository;
import com.android.settings.network.telephony.euicc.EuiccRepository;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
import com.android.settingslib.mobile.dataservice.UiccInfoEntity;
import java.util.List;
import java.util.stream.Collectors;
public class MobileNetworkSummaryController extends AbstractPreferenceController implements
LifecycleObserver, PreferenceControllerMixin,
MobileNetworkRepository.MobileNetworkCallback {
private static final String TAG = "MobileNetSummaryCtlr";
private static final String KEY = "mobile_network_list";
private final MetricsFeatureProvider mMetricsFeatureProvider;
private RestrictedPreference mPreference;
private MobileNetworkRepository mMobileNetworkRepository;
private List<SubscriptionInfoEntity> mSubInfoEntityList;
private List<UiccInfoEntity> mUiccInfoEntityList;
private List<MobileNetworkInfoEntity> 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:
* <ul>
* <li>No subscription: click action begins a UI flow to add a network subscription, and
* the summary text indicates this</li>
*
* <li>One subscription: click action takes you to details for that one network, and
* the summary text is the network name</li>
*
* <li>More than one subscription: click action takes you to a page listing the subscriptions,
* and the summary text gives the count of SIMs</li>
* </ul>
*/
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<SubscriptionInfoEntity> subInfoEntityList) {
mSubInfoEntityList = subInfoEntityList;
update();
}
@Override
public void onAllUiccInfoChanged(List<UiccInfoEntity> uiccInfoEntityList) {
mUiccInfoEntityList = uiccInfoEntityList;
update();
}
@Override
public void onAllMobileNetworkInfoChanged(
List<MobileNetworkInfoEntity> mobileNetworkInfoEntityList) {
mMobileNetworkInfoEntityList = mobileNetworkInfoEntityList;
update();
}
}

View File

@@ -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<Boolean> =
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),
)
}
}

View File

@@ -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<String>) : SubscriptionsState
fun subscriptionsStateFlow(): Flow<SubscriptionsState> =
subDisplayNamesFlow()
.map { displayNames ->
if (displayNames.isEmpty()) {
if (euiccRepository.showEuiccSettings()) AddNetwork else NoSubscriptions
} else {
HasSubscriptions(displayNames)
}
}
.distinctUntilChanged()
.conflate()
.flowOn(Dispatchers.Default)
private fun subDisplayNamesFlow(): Flow<List<String>> =
subscriptionRepository
.selectableSubscriptionInfoListFlow()
.map { subInfos -> subInfos.map(getDisplayName) }
.distinctUntilChanged()
.conflate()
.flowOn(Dispatchers.Default)
}

View File

@@ -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<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context, getSettingsLifecycle(),
this /* LifecycleOwner */);
return buildPreferenceControllers(context, getSettingsLifecycle());
}
private static List<AbstractPreferenceController> 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<AbstractPreferenceController> 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<AbstractPreferenceController> createPreferenceControllers(Context
context) {
return buildPreferenceControllers(context, null /* lifecycle */,
null /* LifecycleOwner */);
return buildPreferenceControllers(context, null /* lifecycle */);
}
};
}

View File

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

View File

@@ -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<Intent> 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<Intent> 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<Boolean> 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<Boolean> 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<Boolean> 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));
}
}

View File

@@ -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<MobileNetworkSummaryRepository>()
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"
}
}

View File

@@ -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<SubscriptionRepository>()
private val mockEuiccRepository = mock<EuiccRepository>()
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"
}
}