content: /*
   * Copyright (C) 2017 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.server.wifi;
  
  import android.annotation.NonNull;
  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.database.ContentObserver;
  import android.net.Uri;
  import android.net.wifi.EAPConstants;
  import android.net.wifi.WifiEnterpriseConfig;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.PersistableBundle;
  import android.telephony.CarrierConfigManager;
  import android.telephony.ImsiEncryptionInfo;
  import android.telephony.SubscriptionInfo;
  import android.telephony.SubscriptionManager;
  import android.telephony.TelephonyManager;
  import android.util.Base64;
  import android.util.Log;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Class for maintaining/caching carrier Wi-Fi network configurations.
   */
  public class CarrierNetworkConfig {
      private static final String TAG = "CarrierNetworkConfig";
  
      private static final String NETWORK_CONFIG_SEPARATOR = ",";
      private static final int ENCODED_SSID_INDEX = 0;
      private static final int EAP_TYPE_INDEX = 1;
      private static final int CONFIG_ELEMENT_SIZE = 2;
  
      private static final Uri CONTENT_URI = Uri.parse("content://carrier_information/carrier");
  
      private boolean mDbg = false;
  
      private final Map<String, NetworkInfo> mCarrierNetworkMap;
      private boolean mIsCarrierImsiEncryptionInfoAvailable = false;
      private ImsiEncryptionInfo mLastImsiEncryptionInfo = null; // used for dumpsys only
  
      /**
       * Enable/disable verbose logging.
       */
      public void enableVerboseLogging(int verbose) {
          mDbg = verbose > 0;
      }
  
      public CarrierNetworkConfig(@NonNull Context context, @NonNull Looper looper,
              @NonNull FrameworkFacade framework) {
          mCarrierNetworkMap = new HashMap<>();
          updateNetworkConfig(context);
  
          // Monitor for carrier config changes.
          IntentFilter filter = new IntentFilter();
          filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
          context.registerReceiver(new BroadcastReceiver() {
              @Override
              public void onReceive(Context context, Intent intent) {
                  updateNetworkConfig(context);
              }
          }, filter);
  
          framework.registerContentObserver(context, CONTENT_URI, false,
                  new ContentObserver(new Handler(looper)) {
                  @Override
                  public void onChange(boolean selfChange) {
                      updateNetworkConfig(context);
                  }
              });
      }
  
      /**
       * @return true if the given SSID is associated with a carrier network
       */
      public boolean isCarrierNetwork(String ssid) {
          return mCarrierNetworkMap.containsKey(ssid);
      }
  
      /**
       * @return the EAP type associated with a carrier AP, or -1 if the specified AP
       * is not associated with a carrier network
       */
      public int getNetworkEapType(String ssid) {
          NetworkInfo info = mCarrierNetworkMap.get(ssid);
          return info == null ? -1 : info.mEapType;
      }
  
      /**
       * @return the name of carrier associated with a carrier AP, or null if the specified AP
       * is not associated with a carrier network.
       */
      public String getCarrierName(String ssid) {
          NetworkInfo info = mCarrierNetworkMap.get(ssid);
          return info == null ? null : info.mCarrierName;
      }
  
      /**
       * @return True if carrier IMSI encryption info is available, False otherwise.
       */
      public boolean isCarrierEncryptionInfoAvailable() {
          return mIsCarrierImsiEncryptionInfoAvailable;
      }
  
      /**
       * Verify whether carrier IMSI encryption info is available.
       *
       * @param context Current application context
       *
       * @return True if carrier IMSI encryption info is available, False otherwise.
       */
      private boolean verifyCarrierImsiEncryptionInfoIsAvailable(Context context) {
          // TODO(b/132188983): Inject this using WifiInjector
          TelephonyManager telephonyManager =
                  (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
          if (telephonyManager == null) {
              return false;
          }
          try {
              mLastImsiEncryptionInfo = telephonyManager
                      .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId())
                      .getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN);
              if (mLastImsiEncryptionInfo == null) {
                  return false;
              }
          } catch (RuntimeException e) {
              Log.e(TAG, "Failed to get imsi encryption info: " + e.getMessage());
              return false;
          }
  
          return true;
      }
  
      /**
       * Utility class for storing carrier network information.
       */
      private static class NetworkInfo {
          final int mEapType;
          final String mCarrierName;
  
          NetworkInfo(int eapType, String carrierName) {
              mEapType = eapType;
              mCarrierName = carrierName;
          }
  
          @Override
          public String toString() {
              return new StringBuffer("NetworkInfo: eap=").append(mEapType).append(
                      ", carrier=").append(mCarrierName).toString();
          }
      }
  
      /**
       * Update the carrier network map based on the current carrier configuration of the active
       * subscriptions.
       *
       * @param context Current application context
       */
      private void updateNetworkConfig(Context context) {
          mIsCarrierImsiEncryptionInfoAvailable = verifyCarrierImsiEncryptionInfoIsAvailable(context);
  
          // Reset network map.
          mCarrierNetworkMap.clear();
  
          CarrierConfigManager carrierConfigManager =
                  (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
          if (carrierConfigManager == null) {
              return;
          }
  
          SubscriptionManager subscriptionManager = (SubscriptionManager) context.getSystemService(
                  Context.TELEPHONY_SUBSCRIPTION_SERVICE);
          if (subscriptionManager == null) {
              return;
          }
          List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
          if (subInfoList == null) {
              return;
          }
  
          // Process the carrier config for each active subscription.
          for (SubscriptionInfo subInfo : subInfoList) {
              CharSequence displayNameCs = subInfo.getDisplayName();
              String displayNameStr = displayNameCs == null ? "" : displayNameCs.toString();
              PersistableBundle bundle = carrierConfigManager.getConfigForSubId(
                      subInfo.getSubscriptionId());
              processNetworkConfig(bundle, displayNameStr);
          }
      }
  
      /**
       * Process the carrier network config, the network config string is formatted as follow:
       *
       * "[Base64 Encoded SSID],[EAP Type]"
       * Where EAP Type is the standard EAP method number, refer to
       * http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml for more info.
  
       * @param carrierConfig The bundle containing the carrier configuration
       * @param carrierName The display name of the associated carrier
       */
      private void processNetworkConfig(PersistableBundle carrierConfig, String carrierName) {
          if (carrierConfig == null) {
              return;
          }
          String[] networkConfigs = carrierConfig.getStringArray(
                  CarrierConfigManager.KEY_CARRIER_WIFI_STRING_ARRAY);
          if (mDbg) {
              Log.v(TAG, "processNetworkConfig: networkConfigs="
                      + Arrays.deepToString(networkConfigs));
          }
          if (networkConfigs == null) {
              return;
          }
  
          for (String networkConfig : networkConfigs) {
              String[] configArr = networkConfig.split(NETWORK_CONFIG_SEPARATOR);
              if (configArr.length != CONFIG_ELEMENT_SIZE) {
                  Log.e(TAG, "Ignore invalid config: " + networkConfig);
                  continue;
              }
              try {
  
                  String ssid = new String(Base64.decode(
                          configArr[ENCODED_SSID_INDEX], Base64.NO_WRAP));
                  int eapType = parseEapType(Integer.parseInt(configArr[EAP_TYPE_INDEX]));
  
                  // Verify EAP type, must be a SIM based EAP type.
                  if (eapType == -1) {
                      Log.e(TAG, "Invalid EAP type: " + configArr[EAP_TYPE_INDEX]);
                      continue;
                  }
                  mCarrierNetworkMap.put(ssid, new NetworkInfo(eapType, carrierName));
              } catch (NumberFormatException e) {
                  Log.e(TAG, "Failed to parse EAP type: '" + configArr[EAP_TYPE_INDEX] + "' "
                          + e.getMessage());
              } catch (IllegalArgumentException e) {
                  Log.e(TAG, "Failed to decode SSID: '" + configArr[ENCODED_SSID_INDEX] + "' "
                          + e.getMessage());
              }
          }
      }
  
      /**
       * Convert a standard SIM-based EAP type (SIM, AKA, AKA') to the internal EAP type as defined in
       * {@link WifiEnterpriseConfig.Eap}. -1 will be returned if the given EAP type is not
       * SIM-based.
       *
       * @return SIM-based EAP type as defined in {@link WifiEnterpriseConfig.Eap}, or -1 if not
       * SIM-based EAP type
       */
      private static int parseEapType(int eapType) {
          if (eapType == EAPConstants.EAP_SIM) {
              return WifiEnterpriseConfig.Eap.SIM;
          } else if (eapType == EAPConstants.EAP_AKA) {
              return WifiEnterpriseConfig.Eap.AKA;
          } else if (eapType == EAPConstants.EAP_AKA_PRIME) {
              return WifiEnterpriseConfig.Eap.AKA_PRIME;
          }
          return -1;
      }
  
      /** Dump state. */
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          pw.println(TAG + ": ");
          pw.println("mCarrierNetworkMap=" + mCarrierNetworkMap);
          pw.println("mIsCarrierImsiEncryptionInfoAvailable="
                  + mIsCarrierImsiEncryptionInfoAvailable);
          pw.println("mLastImsiEncryptionInfo=" + mLastImsiEncryptionInfo);
      }
  }
  