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.server.wifi;
  
  import static android.telephony.TelephonyManager.NETWORK_TYPE_CDMA;
  import static android.telephony.TelephonyManager.NETWORK_TYPE_EVDO_0;
  import static android.telephony.TelephonyManager.NETWORK_TYPE_GSM;
  import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
  import static android.telephony.TelephonyManager.NETWORK_TYPE_NR;
  import static android.telephony.TelephonyManager.NETWORK_TYPE_TD_SCDMA;
  import static android.telephony.TelephonyManager.NETWORK_TYPE_UMTS;
  import static android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN;
  
  import android.content.Context;
  import android.telephony.CellInfo;
  import android.telephony.CellInfoCdma;
  import android.telephony.CellInfoGsm;
  import android.telephony.CellInfoLte;
  import android.telephony.CellInfoNr;
  import android.telephony.CellInfoTdscdma;
  import android.telephony.CellInfoWcdma;
  import android.telephony.CellSignalStrength;
  import android.telephony.CellSignalStrengthCdma;
  import android.telephony.CellSignalStrengthGsm;
  import android.telephony.CellSignalStrengthLte;
  import android.telephony.CellSignalStrengthNr;
  import android.telephony.CellSignalStrengthTdscdma;
  import android.telephony.CellSignalStrengthWcdma;
  import android.telephony.SignalStrength;
  import android.telephony.SubscriptionManager;
  import android.telephony.TelephonyManager;
  import android.util.Log;
  
  import java.util.ArrayList;
  import java.util.List;
  /**
   * A class collecting the latest cellular link layer stats
   */
  public class CellularLinkLayerStatsCollector {
      private static final String TAG = "CellStatsCollector";
      private static final boolean DBG = false;
  
      private Context mContext;
      private SubscriptionManager mSubManager = null;
      private TelephonyManager mCachedDefaultDataTelephonyManager = null;
      private int mCachedDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
      private CellInfo mLastPrimaryCellInfo = null;
      private int mLastDataNetworkType = NETWORK_TYPE_UNKNOWN;
  
      public CellularLinkLayerStatsCollector(Context context) {
          mContext = context;
      }
  
      /**
       * Get the latest DataNetworkType, SignalStrength, CellInfo and other information from
       * default data sim's TelephonyManager, parse the values of primary registered cell and return
       * them as an instance of CellularLinkLayerStats
       */
      public CellularLinkLayerStats update() {
          CellularLinkLayerStats cellStats = new CellularLinkLayerStats();
  
          retrieveDefaultDataTelephonyManager();
          if (mCachedDefaultDataTelephonyManager == null) {
              if (DBG) Log.v(TAG, cellStats.toString());
              return cellStats;
          }
  
          SignalStrength signalStrength = mCachedDefaultDataTelephonyManager.getSignalStrength();
          List<CellSignalStrength> cssList = null;
          if (signalStrength != null) cssList = signalStrength.getCellSignalStrengths();
  
          if (mCachedDefaultDataTelephonyManager.getDataNetworkType() == NETWORK_TYPE_UNKNOWN
                  || cssList == null || cssList.size() == 0) {
              mLastPrimaryCellInfo = null;
              mLastDataNetworkType = NETWORK_TYPE_UNKNOWN;
              if (DBG) Log.v(TAG, cellStats.toString());
              return cellStats;
          }
          if (DBG) Log.v(TAG, "Cell Signal Strength List size = " + cssList.size());
  
          CellSignalStrength primaryCss = cssList.get(0);
          cellStats.setSignalStrengthDbm(primaryCss.getDbm());
  
          updateSignalStrengthDbAndNetworkTypeOfCellStats(primaryCss, cellStats);
  
          int networkType = cellStats.getDataNetworkType();
          CellInfo primaryCellInfo = getPrimaryCellInfo(mCachedDefaultDataTelephonyManager,
                  networkType);
          boolean isSameRegisteredCell = getIsSameRegisteredCell(primaryCellInfo, networkType);
          cellStats.setIsSameRegisteredCell(isSameRegisteredCell);
  
          // Update for the next call
          mLastPrimaryCellInfo = primaryCellInfo;
          mLastDataNetworkType = networkType;
  
          if (DBG) Log.v(TAG, cellStats.toString());
          return cellStats;
      }
  
      private void retrieveDefaultDataTelephonyManager() {
          if (!initSubManager()) return;
  
          int defaultDataSubId = mSubManager.getDefaultDataSubscriptionId();
          if (DBG) Log.v(TAG, "default Data Sub ID = " + defaultDataSubId);
          if (defaultDataSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
              mCachedDefaultDataTelephonyManager = null;
              return;
          }
  
          if (defaultDataSubId != mCachedDefaultDataSubId
                  || mCachedDefaultDataTelephonyManager == null) {
              mCachedDefaultDataSubId = defaultDataSubId;
              // TODO(b/132188983): Inject this using WifiInjector
              TelephonyManager defaultSubTelephonyManager = (TelephonyManager) mContext
                      .getSystemService(Context.TELEPHONY_SERVICE);
              if (defaultDataSubId == mSubManager.getDefaultSubscriptionId()) {
                  mCachedDefaultDataTelephonyManager = defaultSubTelephonyManager;
              } else {
                  mCachedDefaultDataTelephonyManager =  defaultSubTelephonyManager
                          .createForSubscriptionId(defaultDataSubId);
              }
          }
      }
  
      private boolean initSubManager() {
          if (mSubManager == null) {
              mSubManager = (SubscriptionManager) mContext.getSystemService(
                  Context.TELEPHONY_SUBSCRIPTION_SERVICE);
          }
          return (mSubManager != null);
      }
  
      /**
        * Update dB value and network type base on CellSignalStrength subclass type.
        * It follows the same order as that in SignalStrength.getPrimary().
        * TODO: NR may move up in the future
        */
      private void updateSignalStrengthDbAndNetworkTypeOfCellStats(CellSignalStrength primaryCss,
              CellularLinkLayerStats cellStats) {
          if (primaryCss instanceof CellSignalStrengthLte) {
              CellSignalStrengthLte cssLte = (CellSignalStrengthLte) primaryCss;
              cellStats.setSignalStrengthDb(cssLte.getRsrq());
              cellStats.setDataNetworkType(NETWORK_TYPE_LTE);
          } else if (primaryCss instanceof CellSignalStrengthCdma) {
              CellSignalStrengthCdma cssCdma = (CellSignalStrengthCdma) primaryCss;
              int evdoSnr = cssCdma.getEvdoSnr();
              int cdmaEcio = cssCdma.getCdmaEcio();
              if (evdoSnr != SignalStrength.INVALID) {
                  cellStats.setSignalStrengthDb(evdoSnr);
                  cellStats.setDataNetworkType(NETWORK_TYPE_EVDO_0);
              } else {
                  cellStats.setSignalStrengthDb(cdmaEcio);
                  cellStats.setDataNetworkType(NETWORK_TYPE_CDMA);
              }
          } else if (primaryCss instanceof CellSignalStrengthTdscdma) {
              cellStats.setDataNetworkType(NETWORK_TYPE_TD_SCDMA);
          } else if (primaryCss instanceof CellSignalStrengthWcdma) {
              CellSignalStrengthWcdma cssWcdma = (CellSignalStrengthWcdma) primaryCss;
              cellStats.setSignalStrengthDb(cssWcdma.getEcNo());
              cellStats.setDataNetworkType(NETWORK_TYPE_UMTS);
          } else if (primaryCss instanceof CellSignalStrengthGsm) {
              cellStats.setDataNetworkType(NETWORK_TYPE_GSM);
          } else if (primaryCss instanceof CellSignalStrengthNr) {
              CellSignalStrengthNr cssNr = (CellSignalStrengthNr) primaryCss;
              cellStats.setSignalStrengthDb(cssNr.getCsiSinr());
              cellStats.setDataNetworkType(NETWORK_TYPE_NR);
          } else {
              Log.e(TAG, "invalid CellSignalStrength");
          }
      }
  
      private CellInfo getPrimaryCellInfo(TelephonyManager defaultDataTelephonyManager,
                  int networkType) {
          List<CellInfo> cellInfoList = getRegisteredCellInfo(defaultDataTelephonyManager);
          int cilSize = cellInfoList.size();
          CellInfo primaryCellInfo = null;
          // CellInfo.getCellConnectionStatus() should tell if it is primary serving cell.
          // However, it currently always returns 0 (CONNECTION_NONE) for registered cells.
          // Therefore, the workaround of deriving primary serving cell is
          // to check if the registered cellInfo subclass type matches networkType
          for (int i = 0; i < cilSize; ++i) {
              CellInfo cellInfo = cellInfoList.get(i);
              if ((cellInfo instanceof CellInfoTdscdma && networkType == NETWORK_TYPE_TD_SCDMA)
                      || (cellInfo instanceof CellInfoCdma && (networkType == NETWORK_TYPE_CDMA
                      || networkType == NETWORK_TYPE_EVDO_0))
                      || (cellInfo instanceof CellInfoLte && networkType == NETWORK_TYPE_LTE)
                      || (cellInfo instanceof CellInfoWcdma && networkType == NETWORK_TYPE_UMTS)
                      || (cellInfo instanceof CellInfoGsm && networkType == NETWORK_TYPE_GSM)
                      || (cellInfo instanceof CellInfoNr && networkType == NETWORK_TYPE_NR)) {
                  primaryCellInfo = cellInfo;
              }
          }
          return primaryCellInfo;
      }
  
      private boolean getIsSameRegisteredCell(CellInfo primaryCellInfo, int networkType) {
          boolean isSameRegisteredCell;
          if (primaryCellInfo != null && mLastPrimaryCellInfo != null) {
              isSameRegisteredCell = primaryCellInfo.getCellIdentity()
                  .equals(mLastPrimaryCellInfo.getCellIdentity());
          } else if (primaryCellInfo == null && mLastPrimaryCellInfo == null) {
              // This is a workaround when it can't find primaryCellInfo for two consecutive times.
              isSameRegisteredCell = true;
          } else {
              // only one of them is null and it is a strong indication of primary cell change.
              isSameRegisteredCell = false;
          }
  
          if (mLastDataNetworkType == NETWORK_TYPE_UNKNOWN || mLastDataNetworkType != networkType) {
              isSameRegisteredCell = false;
          }
          return isSameRegisteredCell;
      }
  
      private List<CellInfo> getRegisteredCellInfo(TelephonyManager defaultDataTelephonyManager) {
          List<CellInfo> allList = defaultDataTelephonyManager.getAllCellInfo();
          List<CellInfo> cellInfoList = new ArrayList<>();
          for (CellInfo ci : allList) {
              if (ci.isRegistered()) cellInfoList.add(ci);
              if (DBG) Log.v(TAG, ci.toString());
          }
          return cellInfoList;
      }
  }
  