content: /*
   * Copyright (C) 2013 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;
  
  import android.annotation.UnsupportedAppUsage;
  import android.content.Context;
  import android.content.Intent;
  import android.os.AsyncResult;
  import android.os.Handler;
  import android.os.Message;
  import android.os.PowerManager;
  import android.os.PowerManager.WakeLock;
  import android.telephony.RadioAccessFamily;
  import android.telephony.Rlog;
  import android.telephony.TelephonyManager;
  import android.util.Log;
  
  import com.android.internal.telephony.ims.RcsMessageStoreController;
  import com.android.internal.telephony.uicc.UiccController;
  
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.Random;
  import java.util.concurrent.atomic.AtomicInteger;
  
  public class ProxyController {
      static final String LOG_TAG = "ProxyController";
  
      private static final int EVENT_NOTIFICATION_RC_CHANGED        = 1;
      private static final int EVENT_START_RC_RESPONSE        = 2;
      private static final int EVENT_APPLY_RC_RESPONSE        = 3;
      private static final int EVENT_FINISH_RC_RESPONSE       = 4;
      private static final int EVENT_TIMEOUT                  = 5;
  
      private static final int SET_RC_STATUS_IDLE             = 0;
      private static final int SET_RC_STATUS_STARTING         = 1;
      private static final int SET_RC_STATUS_STARTED          = 2;
      private static final int SET_RC_STATUS_APPLYING         = 3;
      private static final int SET_RC_STATUS_SUCCESS          = 4;
      private static final int SET_RC_STATUS_FAIL             = 5;
  
      // The entire transaction must complete within this amount of time
      // or a FINISH will be issued to each Logical Modem with the old
      // Radio Access Family.
      private static final int SET_RC_TIMEOUT_WAITING_MSEC    = (45 * 1000);
  
      //***** Class Variables
      @UnsupportedAppUsage
      private static ProxyController sProxyController;
  
      private Phone[] mPhones;
  
      private UiccController mUiccController;
  
      private CommandsInterface[] mCi;
  
      private Context mContext;
  
      private PhoneSwitcher mPhoneSwitcher;
  
      //UiccPhoneBookController to use proper IccPhoneBookInterfaceManagerProxy object
      private UiccPhoneBookController mUiccPhoneBookController;
  
      //PhoneSubInfoController to use proper PhoneSubInfoProxy object
      private PhoneSubInfoController mPhoneSubInfoController;
  
      //SmsController to use proper IccSmsInterfaceManager object
      private SmsController mSmsController;
  
      WakeLock mWakeLock;
  
      // record each phone's set radio capability status
      @UnsupportedAppUsage
      private int[] mSetRadioAccessFamilyStatus;
      private int mRadioAccessFamilyStatusCounter;
      private boolean mTransactionFailed = false;
  
      private String[] mCurrentLogicalModemIds;
      private String[] mNewLogicalModemIds;
  
      // Allows the generation of unique Id's for radio capability request session  id
      @UnsupportedAppUsage
      private AtomicInteger mUniqueIdGenerator = new AtomicInteger(new Random().nextInt());
  
      // on-going radio capability request session id
      @UnsupportedAppUsage
      private int mRadioCapabilitySessionId;
  
      // Record new and old Radio Access Family (raf) configuration.
      // The old raf configuration is used to restore each logical modem raf when FINISH is
      // issued if any requests fail.
      private int[] mNewRadioAccessFamily;
      @UnsupportedAppUsage
      private int[] mOldRadioAccessFamily;
  
  
      //***** Class Methods
      public static ProxyController getInstance(Context context, Phone[] phone,
              UiccController uiccController, CommandsInterface[] ci, PhoneSwitcher ps) {
          if (sProxyController == null) {
              sProxyController = new ProxyController(context, phone, uiccController, ci, ps);
          }
          return sProxyController;
      }
  
      @UnsupportedAppUsage
      public static ProxyController getInstance() {
          return sProxyController;
      }
  
      private ProxyController(Context context, Phone[] phone, UiccController uiccController,
              CommandsInterface[] ci, PhoneSwitcher phoneSwitcher) {
          logd("Constructor - Enter");
  
          mContext = context;
          mPhones = phone;
          mUiccController = uiccController;
          mCi = ci;
          mPhoneSwitcher = phoneSwitcher;
  
          RcsMessageStoreController.init(context);
  
          mUiccPhoneBookController = new UiccPhoneBookController(mPhones);
          mPhoneSubInfoController = new PhoneSubInfoController(mContext, mPhones);
          mSmsController = new SmsController(mContext);
          mSetRadioAccessFamilyStatus = new int[mPhones.length];
          mNewRadioAccessFamily = new int[mPhones.length];
          mOldRadioAccessFamily = new int[mPhones.length];
          mCurrentLogicalModemIds = new String[mPhones.length];
          mNewLogicalModemIds = new String[mPhones.length];
  
          // wake lock for set radio capability
          PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
          mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
          mWakeLock.setReferenceCounted(false);
  
          // Clear to be sure we're in the initial state
          clearTransaction();
          for (int i = 0; i < mPhones.length; i++) {
              mPhones[i].registerForRadioCapabilityChanged(
                      mHandler, EVENT_NOTIFICATION_RC_CHANGED, null);
          }
          logd("Constructor - Exit");
      }
  
      public void registerForAllDataDisconnected(int subId, Handler h, int what) {
          int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
  
          if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
              mPhones[phoneId].registerForAllDataDisconnected(h, what);
          }
      }
  
      public void unregisterForAllDataDisconnected(int subId, Handler h) {
          int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
  
          if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
              mPhones[phoneId].unregisterForAllDataDisconnected(h);
          }
      }
  
  
      public boolean areAllDataDisconnected(int subId) {
          int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
  
          if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
              return mPhones[phoneId].areAllDataDisconnected();
          } else {
              // if we can't find a phone for the given subId, it is disconnected.
              return true;
          }
      }
  
      /**
       * Get phone radio type and access technology.
       *
       * @param phoneId which phone you want to get
       * @return phone radio type and access technology for input phone ID
       */
      public int getRadioAccessFamily(int phoneId) {
          if (phoneId >= mPhones.length) {
              return RadioAccessFamily.RAF_UNKNOWN;
          } else {
              return mPhones[phoneId].getRadioAccessFamily();
          }
      }
  
      /**
       * Set phone radio type and access technology for each phone.
       *
       * @param rafs an RadioAccessFamily array to indicate all phone's
       *        new radio access family. The length of RadioAccessFamily
       *        must equal to phone count.
       * @return false if another session is already active and the request is rejected.
       */
      public boolean setRadioCapability(RadioAccessFamily[] rafs) {
          if (rafs.length != mPhones.length) {
              throw new RuntimeException("Length of input rafs must equal to total phone count");
          }
          // Check if there is any ongoing transaction and throw an exception if there
          // is one as this is a programming error.
          synchronized (mSetRadioAccessFamilyStatus) {
              for (int i = 0; i < mPhones.length; i++) {
                  if (mSetRadioAccessFamilyStatus[i] != SET_RC_STATUS_IDLE) {
                      // TODO: The right behaviour is to cancel previous request and send this.
                      loge("setRadioCapability: Phone[" + i + "] is not idle. Rejecting request.");
                      return false;
                  }
              }
          }
  
          // Check we actually need to do anything
          boolean same = true;
          for (int i = 0; i < mPhones.length; i++) {
              if (mPhones[i].getRadioAccessFamily() != rafs[i].getRadioAccessFamily()) {
                  same = false;
              }
          }
          if (same) {
              // All phones are already set to the requested raf
              logd("setRadioCapability: Already in requested configuration, nothing to do.");
              // It isn't really an error, so return true - everything is OK.
              return true;
          }
  
          // Clear to be sure we're in the initial state
          clearTransaction();
  
          // Keep a wake lock until we finish radio capability changed
          mWakeLock.acquire();
  
          return doSetRadioCapabilities(rafs);
      }
  
      /**
       * Get the SmsController.
       * @return the SmsController object.
       */
      public SmsController getSmsController() {
          return mSmsController;
      }
  
      private boolean doSetRadioCapabilities(RadioAccessFamily[] rafs) {
          // A new sessionId for this transaction
          mRadioCapabilitySessionId = mUniqueIdGenerator.getAndIncrement();
  
          // Start timer to make sure all phones respond within a specific time interval.
          // Will send FINISH if a timeout occurs.
          Message msg = mHandler.obtainMessage(EVENT_TIMEOUT, mRadioCapabilitySessionId, 0);
          mHandler.sendMessageDelayed(msg, SET_RC_TIMEOUT_WAITING_MSEC);
  
          synchronized (mSetRadioAccessFamilyStatus) {
              logd("setRadioCapability: new request session id=" + mRadioCapabilitySessionId);
              resetRadioAccessFamilyStatusCounter();
              for (int i = 0; i < rafs.length; i++) {
                  int phoneId = rafs[i].getPhoneId();
                  logd("setRadioCapability: phoneId=" + phoneId + " status=STARTING");
                  mSetRadioAccessFamilyStatus[phoneId] = SET_RC_STATUS_STARTING;
                  mOldRadioAccessFamily[phoneId] = mPhones[phoneId].getRadioAccessFamily();
                  int requestedRaf = rafs[i].getRadioAccessFamily();
                  // TODO Set the new radio access family to the maximum of the requested & supported
                  // int supportedRaf = mPhones[i].getRadioAccessFamily();
                  // mNewRadioAccessFamily[phoneId] = requestedRaf & supportedRaf;
                  mNewRadioAccessFamily[phoneId] = requestedRaf;
  
                  mCurrentLogicalModemIds[phoneId] = mPhones[phoneId].getModemUuId();
                  // get the logical mode corresponds to new raf requested and pass the
                  // same as part of SET_RADIO_CAP APPLY phase
                  mNewLogicalModemIds[phoneId] = getLogicalModemIdFromRaf(requestedRaf);
                  logd("setRadioCapability: mOldRadioAccessFamily[" + phoneId + "]="
                          + mOldRadioAccessFamily[phoneId]);
                  logd("setRadioCapability: mNewRadioAccessFamily[" + phoneId + "]="
                          + mNewRadioAccessFamily[phoneId]);
                  sendRadioCapabilityRequest(
                          phoneId,
                          mRadioCapabilitySessionId,
                          RadioCapability.RC_PHASE_START,
                          mOldRadioAccessFamily[phoneId],
                          mCurrentLogicalModemIds[phoneId],
                          RadioCapability.RC_STATUS_NONE,
                          EVENT_START_RC_RESPONSE);
              }
          }
  
          return true;
      }
  
      private Handler mHandler = new Handler() {
          @Override
          public void handleMessage(Message msg) {
              logd("handleMessage msg.what=" + msg.what);
              switch (msg.what) {
                  case EVENT_START_RC_RESPONSE:
                      onStartRadioCapabilityResponse(msg);
                      break;
  
                  case EVENT_APPLY_RC_RESPONSE:
                      onApplyRadioCapabilityResponse(msg);
                      break;
  
                  case EVENT_NOTIFICATION_RC_CHANGED:
                      onNotificationRadioCapabilityChanged(msg);
                      break;
  
                  case EVENT_FINISH_RC_RESPONSE:
                      onFinishRadioCapabilityResponse(msg);
                      break;
  
                  case EVENT_TIMEOUT:
                      onTimeoutRadioCapability(msg);
                      break;
  
                  default:
                      break;
              }
          }
      };
  
      /**
       * Handle START response
       * @param msg obj field isa RadioCapability
       */
      private void onStartRadioCapabilityResponse(Message msg) {
          synchronized (mSetRadioAccessFamilyStatus) {
              AsyncResult ar = (AsyncResult)msg.obj;
              // Abort here only in Single SIM case, in Multi SIM cases
              // send FINISH with failure so that below layers can re-bind
              // old logical modems.
              if ((TelephonyManager.getDefault().getPhoneCount() == 1) && (ar.exception != null)) {
                  // just abort now.  They didn't take our start so we don't have to revert
                  logd("onStartRadioCapabilityResponse got exception=" + ar.exception);
                  mRadioCapabilitySessionId = mUniqueIdGenerator.getAndIncrement();
                  Intent intent = new Intent(TelephonyIntents.ACTION_SET_RADIO_CAPABILITY_FAILED);
                  mContext.sendBroadcast(intent);
                  clearTransaction();
                  return;
              }
              RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result;
              if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) {
                  logd("onStartRadioCapabilityResponse: Ignore session=" + mRadioCapabilitySessionId
                          + " rc=" + rc);
                  return;
              }
              mRadioAccessFamilyStatusCounter--;
              int id = rc.getPhoneId();
              if (((AsyncResult) msg.obj).exception != null) {
                  logd("onStartRadioCapabilityResponse: Error response session=" + rc.getSession());
                  logd("onStartRadioCapabilityResponse: phoneId=" + id + " status=FAIL");
                  mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_FAIL;
                  mTransactionFailed = true;
              } else {
                  logd("onStartRadioCapabilityResponse: phoneId=" + id + " status=STARTED");
                  mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_STARTED;
              }
  
              if (mRadioAccessFamilyStatusCounter == 0) {
                  HashSet<String> modemsInUse = new HashSet<String>(mNewLogicalModemIds.length);
                  for (String modemId : mNewLogicalModemIds) {
                      if (!modemsInUse.add(modemId)) {
                          mTransactionFailed = true;
                          Log.wtf(LOG_TAG, "ERROR: sending down the same id for different phones");
                      }
                  }
                  logd("onStartRadioCapabilityResponse: success=" + !mTransactionFailed);
                  if (mTransactionFailed) {
                      // Sends a variable number of requests, so don't resetRadioAccessFamilyCounter
                      // here.
                      issueFinish(mRadioCapabilitySessionId);
                  } else {
                      // All logical modem accepted the new radio access family, issue the APPLY
                      resetRadioAccessFamilyStatusCounter();
                      for (int i = 0; i < mPhones.length; i++) {
                          sendRadioCapabilityRequest(
                              i,
                              mRadioCapabilitySessionId,
                              RadioCapability.RC_PHASE_APPLY,
                              mNewRadioAccessFamily[i],
                              mNewLogicalModemIds[i],
                              RadioCapability.RC_STATUS_NONE,
                              EVENT_APPLY_RC_RESPONSE);
  
                          logd("onStartRadioCapabilityResponse: phoneId=" + i + " status=APPLYING");
                          mSetRadioAccessFamilyStatus[i] = SET_RC_STATUS_APPLYING;
                      }
                  }
              }
          }
      }
  
      /**
       * Handle APPLY response
       * @param msg obj field isa RadioCapability
       */
      private void onApplyRadioCapabilityResponse(Message msg) {
          RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result;
          if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) {
              logd("onApplyRadioCapabilityResponse: Ignore session=" + mRadioCapabilitySessionId
                      + " rc=" + rc);
              return;
          }
          logd("onApplyRadioCapabilityResponse: rc=" + rc);
          if (((AsyncResult) msg.obj).exception != null) {
              synchronized (mSetRadioAccessFamilyStatus) {
                  logd("onApplyRadioCapabilityResponse: Error response session=" + rc.getSession());
                  int id = rc.getPhoneId();
                  logd("onApplyRadioCapabilityResponse: phoneId=" + id + " status=FAIL");
                  mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_FAIL;
                  mTransactionFailed = true;
              }
          } else {
              logd("onApplyRadioCapabilityResponse: Valid start expecting notification rc=" + rc);
          }
      }
  
      /**
       * Handle the notification unsolicited response associated with the APPLY
       * @param msg obj field isa RadioCapability
       */
      private void onNotificationRadioCapabilityChanged(Message msg) {
          RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result;
          if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) {
              logd("onNotificationRadioCapabilityChanged: Ignore session=" + mRadioCapabilitySessionId
                      + " rc=" + rc);
              return;
          }
          synchronized (mSetRadioAccessFamilyStatus) {
              logd("onNotificationRadioCapabilityChanged: rc=" + rc);
              // skip the overdue response by checking sessionId
              if (rc.getSession() != mRadioCapabilitySessionId) {
                  logd("onNotificationRadioCapabilityChanged: Ignore session="
                          + mRadioCapabilitySessionId + " rc=" + rc);
                  return;
              }
  
              int id = rc.getPhoneId();
              if ((((AsyncResult) msg.obj).exception != null) ||
                      (rc.getStatus() == RadioCapability.RC_STATUS_FAIL)) {
                  logd("onNotificationRadioCapabilityChanged: phoneId=" + id + " status=FAIL");
                  mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_FAIL;
                  mTransactionFailed = true;
              } else {
                  logd("onNotificationRadioCapabilityChanged: phoneId=" + id + " status=SUCCESS");
                  mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_SUCCESS;
                  // The modems may have been restarted and forgotten this
                  mPhoneSwitcher.onRadioCapChanged(id);
                  mPhones[id].radioCapabilityUpdated(rc);
              }
  
              mRadioAccessFamilyStatusCounter--;
              if (mRadioAccessFamilyStatusCounter == 0) {
                  logd("onNotificationRadioCapabilityChanged: APPLY URC success=" +
                          mTransactionFailed);
                  issueFinish(mRadioCapabilitySessionId);
              }
          }
      }
  
      /**
       * Handle the FINISH Phase response
       * @param msg obj field isa RadioCapability
       */
      void onFinishRadioCapabilityResponse(Message msg) {
          RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result;
          if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) {
              logd("onFinishRadioCapabilityResponse: Ignore session=" + mRadioCapabilitySessionId
                      + " rc=" + rc);
              return;
          }
          synchronized (mSetRadioAccessFamilyStatus) {
              logd(" onFinishRadioCapabilityResponse mRadioAccessFamilyStatusCounter="
                      + mRadioAccessFamilyStatusCounter);
              mRadioAccessFamilyStatusCounter--;
              if (mRadioAccessFamilyStatusCounter == 0) {
                  completeRadioCapabilityTransaction();
              }
          }
      }
  
      private void onTimeoutRadioCapability(Message msg) {
          if (msg.arg1 != mRadioCapabilitySessionId) {
             logd("RadioCapability timeout: Ignore msg.arg1=" + msg.arg1 +
                     "!= mRadioCapabilitySessionId=" + mRadioCapabilitySessionId);
              return;
          }
  
          synchronized(mSetRadioAccessFamilyStatus) {
              // timed-out.  Clean up as best we can
              for (int i = 0; i < mPhones.length; i++) {
                  logd("RadioCapability timeout: mSetRadioAccessFamilyStatus[" + i + "]=" +
                          mSetRadioAccessFamilyStatus[i]);
              }
  
              // Increment the sessionId as we are completing the transaction below
              // so we don't want it completed when the FINISH phase is done.
              mRadioCapabilitySessionId = mUniqueIdGenerator.getAndIncrement();
  
              // Reset the status counter as existing session failed
              mRadioAccessFamilyStatusCounter = 0;
  
              // send FINISH request with fail status and then uniqueDifferentId
              mTransactionFailed = true;
              issueFinish(mRadioCapabilitySessionId);
          }
      }
  
      private void issueFinish(int sessionId) {
          // Issue FINISH
          synchronized(mSetRadioAccessFamilyStatus) {
              for (int i = 0; i < mPhones.length; i++) {
                  logd("issueFinish: phoneId=" + i + " sessionId=" + sessionId
                          + " mTransactionFailed=" + mTransactionFailed);
                  mRadioAccessFamilyStatusCounter++;
                  sendRadioCapabilityRequest(
                          i,
                          sessionId,
                          RadioCapability.RC_PHASE_FINISH,
                          (mTransactionFailed ? mOldRadioAccessFamily[i] :
                          mNewRadioAccessFamily[i]),
                          (mTransactionFailed ? mCurrentLogicalModemIds[i] :
                          mNewLogicalModemIds[i]),
                          (mTransactionFailed ? RadioCapability.RC_STATUS_FAIL :
                          RadioCapability.RC_STATUS_SUCCESS),
                          EVENT_FINISH_RC_RESPONSE);
                  if (mTransactionFailed) {
                      logd("issueFinish: phoneId: " + i + " status: FAIL");
                      // At least one failed, mark them all failed.
                      mSetRadioAccessFamilyStatus[i] = SET_RC_STATUS_FAIL;
                  }
              }
          }
      }
  
      @UnsupportedAppUsage
      private void completeRadioCapabilityTransaction() {
          // Create the intent to broadcast
          Intent intent;
          logd("onFinishRadioCapabilityResponse: success=" + !mTransactionFailed);
          if (!mTransactionFailed) {
              ArrayList<RadioAccessFamily> phoneRAFList = new ArrayList<RadioAccessFamily>();
              for (int i = 0; i < mPhones.length; i++) {
                  int raf = mPhones[i].getRadioAccessFamily();
                  logd("radioAccessFamily[" + i + "]=" + raf);
                  RadioAccessFamily phoneRC = new RadioAccessFamily(i, raf);
                  phoneRAFList.add(phoneRC);
              }
              intent = new Intent(TelephonyIntents.ACTION_SET_RADIO_CAPABILITY_DONE);
              intent.putParcelableArrayListExtra(TelephonyIntents.EXTRA_RADIO_ACCESS_FAMILY,
                      phoneRAFList);
  
              // make messages about the old transaction obsolete (specifically the timeout)
              mRadioCapabilitySessionId = mUniqueIdGenerator.getAndIncrement();
  
              // Reinitialize
              clearTransaction();
          } else {
              intent = new Intent(TelephonyIntents.ACTION_SET_RADIO_CAPABILITY_FAILED);
  
              // now revert.
              mTransactionFailed = false;
              RadioAccessFamily[] rafs = new RadioAccessFamily[mPhones.length];
              for (int phoneId = 0; phoneId < mPhones.length; phoneId++) {
                  rafs[phoneId] = new RadioAccessFamily(phoneId, mOldRadioAccessFamily[phoneId]);
              }
              doSetRadioCapabilities(rafs);
          }
  
          // Broadcast that we're done
          mContext.sendBroadcast(intent, android.Manifest.permission.READ_PHONE_STATE);
      }
  
      // Clear this transaction
      private void clearTransaction() {
          logd("clearTransaction");
          synchronized(mSetRadioAccessFamilyStatus) {
              for (int i = 0; i < mPhones.length; i++) {
                  logd("clearTransaction: phoneId=" + i + " status=IDLE");
                  mSetRadioAccessFamilyStatus[i] = SET_RC_STATUS_IDLE;
                  mOldRadioAccessFamily[i] = 0;
                  mNewRadioAccessFamily[i] = 0;
                  mTransactionFailed = false;
              }
  
              if (mWakeLock.isHeld()) {
                  mWakeLock.release();
              }
          }
      }
  
      private void resetRadioAccessFamilyStatusCounter() {
          mRadioAccessFamilyStatusCounter = mPhones.length;
      }
  
      @UnsupportedAppUsage
      private void sendRadioCapabilityRequest(int phoneId, int sessionId, int rcPhase,
              int radioFamily, String logicalModemId, int status, int eventId) {
          RadioCapability requestRC = new RadioCapability(
                  phoneId, sessionId, rcPhase, radioFamily, logicalModemId, status);
          mPhones[phoneId].setRadioCapability(
                  requestRC, mHandler.obtainMessage(eventId));
      }
  
      // This method will return max number of raf bits supported from the raf
      // values currently stored in all phone objects
      public int getMaxRafSupported() {
          int[] numRafSupported = new int[mPhones.length];
          int maxNumRafBit = 0;
          int maxRaf = RadioAccessFamily.RAF_UNKNOWN;
  
          for (int len = 0; len < mPhones.length; len++) {
              numRafSupported[len] = Integer.bitCount(mPhones[len].getRadioAccessFamily());
              if (maxNumRafBit < numRafSupported[len]) {
                  maxNumRafBit = numRafSupported[len];
                  maxRaf = mPhones[len].getRadioAccessFamily();
              }
          }
  
          return maxRaf;
      }
  
      // This method will return minimum number of raf bits supported from the raf
      // values currently stored in all phone objects
      public int getMinRafSupported() {
          int[] numRafSupported = new int[mPhones.length];
          int minNumRafBit = 0;
          int minRaf = RadioAccessFamily.RAF_UNKNOWN;
  
          for (int len = 0; len < mPhones.length; len++) {
              numRafSupported[len] = Integer.bitCount(mPhones[len].getRadioAccessFamily());
              if ((minNumRafBit == 0) || (minNumRafBit > numRafSupported[len])) {
                  minNumRafBit = numRafSupported[len];
                  minRaf = mPhones[len].getRadioAccessFamily();
              }
          }
          return minRaf;
      }
  
      // This method checks current raf values stored in all phones and
      // whicheve phone raf matches with input raf, returns modemId from that phone
      private String getLogicalModemIdFromRaf(int raf) {
          String modemUuid = null;
  
          for (int phoneId = 0; phoneId < mPhones.length; phoneId++) {
              if (mPhones[phoneId].getRadioAccessFamily() == raf) {
                  modemUuid = mPhones[phoneId].getModemUuId();
                  break;
              }
          }
          return modemUuid;
      }
  
      @UnsupportedAppUsage
      private void logd(String string) {
          Rlog.d(LOG_TAG, string);
      }
  
      private void loge(String string) {
          Rlog.e(LOG_TAG, string);
      }
  }
  