diff --git a/src/com/android/settings/network/SubscriptionsPreferenceController.java b/src/com/android/settings/network/SubscriptionsPreferenceController.java index f4ffd09f801..a17fc60ad04 100644 --- a/src/com/android/settings/network/SubscriptionsPreferenceController.java +++ b/src/com/android/settings/network/SubscriptionsPreferenceController.java @@ -21,10 +21,13 @@ import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; import static com.android.settings.network.telephony.MobileNetworkUtils.NO_CELL_DATA_TYPE_ICON; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.graphics.drawable.Drawable; import android.provider.Settings; +import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -46,6 +49,7 @@ import com.android.settings.network.telephony.DataConnectivityListener; import com.android.settings.network.telephony.MobileNetworkActivity; import com.android.settings.network.telephony.MobileNetworkUtils; import com.android.settings.network.telephony.SignalStrengthListener; +import com.android.settings.widget.GearPreference; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.net.SignalStrengthUtil; @@ -55,9 +59,15 @@ import java.util.Map; import java.util.Set; /** - * This manages a set of Preferences it places into a PreferenceGroup owned by some parent + * If the provider model is not enabled, this controller manages a set of Preferences it places into + * a PreferenceGroup owned by some parent * controller class - one for each available subscription. This controller is only considered * available if there are 2 or more subscriptions. + * + * If the provider model is enabled, this controller manages preference with data subscription + * information and make its state display on preference. + * TODO this class will clean up the multiple subscriptions functionality after the provider + * model is released. */ public class SubscriptionsPreferenceController extends AbstractPreferenceController implements LifecycleObserver, SubscriptionsChangeListener.SubscriptionsChangeListenerClient, @@ -68,16 +78,30 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl private UpdateListener mUpdateListener; private String mPreferenceGroupKey; private PreferenceGroup mPreferenceGroup; - private SubscriptionManager mManager; + private TelephonyManager mTelephonyManager; + private SubscriptionManager mSubscriptionManager; private SubscriptionsChangeListener mSubscriptionsListener; private MobileDataEnabledListener mDataEnabledListener; private DataConnectivityListener mConnectivityListener; private SignalStrengthListener mSignalStrengthListener; + @VisibleForTesting + final BroadcastReceiver mDataSubscriptionChangedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) { + update(); + } + } + }; // Map of subscription id to Preference private Map mSubscriptionPreferences; private int mStartOrder; + private GearPreference mSubsGearPref; + + private SubsPrefCtrlInjector mSubsPrefCtrlInjector; /** * This interface lets a parent of this class know that some change happened - this could * either be because overall availability changed, or because we've added/removed/updated some @@ -107,21 +131,37 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl mUpdateListener = updateListener; mPreferenceGroupKey = preferenceGroupKey; mStartOrder = startOrder; - mManager = context.getSystemService(SubscriptionManager.class); + mTelephonyManager = context.getSystemService(TelephonyManager.class); + mSubscriptionManager = context.getSystemService(SubscriptionManager.class); mSubscriptionPreferences = new ArrayMap<>(); mSubscriptionsListener = new SubscriptionsChangeListener(context, this); mDataEnabledListener = new MobileDataEnabledListener(context, this); mConnectivityListener = new DataConnectivityListener(context, this); mSignalStrengthListener = new SignalStrengthListener(context, this); lifecycle.addObserver(this); + mSubsPrefCtrlInjector = createSubsPrefCtrlInjector(); + } + + private void registerDataSubscriptionChangedReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); + mContext.registerReceiver(mDataSubscriptionChangedReceiver, filter); + } + + private void unRegisterDataSubscriptionChangedReceiver() { + if (mDataSubscriptionChangedReceiver != null) { + mContext.unregisterReceiver(mDataSubscriptionChangedReceiver); + } + } @OnLifecycleEvent(ON_RESUME) public void onResume() { mSubscriptionsListener.start(); - mDataEnabledListener.start(SubscriptionManager.getDefaultDataSubscriptionId()); + mDataEnabledListener.start(mSubsPrefCtrlInjector.getDefaultDataSubscriptionId()); mConnectivityListener.start(); mSignalStrengthListener.resume(); + registerDataSubscriptionChangedReceiver(); update(); } @@ -131,6 +171,7 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl mDataEnabledListener.stop(); mConnectivityListener.stop(); mSignalStrengthListener.pause(); + unRegisterDataSubscriptionChangedReceiver(); } @Override @@ -143,29 +184,116 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl if (mPreferenceGroup == null) { return; } - if (!isAvailable()) { + if (mSubsGearPref != null) { + mPreferenceGroup.removePreference(mSubsGearPref); + } for (Preference pref : mSubscriptionPreferences.values()) { mPreferenceGroup.removePreference(pref); } + mSubscriptionPreferences.clear(); mSignalStrengthListener.updateSubscriptionIds(Collections.emptySet()); mUpdateListener.onChildrenUpdated(); return; } + if (mSubsPrefCtrlInjector.isProviderModelEnabled(mContext)) { + updateForProvider(); + } else { + updateForBase(); + } + } + + private void updateForProvider() { + SubscriptionInfo subInfo = mSubscriptionManager.getDefaultDataSubscriptionInfo(); + if (subInfo == null) { + mPreferenceGroup.removeAll(); + return; + } + if (mSubsGearPref == null) { + mPreferenceGroup.removeAll(); + mSubsGearPref = new GearPreference(mContext, null); + mSubsGearPref.setOnPreferenceClickListener(preference -> { + //TODO(b/176141379) Wait for wifiManager#selectCarrier(int subscriptionId) + return true; + }); + mSubsGearPref.setOnGearClickListener(p -> + startMobileNetworkActivity(mContext, subInfo.getSubscriptionId())); + } + + mSubsGearPref.setTitle(subInfo.getDisplayName()); + mSubsGearPref.setOrder(mStartOrder); + //TODO(b/176141828) Wait for api provided by system ui. + mSubsGearPref.setSummary(getMobilePreferenceSummary()); + mSubsGearPref.setIcon(getIcon(subInfo.getSubscriptionId())); + mPreferenceGroup.addPreference(mSubsGearPref); + + final Set activeDataSubIds = new ArraySet<>(); + activeDataSubIds.add(subInfo.getSubscriptionId()); + mSignalStrengthListener.updateSubscriptionIds(activeDataSubIds); + mUpdateListener.onChildrenUpdated(); + } + + private String getMobilePreferenceSummary() { + //TODO(b/176141828) Waiting for the api provided by system UI. + String result = "5G"; + if (MobileNetworkUtils.activeNetworkIsCellular(mContext)) { + result = "Active, " + result; + } + return result; + } + + private Drawable getIcon(int subId) { + final TelephonyManager tmForSubId = mTelephonyManager.createForSubscriptionId(subId); + final SignalStrength strength = tmForSubId.getSignalStrength(); + int level = (strength == null) ? 0 : strength.getLevel(); + + int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS; + if (shouldInflateSignalStrength(subId)) { + level += 1; + numLevels += 1; + } + + final boolean isMobileDataOn = tmForSubId.isDataEnabled(); + final boolean isActiveCellularNetwork = + mSubsPrefCtrlInjector.isActiveCellularNetwork(mContext); + final boolean isMobileDataAccessible = tmForSubId.getDataState() + == TelephonyManager.DATA_CONNECTED; + final ServiceState serviceState = tmForSubId.getServiceState(); + final boolean isVoiceOutOfService = (serviceState == null) + ? true + : (serviceState.getState() == ServiceState.STATE_OUT_OF_SERVICE); + + Drawable icon = mSubsPrefCtrlInjector.getIcon(mContext, level, numLevels, false); + + if (isActiveCellularNetwork) { + icon.setTint(Utils.getColorAccentDefaultColor(mContext)); + return icon; + } + if ((isMobileDataOn && isMobileDataAccessible) + || (!isMobileDataOn && !isVoiceOutOfService)) { + return icon; + } + + icon = mContext.getDrawable(R.drawable.ic_signal_strength_zero_bar_no_internet); + return icon; + } + + private void updateForBase() { final Map existingPrefs = mSubscriptionPreferences; mSubscriptionPreferences = new ArrayMap<>(); int order = mStartOrder; final Set activeSubIds = new ArraySet<>(); - final int dataDefaultSubId = SubscriptionManager.getDefaultDataSubscriptionId(); - for (SubscriptionInfo info : SubscriptionUtil.getActiveSubscriptions(mManager)) { + final int dataDefaultSubId = mSubsPrefCtrlInjector.getDefaultDataSubscriptionId(); + for (SubscriptionInfo info : + SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager)) { final int subId = info.getSubscriptionId(); // Avoid from showing subscription(SIM)s which has been marked as hidden // For example, only one subscription will be shown when there're multiple // subscriptions with same group UUID. - if (!canSubscriptionBeDisplayed(mContext, subId)) { + if (!mSubsPrefCtrlInjector.canSubscriptionBeDisplayed(mContext, subId)) { continue; } activeSubIds.add(subId); @@ -181,9 +309,7 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl pref.setOrder(order++); pref.setOnPreferenceClickListener(clickedPref -> { - final Intent intent = new Intent(mContext, MobileNetworkActivity.class); - intent.putExtra(Settings.EXTRA_SUB_ID, subId); - mContext.startActivity(intent); + startMobileNetworkActivity(mContext, subId); return true; }); @@ -198,6 +324,12 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl mUpdateListener.onChildrenUpdated(); } + private static void startMobileNetworkActivity(Context context, int subId) { + final Intent intent = new Intent(context, MobileNetworkActivity.class); + intent.putExtra(Settings.EXTRA_SUB_ID, subId); + context.startActivity(intent); + } + @VisibleForTesting boolean shouldInflateSignalStrength(int subId) { return SignalStrengthUtil.shouldInflateSignalStrength(mContext, subId); @@ -214,14 +346,9 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl level += 1; numLevels += 1; } - final boolean showCutOut = !isDefaultForData || !mgr.isDataEnabled(); - pref.setIcon(getIcon(level, numLevels, showCutOut)); - } - @VisibleForTesting - Drawable getIcon(int level, int numLevels, boolean cutOut) { - return MobileNetworkUtils.getSignalStrengthIcon(mContext, level, numLevels, - NO_CELL_DATA_TYPE_ICON, cutOut); + final boolean showCutOut = !isDefaultForData || !mgr.isDataEnabled(); + pref.setIcon(mSubsPrefCtrlInjector.getIcon(mContext, level, numLevels, showCutOut)); } /** @@ -236,8 +363,8 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl * If a subscription isn't the default for anything, we just say it is available. */ protected String getSummary(int subId, boolean isDefaultForData) { - final int callsDefaultSubId = SubscriptionManager.getDefaultVoiceSubscriptionId(); - final int smsDefaultSubId = SubscriptionManager.getDefaultSmsSubscriptionId(); + final int callsDefaultSubId = mSubsPrefCtrlInjector.getDefaultVoiceSubscriptionId(); + final int smsDefaultSubId = mSubsPrefCtrlInjector.getDefaultSmsSubscriptionId(); String line1 = null; if (subId == callsDefaultSubId && subId == smsDefaultSubId) { @@ -253,7 +380,7 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl final TelephonyManager telMgrForSub = mContext.getSystemService( TelephonyManager.class).createForSubscriptionId(subId); final boolean dataEnabled = telMgrForSub.isDataEnabled(); - if (dataEnabled && MobileNetworkUtils.activeNetworkIsCellular(mContext)) { + if (dataEnabled && mSubsPrefCtrlInjector.isActiveCellularNetwork(mContext)) { line2 = mContext.getString(R.string.mobile_data_active); } else if (!dataEnabled) { line2 = mContext.getString(R.string.mobile_data_off); @@ -274,14 +401,16 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl } /** - * @return true if there are at least 2 available subscriptions. + * @return true if there are at least 2 available subscriptions, + * or if there is at least 1 available subscription for provider model. */ @Override public boolean isAvailable() { if (mSubscriptionsListener.isAirplaneModeOn()) { return false; } - List subInfoList = SubscriptionUtil.getActiveSubscriptions(mManager); + List subInfoList = + SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager); if (subInfoList == null) { return false; } @@ -290,8 +419,9 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl // For example, only one subscription will be shown when there're multiple // subscriptions with same group UUID. .filter(subInfo -> - canSubscriptionBeDisplayed(mContext, subInfo.getSubscriptionId())) - .count() >= (Utils.isProviderModelEnabled(mContext) ? 1 : 2); + mSubsPrefCtrlInjector.canSubscriptionBeDisplayed(mContext, + subInfo.getSubscriptionId())) + .count() >= (mSubsPrefCtrlInjector.isProviderModelEnabled(mContext) ? 1 : 2); } @Override @@ -307,7 +437,7 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl @Override public void onSubscriptionsChanged() { // See if we need to change which sub id we're using to listen for enabled/disabled changes. - int defaultDataSubId = SubscriptionManager.getDefaultDataSubscriptionId(); + int defaultDataSubId = mSubsPrefCtrlInjector.getDefaultDataSubscriptionId(); if (defaultDataSubId != mDataEnabledListener.getSubId()) { mDataEnabledListener.stop(); mDataEnabledListener.start(defaultDataSubId); @@ -335,4 +465,65 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl return (SubscriptionUtil.getAvailableSubscription(context, ProxySubscriptionManager.getInstance(context), subId) != null); } -} + + SubsPrefCtrlInjector createSubsPrefCtrlInjector() { + return new SubsPrefCtrlInjector(); + } + + /** + * To inject necessary data from each static api. + */ + @VisibleForTesting + public static class SubsPrefCtrlInjector { + /** + * Use to inject function and value for class and test class. + */ + public boolean canSubscriptionBeDisplayed(Context context, int subId) { + return (SubscriptionUtil.getAvailableSubscription(context, + ProxySubscriptionManager.getInstance(context), subId) != null); + } + + /** + * Check SIM be able to display on UI. + */ + public int getDefaultSmsSubscriptionId() { + return SubscriptionManager.getDefaultSmsSubscriptionId(); + } + + /** + * Get default voice subscription ID. + */ + public int getDefaultVoiceSubscriptionId() { + return SubscriptionManager.getDefaultVoiceSubscriptionId(); + } + + /** + * Get default data subscription ID. + */ + public int getDefaultDataSubscriptionId() { + return SubscriptionManager.getDefaultDataSubscriptionId(); + } + + /** + * Confirm the current network is cellular and active. + */ + public boolean isActiveCellularNetwork(Context context) { + return MobileNetworkUtils.activeNetworkIsCellular(context); + } + + /** + * Confirm the flag of Provider Model switch is turned on or not. + */ + public boolean isProviderModelEnabled(Context context) { + return Utils.isProviderModelEnabled(context); + } + + /** + * Get signal icon with different signal level. + */ + public Drawable getIcon(Context context, int level, int numLevels, boolean cutOut) { + return MobileNetworkUtils.getSignalStrengthIcon(context, level, numLevels, + NO_CELL_DATA_TYPE_ICON, cutOut); + } + } +} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/network/SubscriptionsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/SubscriptionsPreferenceControllerTest.java deleted file mode 100644 index 57b2e2fd6cb..00000000000 --- a/tests/robotests/src/com/android/settings/network/SubscriptionsPreferenceControllerTest.java +++ /dev/null @@ -1,553 +0,0 @@ -/* - * Copyright (C) 2018 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 android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS; -import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GOOD; -import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT; -import static android.telephony.SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; -import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR; -import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.provider.Settings; -import android.telephony.SignalStrength; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; - -import androidx.lifecycle.LifecycleOwner; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceScreen; - -import com.android.settings.R; -import com.android.settingslib.core.lifecycle.Lifecycle; - -import org.junit.After; -import org.junit.Before; -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 org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowSubscriptionManager; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowSubscriptionManager.class) -public class SubscriptionsPreferenceControllerTest { - private static final String KEY = "preference_group"; - - @Mock - private PreferenceScreen mScreen; - @Mock - private PreferenceCategory mPreferenceCategory; - @Mock - private SubscriptionManager mSubscriptionManager; - @Mock - private ConnectivityManager mConnectivityManager; - @Mock - private TelephonyManager mTelephonyManager; - @Mock - private Network mActiveNetwork; - @Mock - private NetworkCapabilities mCapabilities; - @Mock - private Drawable mSignalStrengthIcon; - - private Context mContext; - private LifecycleOwner mLifecycleOwner; - private Lifecycle mLifecycle; - private SubscriptionsPreferenceController mController; - private int mOnChildUpdatedCount; - private SubscriptionsPreferenceController.UpdateListener mUpdateListener; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - mLifecycleOwner = () -> mLifecycle; - mLifecycle = new Lifecycle(mLifecycleOwner); - when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); - when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager); - when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); - when(mConnectivityManager.getActiveNetwork()).thenReturn(mActiveNetwork); - when(mConnectivityManager.getNetworkCapabilities(mActiveNetwork)).thenReturn(mCapabilities); - when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); - when(mScreen.findPreference(eq(KEY))).thenReturn(mPreferenceCategory); - when(mPreferenceCategory.getContext()).thenReturn(mContext); - mOnChildUpdatedCount = 0; - mUpdateListener = () -> mOnChildUpdatedCount++; - - mController = spy( - new SubscriptionsPreferenceController(mContext, mLifecycle, mUpdateListener, - KEY, 5)); - doReturn(true).when(mController).canSubscriptionBeDisplayed(any(), anyInt()); - doReturn(mSignalStrengthIcon).when(mController).getIcon(anyInt(), anyInt(), anyBoolean()); - } - - @After - public void tearDown() { - SubscriptionUtil.setActiveSubscriptionsForTesting(null); - } - - @Test - public void isAvailable_oneSubscription_availableFalse() { - setupMockSubscriptions(1); - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void isAvailable_twoSubscriptions_availableTrue() { - setupMockSubscriptions(2); - assertThat(mController.isAvailable()).isTrue(); - } - - @Test - public void isAvailable_fiveSubscriptions_availableTrue() { - setupMockSubscriptions(5); - assertThat(mController.isAvailable()).isTrue(); - } - - @Test - public void isAvailable_airplaneModeOn_availableFalse() { - setupMockSubscriptions(2); - assertThat(mController.isAvailable()).isTrue(); - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void onAirplaneModeChanged_airplaneModeTurnedOn_eventFired() { - setupMockSubscriptions(2); - mController.onResume(); - mController.displayPreference(mScreen); - assertThat(mController.isAvailable()).isTrue(); - - final int updateCountBeforeModeChange = mOnChildUpdatedCount; - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); - mController.onAirplaneModeChanged(true); - assertThat(mController.isAvailable()).isFalse(); - assertThat(mOnChildUpdatedCount).isEqualTo(updateCountBeforeModeChange + 1); - } - - @Test - public void onAirplaneModeChanged_airplaneModeTurnedOff_eventFired() { - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); - setupMockSubscriptions(2); - mController.onResume(); - mController.displayPreference(mScreen); - assertThat(mController.isAvailable()).isFalse(); - - final int updateCountBeforeModeChange = mOnChildUpdatedCount; - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0); - mController.onAirplaneModeChanged(false); - assertThat(mController.isAvailable()).isTrue(); - assertThat(mOnChildUpdatedCount).isEqualTo(updateCountBeforeModeChange + 1); - } - - @Test - public void onSubscriptionsChanged_countBecameTwo_eventFired() { - final List subs = setupMockSubscriptions(2); - SubscriptionUtil.setActiveSubscriptionsForTesting(subs.subList(0, 1)); - mController.onResume(); - mController.displayPreference(mScreen); - assertThat(mController.isAvailable()).isFalse(); - - final int updateCountBeforeSubscriptionChange = mOnChildUpdatedCount; - SubscriptionUtil.setActiveSubscriptionsForTesting(subs); - mController.onSubscriptionsChanged(); - assertThat(mController.isAvailable()).isTrue(); - assertThat(mOnChildUpdatedCount).isEqualTo(updateCountBeforeSubscriptionChange + 1); - } - - @Test - public void onSubscriptionsChanged_countBecameOne_eventFiredAndPrefsRemoved() { - final List subs = setupMockSubscriptions(2); - mController.onResume(); - mController.displayPreference(mScreen); - assertThat(mController.isAvailable()).isTrue(); - verify(mPreferenceCategory, times(2)).addPreference(any(Preference.class)); - - final int updateCountBeforeSubscriptionChange = mOnChildUpdatedCount; - SubscriptionUtil.setActiveSubscriptionsForTesting(subs.subList(0, 1)); - mController.onSubscriptionsChanged(); - assertThat(mController.isAvailable()).isFalse(); - assertThat(mOnChildUpdatedCount).isEqualTo(updateCountBeforeSubscriptionChange + 1); - - verify(mPreferenceCategory, times(2)).removePreference(any(Preference.class)); - } - - @Test - public void onSubscriptionsChanged_subscriptionReplaced_preferencesChanged() { - final List subs = setupMockSubscriptions(3); - - // Start out with only sub1 and sub2. - SubscriptionUtil.setActiveSubscriptionsForTesting(subs.subList(0, 2)); - mController.onResume(); - mController.displayPreference(mScreen); - final ArgumentCaptor captor = ArgumentCaptor.forClass(Preference.class); - verify(mPreferenceCategory, times(2)).addPreference(captor.capture()); - assertThat(captor.getAllValues().size()).isEqualTo(2); - assertThat(captor.getAllValues().get(0).getTitle()).isEqualTo("sub1"); - assertThat(captor.getAllValues().get(1).getTitle()).isEqualTo("sub2"); - - // Now replace sub2 with sub3, and make sure the old preference was removed and the new - // preference was added. - final int updateCountBeforeSubscriptionChange = mOnChildUpdatedCount; - SubscriptionUtil.setActiveSubscriptionsForTesting(Arrays.asList(subs.get(0), subs.get(2))); - mController.onSubscriptionsChanged(); - assertThat(mController.isAvailable()).isTrue(); - assertThat(mOnChildUpdatedCount).isEqualTo(updateCountBeforeSubscriptionChange + 1); - - verify(mPreferenceCategory).removePreference(captor.capture()); - assertThat(captor.getValue().getTitle()).isEqualTo("sub2"); - verify(mPreferenceCategory, times(3)).addPreference(captor.capture()); - assertThat(captor.getValue().getTitle()).isEqualTo("sub3"); - } - - - /** - * Helper to create a specified number of subscriptions, display them, and then click on one and - * verify that the intent fires and has the right subscription id extra. - * - * @param subscriptionCount the number of subscriptions - * @param selectedPrefIndex index of the subscription to click on - */ - private void runPreferenceClickTest(final int subscriptionCount, final int selectedPrefIndex) { - final List subs = setupMockSubscriptions(subscriptionCount); - final ArgumentCaptor prefCaptor = ArgumentCaptor.forClass(Preference.class); - mController.displayPreference(mScreen); - verify(mPreferenceCategory, times(subscriptionCount)).addPreference(prefCaptor.capture()); - final List prefs = prefCaptor.getAllValues(); - final Preference pref = prefs.get(selectedPrefIndex); - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - doNothing().when(mContext).startActivity(intentCaptor.capture()); - pref.getOnPreferenceClickListener().onPreferenceClick(pref); - final Intent intent = intentCaptor.getValue(); - assertThat(intent).isNotNull(); - assertThat(intent.hasExtra(Settings.EXTRA_SUB_ID)).isTrue(); - final int subIdFromIntent = intent.getIntExtra(Settings.EXTRA_SUB_ID, - INVALID_SUBSCRIPTION_ID); - assertThat(subIdFromIntent).isEqualTo( - subs.get(selectedPrefIndex).getSubscriptionId()); - } - - @Test - public void twoPreferences_firstPreferenceClicked_correctIntentFires() { - runPreferenceClickTest(2, 0); - } - - @Test - public void twoPreferences_secondPreferenceClicked_correctIntentFires() { - runPreferenceClickTest(2, 1); - } - - @Test - public void threePreferences_secondPreferenceClicked_correctIntentFires() { - runPreferenceClickTest(3, 1); - } - - @Test - public void threePreferences_thirdPreferenceClicked_correctIntentFires() { - runPreferenceClickTest(3, 2); - } - - @Test - public void getSummary_twoSubsOneDefaultForEverythingDataActive() { - setupMockSubscriptions(2); - - ShadowSubscriptionManager.setDefaultSmsSubscriptionId(11); - ShadowSubscriptionManager.setDefaultVoiceSubscriptionId(11); - when(mTelephonyManager.isDataEnabled()).thenReturn(true); - when(mCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(true); - - assertThat(mController.getSummary(11, true)).isEqualTo( - mContext.getString(R.string.default_for_calls_and_sms) + System.lineSeparator() - + mContext.getString(R.string.mobile_data_active)); - - assertThat(mController.getSummary(22, false)).isEqualTo( - mContext.getString(R.string.subscription_available)); - } - - @Test - public void getSummary_twoSubsOneDefaultForEverythingDataNotActive() { - setupMockSubscriptions(2, 1, true); - - ShadowSubscriptionManager.setDefaultSmsSubscriptionId(1); - ShadowSubscriptionManager.setDefaultVoiceSubscriptionId(1); - - assertThat(mController.getSummary(1, true)).isEqualTo( - mContext.getString(R.string.default_for_calls_and_sms) + System.lineSeparator() - + mContext.getString(R.string.default_for_mobile_data)); - - assertThat(mController.getSummary(2, false)).isEqualTo( - mContext.getString(R.string.subscription_available)); - } - - @Test - public void getSummary_twoSubsOneDefaultForEverythingDataDisabled() { - setupMockSubscriptions(2); - - ShadowSubscriptionManager.setDefaultVoiceSubscriptionId(1); - ShadowSubscriptionManager.setDefaultSmsSubscriptionId(1); - - assertThat(mController.getSummary(1, true)).isEqualTo( - mContext.getString(R.string.default_for_calls_and_sms) + System.lineSeparator() - + mContext.getString(R.string.mobile_data_off)); - - assertThat(mController.getSummary(2, false)).isEqualTo( - mContext.getString(R.string.subscription_available)); - } - - @Test - public void getSummary_twoSubsOneForCallsAndDataOneForSms() { - setupMockSubscriptions(2, 1, true); - - ShadowSubscriptionManager.setDefaultSmsSubscriptionId(2); - ShadowSubscriptionManager.setDefaultVoiceSubscriptionId(1); - - assertThat(mController.getSummary(1, true)).isEqualTo( - mContext.getString(R.string.default_for_calls) + System.lineSeparator() - + mContext.getString(R.string.default_for_mobile_data)); - - assertThat(mController.getSummary(2, false)).isEqualTo( - mContext.getString(R.string.default_for_sms)); - } - - @Test - public void setIcon_nullStrength_noCrash() { - final List subs = setupMockSubscriptions(2); - setMockSubSignalStrength(subs, 0, -1); - final Preference pref = mock(Preference.class); - - mController.setIcon(pref, 1, true /* isDefaultForData */); - verify(mController).getIcon(eq(0), eq(NUM_SIGNAL_STRENGTH_BINS), eq(true)); - } - - @Test - public void setIcon_noSignal_correctLevels() { - final List subs = setupMockSubscriptions(2, 1, true); - setMockSubSignalStrength(subs, 0, SIGNAL_STRENGTH_NONE_OR_UNKNOWN); - setMockSubSignalStrength(subs, 1, SIGNAL_STRENGTH_NONE_OR_UNKNOWN); - setMockSubDataEnabled(subs, 0, true); - final Preference pref = mock(Preference.class); - - mController.setIcon(pref, 1, true /* isDefaultForData */); - verify(mController).getIcon(eq(0), eq(NUM_SIGNAL_STRENGTH_BINS), eq(false)); - - mController.setIcon(pref, 2, false /* isDefaultForData */); - verify(mController).getIcon(eq(0), eq(NUM_SIGNAL_STRENGTH_BINS), eq(true)); - } - - @Test - public void setIcon_noSignal_withInflation_correctLevels() { - final List subs = setupMockSubscriptions(2, 1, true); - setMockSubSignalStrength(subs, 0, SIGNAL_STRENGTH_NONE_OR_UNKNOWN); - setMockSubSignalStrength(subs, 1, SIGNAL_STRENGTH_NONE_OR_UNKNOWN); - final Preference pref = mock(Preference.class); - doReturn(true).when(mController).shouldInflateSignalStrength(anyInt()); - - mController.setIcon(pref, 1, true /* isDefaultForData */); - verify(mController).getIcon(eq(1), eq(NUM_SIGNAL_STRENGTH_BINS + 1), eq(false)); - - mController.setIcon(pref, 2, false /* isDefaultForData */); - verify(mController).getIcon(eq(1), eq(NUM_SIGNAL_STRENGTH_BINS + 1), eq(true)); - } - - @Test - public void setIcon_greatSignal_correctLevels() { - final List subs = setupMockSubscriptions(2, 1, true); - setMockSubSignalStrength(subs, 0, SIGNAL_STRENGTH_GREAT); - setMockSubSignalStrength(subs, 1, SIGNAL_STRENGTH_GREAT); - final Preference pref = mock(Preference.class); - - mController.setIcon(pref, 1, true /* isDefaultForData */); - verify(mController).getIcon(eq(4), eq(NUM_SIGNAL_STRENGTH_BINS), eq(false)); - - mController.setIcon(pref, 2, false /* isDefaultForData */); - verify(mController).getIcon(eq(4), eq(NUM_SIGNAL_STRENGTH_BINS), eq(true)); - } - - @Test - public void onSignalStrengthChanged_subTwoGoesFromGoodToGreat_correctLevels() { - final List subs = setupMockSubscriptions(2); - setMockSubSignalStrength(subs, 0, SIGNAL_STRENGTH_POOR); - setMockSubSignalStrength(subs, 1, SIGNAL_STRENGTH_GOOD); - - mController.onResume(); - mController.displayPreference(mScreen); - - // Now change the signal strength for the 2nd subscription from Good to Great - setMockSubSignalStrength(subs, 1, SIGNAL_STRENGTH_GREAT); - mController.onSignalStrengthChanged(); - - final ArgumentCaptor level = ArgumentCaptor.forClass(Integer.class); - verify(mController, times(4)).getIcon(level.capture(), eq(NUM_SIGNAL_STRENGTH_BINS), - eq(true)); - assertThat(level.getAllValues().get(0)).isEqualTo(1); - assertThat(level.getAllValues().get(1)).isEqualTo(3); // sub2, first time - assertThat(level.getAllValues().get(2)).isEqualTo(1); - assertThat(level.getAllValues().get(3)).isEqualTo(4); // sub2, after change - } - - @Test - public void displayPreference_mobileDataOff_bothSubsHaveCutOut() { - setupMockSubscriptions(2, 1, false); - - mController.onResume(); - mController.displayPreference(mScreen); - - verify(mController, times(2)).getIcon(eq(SIGNAL_STRENGTH_GOOD), - eq(NUM_SIGNAL_STRENGTH_BINS), eq(true)); - } - - @Test - public void displayPreference_mobileDataOn_onlyNonDefaultSubHasCutOut() { - final List subs = setupMockSubscriptions(2, 1, true); - setMockSubSignalStrength(subs, 1, SIGNAL_STRENGTH_POOR); - - mController.onResume(); - mController.displayPreference(mScreen); - - verify(mController).getIcon(eq(SIGNAL_STRENGTH_GOOD), eq(NUM_SIGNAL_STRENGTH_BINS), - eq(false)); - verify(mController).getIcon(eq(SIGNAL_STRENGTH_POOR), eq(NUM_SIGNAL_STRENGTH_BINS), - eq(true)); - } - - @Test - public void displayPreference_subscriptionsWithSameGroupUUID_onlyOneWillBeSeen() { - doReturn(false).when(mController).canSubscriptionBeDisplayed(any(), eq(3)); - final List subs = setupMockSubscriptions(3); - SubscriptionUtil.setActiveSubscriptionsForTesting(subs.subList(0, 3)); - - mController.onResume(); - mController.displayPreference(mScreen); - - verify(mPreferenceCategory, times(2)).addPreference(any(Preference.class)); - } - - @Test - public void onMobileDataEnabledChange_mobileDataTurnedOff_bothSubsHaveCutOut() { - final List subs = setupMockSubscriptions(2, 1, true); - - mController.onResume(); - mController.displayPreference(mScreen); - - setMockSubDataEnabled(subs, 0, false); - mController.onMobileDataEnabledChange(); - - final ArgumentCaptor cutOutCaptor = ArgumentCaptor.forClass(Boolean.class); - verify(mController, times(4)).getIcon(eq(SIGNAL_STRENGTH_GOOD), - eq(NUM_SIGNAL_STRENGTH_BINS), cutOutCaptor.capture()); - assertThat(cutOutCaptor.getAllValues().get(0)).isEqualTo(false); // sub1, first time - assertThat(cutOutCaptor.getAllValues().get(1)).isEqualTo(true); - assertThat(cutOutCaptor.getAllValues().get(2)).isEqualTo(true); // sub1, second time - assertThat(cutOutCaptor.getAllValues().get(3)).isEqualTo(true); - } - - private List setupMockSubscriptions(int count) { - return setupMockSubscriptions(count, 0, true); - } - - /** Helper method to setup several mock active subscriptions. The generated subscription id's - * start at 1. - * - * @param count How many subscriptions to create - * @param defaultDataSubId The subscription id of the default data subscription - pass - * INVALID_SUBSCRIPTION_ID if there should not be one - * @param mobileDataEnabled Whether mobile data should be considered enabled for the default - * data subscription - */ - private List setupMockSubscriptions(int count, int defaultDataSubId, - boolean mobileDataEnabled) { - if (defaultDataSubId != INVALID_SUBSCRIPTION_ID) { - ShadowSubscriptionManager.setDefaultDataSubscriptionId(defaultDataSubId); - } - final ArrayList infos = new ArrayList<>(); - for (int i = 0; i < count; i++) { - final int subscriptionId = i + 1; - final SubscriptionInfo info = mock(SubscriptionInfo.class); - final TelephonyManager mgrForSub = mock(TelephonyManager.class); - final SignalStrength signalStrength = mock(SignalStrength.class); - - if (subscriptionId == defaultDataSubId) { - when(mgrForSub.isDataEnabled()).thenReturn(mobileDataEnabled); - } - when(info.getSubscriptionId()).thenReturn(i + 1); - when(info.getDisplayName()).thenReturn("sub" + (i + 1)); - doReturn(mgrForSub).when(mTelephonyManager).createForSubscriptionId(eq(subscriptionId)); - when(mgrForSub.getSignalStrength()).thenReturn(signalStrength); - when(signalStrength.getLevel()).thenReturn(SIGNAL_STRENGTH_GOOD); - - infos.add(info); - } - SubscriptionUtil.setActiveSubscriptionsForTesting(infos); - return infos; - } - - /** - * Helper method to set the signal strength returned for a mock subscription - * @param subs The list of subscriptions - * @param index The index in of the subscription in |subs| to change - * @param level The signal strength level to return for the subscription. Pass -1 to force - * return of a null SignalStrength object for the subscription. - */ - private void setMockSubSignalStrength(List subs, int index, int level) { - final TelephonyManager mgrForSub = - mTelephonyManager.createForSubscriptionId(subs.get(index).getSubscriptionId()); - if (level == -1) { - when(mgrForSub.getSignalStrength()).thenReturn(null); - } else { - final SignalStrength signalStrength = mgrForSub.getSignalStrength(); - when(signalStrength.getLevel()).thenReturn(level); - } - } - - private void setMockSubDataEnabled(List subs, int index, boolean enabled) { - final TelephonyManager mgrForSub = - mTelephonyManager.createForSubscriptionId(subs.get(index).getSubscriptionId()); - when(mgrForSub.isDataEnabled()).thenReturn(enabled); - } -} diff --git a/tests/unit/src/com/android/settings/network/SubscriptionsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/SubscriptionsPreferenceControllerTest.java new file mode 100644 index 00000000000..5ee4c4210d0 --- /dev/null +++ b/tests/unit/src/com/android/settings/network/SubscriptionsPreferenceControllerTest.java @@ -0,0 +1,649 @@ +/* + * 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 android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS; +import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GOOD; +import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq;; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +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.graphics.drawable.Drawable; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.os.Looper; +import android.provider.Settings; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; + +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LifecycleRegistry; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +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.Utils; +import com.android.settings.network.SubscriptionsPreferenceController.SubsPrefCtrlInjector; +import com.android.settings.testutils.ResourcesUtils; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.After; +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.Arrays; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class SubscriptionsPreferenceControllerTest { + private static final String KEY = "preference_group"; + + @Mock + private SubscriptionManager mSubscriptionManager; + @Mock + private ConnectivityManager mConnectivityManager; + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private TelephonyManager mTelephonyManagerForSub; + @Mock + private Network mActiveNetwork; + @Mock + private Lifecycle mLifecycle; + @Mock + private LifecycleOwner mLifecycleOwner; + private LifecycleRegistry mLifecycleRegistry; + private int mOnChildUpdatedCount; + private Context mContext; + private SubscriptionsPreferenceController.UpdateListener mUpdateListener; + private PreferenceCategory mPreferenceCategory; + private PreferenceScreen mPreferenceScreen; + private PreferenceManager mPreferenceManager; + private NetworkCapabilities mNetworkCapabilities; + + private FakeSubscriptionsPreferenceController mController; + private static SubsPrefCtrlInjector sInjector; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mLifecycleRegistry = new LifecycleRegistry(mLifecycleOwner); + + when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); + when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); + when(mConnectivityManager.getActiveNetwork()).thenReturn(mActiveNetwork); + when(mConnectivityManager.getNetworkCapabilities(mActiveNetwork)) + .thenReturn(mNetworkCapabilities); + when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry); + + mPreferenceManager = new PreferenceManager(mContext); + mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext); + mPreferenceScreen.setInitialExpandedChildrenCount(3); + mPreferenceCategory = new PreferenceCategory(mContext); + mPreferenceCategory.setKey(KEY); + mPreferenceCategory.setOrderingAsAdded(true); + mPreferenceScreen.addPreference(mPreferenceCategory); + + mOnChildUpdatedCount = 0; + mUpdateListener = () -> mOnChildUpdatedCount++; + sInjector = spy(new SubsPrefCtrlInjector()); + initializeMethod(true, 1, 1, 1, false, false); + mController = new FakeSubscriptionsPreferenceController(mContext, mLifecycle, + mUpdateListener, KEY, 5); + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0); + } + + @After + public void tearDown() { + SubscriptionUtil.setActiveSubscriptionsForTesting(null); + } + + @Test + public void isAvailable_oneSubscription_availableFalse() { + setupMockSubscriptions(1); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_oneSubAndProviderOn_availableTrue() { + doReturn(true).when(sInjector).isProviderModelEnabled(mContext); + setupMockSubscriptions(1); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_twoSubscriptions_availableTrue() { + setupMockSubscriptions(2); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_fiveSubscriptions_availableTrue() { + doReturn(true).when(sInjector).canSubscriptionBeDisplayed(mContext, 0); + setupMockSubscriptions(5); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isAvailable_airplaneModeOn_availableFalse() { + setupMockSubscriptions(2); + + assertThat(mController.isAvailable()).isTrue(); + + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + @UiThreadTest + public void onAirplaneModeChanged_airplaneModeTurnedOn_eventFired() { + setupMockSubscriptions(2); + + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + + assertThat(mController.isAvailable()).isTrue(); + + final int updateCountBeforeModeChange = mOnChildUpdatedCount; + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); + + mController.onAirplaneModeChanged(true); + + assertThat(mController.isAvailable()).isFalse(); + assertThat(mOnChildUpdatedCount).isEqualTo(updateCountBeforeModeChange + 1); + } + + @Test + @UiThreadTest + public void onAirplaneModeChanged_airplaneModeTurnedOff_eventFired() { + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); + setupMockSubscriptions(2); + + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + assertThat(mController.isAvailable()).isFalse(); + + final int updateCountBeforeModeChange = mOnChildUpdatedCount; + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0); + + mController.onAirplaneModeChanged(true); + + assertThat(mController.isAvailable()).isTrue(); + assertThat(mOnChildUpdatedCount).isEqualTo(updateCountBeforeModeChange + 1); + } + + @Test + @UiThreadTest + public void onSubscriptionsChanged_countBecameTwo_eventFired() { + final List subs = setupMockSubscriptions(2); + SubscriptionUtil.setActiveSubscriptionsForTesting(subs.subList(0, 1)); + + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + + assertThat(mController.isAvailable()).isFalse(); + + final int updateCountBeforeSubscriptionChange = mOnChildUpdatedCount; + SubscriptionUtil.setActiveSubscriptionsForTesting(subs); + + mController.onSubscriptionsChanged(); + + assertThat(mController.isAvailable()).isTrue(); + assertThat(mOnChildUpdatedCount).isEqualTo(updateCountBeforeSubscriptionChange + 1); + } + + @Test + @UiThreadTest + public void onSubscriptionsChanged_countBecameOne_eventFiredAndPrefsRemoved() { + final List subs = setupMockSubscriptions(2); + + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + + assertThat(mController.isAvailable()).isTrue(); + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2); + + final int updateCountBeforeSubscriptionChange = mOnChildUpdatedCount; + SubscriptionUtil.setActiveSubscriptionsForTesting(subs.subList(0, 1)); + + mController.onSubscriptionsChanged(); + + assertThat(mController.isAvailable()).isFalse(); + assertThat(mOnChildUpdatedCount).isEqualTo(updateCountBeforeSubscriptionChange + 1); + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(0); + } + + @Test + @UiThreadTest + public void onSubscriptionsChanged_subscriptionReplaced_preferencesChanged() { + final List subs = setupMockSubscriptions(3); + + // Start out with only sub1 and sub2. + SubscriptionUtil.setActiveSubscriptionsForTesting(subs.subList(0, 2)); + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2); + assertThat(mPreferenceCategory.getPreference(0).getTitle()).isEqualTo("sub2"); + assertThat(mPreferenceCategory.getPreference(1).getTitle()).isEqualTo("sub1"); + + // Now replace sub2 with sub3, and make sure the old preference was removed and the new + // preference was added. + final int updateCountBeforeSubscriptionChange = mOnChildUpdatedCount; + SubscriptionUtil.setActiveSubscriptionsForTesting(Arrays.asList(subs.get(0), subs.get(2))); + mController.onSubscriptionsChanged(); + + assertThat(mController.isAvailable()).isTrue(); + assertThat(mOnChildUpdatedCount).isEqualTo(updateCountBeforeSubscriptionChange + 1); + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2); + assertThat(mPreferenceCategory.getPreference(0).getTitle()).isEqualTo("sub3"); + assertThat(mPreferenceCategory.getPreference(1).getTitle()).isEqualTo("sub1"); + } + + @Test + public void getSummary_twoSubsOneDefaultForEverythingDataActive() { + setupMockSubscriptions(2); + + doReturn(11).when(sInjector).getDefaultSmsSubscriptionId(); + doReturn(11).when(sInjector).getDefaultVoiceSubscriptionId(); + when(mTelephonyManager.isDataEnabled()).thenReturn(true); + doReturn(true).when(sInjector).isActiveCellularNetwork(mContext); + + assertThat(mController.getSummary(11, true)).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "default_for_calls_and_sms") + + System.lineSeparator() + + ResourcesUtils.getResourcesString(mContext, "mobile_data_active")); + + assertThat(mController.getSummary(22, false)).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "subscription_available")); + } + + @Test + public void getSummary_twoSubsOneDefaultForEverythingDataNotActive() { + setupMockSubscriptions(2, 1, true); + + doReturn(1).when(sInjector).getDefaultSmsSubscriptionId(); + doReturn(1).when(sInjector).getDefaultVoiceSubscriptionId(); + + assertThat(mController.getSummary(1, true)).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "default_for_calls_and_sms") + + System.lineSeparator() + + ResourcesUtils.getResourcesString(mContext, "default_for_mobile_data")); + + assertThat(mController.getSummary(2, false)).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "subscription_available")); + } + + @Test + public void getSummary_twoSubsOneDefaultForEverythingDataDisabled() { + setupMockSubscriptions(2); + + doReturn(1).when(sInjector).getDefaultSmsSubscriptionId(); + doReturn(1).when(sInjector).getDefaultVoiceSubscriptionId(); + + assertThat(mController.getSummary(1, true)).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "default_for_calls_and_sms") + + System.lineSeparator() + + ResourcesUtils.getResourcesString(mContext, "mobile_data_off")); + + assertThat(mController.getSummary(2, false)).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "subscription_available")); + } + + @Test + public void getSummary_twoSubsOneForCallsAndDataOneForSms() { + setupMockSubscriptions(2, 1, true); + + doReturn(2).when(sInjector).getDefaultSmsSubscriptionId(); + doReturn(1).when(sInjector).getDefaultVoiceSubscriptionId(); + + assertThat(mController.getSummary(1, true)).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "default_for_calls") + + System.lineSeparator() + + ResourcesUtils.getResourcesString(mContext, "default_for_mobile_data")); + + assertThat(mController.getSummary(2, false)).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "default_for_sms")); + } + + @Test + @UiThreadTest + public void setIcon_greatSignal_correctLevels() { + final List subs = setupMockSubscriptions(2, 1, true); + setMockSubSignalStrength(subs, 0, SIGNAL_STRENGTH_GREAT); + setMockSubSignalStrength(subs, 1, SIGNAL_STRENGTH_GREAT); + final Preference pref = new Preference(mContext); + final Drawable greatDrawWithoutCutOff = mock(Drawable.class); + doReturn(greatDrawWithoutCutOff).when(sInjector) + .getIcon(mContext, 4, NUM_SIGNAL_STRENGTH_BINS, true); + + mController.setIcon(pref, 1, true /* isDefaultForData */); + assertThat(pref.getIcon()).isEqualTo(greatDrawWithoutCutOff); + + final Drawable greatDrawWithCutOff = mock(Drawable.class); + doReturn(greatDrawWithCutOff).when(sInjector) + .getIcon(mContext, 4, NUM_SIGNAL_STRENGTH_BINS, true); + mController.setIcon(pref, 2, false /* isDefaultForData */); + assertThat(pref.getIcon()).isEqualTo(greatDrawWithCutOff); + } + + @Test + @UiThreadTest + public void displayPreference_providerAndHasSim_showPreference() { + final List sub = setupMockSubscriptions(1); + doReturn(true).when(sInjector).isProviderModelEnabled(mContext); + doReturn(sub.get(0)).when(mSubscriptionManager).getDefaultDataSubscriptionInfo(); + + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1); + assertThat(mPreferenceCategory.getPreference(0).getTitle()).isEqualTo("sub1"); + } + + @Test + @UiThreadTest + public void displayPreference_providerAndHasMultiSim_showDataSubPreference() { + final List sub = setupMockSubscriptions(2); + doReturn(true).when(sInjector).isProviderModelEnabled(mContext); + doReturn(sub.get(0)).when(mSubscriptionManager).getDefaultDataSubscriptionInfo(); + + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1); + assertThat(mPreferenceCategory.getPreference(0).getTitle()).isEqualTo("sub1"); + } + + @Test + @UiThreadTest + public void displayPreference_providerAndNoSim_noPreference() { + doReturn(true).when(sInjector).isProviderModelEnabled(mContext); + doReturn(null).when(mSubscriptionManager).getDefaultDataSubscriptionInfo(); + + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(0); + } + + @Test + @UiThreadTest + public void onAirplaneModeChanged_providerAndHasSim_noPreference() { + setupMockSubscriptions(1); + doReturn(true).when(sInjector).isProviderModelEnabled(mContext); + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1); + + mController.onAirplaneModeChanged(true); + + assertThat(mController.isAvailable()).isFalse(); + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(0); + } + + @Test + @UiThreadTest + public void dataSubscriptionChanged_providerAndHasMultiSim_showSubId1Preference() { + final List sub = setupMockSubscriptions(2); + doReturn(true).when(sInjector).isProviderModelEnabled(mContext); + doReturn(sub.get(0)).when(mSubscriptionManager).getDefaultDataSubscriptionInfo(); + Intent intent = new Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); + + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + mController.mDataSubscriptionChangedReceiver.onReceive(mContext, intent); + + assertThat(mController.isAvailable()).isTrue(); + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1); + assertThat(mPreferenceCategory.getPreference(0).getTitle()).isEqualTo("sub1"); + } + + @Test + @UiThreadTest + public void dataSubscriptionChanged_providerAndHasMultiSim_showSubId2Preference() { + final List sub = setupMockSubscriptions(2); + final int subId = sub.get(0).getSubscriptionId(); + doReturn(true).when(sInjector).isProviderModelEnabled(mContext); + doReturn(sub.get(0)).when(mSubscriptionManager).getDefaultDataSubscriptionInfo(); + Intent intent = new Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); + + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + + assertThat(mController.isAvailable()).isTrue(); + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1); + assertThat(mPreferenceCategory.getPreference(0).getTitle()).isEqualTo("sub1"); + + doReturn(sub.get(1)).when(mSubscriptionManager).getDefaultDataSubscriptionInfo(); + + mController.mDataSubscriptionChangedReceiver.onReceive(mContext, intent); + + assertThat(mController.isAvailable()).isTrue(); + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1); + assertThat(mPreferenceCategory.getPreference(0).getTitle()).isEqualTo("sub2"); + } + + @Test + @UiThreadTest + public void getIcon_cellularIsActive_iconColorIsAccentDefaultColor() { + final List sub = setupMockSubscriptions(1); + doReturn(true).when(sInjector).isProviderModelEnabled(mContext); + doReturn(sub.get(0)).when(mSubscriptionManager).getDefaultDataSubscriptionInfo(); + Drawable icon = mock(Drawable.class); + doReturn(icon).when(sInjector).getIcon(any(), anyInt(), anyInt(), eq(false)); + setupGetIconConditions(sub.get(0).getSubscriptionId(), true, true, + TelephonyManager.DATA_CONNECTED, ServiceState.STATE_IN_SERVICE); + + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + + verify(icon).setTint(Utils.getColorAccentDefaultColor(mContext)); + } + + @Test + @UiThreadTest + public void getIcon_dataStateConnectedAndMobileDataOn_iconIsSignalIcon() { + final List subs = setupMockSubscriptions(1); + final int subId = subs.get(0).getSubscriptionId(); + doReturn(true).when(sInjector).isProviderModelEnabled(mContext); + doReturn(subs.get(0)).when(mSubscriptionManager).getDefaultDataSubscriptionInfo(); + Drawable icon = mock(Drawable.class); + doReturn(icon).when(sInjector).getIcon(any(), anyInt(), anyInt(), eq(false)); + setupGetIconConditions(subId, false, true, + TelephonyManager.DATA_CONNECTED, ServiceState.STATE_IN_SERVICE); + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + Drawable actualIcon = mPreferenceCategory.getPreference(0).getIcon(); + + assertThat(icon).isEqualTo(actualIcon); + } + + @Test + @UiThreadTest + public void getIcon_voiceInServiceAndMobileDataOff_iconIsSignalIcon() { + final List subs = setupMockSubscriptions(1); + final int subId = subs.get(0).getSubscriptionId(); + doReturn(true).when(sInjector).isProviderModelEnabled(mContext); + doReturn(subs.get(0)).when(mSubscriptionManager).getDefaultDataSubscriptionInfo(); + Drawable icon = mock(Drawable.class); + doReturn(icon).when(sInjector).getIcon(any(), anyInt(), anyInt(), eq(false)); + + setupGetIconConditions(subId, false, false, + TelephonyManager.DATA_DISCONNECTED, ServiceState.STATE_IN_SERVICE); + + mController.onResume(); + mController.displayPreference(mPreferenceScreen); + Drawable actualIcon = mPreferenceCategory.getPreference(0).getIcon(); + doReturn(TelephonyManager.DATA_CONNECTED).when(mTelephonyManagerForSub).getDataState(); + + assertThat(icon).isEqualTo(actualIcon); + } + + private void setupGetIconConditions(int subId, boolean isActiveCellularNetwork, + boolean isDataEnable, int dataState, int servicestate) { + doReturn(mTelephonyManagerForSub).when(mTelephonyManager).createForSubscriptionId(subId); + doReturn(isActiveCellularNetwork).when(sInjector).isActiveCellularNetwork(mContext); + doReturn(isDataEnable).when(mTelephonyManagerForSub).isDataEnabled(); + doReturn(dataState).when(mTelephonyManagerForSub).getDataState(); + ServiceState ss = mock(ServiceState.class); + doReturn(ss).when(mTelephonyManagerForSub).getServiceState(); + doReturn(servicestate).when(ss).getState(); + } + + private List setupMockSubscriptions(int count) { + return setupMockSubscriptions(count, 0, true); + } + + /** Helper method to setup several mock active subscriptions. The generated subscription id's + * start at 1. + * + * @param count How many subscriptions to create + * @param defaultDataSubId The subscription id of the default data subscription - pass + * INVALID_SUBSCRIPTION_ID if there should not be one + * @param mobileDataEnabled Whether mobile data should be considered enabled for the default + * data subscription + */ + private List setupMockSubscriptions(int count, int defaultDataSubId, + boolean mobileDataEnabled) { + if (defaultDataSubId != INVALID_SUBSCRIPTION_ID) { + when(sInjector.getDefaultDataSubscriptionId()).thenReturn(defaultDataSubId); + } + final ArrayList infos = new ArrayList<>(); + for (int i = 0; i < count; i++) { + final int subscriptionId = i + 1; + final SubscriptionInfo info = mock(SubscriptionInfo.class); + final TelephonyManager mgrForSub = mock(TelephonyManager.class); + final SignalStrength signalStrength = mock(SignalStrength.class); + + if (subscriptionId == defaultDataSubId) { + when(mgrForSub.isDataEnabled()).thenReturn(mobileDataEnabled); + } + when(info.getSubscriptionId()).thenReturn(subscriptionId); + when(info.getDisplayName()).thenReturn("sub" + (subscriptionId)); + doReturn(mgrForSub).when(mTelephonyManager).createForSubscriptionId(eq(subscriptionId)); + when(mgrForSub.getSignalStrength()).thenReturn(signalStrength); + when(signalStrength.getLevel()).thenReturn(SIGNAL_STRENGTH_GOOD); + doReturn(true).when(sInjector).canSubscriptionBeDisplayed(mContext, subscriptionId); + infos.add(info); + } + SubscriptionUtil.setActiveSubscriptionsForTesting(infos); + return infos; + } + + /** + * Helper method to set the signal strength returned for a mock subscription + * @param subs The list of subscriptions + * @param index The index in of the subscription in |subs| to change + * @param level The signal strength level to return for the subscription. Pass -1 to force + * return of a null SignalStrength object for the subscription. + */ + private void setMockSubSignalStrength(List subs, int index, int level) { + final int subId = subs.get(index).getSubscriptionId(); + doReturn(mTelephonyManagerForSub).when(mTelephonyManager).createForSubscriptionId(subId); + if (level == -1) { + when(mTelephonyManagerForSub.getSignalStrength()).thenReturn(null); + } else { + final SignalStrength signalStrength = mock(SignalStrength.class); + doReturn(signalStrength).when(mTelephonyManagerForSub).getSignalStrength(); + when(signalStrength.getLevel()).thenReturn(level); + } + } + + private void initializeMethod(boolean isSubscriptionCanBeDisplayed, + int defaultSmsSubscriptionId, int defaultVoiceSubscriptionId, + int defaultDataSubscriptionId, boolean isActiveCellularNetwork, + boolean isProviderModelEnabled) { + doReturn(isSubscriptionCanBeDisplayed) + .when(sInjector).canSubscriptionBeDisplayed(mContext, eq(anyInt())); + doReturn(defaultSmsSubscriptionId).when(sInjector).getDefaultSmsSubscriptionId(); + doReturn(defaultVoiceSubscriptionId).when(sInjector).getDefaultVoiceSubscriptionId(); + doReturn(defaultDataSubscriptionId).when(sInjector).getDefaultDataSubscriptionId(); + doReturn(isActiveCellularNetwork).when(sInjector).isActiveCellularNetwork(mContext); + doReturn(isProviderModelEnabled).when(sInjector).isProviderModelEnabled(mContext); + doReturn(mock(Drawable.class)) + .when(sInjector).getIcon(any(), anyInt(), anyInt(), eq(false)); + } + + private static class FakeSubscriptionsPreferenceController + extends SubscriptionsPreferenceController { + + /** + * @param context the context for the UI where we're placing these preferences + * @param lifecycle for listening to lifecycle events for the UI + * @param updateListener called to let our parent controller know that our + * availability has + * changed, or that one or more of the preferences we've placed + * in the + * PreferenceGroup has changed + * @param preferenceGroupKey the key used to lookup the PreferenceGroup where Preferences + * will + * be placed + * @param startOrder the order that should be given to the first Preference + * placed into + * the PreferenceGroup; the second will use startOrder+1, third + * will + * use startOrder+2, etc. - this is useful for when the parent + * wants + * to have other preferences in the same PreferenceGroup and wants + */ + FakeSubscriptionsPreferenceController(Context context, Lifecycle lifecycle, + UpdateListener updateListener, String preferenceGroupKey, int startOrder) { + super(context, lifecycle, updateListener, preferenceGroupKey, startOrder); + } + + @Override + protected SubsPrefCtrlInjector createSubsPrefCtrlInjector() { + return sInjector; + } + } +}