Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 65f8aa2f authored by tom hsu's avatar tom hsu
Browse files

[Provider model] Internet picker Part II

 - Add mobile internet preference with default data subscription.
 - Make prefrence to show dynamically when data subscription changed.

Video exhibition
 - http://rcll/gcmsphfhJ1UzfPSvjWOuXK/c1B2CtaFz27rlIQ2LSTJmo

Code difference
 - SubscriptionsPreferenceControllerTest.java between robolectric and junit.
  - https://diff.googleplex.com/#key=1Zm7JGPhoZwY

Bug: 172229552
Test: atest SubscriptionsPreferenceControllerTest

Change-Id: Ib50c2c51159f60f19631d1a02081eafde3436e94
parent 21338568
Loading
Loading
Loading
Loading
+218 −27
Original line number Diff line number Diff line
@@ -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<Integer, Preference> 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<Integer> 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<Integer, Preference> existingPrefs = mSubscriptionPreferences;
        mSubscriptionPreferences = new ArrayMap<>();

        int order = mStartOrder;
        final Set<Integer> 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<SubscriptionInfo> subInfoList = SubscriptionUtil.getActiveSubscriptions(mManager);
        List<SubscriptionInfo> 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