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 com.android.internal.telephony.dataconnection;
  
  import android.content.Context;
  import android.os.PersistableBundle;
  import android.telephony.CarrierConfigManager;
  import android.telephony.Rlog;
  import android.telephony.data.ApnSetting;
  import android.telephony.data.ApnSetting.ApnType;
  import android.util.Log;
  
  import com.android.internal.telephony.Phone;
  import com.android.internal.telephony.uicc.IccRecords;
  
  import java.util.Arrays;
  import java.util.HashSet;
  
  /**
   * This class represents a apn setting for create PDP link
   */
  public class ApnSettingUtils {
  
      static final String LOG_TAG = "ApnSetting";
  
      private static final boolean DBG = false;
  
      private static boolean iccidMatches(String mvnoData, String iccId) {
          String[] mvnoIccidList = mvnoData.split(",");
          for (String mvnoIccid : mvnoIccidList) {
              if (iccId.startsWith(mvnoIccid)) {
                  Log.d(LOG_TAG, "mvno icc id match found");
                  return true;
              }
          }
          return false;
      }
  
      private static boolean imsiMatches(String imsiDB, String imsiSIM) {
          // Note: imsiDB value has digit number or 'x' character for seperating USIM information
          // for MVNO operator. And then digit number is matched at same order and 'x' character
          // could replace by any digit number.
          // ex) if imsiDB inserted '310260x10xxxxxx' for GG Operator,
          //     that means first 6 digits, 8th and 9th digit
          //     should be set in USIM for GG Operator.
          int len = imsiDB.length();
  
          if (len <= 0) return false;
          if (len > imsiSIM.length()) return false;
  
          for (int idx = 0; idx < len; idx++) {
              char c = imsiDB.charAt(idx);
              if ((c == 'x') || (c == 'X') || (c == imsiSIM.charAt(idx))) {
                  continue;
              } else {
                  return false;
              }
          }
          return true;
      }
  
      /**
       * Check if MVNO type and data match IccRecords.
       *
       * @param r the IccRecords
       * @param mvnoType the MVNO type
       * @param mvnoMatchData the MVNO match data
       * @return {@code true} if MVNO type and data match IccRecords, {@code false} otherwise.
       */
      public static boolean mvnoMatches(IccRecords r, int mvnoType, String mvnoMatchData) {
          if (mvnoType == ApnSetting.MVNO_TYPE_SPN) {
              String spn = r.getServiceProviderNameWithBrandOverride();
              if ((spn != null) && spn.equalsIgnoreCase(mvnoMatchData)) {
                  return true;
              }
          } else if (mvnoType == ApnSetting.MVNO_TYPE_IMSI) {
              String imsiSIM = r.getIMSI();
              if ((imsiSIM != null) && imsiMatches(mvnoMatchData, imsiSIM)) {
                  return true;
              }
          } else if (mvnoType == ApnSetting.MVNO_TYPE_GID) {
              String gid1 = r.getGid1();
              int mvno_match_data_length = mvnoMatchData.length();
              if ((gid1 != null) && (gid1.length() >= mvno_match_data_length)
                      && gid1.substring(0, mvno_match_data_length).equalsIgnoreCase(mvnoMatchData)) {
                  return true;
              }
          } else if (mvnoType == ApnSetting.MVNO_TYPE_ICCID) {
              String iccId = r.getIccId();
              if ((iccId != null) && iccidMatches(mvnoMatchData, iccId)) {
                  return true;
              }
          }
  
          return false;
      }
  
      /**
       * Check if this APN type is metered.
       *
       * @param apnType the APN type
       * @param phone the phone object
       * @return {@code true} if the APN type is metered, {@code false} otherwise.
       */
      public static boolean isMeteredApnType(@ApnType int apnType, Phone phone) {
          if (phone == null) {
              return true;
          }
  
          boolean isRoaming = phone.getServiceState().getDataRoaming();
          int subId = phone.getSubId();
  
          String carrierConfig;
          // First check if the device is roaming. If yes, use the roaming metered APN list.
          // Otherwise use the normal metered APN list.
          if (isRoaming) {
              carrierConfig = CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS;
          } else {
              carrierConfig = CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS;
          }
  
          if (DBG) {
              Rlog.d(LOG_TAG, "isMeteredApnType: isRoaming=" + isRoaming);
          }
  
          CarrierConfigManager configManager = (CarrierConfigManager)
                  phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
          if (configManager == null) {
              Rlog.e(LOG_TAG, "Carrier config service is not available");
              return true;
          }
  
          PersistableBundle b = configManager.getConfigForSubId(subId);
          if (b == null) {
              Rlog.e(LOG_TAG, "Can't get the config. subId = " + subId);
              return true;
          }
  
          String[] meteredApnTypes = b.getStringArray(carrierConfig);
          if (meteredApnTypes == null) {
              Rlog.e(LOG_TAG, carrierConfig +  " is not available. " + "subId = " + subId);
              return true;
          }
  
          HashSet<String> meteredApnSet = new HashSet<>(Arrays.asList(meteredApnTypes));
          if (DBG) {
              Rlog.d(LOG_TAG, "For subId = " + subId + ", metered APN types are "
                      + Arrays.toString(meteredApnSet.toArray()));
          }
  
          if (meteredApnSet.contains(ApnSetting.getApnTypeString(apnType))) {
              if (DBG) Rlog.d(LOG_TAG, ApnSetting.getApnTypeString(apnType) + " is metered.");
              return true;
          } else if (apnType == ApnSetting.TYPE_ALL) {
              // Assuming no configuration error, if at least one APN type is
              // metered, then this APN setting is metered.
              if (meteredApnSet.size() > 0) {
                  if (DBG) Rlog.d(LOG_TAG, "APN_TYPE_ALL APN is metered.");
                  return true;
              }
          }
  
          if (DBG) Rlog.d(LOG_TAG, ApnSetting.getApnTypeString(apnType) + " is not metered.");
          return false;
      }
  
      /**
       * Check if this APN setting is metered.
       *
       * @param apn APN setting
       * @param phone The phone object
       * @return True if this APN setting is metered, otherwise false.
       */
      public static boolean isMetered(ApnSetting apn, Phone phone) {
          if (phone == null || apn == null) {
              return true;
          }
  
          for (int apnType : apn.getApnTypes()) {
              // If one of the APN type is metered, then this APN setting is metered.
              if (isMeteredApnType(apnType, phone)) {
                  return true;
              }
          }
          return false;
      }
  }
  