content: /*
   * 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.SubscriptionManager;
  import android.telephony.TelephonyManager;
  import android.telephony.data.ApnSetting;
  import android.telephony.data.ApnSetting.ApnType;
  import android.text.TextUtils;
  import android.util.ArrayMap;
  import android.util.Log;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.telephony.Phone;
  import com.android.internal.telephony.PhoneConstants;
  import com.android.internal.telephony.PhoneFactory;
  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
                      | OverrideConditions.CONDITION_DEFAULT_DATA_ENABLED
                      | OverrideConditions.CONDITION_DSDS_ENABLED);
  
      /**
       * 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 only when default data is on */
          static final int CONDITION_DEFAULT_DATA_ENABLED = 1 << 2;
  
          /** Enable data only when device is in DSDS mode */
          static final int CONDITION_DSDS_ENABLED = 1 << 3;
  
          /** 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";
  
          /** Enable data only when default data is on in string format */
          static final String CONDITION_DEFAULT_DATA_ENABLED_STRING = "DefaultDataOn";
  
          /** Enable data only when device is in DSDS mode in string format */
          static final String CONDITION_DSDS_ENABLED_STRING = "dsdsEnabled";
  
          /** @hide */
          @IntDef(flag = true, prefix = { "OVERRIDE_CONDITION_" }, value = {
                  CONDITION_NON_DEFAULT,
                  CONDITION_IN_VOICE_CALL,
                  CONDITION_DEFAULT_DATA_ENABLED,
                  CONDITION_DSDS_ENABLED
          })
          @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_INT_MAP.put(CONDITION_DEFAULT_DATA_ENABLED,
                      CONDITION_DEFAULT_DATA_ENABLED_STRING);
              OVERRIDE_CONDITION_INT_MAP.put(CONDITION_DSDS_ENABLED,
                      CONDITION_DSDS_ENABLED_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);
              OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_DEFAULT_DATA_ENABLED_STRING,
                      CONDITION_DEFAULT_DATA_ENABLED);
              OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_DSDS_ENABLED_STRING,
                      CONDITION_DSDS_ENABLED);
          }
  
          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;
              }
  
              int defaultDataSubId = SubscriptionController.getInstance().getDefaultDataSubId();
  
              if (phone.getSubId() != defaultDataSubId) {
                  conditions |= OverrideConditions.CONDITION_NON_DEFAULT;
              }
  
              if (defaultDataSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
                  int phoneId = SubscriptionController.getInstance().getPhoneId(defaultDataSubId);
                  try {
                      Phone defaultDataPhone = PhoneFactory.getPhone(phoneId);
                      if (defaultDataPhone != null && defaultDataPhone.isUserDataEnabled()) {
                          conditions |= OverrideConditions.CONDITION_DEFAULT_DATA_ENABLED;
                      }
                  } catch (IllegalStateException e) {
                      //ignore the exception and do not add the condition
                      Log.d("DataEnabledOverride", e.getMessage());
                  }
              }
  
              if (TelephonyManager.from(phone.getContext()).isMultiSimEnabled()) {
                  conditions |= OverrideConditions.CONDITION_DSDS_ENABLED;
              }
          }
  
          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() + "\"]";
      }
  }
  