/* Intentionally private for singleton */
  private CatService(
      CommandsInterface ci,
      UiccCardApplication ca,
      IccRecords ir,
      Context context,
      IccFileHandler fh,
      UiccCard ic) {
    if (ci == null || ca == null || ir == null || context == null || fh == null || ic == null) {
      throw new NullPointerException("Service: Input parameters must not be null");
    }
    mCmdIf = ci;
    mContext = context;

    // Get the RilMessagesDecoder for decoding the messages.
    mMsgDecoder = RilMessageDecoder.getInstance(this, fh);

    // Register ril events handling.
    mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null);
    mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);
    mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null);
    mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null);
    // mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null);

    mIccRecords = ir;
    mUiccApplication = ca;

    // Register for SIM ready event.
    mUiccApplication.registerForReady(this, MSG_ID_SIM_READY, null);
    mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);

    // Check if STK application is availalbe
    mStkAppInstalled = isStkAppInstalled();

    CatLog.d(this, "Running CAT service. STK app installed:" + mStkAppInstalled);
  }
  /**
   * Used for instantiating/updating the Service from the GsmPhone or CdmaPhone constructor.
   *
   * @param ci CommandsInterface object
   * @param ir IccRecords object
   * @param context phone app context
   * @param fh Icc file handler
   * @param ic Icc card
   * @return The only Service object in the system
   */
  public static CatService getInstance(CommandsInterface ci, Context context, UiccCard ic) {
    UiccCardApplication ca = null;
    IccFileHandler fh = null;
    IccRecords ir = null;
    if (ic != null) {
      /* Since Cat is not tied to any application, but rather is Uicc application
       * in itself - just get first FileHandler and IccRecords object
       */
      ca = ic.getApplicationIndex(0);
      if (ca != null) {
        fh = ca.getIccFileHandler();
        ir = ca.getIccRecords();
      }
    }
    synchronized (sInstanceLock) {
      if (sInstance == null) {
        if (ci == null || ca == null || ir == null || context == null || fh == null || ic == null) {
          return null;
        }
        HandlerThread thread = new HandlerThread("Cat Telephony service");
        thread.start();
        sInstance = new CatService(ci, ca, ir, context, fh, ic);
        CatLog.d(sInstance, "NEW sInstance");
      } else if ((ir != null) && (mIccRecords != ir)) {
        if (mIccRecords != null) {
          mIccRecords.unregisterForRecordsLoaded(sInstance);
        }

        if (mUiccApplication != null) {
          mUiccApplication.unregisterForReady(sInstance);
        }
        CatLog.d(sInstance, "Reinitialize the Service with SIMRecords and UiccCardApplication");
        mIccRecords = ir;
        mUiccApplication = ca;

        // re-Register for SIM ready event.
        mIccRecords.registerForRecordsLoaded(sInstance, MSG_ID_ICC_RECORDS_LOADED, null);
        mUiccApplication.registerForReady(sInstance, MSG_ID_SIM_READY, null);
        CatLog.d(sInstance, "sr changed reinitialize and return current sInstance");
      } else {
        CatLog.d(sInstance, "Return current sInstance");
      }
      return sInstance;
    }
  }
  public void dispose() {
    mIccRecords.unregisterForRecordsLoaded(this);
    mCmdIf.unSetOnCatSessionEnd(this);
    mCmdIf.unSetOnCatProactiveCmd(this);
    mCmdIf.unSetOnCatEvent(this);
    mCmdIf.unSetOnCatCallSetUp(this);

    this.removeCallbacksAndMessages(null);
  }
  private void handleSimLoaded(int slotId) {
    logd("handleSimStateLoadedInternal: slotId: " + slotId);

    // The SIM should be loaded at this state, but it is possible in cases such as SIM being
    // removed or a refresh RESET that the IccRecords could be null. The right behavior is to
    // not broadcast the SIM loaded.
    IccRecords records = mPhone[slotId].getIccCard().getIccRecords();
    if (records == null) { // Possibly a race condition.
      logd("onRecieve: IccRecords null");
      return;
    }
    if (records.getIccId() == null) {
      logd("onRecieve: IccID null");
      return;
    }
    mIccId[slotId] = records.getIccId();

    if (isAllIccIdQueryDone()) {
      updateSubscriptionInfoByIccId();
    }

    int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
    int[] subIds = SubscriptionController.getInstance().getSubId(slotId);
    if (subIds != null) { // Why an array?
      subId = subIds[0];
    }

    if (SubscriptionManager.isValidSubscriptionId(subId)) {
      String operator = records.getOperatorNumeric();
      if (operator != null) {
        if (subId == SubscriptionController.getInstance().getDefaultSubId()) {
          MccTable.updateMccMncConfiguration(mContext, operator, false);
        }
        SubscriptionController.getInstance().setMccMnc(operator, subId);
      } else {
        logd("EVENT_RECORDS_LOADED Operator name is null");
      }
      TelephonyManager tm = TelephonyManager.getDefault();
      String msisdn = tm.getLine1NumberForSubscriber(subId);
      ContentResolver contentResolver = mContext.getContentResolver();

      if (msisdn != null) {
        ContentValues number = new ContentValues(1);
        number.put(SubscriptionManager.NUMBER, msisdn);
        contentResolver.update(
            SubscriptionManager.CONTENT_URI,
            number,
            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId),
            null);
      }

      SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
      String nameToSet;
      String simCarrierName = tm.getSimOperatorNameForSubscription(subId);
      ContentValues name = new ContentValues(1);

      if (subInfo != null
          && subInfo.getNameSource() != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
        if (!TextUtils.isEmpty(simCarrierName)) {
          nameToSet = simCarrierName;
        } else {
          nameToSet = "CARD " + Integer.toString(slotId + 1);
        }
        name.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
        logd("sim name = " + nameToSet);
        contentResolver.update(
            SubscriptionManager.CONTENT_URI,
            name,
            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId),
            null);
      }

      /* Update preferred network type and network selection mode on SIM change.
       * Storing last subId in SharedPreference for now to detect SIM change. */
      SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
      int storedSubId = sp.getInt(CURR_SUBID + slotId, -1);

      if (storedSubId != subId) {
        int networkType = RILConstants.PREFERRED_NETWORK_MODE;

        // Set the modem network mode
        mPhone[slotId].setPreferredNetworkType(networkType, null);
        Settings.Global.putInt(
            mPhone[slotId].getContext().getContentResolver(),
            Settings.Global.PREFERRED_NETWORK_MODE + subId,
            networkType);

        // Only support automatic selection mode on SIM change.
        mPhone[slotId].getNetworkSelectionMode(
            obtainMessage(EVENT_GET_NETWORK_SELECTION_MODE_DONE, new Integer(slotId)));

        // Update stored subId
        SharedPreferences.Editor editor = sp.edit();
        editor.putInt(CURR_SUBID + slotId, subId);
        editor.apply();
      }
    } else {
      logd("Invalid subId, could not update ContentResolver");
    }

    // Update set of enabled carrier apps now that the privilege rules may have changed.
    CarrierAppUtils.disableCarrierAppsUntilPrivileged(
        mContext.getOpPackageName(),
        mPackageManager,
        TelephonyManager.getDefault(),
        mCurrentlyActiveUserId);

    broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
    updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
  }