Loading src/java/com/android/internal/telephony/PhoneInternalInterface.java +1 −2 Original line number Diff line number Diff line Loading @@ -156,8 +156,7 @@ public interface PhoneInternalInterface { "carrierActionDisableMeteredApn"; static final String REASON_CSS_INDICATOR_CHANGED = "cssIndicatorChanged"; static final String REASON_RELEASED_BY_CONNECTIVITY_SERVICE = "releasedByConnectivityService"; static final String REASON_APN_ADDED_TO_WHITELIST = "apnAddedToWhiteList"; static final String REASON_APN_REMOVED_FROM_WHITELIST = "apnRemovedFromWhiteList"; static final String REASON_DATA_ENABLED_OVERRIDE = "dataEnabledOverride"; // Used for band mode selection methods static final int BM_UNSPECIFIED = RILConstants.BAND_MODE_UNSPECIFIED; // automatic Loading src/java/com/android/internal/telephony/PhoneSwitcher.java +3 −1 Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ import android.telephony.PreciseCallState; import android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.util.LocalLog; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -818,7 +819,8 @@ public class PhoneSwitcher extends Handler { // requests. private void updatePreferredDataPhoneId() { Phone voicePhone = findPhoneById(mPhoneIdInVoiceCall); if (voicePhone != null && voicePhone.isUserDataEnabled()) { if (voicePhone != null && voicePhone.getDataEnabledSettings().isDataEnabled( ApnSetting.TYPE_DEFAULT)) { // If a phone is in call and user enabled its mobile data, we // should switch internet connection to it. Because the other modem // will lose data connection anyway. Loading src/java/com/android/internal/telephony/SubscriptionController.java +65 −51 Original line number Diff line number Diff line Loading @@ -18,9 +18,9 @@ package com.android.internal.telephony; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT; import static android.telephony.data.ApnSetting.TYPE_MMS; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.app.AppOpsManager; Loading @@ -41,7 +41,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.AccessNetworkConstants; import android.telephony.CarrierConfigManager; import android.telephony.RadioAccessFamily; import android.telephony.Rlog; Loading @@ -50,7 +49,6 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccAccessRule; import android.telephony.UiccSlotInfo; import android.telephony.data.ApnSetting; import android.telephony.euicc.EuiccManager; import android.text.TextUtils; import android.util.LocalLog; Loading @@ -58,6 +56,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IccCardConstants.State; import com.android.internal.telephony.dataconnection.DataEnabledOverride; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.uicc.UiccCard; Loading Loading @@ -1729,11 +1728,11 @@ public class SubscriptionController extends ISub.Stub { public void syncGroupedSetting(int refSubId) { // Currently it only syncs allow MMS. Sync other settings as well if needed. int apnWhiteList = Integer.valueOf(getSubscriptionProperty( refSubId, SubscriptionManager.WHITE_LISTED_APN_DATA, mContext.getOpPackageName())); String dataEnabledOverrideRules = getSubscriptionProperty( refSubId, SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES); ContentValues value = new ContentValues(1); value.put(SubscriptionManager.WHITE_LISTED_APN_DATA, apnWhiteList); value.put(SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES, dataEnabledOverrideRules); databaseUpdateHelper(value, refSubId, true); } Loading Loading @@ -2554,11 +2553,11 @@ public class SubscriptionController extends ISub.Stub { } /** * Store properties associated with SubscriptionInfo in database * Get properties associated with SubscriptionInfo from database * * @param subId Subscription Id of Subscription * @param propKey Column name in SubscriptionInfo database * @return Value associated with subId and propKey column in database * @hide */ @Override public String getSubscriptionProperty(int subId, String propKey, String callingPackage) { Loading @@ -2566,14 +2565,29 @@ public class SubscriptionController extends ISub.Stub { mContext, subId, callingPackage, "getSubscriptionProperty")) { return null; } final long identity = Binder.clearCallingIdentity(); try { return getSubscriptionProperty(subId, propKey); } finally { Binder.restoreCallingIdentity(identity); } } /** * Get properties associated with SubscriptionInfo from database. Note this is the version * without permission check for telephony internal use only. * * @param subId Subscription Id of Subscription * @param propKey Column name in SubscriptionInfo database * @return Value associated with subId and propKey column in database */ public String getSubscriptionProperty(int subId, String propKey) { String resultValue = null; ContentResolver resolver = mContext.getContentResolver(); Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI, try (Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI, new String[]{propKey}, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?", new String[]{subId + ""}, null); try { new String[]{subId + ""}, null)) { if (cursor != null) { if (cursor.moveToFirst()) { switch (propKey) { Loading @@ -2600,6 +2614,9 @@ public class SubscriptionController extends ISub.Stub { case SubscriptionManager.WHITE_LISTED_APN_DATA: resultValue = cursor.getInt(0) + ""; break; case SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES: resultValue = cursor.getString(0); break; default: if(DBG) logd("Invalid column name"); break; Loading @@ -2610,11 +2627,8 @@ public class SubscriptionController extends ISub.Stub { } else { if(DBG) logd("Query failed"); } } finally { if (cursor != null) { cursor.close(); } } if (DBG) logd("getSubscriptionProperty Query value = " + resultValue); return resultValue; } Loading Loading @@ -3632,13 +3646,30 @@ public class SubscriptionController extends ISub.Stub { final long identity = Binder.clearCallingIdentity(); try { validateSubId(subId); DataEnabledOverride dataEnabledOverride = new DataEnabledOverride(getDataEnabledOverrideRules(subId)); dataEnabledOverride.setAlwaysAllowMms(alwaysAllow); return setDataEnabledOverrideRules(subId, dataEnabledOverride.getRules()); } finally { Binder.restoreCallingIdentity(identity); } } /** * Set allowing mobile data during voice call. * * @param subId Subscription index * @param rules Data enabled override rules in string format. See {@link DataEnabledOverride} * for details. * @return {@code true} if settings changed, otherwise {@code false}. */ public boolean setDataEnabledOverrideRules(int subId, @NonNull String rules) { if (DBG) logd("[setDataEnabledOverrideRules]+ rules:" + rules + " subId:" + subId); validateSubId(subId); ContentValues value = new ContentValues(1); int apnWhiteList = Integer.valueOf(getSubscriptionProperty(subId, SubscriptionManager.WHITE_LISTED_APN_DATA, mContext.getOpPackageName())); apnWhiteList = alwaysAllow ? (apnWhiteList | TYPE_MMS) : (apnWhiteList & ~TYPE_MMS); value.put(SubscriptionManager.WHITE_LISTED_APN_DATA, apnWhiteList); if (DBG) logd("[setAlwaysAllowMmsData]- alwaysAllow:" + alwaysAllow + " set"); value.put(SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES, rules); boolean result = databaseUpdateHelper(value, subId, true) > 0; Loading @@ -3646,37 +3677,20 @@ public class SubscriptionController extends ISub.Stub { // Refresh the Cache of Active Subscription Info List refreshCachedActiveSubscriptionInfoList(); notifySubscriptionInfoChanged(); Phone phone = PhoneFactory.getPhone(getPhoneId(subId)); if (phone != null) { phone.getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) .notifyApnWhiteListChange(TYPE_MMS, alwaysAllow); } } return result; } finally { Binder.restoreCallingIdentity(identity); } } /** * whether apnType is whitelisted. Being white listed means data connection is allowed * even if user data is turned off. * Get data enabled override rules. * * @param subId Subscription index * @return Data enabled override rules in string */ public boolean isApnWhiteListed(int subId, String callingPackage, int apnType) { return (getWhiteListedApnDataTypes(subId, callingPackage) & apnType) == apnType; } private @ApnSetting.ApnType int getWhiteListedApnDataTypes(int subId, String callingPackage) { String whiteListedApnData = getSubscriptionProperty(subId, SubscriptionManager.WHITE_LISTED_APN_DATA, callingPackage); try { return Integer.valueOf(whiteListedApnData); } catch (NumberFormatException e) { loge("[getWhiteListedApnDataTypes] couldn't parse apn data:" + whiteListedApnData); } return ApnSetting.TYPE_NONE; @NonNull public String getDataEnabledOverrideRules(int subId) { return TextUtils.emptyIfNull(getSubscriptionProperty(subId, SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES)); } } src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java 0 → 100644 +406 −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.dataconnection; import android.annotation.IntDef; import android.annotation.NonNull; import android.telephony.data.ApnSetting; import android.telephony.data.ApnSetting.ApnType; import android.text.TextUtils; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.dataconnection.DataEnabledOverride.OverrideConditions.Condition; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; /** * This class represents the rules for overriding data enabled settings in different conditions. * When data is disabled by the user, data can still be turned on temporarily when conditions * satisfy any rule here. */ public class DataEnabledOverride { private final Set<OverrideRule> mRules = new HashSet<>(); /** * The rule for allowing data during voice call. */ private static final OverrideRule OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL = new OverrideRule(ApnSetting.TYPE_ALL, OverrideConditions.CONDITION_IN_VOICE_CALL | OverrideConditions.CONDITION_NON_DEFAULT); /** * The rule for always allowing mms. Without adding any condition to the rule, any condition can * satisfy this rule for mms. */ private static final OverrideRule OVERRIDE_RULE_ALWAYS_ALLOW_MMS = new OverrideRule(ApnSetting.TYPE_MMS, OverrideConditions.CONDITION_UNCONDITIONALLY); /** * Data enabled override rule */ private static class OverrideRule { /** * APN type of the rule. The rule is APN type specific. The override is applicable to the * specified APN type as well. For now we only support one APN type per rule. Can be * expanded to multiple APN types in the future. */ private final @ApnType int mApnType; /** The required conditions for overriding */ private final OverrideConditions mRequiredConditions; /** * Constructor * * @param rule The override rule string. For example, {@code mms=nonDefault} or * {@code default=voiceCall & nonDefault} */ OverrideRule(@NonNull String rule) { String[] tokens = rule.trim().split("\\s*=\\s*"); if (tokens.length != 2) { throw new IllegalArgumentException("Invalid data enabled override rule format: " + rule); } if (TextUtils.isEmpty(tokens[0])) { throw new IllegalArgumentException("APN type can't be empty"); } mApnType = ApnSetting.getApnTypesBitmaskFromString(tokens[0]); if (mApnType == ApnSetting.TYPE_NONE) { throw new IllegalArgumentException("Invalid APN type. Rule=" + rule); } mRequiredConditions = new OverrideConditions(tokens[1]); } /** * Constructor * * @param apnType APN type of the rule * @param requiredConditions The required conditions for the rule */ private OverrideRule(int apnType, int requiredConditions) { mApnType = apnType; mRequiredConditions = new OverrideConditions(requiredConditions); } /** * Check if this rule can be satisfied by the given APN type and provided conditions. * * @param apnType APN type to check * @param providedConditions The provided conditions to check * @return {@code true} if satisfied */ boolean isSatisfiedByConditions(@ApnType int apnType, @Condition int providedConditions) { return (mApnType == apnType || mApnType == ApnSetting.TYPE_ALL) && mRequiredConditions.allMet(providedConditions); } @Override public String toString() { return ApnSetting.getApnTypeString(mApnType) + "=" + mRequiredConditions; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; OverrideRule that = (OverrideRule) o; return mApnType == that.mApnType && Objects.equals(mRequiredConditions, that.mRequiredConditions); } @Override public int hashCode() { return Objects.hash(mApnType, mRequiredConditions); } } /** * Represent the conditions for overriding data enabled settings */ static class OverrideConditions { // Possible values for data enabled override condition. Note these flags are bitmasks. /** Unconditionally override enabled settings */ static final int CONDITION_UNCONDITIONALLY = 0; /** Enable data only on subscription that is not user selected default data subscription */ static final int CONDITION_NON_DEFAULT = 1 << 0; /** Enable data only when device has ongoing voice call */ static final int CONDITION_IN_VOICE_CALL = 1 << 1; /** Enable data unconditionally in string format */ static final String CONDITION_UNCONDITIONALLY_STRING = "unconditionally"; /** Enable data only on subscription that is not default in string format */ static final String CONDITION_NON_DEFAULT_STRING = "nonDefault"; /** Enable data only when device has ongoing voice call in string format */ static final String CONDITION_VOICE_CALL_STRING = "inVoiceCall"; /** @hide */ @IntDef(flag = true, prefix = { "OVERRIDE_CONDITION_" }, value = { CONDITION_NON_DEFAULT, CONDITION_IN_VOICE_CALL, }) @Retention(RetentionPolicy.SOURCE) public @interface Condition {} private static final Map<Integer, String> OVERRIDE_CONDITION_INT_MAP = new ArrayMap<>(); private static final Map<String, Integer> OVERRIDE_CONDITION_STRING_MAP = new ArrayMap<>(); static { OVERRIDE_CONDITION_INT_MAP.put(CONDITION_NON_DEFAULT, CONDITION_NON_DEFAULT_STRING); OVERRIDE_CONDITION_INT_MAP.put(CONDITION_IN_VOICE_CALL, CONDITION_VOICE_CALL_STRING); OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_UNCONDITIONALLY_STRING, CONDITION_UNCONDITIONALLY); OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_NON_DEFAULT_STRING, CONDITION_NON_DEFAULT); OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_VOICE_CALL_STRING, CONDITION_IN_VOICE_CALL); } private final @Condition int mConditions; /** * Conditions for overriding data enabled setting * * @param conditions Conditions in string format */ OverrideConditions(@NonNull String conditions) { mConditions = getBitmaskFromString(conditions); } /** * Conditions for overriding data enabled setting * * @param conditions Conditions in bitmask */ OverrideConditions(@Condition int conditions) { mConditions = conditions; } private static String getStringFromBitmask(@Condition int conditions) { if (conditions == CONDITION_UNCONDITIONALLY) { return CONDITION_UNCONDITIONALLY_STRING; } List<String> conditionsStrings = new ArrayList<>(); for (Integer condition : OVERRIDE_CONDITION_INT_MAP.keySet()) { if ((conditions & condition) == condition) { conditionsStrings.add(OVERRIDE_CONDITION_INT_MAP.get(condition)); } } return TextUtils.join("&", conditionsStrings); } private static @Condition int getBitmaskFromString(@NonNull String str) { if (TextUtils.isEmpty(str)) { throw new IllegalArgumentException("Empty rule string"); } String[] conditionStrings = str.trim().split("\\s*&\\s*"); int bitmask = 0; for (String conditionStr : conditionStrings) { if (!TextUtils.isEmpty(conditionStr)) { if (!OVERRIDE_CONDITION_STRING_MAP.containsKey(conditionStr)) { throw new IllegalArgumentException("Invalid conditions: " + str); } bitmask |= OVERRIDE_CONDITION_STRING_MAP.get(conditionStr); } } return bitmask; } /** * Check if provided conditions can meet all conditions in the rule. * * @param providedConditions The provided conditions * @return {@code true} if all conditions are met. */ boolean allMet(@Condition int providedConditions) { return (providedConditions & mConditions) == mConditions; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; OverrideConditions that = (OverrideConditions) o; return mConditions == that.mConditions; } @Override public int hashCode() { return Objects.hash(mConditions); } @Override public String toString() { return getStringFromBitmask(mConditions); } } /** * Constructor * * @param rules Data enabled override rules */ public DataEnabledOverride(@NonNull String rules) { updateRules(rules); } /** * Update the data enabled override rules. * * @param newRules New override rules */ @VisibleForTesting public void updateRules(@NonNull String newRules) { mRules.clear(); String[] rulesString = newRules.trim().split("\\s*,\\s*"); for (String rule : rulesString) { if (!TextUtils.isEmpty(rule)) { mRules.add(new OverrideRule(rule)); } } } /** * Set always allowing MMS * * @param allow {@code true} if always allowing, otherwise {@code false}. */ public void setAlwaysAllowMms(boolean allow) { if (allow) { mRules.add(OVERRIDE_RULE_ALWAYS_ALLOW_MMS); } else { mRules.remove(OVERRIDE_RULE_ALWAYS_ALLOW_MMS); } } /** * Set allowing mobile data during voice call. * * @param allow {@code true} if allowing using data during voice call, {@code false} if * disallowed. */ public void setDataAllowedInVoiceCall(boolean allow) { if (allow) { mRules.add(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL); } else { mRules.remove(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL); } } /** * Check if data is allowed during voice call. * * @return {@code true} if data is allowed during voice call. */ public boolean isDataAllowedInVoiceCall() { return mRules.contains(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL); } private boolean canSatisfyAnyRule(@ApnType int apnType, @Condition int providedConditions) { for (OverrideRule rule : mRules) { if (rule.isSatisfiedByConditions(apnType, providedConditions)) { return true; } } return false; } private @Condition int getCurrentConditions(Phone phone) { int conditions = 0; if (phone != null) { // Check if the device is on voice call if (phone.getCallTracker().getState() != PhoneConstants.State.IDLE) { conditions |= OverrideConditions.CONDITION_IN_VOICE_CALL; } if (phone.getSubId() != SubscriptionController.getInstance().getDefaultDataSubId()) { conditions |= OverrideConditions.CONDITION_NON_DEFAULT; } } return conditions; } /** * Check for given APN type if we should enable data. * * @param phone Phone object * @param apnType APN type * @return {@code true} if data should be enabled for the current condition. */ public boolean shouldOverrideDataEnabledSettings(Phone phone, @ApnType int apnType) { return canSatisfyAnyRule(apnType, getCurrentConditions(phone)); } /** * Get data enabled override rules. * * @return Get data enabled override rules in string format */ @NonNull public String getRules() { List<String> ruleStrings = new ArrayList<>(); for (OverrideRule rule : mRules) { ruleStrings.add(rule.toString()); } return TextUtils.join(",", ruleStrings); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DataEnabledOverride that = (DataEnabledOverride) o; return mRules.equals(that.mRules); } @Override public int hashCode() { return Objects.hash(mRules); } @Override public String toString() { return "DataEnabledOverride: [rules=\"" + getRules() + "\"]"; } } src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java +87 −11 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
src/java/com/android/internal/telephony/PhoneInternalInterface.java +1 −2 Original line number Diff line number Diff line Loading @@ -156,8 +156,7 @@ public interface PhoneInternalInterface { "carrierActionDisableMeteredApn"; static final String REASON_CSS_INDICATOR_CHANGED = "cssIndicatorChanged"; static final String REASON_RELEASED_BY_CONNECTIVITY_SERVICE = "releasedByConnectivityService"; static final String REASON_APN_ADDED_TO_WHITELIST = "apnAddedToWhiteList"; static final String REASON_APN_REMOVED_FROM_WHITELIST = "apnRemovedFromWhiteList"; static final String REASON_DATA_ENABLED_OVERRIDE = "dataEnabledOverride"; // Used for band mode selection methods static final int BM_UNSPECIFIED = RILConstants.BAND_MODE_UNSPECIFIED; // automatic Loading
src/java/com/android/internal/telephony/PhoneSwitcher.java +3 −1 Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ import android.telephony.PreciseCallState; import android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.util.LocalLog; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -818,7 +819,8 @@ public class PhoneSwitcher extends Handler { // requests. private void updatePreferredDataPhoneId() { Phone voicePhone = findPhoneById(mPhoneIdInVoiceCall); if (voicePhone != null && voicePhone.isUserDataEnabled()) { if (voicePhone != null && voicePhone.getDataEnabledSettings().isDataEnabled( ApnSetting.TYPE_DEFAULT)) { // If a phone is in call and user enabled its mobile data, we // should switch internet connection to it. Because the other modem // will lose data connection anyway. Loading
src/java/com/android/internal/telephony/SubscriptionController.java +65 −51 Original line number Diff line number Diff line Loading @@ -18,9 +18,9 @@ package com.android.internal.telephony; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT; import static android.telephony.data.ApnSetting.TYPE_MMS; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.app.AppOpsManager; Loading @@ -41,7 +41,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.AccessNetworkConstants; import android.telephony.CarrierConfigManager; import android.telephony.RadioAccessFamily; import android.telephony.Rlog; Loading @@ -50,7 +49,6 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccAccessRule; import android.telephony.UiccSlotInfo; import android.telephony.data.ApnSetting; import android.telephony.euicc.EuiccManager; import android.text.TextUtils; import android.util.LocalLog; Loading @@ -58,6 +56,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IccCardConstants.State; import com.android.internal.telephony.dataconnection.DataEnabledOverride; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.uicc.UiccCard; Loading Loading @@ -1729,11 +1728,11 @@ public class SubscriptionController extends ISub.Stub { public void syncGroupedSetting(int refSubId) { // Currently it only syncs allow MMS. Sync other settings as well if needed. int apnWhiteList = Integer.valueOf(getSubscriptionProperty( refSubId, SubscriptionManager.WHITE_LISTED_APN_DATA, mContext.getOpPackageName())); String dataEnabledOverrideRules = getSubscriptionProperty( refSubId, SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES); ContentValues value = new ContentValues(1); value.put(SubscriptionManager.WHITE_LISTED_APN_DATA, apnWhiteList); value.put(SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES, dataEnabledOverrideRules); databaseUpdateHelper(value, refSubId, true); } Loading Loading @@ -2554,11 +2553,11 @@ public class SubscriptionController extends ISub.Stub { } /** * Store properties associated with SubscriptionInfo in database * Get properties associated with SubscriptionInfo from database * * @param subId Subscription Id of Subscription * @param propKey Column name in SubscriptionInfo database * @return Value associated with subId and propKey column in database * @hide */ @Override public String getSubscriptionProperty(int subId, String propKey, String callingPackage) { Loading @@ -2566,14 +2565,29 @@ public class SubscriptionController extends ISub.Stub { mContext, subId, callingPackage, "getSubscriptionProperty")) { return null; } final long identity = Binder.clearCallingIdentity(); try { return getSubscriptionProperty(subId, propKey); } finally { Binder.restoreCallingIdentity(identity); } } /** * Get properties associated with SubscriptionInfo from database. Note this is the version * without permission check for telephony internal use only. * * @param subId Subscription Id of Subscription * @param propKey Column name in SubscriptionInfo database * @return Value associated with subId and propKey column in database */ public String getSubscriptionProperty(int subId, String propKey) { String resultValue = null; ContentResolver resolver = mContext.getContentResolver(); Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI, try (Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI, new String[]{propKey}, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?", new String[]{subId + ""}, null); try { new String[]{subId + ""}, null)) { if (cursor != null) { if (cursor.moveToFirst()) { switch (propKey) { Loading @@ -2600,6 +2614,9 @@ public class SubscriptionController extends ISub.Stub { case SubscriptionManager.WHITE_LISTED_APN_DATA: resultValue = cursor.getInt(0) + ""; break; case SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES: resultValue = cursor.getString(0); break; default: if(DBG) logd("Invalid column name"); break; Loading @@ -2610,11 +2627,8 @@ public class SubscriptionController extends ISub.Stub { } else { if(DBG) logd("Query failed"); } } finally { if (cursor != null) { cursor.close(); } } if (DBG) logd("getSubscriptionProperty Query value = " + resultValue); return resultValue; } Loading Loading @@ -3632,13 +3646,30 @@ public class SubscriptionController extends ISub.Stub { final long identity = Binder.clearCallingIdentity(); try { validateSubId(subId); DataEnabledOverride dataEnabledOverride = new DataEnabledOverride(getDataEnabledOverrideRules(subId)); dataEnabledOverride.setAlwaysAllowMms(alwaysAllow); return setDataEnabledOverrideRules(subId, dataEnabledOverride.getRules()); } finally { Binder.restoreCallingIdentity(identity); } } /** * Set allowing mobile data during voice call. * * @param subId Subscription index * @param rules Data enabled override rules in string format. See {@link DataEnabledOverride} * for details. * @return {@code true} if settings changed, otherwise {@code false}. */ public boolean setDataEnabledOverrideRules(int subId, @NonNull String rules) { if (DBG) logd("[setDataEnabledOverrideRules]+ rules:" + rules + " subId:" + subId); validateSubId(subId); ContentValues value = new ContentValues(1); int apnWhiteList = Integer.valueOf(getSubscriptionProperty(subId, SubscriptionManager.WHITE_LISTED_APN_DATA, mContext.getOpPackageName())); apnWhiteList = alwaysAllow ? (apnWhiteList | TYPE_MMS) : (apnWhiteList & ~TYPE_MMS); value.put(SubscriptionManager.WHITE_LISTED_APN_DATA, apnWhiteList); if (DBG) logd("[setAlwaysAllowMmsData]- alwaysAllow:" + alwaysAllow + " set"); value.put(SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES, rules); boolean result = databaseUpdateHelper(value, subId, true) > 0; Loading @@ -3646,37 +3677,20 @@ public class SubscriptionController extends ISub.Stub { // Refresh the Cache of Active Subscription Info List refreshCachedActiveSubscriptionInfoList(); notifySubscriptionInfoChanged(); Phone phone = PhoneFactory.getPhone(getPhoneId(subId)); if (phone != null) { phone.getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) .notifyApnWhiteListChange(TYPE_MMS, alwaysAllow); } } return result; } finally { Binder.restoreCallingIdentity(identity); } } /** * whether apnType is whitelisted. Being white listed means data connection is allowed * even if user data is turned off. * Get data enabled override rules. * * @param subId Subscription index * @return Data enabled override rules in string */ public boolean isApnWhiteListed(int subId, String callingPackage, int apnType) { return (getWhiteListedApnDataTypes(subId, callingPackage) & apnType) == apnType; } private @ApnSetting.ApnType int getWhiteListedApnDataTypes(int subId, String callingPackage) { String whiteListedApnData = getSubscriptionProperty(subId, SubscriptionManager.WHITE_LISTED_APN_DATA, callingPackage); try { return Integer.valueOf(whiteListedApnData); } catch (NumberFormatException e) { loge("[getWhiteListedApnDataTypes] couldn't parse apn data:" + whiteListedApnData); } return ApnSetting.TYPE_NONE; @NonNull public String getDataEnabledOverrideRules(int subId) { return TextUtils.emptyIfNull(getSubscriptionProperty(subId, SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES)); } }
src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java 0 → 100644 +406 −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.dataconnection; import android.annotation.IntDef; import android.annotation.NonNull; import android.telephony.data.ApnSetting; import android.telephony.data.ApnSetting.ApnType; import android.text.TextUtils; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.dataconnection.DataEnabledOverride.OverrideConditions.Condition; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; /** * This class represents the rules for overriding data enabled settings in different conditions. * When data is disabled by the user, data can still be turned on temporarily when conditions * satisfy any rule here. */ public class DataEnabledOverride { private final Set<OverrideRule> mRules = new HashSet<>(); /** * The rule for allowing data during voice call. */ private static final OverrideRule OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL = new OverrideRule(ApnSetting.TYPE_ALL, OverrideConditions.CONDITION_IN_VOICE_CALL | OverrideConditions.CONDITION_NON_DEFAULT); /** * The rule for always allowing mms. Without adding any condition to the rule, any condition can * satisfy this rule for mms. */ private static final OverrideRule OVERRIDE_RULE_ALWAYS_ALLOW_MMS = new OverrideRule(ApnSetting.TYPE_MMS, OverrideConditions.CONDITION_UNCONDITIONALLY); /** * Data enabled override rule */ private static class OverrideRule { /** * APN type of the rule. The rule is APN type specific. The override is applicable to the * specified APN type as well. For now we only support one APN type per rule. Can be * expanded to multiple APN types in the future. */ private final @ApnType int mApnType; /** The required conditions for overriding */ private final OverrideConditions mRequiredConditions; /** * Constructor * * @param rule The override rule string. For example, {@code mms=nonDefault} or * {@code default=voiceCall & nonDefault} */ OverrideRule(@NonNull String rule) { String[] tokens = rule.trim().split("\\s*=\\s*"); if (tokens.length != 2) { throw new IllegalArgumentException("Invalid data enabled override rule format: " + rule); } if (TextUtils.isEmpty(tokens[0])) { throw new IllegalArgumentException("APN type can't be empty"); } mApnType = ApnSetting.getApnTypesBitmaskFromString(tokens[0]); if (mApnType == ApnSetting.TYPE_NONE) { throw new IllegalArgumentException("Invalid APN type. Rule=" + rule); } mRequiredConditions = new OverrideConditions(tokens[1]); } /** * Constructor * * @param apnType APN type of the rule * @param requiredConditions The required conditions for the rule */ private OverrideRule(int apnType, int requiredConditions) { mApnType = apnType; mRequiredConditions = new OverrideConditions(requiredConditions); } /** * Check if this rule can be satisfied by the given APN type and provided conditions. * * @param apnType APN type to check * @param providedConditions The provided conditions to check * @return {@code true} if satisfied */ boolean isSatisfiedByConditions(@ApnType int apnType, @Condition int providedConditions) { return (mApnType == apnType || mApnType == ApnSetting.TYPE_ALL) && mRequiredConditions.allMet(providedConditions); } @Override public String toString() { return ApnSetting.getApnTypeString(mApnType) + "=" + mRequiredConditions; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; OverrideRule that = (OverrideRule) o; return mApnType == that.mApnType && Objects.equals(mRequiredConditions, that.mRequiredConditions); } @Override public int hashCode() { return Objects.hash(mApnType, mRequiredConditions); } } /** * Represent the conditions for overriding data enabled settings */ static class OverrideConditions { // Possible values for data enabled override condition. Note these flags are bitmasks. /** Unconditionally override enabled settings */ static final int CONDITION_UNCONDITIONALLY = 0; /** Enable data only on subscription that is not user selected default data subscription */ static final int CONDITION_NON_DEFAULT = 1 << 0; /** Enable data only when device has ongoing voice call */ static final int CONDITION_IN_VOICE_CALL = 1 << 1; /** Enable data unconditionally in string format */ static final String CONDITION_UNCONDITIONALLY_STRING = "unconditionally"; /** Enable data only on subscription that is not default in string format */ static final String CONDITION_NON_DEFAULT_STRING = "nonDefault"; /** Enable data only when device has ongoing voice call in string format */ static final String CONDITION_VOICE_CALL_STRING = "inVoiceCall"; /** @hide */ @IntDef(flag = true, prefix = { "OVERRIDE_CONDITION_" }, value = { CONDITION_NON_DEFAULT, CONDITION_IN_VOICE_CALL, }) @Retention(RetentionPolicy.SOURCE) public @interface Condition {} private static final Map<Integer, String> OVERRIDE_CONDITION_INT_MAP = new ArrayMap<>(); private static final Map<String, Integer> OVERRIDE_CONDITION_STRING_MAP = new ArrayMap<>(); static { OVERRIDE_CONDITION_INT_MAP.put(CONDITION_NON_DEFAULT, CONDITION_NON_DEFAULT_STRING); OVERRIDE_CONDITION_INT_MAP.put(CONDITION_IN_VOICE_CALL, CONDITION_VOICE_CALL_STRING); OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_UNCONDITIONALLY_STRING, CONDITION_UNCONDITIONALLY); OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_NON_DEFAULT_STRING, CONDITION_NON_DEFAULT); OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_VOICE_CALL_STRING, CONDITION_IN_VOICE_CALL); } private final @Condition int mConditions; /** * Conditions for overriding data enabled setting * * @param conditions Conditions in string format */ OverrideConditions(@NonNull String conditions) { mConditions = getBitmaskFromString(conditions); } /** * Conditions for overriding data enabled setting * * @param conditions Conditions in bitmask */ OverrideConditions(@Condition int conditions) { mConditions = conditions; } private static String getStringFromBitmask(@Condition int conditions) { if (conditions == CONDITION_UNCONDITIONALLY) { return CONDITION_UNCONDITIONALLY_STRING; } List<String> conditionsStrings = new ArrayList<>(); for (Integer condition : OVERRIDE_CONDITION_INT_MAP.keySet()) { if ((conditions & condition) == condition) { conditionsStrings.add(OVERRIDE_CONDITION_INT_MAP.get(condition)); } } return TextUtils.join("&", conditionsStrings); } private static @Condition int getBitmaskFromString(@NonNull String str) { if (TextUtils.isEmpty(str)) { throw new IllegalArgumentException("Empty rule string"); } String[] conditionStrings = str.trim().split("\\s*&\\s*"); int bitmask = 0; for (String conditionStr : conditionStrings) { if (!TextUtils.isEmpty(conditionStr)) { if (!OVERRIDE_CONDITION_STRING_MAP.containsKey(conditionStr)) { throw new IllegalArgumentException("Invalid conditions: " + str); } bitmask |= OVERRIDE_CONDITION_STRING_MAP.get(conditionStr); } } return bitmask; } /** * Check if provided conditions can meet all conditions in the rule. * * @param providedConditions The provided conditions * @return {@code true} if all conditions are met. */ boolean allMet(@Condition int providedConditions) { return (providedConditions & mConditions) == mConditions; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; OverrideConditions that = (OverrideConditions) o; return mConditions == that.mConditions; } @Override public int hashCode() { return Objects.hash(mConditions); } @Override public String toString() { return getStringFromBitmask(mConditions); } } /** * Constructor * * @param rules Data enabled override rules */ public DataEnabledOverride(@NonNull String rules) { updateRules(rules); } /** * Update the data enabled override rules. * * @param newRules New override rules */ @VisibleForTesting public void updateRules(@NonNull String newRules) { mRules.clear(); String[] rulesString = newRules.trim().split("\\s*,\\s*"); for (String rule : rulesString) { if (!TextUtils.isEmpty(rule)) { mRules.add(new OverrideRule(rule)); } } } /** * Set always allowing MMS * * @param allow {@code true} if always allowing, otherwise {@code false}. */ public void setAlwaysAllowMms(boolean allow) { if (allow) { mRules.add(OVERRIDE_RULE_ALWAYS_ALLOW_MMS); } else { mRules.remove(OVERRIDE_RULE_ALWAYS_ALLOW_MMS); } } /** * Set allowing mobile data during voice call. * * @param allow {@code true} if allowing using data during voice call, {@code false} if * disallowed. */ public void setDataAllowedInVoiceCall(boolean allow) { if (allow) { mRules.add(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL); } else { mRules.remove(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL); } } /** * Check if data is allowed during voice call. * * @return {@code true} if data is allowed during voice call. */ public boolean isDataAllowedInVoiceCall() { return mRules.contains(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL); } private boolean canSatisfyAnyRule(@ApnType int apnType, @Condition int providedConditions) { for (OverrideRule rule : mRules) { if (rule.isSatisfiedByConditions(apnType, providedConditions)) { return true; } } return false; } private @Condition int getCurrentConditions(Phone phone) { int conditions = 0; if (phone != null) { // Check if the device is on voice call if (phone.getCallTracker().getState() != PhoneConstants.State.IDLE) { conditions |= OverrideConditions.CONDITION_IN_VOICE_CALL; } if (phone.getSubId() != SubscriptionController.getInstance().getDefaultDataSubId()) { conditions |= OverrideConditions.CONDITION_NON_DEFAULT; } } return conditions; } /** * Check for given APN type if we should enable data. * * @param phone Phone object * @param apnType APN type * @return {@code true} if data should be enabled for the current condition. */ public boolean shouldOverrideDataEnabledSettings(Phone phone, @ApnType int apnType) { return canSatisfyAnyRule(apnType, getCurrentConditions(phone)); } /** * Get data enabled override rules. * * @return Get data enabled override rules in string format */ @NonNull public String getRules() { List<String> ruleStrings = new ArrayList<>(); for (OverrideRule rule : mRules) { ruleStrings.add(rule.toString()); } return TextUtils.join(",", ruleStrings); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DataEnabledOverride that = (DataEnabledOverride) o; return mRules.equals(that.mRules); } @Override public int hashCode() { return Objects.hash(mRules); } @Override public String toString() { return "DataEnabledOverride: [rules=\"" + getRules() + "\"]"; } }
src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java +87 −11 File changed.Preview size limit exceeded, changes collapsed. Show changes