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

Commit c38557b3 authored by Xiangyu/Malcolm Chen's avatar Xiangyu/Malcolm Chen Committed by Gerrit Code Review
Browse files

Merge changes I976a8a5a,I53f7fad8

* changes:
  Correct EuiccController#switchToSubscription permission check.
  Adding MultiSimSettingController class.
parents ef9e5a29 ef1814d8
Loading
Loading
Loading
Loading
+96 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.internal.telephony;

import android.content.Context;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;

/**
 * Helper class that reads and writes Global.Setting values for Telephony. It will:
 * For Single SIM case, read from or write to the singleton setting value.
 * For Multi-SIM case, read from or write to the per subscription value.
 */
public class GlobalSettingsHelper {
    /**
     * Helper function to get integer value.
     */
    public static int getInt(Context context, String settingName, int subId,
            int defaultValue) {
        settingName = getSettingName(context, settingName, subId);
        return Settings.Global.getInt(context.getContentResolver(), settingName, defaultValue);
    }

    /**
     * Helper function to get boolean value.
     */
    public static boolean getBoolean(Context context, String settingName, int subId,
            boolean defaultValue) {
        settingName = getSettingName(context, settingName, subId);
        return Settings.Global.getInt(context.getContentResolver(), settingName,
                defaultValue ? 1 : 0) == 1;
    }

    /**
     * Helper function to get boolean value or throws SettingNotFoundException if not set.
     */
    public static boolean getBoolean(Context context, String settingName, int subId)
            throws SettingNotFoundException {
        settingName = getSettingName(context, settingName, subId);
        return Settings.Global.getInt(context.getContentResolver(), settingName) == 1;
    }

    /**
     * Helper function to set integer value.
     * Returns whether the value is changed or initially set.
     */
    public static boolean setInt(Context context, String settingName, int subId, int value) {
        settingName = getSettingName(context, settingName, subId);

        boolean needChange;
        try {
            needChange = Settings.Global.getInt(context.getContentResolver(), settingName) != value;
        } catch (SettingNotFoundException exception) {
            needChange = true;
        }
        if (needChange) Settings.Global.putInt(context.getContentResolver(), settingName, value);

        return needChange;
    }

    /**
     * Helper function to set boolean value.
     * Returns whether the value is changed or initially set.
     */
    public static boolean setBoolean(Context context, String settingName, int subId,
            boolean value) {
        return setInt(context, settingName, subId, value ? 1 : 0);
    }

    private static String getSettingName(Context context, String settingName, int subId) {
        // For single SIM phones, this is a per phone property. Or if it's invalid subId, we
        // read default setting.
        if (TelephonyManager.from(context).getSimCount() > 1
                && SubscriptionManager.isValidSubscriptionId(subId)) {
            return settingName + subId;
        } else {
            return settingName;
        }
    }
}
+338 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.internal.telephony;

import android.content.Context;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;

import java.util.List;
import java.util.stream.Collectors;

/**
 * This class will make sure below setting rules are coordinated across different subscriptions
 * and phones in multi-SIM case:
 *
 * 1) Grouped subscriptions will have same settings for MOBILE_DATA and DATA_ROAMING.
 * 2) Default settings updated automatically. It may be cleared or inherited within group.
 *    If default subscription A switches to profile B which is in the same group, B will
 *    become the new default.
 * 3) For primary subscriptions, only default data subscription will have MOBILE_DATA on.
 */
public class MultiSimSettingController {
    static final String LOG_TAG = "MultiSimSettingController";
    static final boolean DBG = true;

    private final Context mContext;
    private final Phone[] mPhones;
    private final SubscriptionController mSubController;
    private boolean mIsAllSubscriptionsLoaded;

    /** The singleton instance. */
    private static MultiSimSettingController sInstance = null;

    /**
     * Return the singleton or create one if not existed.
     */
    public static MultiSimSettingController getInstance() {
        synchronized (SubscriptionController.class) {
            if (sInstance == null) {
                sInstance = new MultiSimSettingController();
            }
            return sInstance;
        }
    }

    @VisibleForTesting
    public MultiSimSettingController() {
        mContext = PhoneFactory.getDefaultPhone().getContext();
        mPhones = PhoneFactory.getPhones();
        mSubController = SubscriptionController.getInstance();
    }

    /**
     * Make sure MOBILE_DATA of subscriptions in same group are synced.
     *
     * If user is enabling a non-default non-opportunistic subscription, make it default
     * data subscription.
     */
    public synchronized void onUserDataEnabled(int subId, boolean enable) {
        if (DBG) log("onUserDataEnabled");
        // Make sure MOBILE_DATA of subscriptions in same group are synced.
        setUserDataEnabledForGroup(subId, enable);

        // If user is enabling a non-default non-opportunistic subscription, make it default.
        if (mSubController.getDefaultDataSubId() != subId && !mSubController.isOpportunistic(subId)
                && enable) {
            mSubController.setDefaultDataSubId(subId);
        }
    }

    /**
     * Make sure DATA_ROAMING of subscriptions in same group are synced.
     */
    public synchronized void onRoamingDataEnabled(int subId, boolean enable) {
        if (DBG) log("onRoamingDataEnabled");
        setRoamingDataEnabledForGroup(subId, enable);

        // Also inform SubscriptionController as it keeps another copy of user setting.
        mSubController.setDataRoaming(enable ? 1 : 0, subId);
    }

    /**
     * Mark mIsAllSubscriptionsLoaded and update defaults and mobile data enabling.
     */
    public synchronized void onAllSubscriptionsLoaded() {
        if (DBG) log("onAllSubscriptionsLoaded");
        mIsAllSubscriptionsLoaded = true;
        updateDefaults();
        disableDataForNonDefaultNonOpportunisticSubscriptions();
    }

    /**
     * Make sure default values are cleaned or updated.
     *
     * Make sure non-default non-opportunistic subscriptions has data off.
     */
    public synchronized void onSubscriptionsChanged() {
        if (DBG) log("onSubscriptionsChanged");
        if (!mIsAllSubscriptionsLoaded) return;
        updateDefaults();
        disableDataForNonDefaultNonOpportunisticSubscriptions();
    }

    /**
     * Make sure non-default non-opportunistic subscriptions has data disabled.
     */
    public synchronized void onDefaultDataSettingChanged() {
        if (DBG) log("onDefaultDataSettingChanged");
        disableDataForNonDefaultNonOpportunisticSubscriptions();
    }

    /**
     * When a subscription group is created or new subscriptions are added in the group, make
     * sure the settings among them are synced.
     */
    public synchronized void onSubscriptionGroupCreated(int[] subGroup) {
        if (DBG) log("onSubscriptionGroupCreated");
        if (subGroup == null || subGroup.length == 0) return;

        // Get a referece subscription from which all subscriptions in the group will copy settings.
        // TODO: the reference sub should be passed in from external caller.
        int refSubId = subGroup[0];
        for (int subId : subGroup) {
            if (mSubController.isActiveSubId(subId) && !mSubController.isOpportunistic(subId)) {
                refSubId = subId;
                break;
            }
        }
        if (DBG) log("refSubId is " + refSubId);

        try {
            boolean enable = GlobalSettingsHelper.getBoolean(
                    mContext, Settings.Global.MOBILE_DATA, refSubId);
            onUserDataEnabled(refSubId, enable);
        } catch (SettingNotFoundException exception) {
            // Do nothing if it's never set.
        }

        try {
            boolean enable = GlobalSettingsHelper.getBoolean(
                    mContext, Settings.Global.DATA_ROAMING, refSubId);
            onRoamingDataEnabled(refSubId, enable);
        } catch (SettingNotFoundException exception) {
            // Do nothing if it's never set.
        }
    }

    /**
     * Automatically update default settings (data / voice / sms).
     *
     * Opportunistic subscriptions can't be default data / voice / sms subscription.
     *
     * 1) If the default subscription is still active, keep it unchanged.
     * 2) Or if there's another active primary subscription that's in the same group,
     *    make it the new default value.
     * 3) Or if there's only one active primary subscription, automatically set default
     *    data subscription on it. Because default data in Android Q is an internal value,
     *    not a user settable value anymore.
     * 4) If non above is met, clear the default value to INVALID.
     */
    public synchronized void updateDefaults() {
        if (DBG) log("updateDefaults");
        List<SubscriptionInfo> subInfos = mSubController
                .getActiveSubscriptionInfoList(mContext.getOpPackageName());
        // If subscription controller is not ready, do nothing.
        if (subInfos == null) return;

        // Opportunistic subscriptions can't be default data / voice / sms subscription.
        subInfos = subInfos.stream()
                .filter(info -> !info.isOpportunistic())
                .collect(Collectors.toList());

        if (DBG) log("[updateDefaultValues] records: " + subInfos);

        // TODO: b/121394765 update logic once confirmed the behaviors.
        // Update default data subscription.
        if (DBG) log("[updateDefaultValues] Update default data subscription");
        updateDefaultValue(subInfos, mSubController.getDefaultDataSubId(), true, true,
                (newValue -> mSubController.setDefaultDataSubId(newValue)));

        // Update default voice subscription.
        if (DBG) log("[updateDefaultValues] Update default voice subscription");
        updateDefaultValue(subInfos, mSubController.getDefaultVoiceSubId(), true, false,
                (newValue -> mSubController.setDefaultVoiceSubId(newValue)));

        // Update default sms subscription.
        if (DBG) log("[updateDefaultValues] Update default sms subscription");
        updateDefaultValue(subInfos, mSubController.getDefaultSmsSubId(), true, false,
                (newValue -> mSubController.setDefaultSmsSubId(newValue)));
    }

    private void disableDataForNonDefaultNonOpportunisticSubscriptions() {
        int defaultDataSub = mSubController.getDefaultDataSubId();

        for (Phone phone : mPhones) {
            if (phone.getSubId() != defaultDataSub
                    && SubscriptionManager.isValidSubscriptionId(phone.getSubId())
                    && !mSubController.isOpportunistic(phone.getSubId())
                    && phone.isUserDataEnabled()) {
                log("setting data to false on " + phone.getSubId());
                phone.getDataEnabledSettings().setUserDataEnabled(false);
            }
        }
    }

    /**
     * Make sure MOBILE_DATA of subscriptions in the same group with the subId
     * are synced.
     */
    private synchronized void setUserDataEnabledForGroup(int subId, boolean enable) {
        log("setUserDataEnabledForGroup subId " + subId + " enable " + enable);
        List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup(
                subId, mContext.getOpPackageName());

        if (infoList == null) return;

        for (SubscriptionInfo info : infoList) {
            int currentSubId = info.getSubscriptionId();
            if (currentSubId == subId) continue;
            // TODO: simplify when setUserDataEnabled becomes singleton
            if (mSubController.isActiveSubId(currentSubId)) {
                // If we end up enabling two active primary subscriptions, don't enable the
                // non-default data sub. This will only happen if two primary subscriptions
                // in a group are both active. This is not a valid use-case now, but we are
                // handling it just in case.
                if (enable && !mSubController.isOpportunistic(currentSubId)
                        && currentSubId != mSubController.getDefaultSubId()) {
                    loge("Can not enable two active primary subscriptions.");
                    continue;
                }
                // For active subscription, call setUserDataEnabled through DataEnabledSettings.
                Phone phone = PhoneFactory.getPhone(mSubController.getPhoneId(currentSubId));
                // If enable is true and it's not opportunistic subscription, we don't enable it,
                // as there can't e two
                if (phone != null) {
                    phone.getDataEnabledSettings().setUserDataEnabled(enable);
                }
            } else {
                // For inactive subscription, directly write into global settings.
                GlobalSettingsHelper.setBoolean(
                        mContext, Settings.Global.MOBILE_DATA, currentSubId, enable);
            }
        }
    }

    /**
     * Make sure DATA_ROAMING of subscriptions in the same group with the subId
     * are synced.
     */
    private synchronized void setRoamingDataEnabledForGroup(int subId, boolean enable) {
        SubscriptionController subController = SubscriptionController.getInstance();
        List<SubscriptionInfo> infoList = subController.getSubscriptionsInGroup(
                subId, mContext.getOpPackageName());

        if (infoList == null) return;

        for (SubscriptionInfo info : infoList) {
            // For inactive subscription, directly write into global settings.
            GlobalSettingsHelper.setBoolean(
                    mContext, Settings.Global.DATA_ROAMING, info.getSubscriptionId(), enable);
        }
    }

    private String getGroupUuid(int subId) {
        String groupUuid;
        List<SubscriptionInfo> subInfo = mSubController.getSubInfo(
                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
                        + "=" + subId, null);
        if (subInfo == null || subInfo.size() == 0) {
            groupUuid = null;
        } else {
            groupUuid = subInfo.get(0).getGroupUuid();
        }

        return groupUuid;
    }

    private interface UpdateDefaultAction {
        void update(int newValue);
    }

    private void updateDefaultValue(List<SubscriptionInfo> subInfos, int oldValue,
            boolean inheritWithinGroup, boolean setToOnlyCandidate, UpdateDefaultAction action) {
        int newValue = SubscriptionManager.INVALID_SUBSCRIPTION_ID;

        if (subInfos.size() == 1 && setToOnlyCandidate) {
            newValue = subInfos.get(0).getSubscriptionId();
        } else if (subInfos.size() > 0) {
            // Get groupUuid of old
            String groupUuid = getGroupUuid(oldValue);

            for (SubscriptionInfo info : subInfos) {
                int id = info.getSubscriptionId();
                if (DBG) log("[updateDefaultValue] Record.id: " + id);
                // If the old subId is still active, or there's another active primary subscription
                // that is in the same group, that should become the new default subscription.
                if (id == oldValue || (groupUuid != null && groupUuid.equals(info.getGroupUuid())
                        && inheritWithinGroup)) {
                    log("[updateDefaultValue] updates to subId=" + id);
                    newValue = id;
                    break;
                }
            }
        }

        if (oldValue != newValue) {
            if (DBG) log("[updateDefaultValue: subId] from " + oldValue + " to " + newValue);
            action.update(newValue);
        }
    }

    private void log(String msg) {
        Log.d(LOG_TAG, msg);
    }

    private void loge(String msg) {
        Log.e(LOG_TAG, msg);
    }
}
+29 −55
Original line number Diff line number Diff line
@@ -306,6 +306,7 @@ public class SubscriptionController extends ISub.Stub {
        // FIXME: Remove if listener technique accepted.
        broadcastSimInfoContentChanged();

        MultiSimSettingController.getInstance().onSubscriptionsChanged();
        TelephonyMetrics metrics = TelephonyMetrics.getInstance();
        metrics.updateActiveSubscriptionInfoList(
                Collections.unmodifiableList(mCacheActiveSubInfoList));
@@ -405,7 +406,7 @@ public class SubscriptionController extends ISub.Stub {
     * @return Array list of queried result from database
     */
     @UnsupportedAppUsage
     private List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) {
     public List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) {
        if (VDBG) logd("selection:" + selection + ", querykey: " + queryKey);
        String[] selectionArgs = null;
        if (queryKey != null) {
@@ -1630,8 +1631,7 @@ public class SubscriptionController extends ISub.Stub {
            value.put(SubscriptionManager.DATA_ROAMING, roaming);
            if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set");

            int result = mContext.getContentResolver().update(
                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
            int result = databaseUpdateHelper(value, subId, true);

            // Refresh the Cache of Active Subscription Info List
            refreshCachedActiveSubscriptionInfoList();
@@ -1644,6 +1644,24 @@ public class SubscriptionController extends ISub.Stub {
        }
    }

    private int databaseUpdateHelper(ContentValues value, int subId, boolean updateEntireGroup) {
        List<SubscriptionInfo> infoList = getSubscriptionsInGroup(subId,
                mContext.getOpPackageName());
        if (!updateEntireGroup || infoList == null || infoList.size() == 0) {
            // Only update specified subscriptions.
            return mContext.getContentResolver().update(
                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
        } else {
            // Update all subscriptions in the same group.
            int[] subIdList = new int[infoList.size()];
            for (int i = 0; i < infoList.size(); i++) {
                subIdList[i] = infoList.get(i).getSubscriptionId();
            }
            return mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
                    value, getSelectionForSubIdList(subIdList), null);
        }
    }

    /**
     * Set carrier id by subId
     * @param carrierId the subscription carrier id.
@@ -2073,7 +2091,7 @@ public class SubscriptionController extends ISub.Stub {

        Settings.Global.putInt(mContext.getContentResolver(),
                Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
        updateDataEnabledSettings();
        MultiSimSettingController.getInstance().onDefaultDataSettingChanged();
        broadcastDefaultDataSubIdChanged(subId);
    }

@@ -2156,64 +2174,18 @@ public class SubscriptionController extends ISub.Stub {
        // Now that all security checks passes, perform the operation as ourselves.
        final long identity = Binder.clearCallingIdentity();
        try {
            final List<SubscriptionInfo> records = getActiveSubscriptionInfoList(
                    mContext.getOpPackageName());
            if (DBG) logdl("[clearDefaultsForInactiveSubIds] records: " + records);
            if (shouldDefaultBeCleared(records, getDefaultDataSubId())) {
                if (DBG) logd("[clearDefaultsForInactiveSubIds] clearing default data sub id");
                setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
            }
            if (shouldDefaultBeCleared(records, getDefaultSmsSubId())) {
                if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default sms sub id");
                setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
            }
            if (shouldDefaultBeCleared(records, getDefaultVoiceSubId())) {
                if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default voice sub id");
                setDefaultVoiceSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
            }
            MultiSimSettingController.getInstance().updateDefaults();
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    private boolean shouldDefaultBeCleared(List<SubscriptionInfo> records, int subId) {
        if (DBG) logdl("[shouldDefaultBeCleared: subId] " + subId);
        if (records == null) {
            if (DBG) logdl("[shouldDefaultBeCleared] return true no records subId=" + subId);
            return true;
        }
        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
            // If the subId parameter is not valid its already cleared so return false.
            if (DBG) logdl("[shouldDefaultBeCleared] return false only one subId, subId=" + subId);
            return false;
        }
        for (SubscriptionInfo record : records) {
            int id = record.getSubscriptionId();
            if (DBG) logdl("[shouldDefaultBeCleared] Record.id: " + id);
            if (id == subId) {
                logdl("[shouldDefaultBeCleared] return false subId is active, subId=" + subId);
                return false;
            }
        }
        if (DBG) logdl("[shouldDefaultBeCleared] return true not active subId=" + subId);
        return true;
    }

    /**
     * Make sure in multi SIM scenarios, user data is enabled or disabled correctly.
     * Whether a subscription is opportunistic or not.
     */
    public void updateDataEnabledSettings() {
        Phone[] phones = PhoneFactory.getPhones();
        if (phones == null || phones.length < 2) return;

        for (Phone phone : phones) {
            if (isActiveSubId(phone.getSubId())) {
                // Only enable it if it was enabled and it's the default data subscription.
                // Otherwise it should be disabled.
                phone.getDataEnabledSettings().setUserDataEnabled(
                        phone.isUserDataEnabled() && phone.getSubId() == getDefaultDataSubId());
            }
        }
    public boolean isOpportunistic(int subId) {
        SubscriptionInfo info = getActiveSubscriptionInfo(subId, mContext.getOpPackageName());
        return (info != null) && info.isOpportunistic();
    }

    // FIXME: We need we should not be assuming phoneId == slotIndex as it will not be true
@@ -2826,6 +2798,8 @@ public class SubscriptionController extends ISub.Stub {

            notifySubscriptionInfoChanged();

            MultiSimSettingController.getInstance().onSubscriptionGroupCreated(subIdList);

            return groupUUID;
        } finally {
            Binder.restoreCallingIdentity(identity);
+1 −2
Original line number Diff line number Diff line
@@ -610,8 +610,7 @@ public class SubscriptionInfoUpdater extends Handler {
                        .forEach(cardId -> updateEmbeddedSubscriptions(cardId));
            }
            // update default subId
            SubscriptionController.getInstance().clearDefaultsForInactiveSubIds();
            SubscriptionController.getInstance().updateDataEnabledSettings();
            MultiSimSettingController.getInstance().onAllSubscriptionsLoaded();
        }

        SubscriptionController.getInstance().notifySubscriptionInfoChanged();
+24 −60

File changed.

Preview size limit exceeded, changes collapsed.

Loading