blob: 0cd565fca694a555f6dcd734d6ab470ab269eda8 [file] [log] [blame]
/*
* 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.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() + "\"]";
}
}