content: /*
   * Copyright (C) 2018 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 android.telephony;
  
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.SystemApi;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.service.carrier.CarrierIdentifier;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Objects;
  
  /**
   * Contains the list of carrier restrictions.
   * Allowed list: it indicates the list of carriers that are allowed.
   * Excluded list: it indicates the list of carriers that are excluded.
   * Default carrier restriction: it indicates the default behavior and the priority between the two
   * lists:
   *  - not allowed: the device only allows usage of carriers that are present in the allowed list
   *    and not present in the excluded list. This implies that if a carrier is not present in either
   *    list, it is not allowed.
   *  - allowed: the device allows all carriers, except those present in the excluded list and not
   *    present in the allowed list. This implies that if a carrier is not present in either list,
   *    it is allowed.
   * MultiSim policy: it indicates the behavior in case of devices with two or more SIM cards.
   *  - MULTISIM_POLICY_NONE: the same configuration is applied to all SIM slots independently. This
   *    is the default value if none is set.
   *  - MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT: it indicates that any SIM card can be used
   *    as far as one SIM card matching the configuration is present in the device.
   *
   * Both lists support the character '?' as wild character. For example, an entry indicating
   * MCC=310 and MNC=??? will match all networks with MCC=310.
   *
   * Example 1: Allowed list contains MCC and MNC of operator A. Excluded list contains operator B,
   *            which has same MCC and MNC, but also GID1 value. The priority allowed list is set
   *            to true. Only SIM cards of operator A are allowed, but not those of B or any other
   *            operator.
   * Example 2: Allowed list contains MCC and MNC of operator A. Excluded list contains an entry
   *            with same MCC, and '???' as MNC. The priority allowed list is set to false.
   *            SIM cards of operator A and all SIM cards with a different MCC value are allowed.
   *            SIM cards of operators with same MCC value and different MNC are not allowed.
   * @hide
   */
  @SystemApi
  public final class CarrierRestrictionRules implements Parcelable {
      /**
       * The device only allows usage of carriers that are present in the allowed list and not
       * present in the excluded list. This implies that if a carrier is not present in either list,
       * it is not allowed.
       */
      public static final int CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED = 0;
  
      /**
       * The device allows all carriers, except those present in the excluded list and not present
       * in the allowed list. This implies that if a carrier is not present in either list, it is
       * allowed.
       */
      public static final int CARRIER_RESTRICTION_DEFAULT_ALLOWED = 1;
  
      /** The same configuration is applied to all SIM slots independently. */
      public static final int MULTISIM_POLICY_NONE = 0;
  
      /** Any SIM card can be used as far as one SIM card matching the configuration is present. */
      public static final int MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT = 1;
  
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(prefix = "MULTISIM_POLICY_",
              value = {MULTISIM_POLICY_NONE, MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT})
      public @interface MultiSimPolicy {}
  
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(prefix = "CARRIER_RESTRICTION_DEFAULT_",
              value = {CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED, CARRIER_RESTRICTION_DEFAULT_ALLOWED})
      public @interface CarrierRestrictionDefault {}
  
      /* Wild character for comparison */
      private static final char WILD_CHARACTER = '?';
  
      private List<CarrierIdentifier> mAllowedCarriers;
      private List<CarrierIdentifier> mExcludedCarriers;
      @CarrierRestrictionDefault
      private int mCarrierRestrictionDefault;
      @MultiSimPolicy
      private int mMultiSimPolicy;
  
      private CarrierRestrictionRules() {
          mAllowedCarriers = new ArrayList<CarrierIdentifier>();
          mExcludedCarriers = new ArrayList<CarrierIdentifier>();
          mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
          mMultiSimPolicy = MULTISIM_POLICY_NONE;
      }
  
      private CarrierRestrictionRules(Parcel in) {
          mAllowedCarriers = new ArrayList<CarrierIdentifier>();
          mExcludedCarriers = new ArrayList<CarrierIdentifier>();
  
          in.readTypedList(mAllowedCarriers, CarrierIdentifier.CREATOR);
          in.readTypedList(mExcludedCarriers, CarrierIdentifier.CREATOR);
          mCarrierRestrictionDefault = in.readInt();
          mMultiSimPolicy = in.readInt();
      }
  
      /**
       * Creates a new builder for this class
       * @hide
       */
      public static Builder newBuilder() {
          return new Builder();
      }
  
      /**
       * Indicates if all carriers are allowed
       */
      public boolean isAllCarriersAllowed() {
          return (mAllowedCarriers.isEmpty() && mExcludedCarriers.isEmpty()
                  && mCarrierRestrictionDefault == CARRIER_RESTRICTION_DEFAULT_ALLOWED);
      }
  
      /**
       * Retrieves list of allowed carriers
       *
       * @return the list of allowed carriers
       */
      public @NonNull List<CarrierIdentifier> getAllowedCarriers() {
          return mAllowedCarriers;
      }
  
      /**
       * Retrieves list of excluded carriers
       *
       * @return the list of excluded carriers
       */
      public @NonNull List<CarrierIdentifier> getExcludedCarriers() {
          return mExcludedCarriers;
      }
  
      /**
       * Retrieves the default behavior of carrier restrictions
       */
      public @CarrierRestrictionDefault int getDefaultCarrierRestriction() {
          return mCarrierRestrictionDefault;
      }
  
      /**
       * @return The policy used for multi-SIM devices
       */
      public @MultiSimPolicy int getMultiSimPolicy() {
          return mMultiSimPolicy;
      }
  
      /**
       * Tests an array of carriers with the carrier restriction configuration. The list of carrier
       * ids passed as argument does not need to be the same as currently present in the device.
       *
       * @param carrierIds list of {@link CarrierIdentifier}, one for each SIM slot on the device
       * @return a list of boolean with the same size as input, indicating if each
       * {@link CarrierIdentifier} is allowed or not.
       */
      public @NonNull List<Boolean> areCarrierIdentifiersAllowed(
              @NonNull List<CarrierIdentifier> carrierIds) {
          ArrayList<Boolean> result = new ArrayList<>(carrierIds.size());
  
          // First calculate the result for each slot independently
          for (int i = 0; i < carrierIds.size(); i++) {
              boolean inAllowedList = isCarrierIdInList(carrierIds.get(i), mAllowedCarriers);
              boolean inExcludedList = isCarrierIdInList(carrierIds.get(i), mExcludedCarriers);
              if (mCarrierRestrictionDefault == CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED) {
                  result.add((inAllowedList && !inExcludedList) ? true : false);
              } else {
                  result.add((inExcludedList && !inAllowedList) ? false : true);
              }
          }
          // Apply the multi-slot policy, if needed.
          if (mMultiSimPolicy == MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT) {
              for (boolean b : result) {
                  if (b) {
                      result.replaceAll(x -> true);
                      break;
                  }
              }
          }
          return result;
      }
  
      /**
       * Indicates if a certain carrier {@code id} is present inside a {@code list}
       *
       * @return true if the carrier {@code id} is present, false otherwise
       */
      private static boolean isCarrierIdInList(CarrierIdentifier id, List<CarrierIdentifier> list) {
          for (CarrierIdentifier listItem : list) {
              // Compare MCC and MNC
              if (!patternMatch(id.getMcc(), listItem.getMcc())
                      || !patternMatch(id.getMnc(), listItem.getMnc())) {
                  continue;
              }
  
              // Compare SPN. Comparison is on the complete strings, case insensitive and with wild
              // characters.
              String listItemValue = convertNullToEmpty(listItem.getSpn());
              String idValue = convertNullToEmpty(id.getSpn());
              if (!listItemValue.isEmpty()) {
                  if (!patternMatch(idValue, listItemValue)) {
                      continue;
                  }
              }
  
              // The IMSI of the configuration can be shorter than actual IMSI in the SIM card.
              listItemValue = convertNullToEmpty(listItem.getImsi());
              idValue = convertNullToEmpty(id.getImsi());
              if (!patternMatch(
                      idValue.substring(0, Math.min(idValue.length(), listItemValue.length())),
                      listItemValue)) {
                  continue;
              }
  
              // The GID1 of the configuration can be shorter than actual GID1 in the SIM card.
              listItemValue = convertNullToEmpty(listItem.getGid1());
              idValue = convertNullToEmpty(id.getGid1());
              if (!patternMatch(
                      idValue.substring(0, Math.min(idValue.length(), listItemValue.length())),
                      listItemValue)) {
                  continue;
              }
  
              // The GID2 of the configuration can be shorter than actual GID2 in the SIM card.
              listItemValue = convertNullToEmpty(listItem.getGid2());
              idValue = convertNullToEmpty(id.getGid2());
              if (!patternMatch(
                      idValue.substring(0, Math.min(idValue.length(), listItemValue.length())),
                      listItemValue)) {
                  continue;
              }
  
              // Valid match was found in the list
              return true;
          }
          return false;
      }
  
      private static String convertNullToEmpty(String value) {
          return Objects.toString(value, "");
      }
  
      /**
       * Performs a case insensitive string comparison against a given pattern. The character '?'
       * is used in the pattern as wild character in the comparison. The string must have the same
       * length as the pattern.
       *
       * @param str string to match
       * @param pattern string containing the pattern
       * @return true in case of match, false otherwise
       */
      private static boolean patternMatch(String str, String pattern) {
          if (str.length() != pattern.length()) {
              return false;
          }
          String lowerCaseStr = str.toLowerCase();
          String lowerCasePattern = pattern.toLowerCase();
  
          for (int i = 0; i < lowerCasePattern.length(); i++) {
              if (lowerCasePattern.charAt(i) != lowerCaseStr.charAt(i)
                      && lowerCasePattern.charAt(i) != WILD_CHARACTER) {
                  return false;
              }
          }
          return true;
      }
  
      /**
       * {@link Parcelable#writeToParcel}
       */
      @Override
      public void writeToParcel(Parcel out, int flags) {
          out.writeTypedList(mAllowedCarriers);
          out.writeTypedList(mExcludedCarriers);
          out.writeInt(mCarrierRestrictionDefault);
          out.writeInt(mMultiSimPolicy);
      }
  
      /**
       * {@link Parcelable#describeContents}
       */
      @Override
      public int describeContents() {
          return 0;
      }
  
      /**
       * {@link Parcelable.Creator}
       */
      public static final @android.annotation.NonNull Creator<CarrierRestrictionRules> CREATOR =
              new Creator<CarrierRestrictionRules>() {
          @Override
          public CarrierRestrictionRules createFromParcel(Parcel in) {
              return new CarrierRestrictionRules(in);
          }
  
          @Override
          public CarrierRestrictionRules[] newArray(int size) {
              return new CarrierRestrictionRules[size];
          }
      };
  
      @Override
      public String toString() {
          return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:"
                  + mExcludedCarriers + ", default:" + mCarrierRestrictionDefault
                  + ", multisim policy:" + mMultiSimPolicy + ")";
      }
  
      /**
       * Builder for a {@link CarrierRestrictionRules}.
       */
      public static final class Builder {
          private final CarrierRestrictionRules mRules;
  
          public Builder() {
              mRules = new CarrierRestrictionRules();
          }
  
          /** build command */
          public @NonNull CarrierRestrictionRules build() {
              return mRules;
          }
  
          /**
           * Indicate that all carriers are allowed.
           */
          public @NonNull Builder setAllCarriersAllowed() {
              mRules.mAllowedCarriers.clear();
              mRules.mExcludedCarriers.clear();
              mRules.mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_ALLOWED;
              return this;
          }
  
          /**
           * Set list of allowed carriers.
           *
           * @param allowedCarriers list of allowed carriers
           */
          public @NonNull Builder setAllowedCarriers(
                  @NonNull List<CarrierIdentifier> allowedCarriers) {
              mRules.mAllowedCarriers = new ArrayList<CarrierIdentifier>(allowedCarriers);
              return this;
          }
  
          /**
           * Set list of excluded carriers.
           *
           * @param excludedCarriers list of excluded carriers
           */
          public @NonNull Builder setExcludedCarriers(
                  @NonNull List<CarrierIdentifier> excludedCarriers) {
              mRules.mExcludedCarriers = new ArrayList<CarrierIdentifier>(excludedCarriers);
              return this;
          }
  
          /**
           * Set the default behavior of the carrier restrictions
           *
           * @param carrierRestrictionDefault prioritized carrier list
           */
          public @NonNull Builder setDefaultCarrierRestriction(
                  @CarrierRestrictionDefault int carrierRestrictionDefault) {
              mRules.mCarrierRestrictionDefault = carrierRestrictionDefault;
              return this;
          }
  
          /**
           * Set the policy to be used for multi-SIM devices
           *
           * @param multiSimPolicy multi SIM policy
           */
          public @NonNull Builder setMultiSimPolicy(@MultiSimPolicy int multiSimPolicy) {
              mRules.mMultiSimPolicy = multiSimPolicy;
              return this;
          }
      }
  }
  