/**
   * Some dial strings in GSM are defined to do non-call setup things, such as modify or query
   * supplementary service settings (eg, call forwarding). These are generally referred to as "MMI
   * codes". We look to see if the dial string contains a valid MMI code (potentially with a dial
   * string at the end as well) and return info here.
   *
   * <p>If the dial string contains no MMI code, we return an instance with only "dialingNumber" set
   *
   * <p>Please see flow chart in TS 22.030 6.5.3.2
   */
  static GsmMmiCode newFromDialString(String dialString, GSMPhone phone, UiccCardApplication app) {
    Matcher m;
    GsmMmiCode ret = null;

    if (SystemProperties.getBoolean("ro.config.multimode_cdma", false)) {
      m = sPatternSuppServiceGlobalDev.matcher(dialString);
      if (m.matches()) {
        ret = new GsmMmiCode(phone, app);
        ret.action = makeEmptyNull(m.group(MATCH_GROUP_ACTION));
        String DialCode = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
        if (DialCode.equals(SC_GLOBALDEV_VM)) {
          ret.sc = SC_GLOBALDEV_VM;
          ret.dialingNumber = "+1" + phone.getMdn();
          return ret;
        } else if (DialCode.equals(SC_GLOBALDEV_CS)) {
          ret.sc = SC_GLOBALDEV_CS;
          ret.dialingNumber = GLOBALDEV_CS;
          return ret;
        } else if (DialCode.length() >= 3 && DialCode.startsWith(SC_GLOBALDEV_CLIR_INVK)) {
          // Dial "#31#PhoneNum" to invoke CLIR temporarily
          dialString = ACTION_DEACTIVATE + SC_CLIR + ACTION_DEACTIVATE + DialCode.substring(2);
        } else if (DialCode.length() >= 3 && DialCode.startsWith(SC_GLOBALDEV_CLIR_SUPP)) {
          // Dial "*31#PhoneNum" to suppress CLIR temporarily
          dialString = ACTION_ACTIVATE + SC_CLIR + ACTION_DEACTIVATE + DialCode.substring(2);
        }
      }
    }

    m = sPatternSuppService.matcher(dialString);

    // Is this formatted like a standard supplementary service code?
    if (m.matches()) {
      ret = new GsmMmiCode(phone, app);
      ret.poundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING));
      ret.action = makeEmptyNull(m.group(MATCH_GROUP_ACTION));
      ret.sc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
      ret.sia = makeEmptyNull(m.group(MATCH_GROUP_SIA));
      ret.sib = makeEmptyNull(m.group(MATCH_GROUP_SIB));
      ret.sic = makeEmptyNull(m.group(MATCH_GROUP_SIC));
      ret.pwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM));
      ret.dialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER));

    } else if (dialString.endsWith("#")) {
      // TS 22.030 sec 6.5.3.2
      // "Entry of any characters defined in the 3GPP TS 23.038 [8] Default Alphabet
      // (up to the maximum defined in 3GPP TS 24.080 [10]), followed by #SEND".

      ret = new GsmMmiCode(phone, app);
      ret.poundString = dialString;
    } else if (isTwoDigitShortCode(phone.getContext(), dialString)) {
      // Is a country-specific exception to short codes as defined in TS 22.030, 6.5.3.2
      ret = null;
    } else if (isShortCode(dialString, phone)) {
      // this may be a short code, as defined in TS 22.030, 6.5.3.2
      ret = new GsmMmiCode(phone, app);
      ret.dialingNumber = dialString;
    }

    return ret;
  }
 GsmMmiCode(GSMPhone phone, UiccCardApplication app) {
   // The telephony unit-test cases may create GsmMmiCode's
   // in secondary threads
   super(phone.getHandler().getLooper());
   this.phone = phone;
   this.context = phone.getContext();
   mUiccApp = app;
 }
  /** clirMode is one of the CLIR_ constants */
  synchronized Connection dial(String dialString, int clirMode, UUSInfo uusInfo)
      throws CallStateException {
    // note that this triggers call state changed notif
    clearDisconnected();

    if (!canDial()) {
      throw new CallStateException("cannot dial in current state");
    }

    // The new call must be assigned to the foreground call.
    // That call must be idle, so place anything that's
    // there on hold
    if (foregroundCall.getState() == GsmCall.State.ACTIVE) {
      // this will probably be done by the radio anyway
      // but the dial might fail before this happens
      // and we need to make sure the foreground call is clear
      // for the newly dialed connection
      switchWaitingOrHoldingAndActive();

      // Fake local state so that
      // a) foregroundCall is empty for the newly dialed connection
      // b) hasNonHangupStateChanged remains false in the
      // next poll, so that we don't clear a failed dialing call
      fakeHoldForegroundBeforeDial();
    }

    if (foregroundCall.getState() != GsmCall.State.IDLE) {
      // we should have failed in !canDial() above before we get here
      throw new CallStateException("cannot dial in current state");
    }

    pendingMO =
        new GsmConnection(
            phone.getContext(), checkForTestEmergencyNumber(dialString), this, foregroundCall);
    hangupPendingMO = false;

    if (pendingMO.address == null
        || pendingMO.address.length() == 0
        || pendingMO.address.indexOf(PhoneNumberUtils.WILD) >= 0) {
      // Phone number is invalid
      pendingMO.cause = Connection.DisconnectCause.INVALID_NUMBER;

      // handlePollCalls() will notice this call not present
      // and will mark it as dropped.
      pollCallsWhenSafe();
    } else {
      // Always unmute when initiating a new call
      setMute(false);

      cm.dial(pendingMO.address, clirMode, uusInfo, obtainCompleteMessage());
    }

    updatePhoneState();
    phone.notifyPreciseCallStateChanged();

    return pendingMO;
  }
  private void onQueryCfComplete(AsyncResult ar) {
    StringBuilder sb = new StringBuilder(getScString());
    sb.append("\n");

    if (ar.exception != null) {
      state = State.FAILED;
      sb.append(getErrorMessage(ar));
    } else {
      CallForwardInfo infos[];

      infos = (CallForwardInfo[]) ar.result;

      if (infos.length == 0) {
        // Assume the default is not active
        sb.append(context.getText(com.android.internal.R.string.serviceDisabled));

        // Set unconditional CFF in SIM to false
        if (phone.mSIMRecords != null) {
          phone.setCallForwardingPreference(false);
          phone.mSIMRecords.setVoiceCallForwardingFlag(1, false);
        } else {
          Log.w(LOG_TAG, "setVoiceCallForwardingFlag aborted. sim records is null.");
        }
      } else {

        SpannableStringBuilder tb = new SpannableStringBuilder();

        // Each bit in the service class gets its own result line
        // The service classes may be split up over multiple
        // CallForwardInfos. So, for each service class, find out
        // which CallForwardInfo represents it and then build
        // the response text based on that

        for (int serviceClassMask = 1;
            serviceClassMask <= SERVICE_CLASS_MAX;
            serviceClassMask <<= 1) {
          for (int i = 0, s = infos.length; i < s; i++) {
            if ((serviceClassMask & infos[i].serviceClass) != 0) {
              tb.append(makeCFQueryResultMessage(infos[i], serviceClassMask));
              tb.append("\n");
            }
          }
        }
        sb.append(tb);
      }

      state = State.COMPLETE;
    }

    message = sb;
    phone.onMMIDone(this);
  }
  /* package */ void hangup(GsmCall call) throws CallStateException {
    if (call.getConnections().size() == 0) {
      throw new CallStateException("no connections in call");
    }

    if (call == ringingCall) {
      if (Phone.DEBUG_PHONE) log("(ringing) hangup waiting or background");
      cm.hangupWaitingOrBackground(obtainCompleteMessage());
    } else if (call == foregroundCall) {
      if (call.isDialingOrAlerting()) {
        if (Phone.DEBUG_PHONE) {
          log("(foregnd) hangup dialing or alerting...");
        }
        hangup((GsmConnection) (call.getConnections().get(0)));
      } else {
        hangupForegroundResumeBackground();
      }
    } else if (call == backgroundCall) {
      if (ringingCall.isRinging()) {
        if (Phone.DEBUG_PHONE) {
          log("hangup all conns in background call");
        }
        hangupAllConnections(call);
      } else {
        hangupWaitingOrBackground();
      }
    } else {
      throw new RuntimeException("GsmCall " + call + "does not belong to GsmCallTracker " + this);
    }

    call.onHangupLocal();
    phone.notifyPreciseCallStateChanged();
  }
  // inherited javadoc suffices
  public void cancel() {
    // Complete or failed cannot be cancelled
    if (state == State.COMPLETE || state == State.FAILED) {
      return;
    }

    state = State.CANCELLED;

    if (isPendingUSSD) {
      /*
       * There can only be one pending USSD session, so tell the radio to
       * cancel it.
       */
      phone.mCM.cancelPendingUssd(obtainMessage(EVENT_USSD_CANCEL_COMPLETE, this));

      /*
       * Don't call phone.onMMIDone here; wait for CANCEL_COMPLETE notice
       * from RIL.
       */
    } else {
      // TODO in cases other than USSD, it would be nice to cancel
      // the pending radio operation. This requires RIL cancellation
      // support, which does not presently exist.

      phone.onMMIDone(this);
    }
  }
  private void onQueryComplete(AsyncResult ar) {
    StringBuilder sb = new StringBuilder(getScString());
    sb.append("\n");

    if (ar.exception != null) {
      state = State.FAILED;
      sb.append(getErrorMessage(ar));
    } else {
      int[] ints = (int[]) ar.result;

      if (ints.length != 0) {
        if (ints[0] == 0) {
          sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
        } else if (sc.equals(SC_WAIT)) {
          // Call Waiting includes additional data in the response.
          sb.append(createQueryCallWaitingResultMessage(ints[1]));
        } else if (isServiceCodeCallBarring(sc)) {
          // ints[0] for Call Barring is a bit vector of services
          sb.append(createQueryCallBarringResultMessage(ints[0]));
        } else if (ints[0] == 1) {
          // for all other services, treat it as a boolean
          sb.append(context.getText(com.android.internal.R.string.serviceEnabled));
        } else {
          sb.append(context.getText(com.android.internal.R.string.mmiError));
        }
      } else {
        sb.append(context.getText(com.android.internal.R.string.mmiError));
      }
      state = State.COMPLETE;
    }

    message = sb;
    phone.onMMIDone(this);
  }
 private void handlePasswordError(int res) {
   state = State.FAILED;
   StringBuilder sb = new StringBuilder(getScString());
   sb.append("\n");
   sb.append(context.getText(res));
   message = sb;
   phone.onMMIDone(this);
 }
  /**
   * Called from GSMPhone
   *
   * <p>The radio has reset, and this is still pending
   */
  void onUssdFinishedError() {
    if (state == State.PENDING) {
      state = State.FAILED;
      message = context.getText(com.android.internal.R.string.mmiError);

      phone.onMMIDone(this);
    }
  }
  /** one CallForwardInfo + serviceClassMask -> one line of text */
  private CharSequence makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) {
    CharSequence template;
    String sources[] = {"{0}", "{1}", "{2}"};
    CharSequence destinations[] = new CharSequence[3];
    boolean needTimeTemplate;

    // CF_REASON_NO_REPLY also has a time value associated with
    // it. All others don't.

    needTimeTemplate = (info.reason == CommandsInterface.CF_REASON_NO_REPLY);

    if (info.status == 1) {
      if (needTimeTemplate) {
        template = context.getText(com.android.internal.R.string.cfTemplateForwardedTime);
      } else {
        template = context.getText(com.android.internal.R.string.cfTemplateForwarded);
      }
    } else if (info.status == 0 && isEmptyOrNull(info.number)) {
      template = context.getText(com.android.internal.R.string.cfTemplateNotForwarded);
    } else {
        /* (info.status == 0) && !isEmptyOrNull(info.number) */
      // A call forward record that is not active but contains
      // a phone number is considered "registered"

      if (needTimeTemplate) {
        template = context.getText(com.android.internal.R.string.cfTemplateRegisteredTime);
      } else {
        template = context.getText(com.android.internal.R.string.cfTemplateRegistered);
      }
    }

    // In the template (from strings.xmls)
    //         {0} is one of "bearerServiceCode*"
    //        {1} is dialing number
    //      {2} is time in seconds

    destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask);
    destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa);
    destinations[2] = Integer.toString(info.timeSeconds);

    if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL
        && (info.serviceClass & serviceClassMask) == CommandsInterface.SERVICE_CLASS_VOICE) {
      boolean cffEnabled = (info.status == 1);
      if (phone.mSIMRecords != null) {
        phone.setCallForwardingPreference(cffEnabled);
        phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled);
      } else {
        Log.w(LOG_TAG, "setVoiceCallForwardingFlag aborted. sim records is null.");
      }
    }

    return TextUtils.replace(template, sources, destinations);
  }
  boolean canDial() {
    boolean ret;
    int serviceState = phone.getServiceState().getState();
    String disableCall = SystemProperties.get(TelephonyProperties.PROPERTY_DISABLE_CALL, "false");

    ret =
        (serviceState != ServiceState.STATE_POWER_OFF)
            && pendingMO == null
            && !ringingCall.isRinging()
            && !disableCall.equals("true")
            && (!foregroundCall.getState().isAlive() || !backgroundCall.getState().isAlive());

    return ret;
  }
  /**
   * Called from GSMPhone
   *
   * <p>An unsolicited USSD NOTIFY or REQUEST has come in matching up with this pending USSD request
   *
   * <p>Note: If REQUEST, this exchange is complete, but the session remains active (ie, the network
   * expects user input).
   */
  void onUssdFinished(String ussdMessage, boolean isUssdRequest) {
    if (state == State.PENDING) {
      if (ussdMessage == null) {
        message = context.getText(com.android.internal.R.string.mmiComplete);
      } else {
        message = ussdMessage;
      }
      this.isUssdRequest = isUssdRequest;
      // If it's a request, leave it PENDING so that it's cancelable.
      if (!isUssdRequest) {
        state = State.COMPLETE;
      }

      phone.onMMIDone(this);
    }
  }
  private void updatePhoneState() {
    PhoneConstants.State oldState = state;

    if (ringingCall.isRinging()) {
      state = PhoneConstants.State.RINGING;
    } else if (pendingMO != null || !(foregroundCall.isIdle() && backgroundCall.isIdle())) {
      state = PhoneConstants.State.OFFHOOK;
    } else {
      state = PhoneConstants.State.IDLE;
    }

    if (state == PhoneConstants.State.IDLE && oldState != state) {
      voiceCallEndedRegistrants.notifyRegistrants(new AsyncResult(null, null, null));
    } else if (oldState == PhoneConstants.State.IDLE && oldState != state) {
      voiceCallStartedRegistrants.notifyRegistrants(new AsyncResult(null, null, null));
    }

    if (state != oldState) {
      phone.notifyPhoneStateChanged();
    }
  }
 /**
  * Helper function for newFromDialString. Returns true if dialString appears to be a short code
  * AND conditions are correct for it to be treated as such.
  */
 private static boolean isShortCode(String dialString, GSMPhone phone) {
   // check for any  Adapt change requirements.
   boolean shortCodeExclusionFlag = false;
   if (SystemProperties.getBoolean("persist.cust.tel.adapt", false)) {
     if (dialString.length() == 2) {
       Log.i(LOG_TAG, "Adapt, Number needs to be checked for short code exclusion list");
       shortCodeExclusionFlag = isExcludedShortCode(dialString);
     }
   }
   // Refer to TS 22.030 Figure 3.5.3.2:
   // A 1 or 2 digit "short code" is treated as USSD if it is entered while on a call or
   // does not satisfy the condition (exactly 2 digits && starts with '1').
   return ((dialString != null && dialString.length() <= 2)
       && !PhoneNumberUtils.isEmergencyNumber(dialString)
       && (phone.isInCall()
           || !((dialString.length() == 2 && dialString.charAt(0) == '1')
               || shortCodeExclusionFlag
               /*
                * While contrary to TS 22.030, there is strong precedence for
                * treating "0" and "00" as call setup strings.
                */
               || dialString.equals("0")
               || dialString.equals("00"))));
 }
  public void handleMessage(Message msg) {
    AsyncResult ar;

    switch (msg.what) {
      case EVENT_POLL_CALLS_RESULT:
        ar = (AsyncResult) msg.obj;

        if (msg == lastRelevantPoll) {
          if (DBG_POLL) log("handle EVENT_POLL_CALL_RESULT: set needsPoll=F");
          needsPoll = false;
          lastRelevantPoll = null;
          handlePollCalls((AsyncResult) msg.obj);
        }
        break;

      case EVENT_OPERATION_COMPLETE:
        ar = (AsyncResult) msg.obj;
        operationComplete();
        break;

      case EVENT_SWITCH_RESULT:
      case EVENT_CONFERENCE_RESULT:
      case EVENT_SEPARATE_RESULT:
      case EVENT_ECT_RESULT:
        ar = (AsyncResult) msg.obj;
        if (ar.exception != null) {
          phone.notifySuppServiceFailed(getFailedService(msg.what));
        }
        operationComplete();
        break;

      case EVENT_GET_LAST_CALL_FAIL_CAUSE:
        int causeCode;
        ar = (AsyncResult) msg.obj;

        operationComplete();

        if (ar.exception != null) {
          // An exception occurred...just treat the disconnect
          // cause as "normal"
          causeCode = CallFailCause.NORMAL_CLEARING;
          Rlog.i(LOG_TAG, "Exception during getLastCallFailCause, assuming normal disconnect");
        } else {
          causeCode = ((int[]) ar.result)[0];
        }
        // Log the causeCode if its not normal
        if (causeCode == CallFailCause.NO_CIRCUIT_AVAIL
            || causeCode == CallFailCause.TEMPORARY_FAILURE
            || causeCode == CallFailCause.SWITCHING_CONGESTION
            || causeCode == CallFailCause.CHANNEL_NOT_AVAIL
            || causeCode == CallFailCause.QOS_NOT_AVAIL
            || causeCode == CallFailCause.BEARER_NOT_AVAIL
            || causeCode == CallFailCause.ERROR_UNSPECIFIED) {
          GsmCellLocation loc = ((GsmCellLocation) phone.getCellLocation());
          EventLog.writeEvent(
              EventLogTags.CALL_DROP,
              causeCode,
              loc != null ? loc.getCid() : -1,
              TelephonyManager.getDefault().getNetworkType());
        }

        for (int i = 0, s = droppedDuringPoll.size(); i < s; i++) {
          GsmConnection conn = droppedDuringPoll.get(i);

          conn.onRemoteDisconnect(causeCode);
        }

        updatePhoneState();

        phone.notifyPreciseCallStateChanged();
        droppedDuringPoll.clear();
        break;

      case EVENT_REPOLL_AFTER_DELAY:
      case EVENT_CALL_STATE_CHANGE:
        pollCallsWhenSafe();
        break;

      case EVENT_RADIO_AVAILABLE:
        handleRadioAvailable();
        break;

      case EVENT_RADIO_NOT_AVAILABLE:
        handleRadioNotAvailable();
        break;
    }
  }
  protected synchronized void handlePollCalls(AsyncResult ar) {
    List polledCalls;

    if (ar.exception == null) {
      polledCalls = (List) ar.result;
    } else if (isCommandExceptionRadioNotAvailable(ar.exception)) {
      // just a dummy empty ArrayList to cause the loop
      // to hang up all the calls
      polledCalls = new ArrayList();
    } else {
      // Radio probably wasn't ready--try again in a bit
      // But don't keep polling if the channel is closed
      pollCallsAfterDelay();
      return;
    }

    Connection newRinging = null; // or waiting
    boolean hasNonHangupStateChanged = false; // Any change besides
    // a dropped connection
    boolean needsPollDelay = false;
    boolean unknownConnectionAppeared = false;

    for (int i = 0, curDC = 0, dcSize = polledCalls.size(); i < connections.length; i++) {
      GsmConnection conn = connections[i];
      DriverCall dc = null;

      // polledCall list is sparse
      if (curDC < dcSize) {
        dc = (DriverCall) polledCalls.get(curDC);

        if (dc.index == i + 1) {
          curDC++;
        } else {
          dc = null;
        }
      }

      if (DBG_POLL) log("poll: conn[i=" + i + "]=" + conn + ", dc=" + dc);

      if (conn == null && dc != null) {
        // Connection appeared in CLCC response that we don't know about
        if (pendingMO != null && pendingMO.compareTo(dc)) {

          if (DBG_POLL) log("poll: pendingMO=" + pendingMO);

          // It's our pending mobile originating call
          connections[i] = pendingMO;
          pendingMO.index = i;
          pendingMO.update(dc);
          pendingMO = null;

          // Someone has already asked to hangup this call
          if (hangupPendingMO) {
            hangupPendingMO = false;
            try {
              if (Phone.DEBUG_PHONE) log("poll: hangupPendingMO, hangup conn " + i);
              hangup(connections[i]);
            } catch (CallStateException ex) {
              Rlog.e(LOG_TAG, "unexpected error on hangup");
            }

            // Do not continue processing this poll
            // Wait for hangup and repoll
            return;
          }
        } else {
          connections[i] = new GsmConnection(phone.getContext(), dc, this, i);

          // it's a ringing call
          if (connections[i].getCall() == ringingCall) {
            newRinging = connections[i];
          } else {
            // Something strange happened: a call appeared
            // which is neither a ringing call or one we created.
            // Either we've crashed and re-attached to an existing
            // call, or something else (eg, SIM) initiated the call.

            Rlog.i(LOG_TAG, "Phantom call appeared " + dc);

            // If it's a connected call, set the connect time so that
            // it's non-zero.  It may not be accurate, but at least
            // it won't appear as a Missed Call.
            if (dc.state != DriverCall.State.ALERTING && dc.state != DriverCall.State.DIALING) {
              connections[i].onConnectedInOrOut();
              if (dc.state == DriverCall.State.HOLDING) {
                // We've transitioned into HOLDING
                connections[i].onStartedHolding();
              }
            }

            unknownConnectionAppeared = true;
          }
        }
        hasNonHangupStateChanged = true;
      } else if (conn != null && dc == null) {
        // Connection missing in CLCC response that we were
        // tracking.
        droppedDuringPoll.add(conn);
        // Dropped connections are removed from the CallTracker
        // list but kept in the GsmCall list
        connections[i] = null;
      } else if (conn != null && dc != null && !conn.compareTo(dc)) {
        // Connection in CLCC response does not match what
        // we were tracking. Assume dropped call and new call

        droppedDuringPoll.add(conn);
        connections[i] = new GsmConnection(phone.getContext(), dc, this, i);

        if (connections[i].getCall() == ringingCall) {
          newRinging = connections[i];
        } // else something strange happened
        hasNonHangupStateChanged = true;
      } else if (conn != null && dc != null) {
          /* implicit conn.compareTo(dc) */
        boolean changed;
        changed = conn.update(dc);
        hasNonHangupStateChanged = hasNonHangupStateChanged || changed;
      }

      if (REPEAT_POLLING) {
        if (dc != null) {
          // FIXME with RIL, we should not need this anymore
          if ((dc.state == DriverCall.State.DIALING
              /*&& cm.getOption(cm.OPTION_POLL_DIALING)*/ )
              || (dc.state == DriverCall.State.ALERTING
              /*&& cm.getOption(cm.OPTION_POLL_ALERTING)*/ )
              || (dc.state == DriverCall.State.INCOMING
              /*&& cm.getOption(cm.OPTION_POLL_INCOMING)*/ )
              || (dc.state == DriverCall.State.WAITING
              /*&& cm.getOption(cm.OPTION_POLL_WAITING)*/ )) {
            // Sometimes there's no unsolicited notification
            // for state transitions
            needsPollDelay = true;
          }
        }
      }
    }

    // This is the first poll after an ATD.
    // We expect the pending call to appear in the list
    // If it does not, we land here
    if (pendingMO != null) {
      Rlog.d(LOG_TAG, "Pending MO dropped before poll fg state:" + foregroundCall.getState());

      droppedDuringPoll.add(pendingMO);
      pendingMO = null;
      hangupPendingMO = false;
    }

    if (newRinging != null) {
      phone.notifyNewRingingConnection(newRinging);
    }

    // clear the "local hangup" and "missed/rejected call"
    // cases from the "dropped during poll" list
    // These cases need no "last call fail" reason
    for (int i = droppedDuringPoll.size() - 1; i >= 0; i--) {
      GsmConnection conn = droppedDuringPoll.get(i);

      if (conn.isIncoming() && conn.getConnectTime() == 0) {
        // Missed or rejected call
        Connection.DisconnectCause cause;
        if (conn.cause == Connection.DisconnectCause.LOCAL) {
          cause = Connection.DisconnectCause.INCOMING_REJECTED;
        } else {
          cause = Connection.DisconnectCause.INCOMING_MISSED;
        }

        if (Phone.DEBUG_PHONE) {
          log("missed/rejected call, conn.cause=" + conn.cause);
          log("setting cause to " + cause);
        }
        droppedDuringPoll.remove(i);
        conn.onDisconnect(cause);
      } else if (conn.cause == Connection.DisconnectCause.LOCAL) {
        // Local hangup
        droppedDuringPoll.remove(i);
        conn.onDisconnect(Connection.DisconnectCause.LOCAL);
      } else if (conn.cause == Connection.DisconnectCause.INVALID_NUMBER) {
        droppedDuringPoll.remove(i);
        conn.onDisconnect(Connection.DisconnectCause.INVALID_NUMBER);
      }
    }

    // Any non-local disconnects: determine cause
    if (droppedDuringPoll.size() > 0) {
      cm.getLastCallFailCause(obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));
    }

    if (needsPollDelay) {
      pollCallsAfterDelay();
    }

    // Cases when we can no longer keep disconnected Connection's
    // with their previous calls
    // 1) the phone has started to ring
    // 2) A Call/Connection object has changed state...
    //    we may have switched or held or answered (but not hung up)
    if (newRinging != null || hasNonHangupStateChanged) {
      internalClearDisconnected();
    }

    updatePhoneState();

    if (unknownConnectionAppeared) {
      phone.notifyUnknownConnection();
    }

    if (hasNonHangupStateChanged || newRinging != null) {
      phone.notifyPreciseCallStateChanged();
    }

    // dumpState();
  }
  /** Process a MMI code or short code...anything that isn't a dialing number */
  void processCode() {
    try {
      if (isShortCode()) {
        Log.d(LOG_TAG, "isShortCode");
        // These just get treated as USSD.
        sendUssd(dialingNumber);
      } else if (dialingNumber != null) {
        // We should have no dialing numbers here
        throw new RuntimeException("Invalid or Unsupported MMI Code");
      } else if (sc != null && sc.equals(SC_CLIP)) {
        Log.d(LOG_TAG, "is CLIP");
        if (isInterrogate()) {
          phone.mCM.queryCLIP(obtainMessage(EVENT_QUERY_COMPLETE, this));
        } else {
          throw new RuntimeException("Invalid or Unsupported MMI Code");
        }
      } else if (sc != null && sc.equals(SC_CLIR)) {
        Log.d(LOG_TAG, "is CLIR");
        if (isActivate()) {
          phone.mCM.setCLIR(
              CommandsInterface.CLIR_INVOCATION, obtainMessage(EVENT_SET_COMPLETE, this));
        } else if (isDeactivate()) {
          phone.mCM.setCLIR(
              CommandsInterface.CLIR_SUPPRESSION, obtainMessage(EVENT_SET_COMPLETE, this));
        } else if (isInterrogate()) {
          phone.mCM.getCLIR(obtainMessage(EVENT_GET_CLIR_COMPLETE, this));
        } else {
          throw new RuntimeException("Invalid or Unsupported MMI Code");
        }
      } else if (isServiceCodeCallForwarding(sc)) {
        Log.d(LOG_TAG, "is CF");

        String dialingNumber = sia;
        int serviceClass = siToServiceClass(sib);
        int reason = scToCallForwardReason(sc);
        int time = siToTime(sic);

        if (isInterrogate()) {
          phone.mCM.queryCallForwardStatus(
              reason, serviceClass, dialingNumber, obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
        } else {
          int cfAction;

          if (isActivate()) {
            if (dialingNumber != null) {
              isCallFwdRegister = true;
              cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
            } else {
              cfAction = CommandsInterface.CF_ACTION_ENABLE;
            }
          } else if (isDeactivate()) {
            cfAction = CommandsInterface.CF_ACTION_DISABLE;
          } else if (isRegister()) {
            cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
          } else if (isErasure()) {
            cfAction = CommandsInterface.CF_ACTION_ERASURE;
          } else {
            throw new RuntimeException("invalid action");
          }

          int isSettingUnconditionalVoice =
              (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL)
                          || (reason == CommandsInterface.CF_REASON_ALL))
                      && (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0)
                          || (serviceClass == CommandsInterface.SERVICE_CLASS_NONE)))
                  ? 1
                  : 0;

          int isEnableDesired =
              ((cfAction == CommandsInterface.CF_ACTION_ENABLE)
                      || (cfAction == CommandsInterface.CF_ACTION_REGISTRATION))
                  ? 1
                  : 0;

          Log.d(LOG_TAG, "is CF setCallForward");
          phone.mCM.setCallForward(
              cfAction,
              reason,
              serviceClass,
              dialingNumber,
              time,
              obtainMessage(
                  EVENT_SET_CFF_COMPLETE, isSettingUnconditionalVoice, isEnableDesired, this));
        }
      } else if (isServiceCodeCallBarring(sc)) {
        // sia = password
        // sib = basic service group

        String password = sia;
        int serviceClass = siToServiceClass(sib);
        String facility = scToBarringFacility(sc);

        if (isInterrogate()) {
          phone.mCM.queryFacilityLock(
              0, "", facility, password, serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this));
        } else if (isActivate() || isDeactivate()) {
          phone.mCM.setFacilityLock(
              0,
              "",
              facility,
              isActivate(),
              password,
              serviceClass,
              obtainMessage(EVENT_SET_COMPLETE, this));
        } else {
          throw new RuntimeException("Invalid or Unsupported MMI Code");
        }

      } else if (sc != null && sc.equals(SC_PWD)) {
        // sia = fac
        // sib = old pwd
        // sic = new pwd
        // pwd = new pwd
        String facility;
        String oldPwd = sib;
        String newPwd = sic;
        if (isActivate() || isRegister()) {
          // Even though ACTIVATE is acceptable, this is really termed a REGISTER
          action = ACTION_REGISTER;

          if (sia == null) {
            // If sc was not specified, treat it as BA_ALL.
            facility = CommandsInterface.CB_FACILITY_BA_ALL;
          } else {
            facility = scToBarringFacility(sia);
          }
          if (newPwd.equals(pwd)) {
            phone.mCM.changeBarringPassword(
                facility, oldPwd, newPwd, obtainMessage(EVENT_SET_COMPLETE, this));
          } else {
            // password mismatch; return error
            handlePasswordError(com.android.internal.R.string.passwordIncorrect);
          }
        } else {
          throw new RuntimeException("Invalid or Unsupported MMI Code");
        }

      } else if (sc != null && sc.equals(SC_WAIT)) {
        // sia = basic service group
        int serviceClass = siToServiceClass(sia);

        if (isActivate() || isDeactivate()) {
          phone.mCM.setCallWaiting(
              isActivate(), serviceClass, obtainMessage(EVENT_SET_COMPLETE, this));
        } else if (isInterrogate()) {
          phone.mCM.queryCallWaiting(serviceClass, obtainMessage(EVENT_QUERY_COMPLETE, this));
        } else {
          throw new RuntimeException("Invalid or Unsupported MMI Code");
        }
      } else if (isPinCommand()) {
        // sia = old PIN or PUK
        // sib = new PIN
        // sic = new PIN
        String oldPinOrPuk = sia;
        String newPin = sib;
        int pinLen = newPin.length();
        if (isRegister()) {
          if (!newPin.equals(sic)) {
            // password mismatch; return error
            handlePasswordError(com.android.internal.R.string.mismatchPin);
          } else if (pinLen < 4 || pinLen > 8) {
            // invalid length
            handlePasswordError(com.android.internal.R.string.invalidPin);
          } else if (sc.equals(SC_PIN)
              && phone.m3gppApplication != null
              && phone.m3gppApplication.getState() == UiccConstants.AppState.APPSTATE_PUK) {
            // Sim is puk-locked
            handlePasswordError(com.android.internal.R.string.needPuk);
          } else {
            // pre-checks OK
            if (sc.equals(SC_PIN)) {
              if (mUiccApp != null)
                mUiccApp.changeIccLockPassword(
                    oldPinOrPuk, newPin, obtainMessage(EVENT_SET_COMPLETE, this));
            } else if (sc.equals(SC_PIN2)) {
              if (mUiccApp != null)
                mUiccApp.changeIccFdnPassword(
                    oldPinOrPuk, newPin, obtainMessage(EVENT_SET_COMPLETE, this));
            } else if (sc.equals(SC_PUK)) {
              if (mUiccApp != null)
                mUiccApp.supplyPuk(oldPinOrPuk, newPin, obtainMessage(EVENT_SET_COMPLETE, this));
            } else if (sc.equals(SC_PUK2)) {
              if (mUiccApp != null)
                mUiccApp.supplyPuk2(oldPinOrPuk, newPin, obtainMessage(EVENT_SET_COMPLETE, this));
            }
          }
        } else {
          throw new RuntimeException("Invalid or Unsupported MMI Code");
        }
      } else if (isServiceCodeUnsupported(sc)) {
        Log.d(LOG_TAG, "Unsupported MMI code: " + sc);
        state = State.FAILED;
        message = context.getText(com.android.internal.R.string.unsupportedMmiCode);
        phone.onMMIDone(this);
      } else if (poundString != null) {
        sendUssd(poundString);
      } else {
        throw new RuntimeException("Invalid or Unsupported MMI Code");
      }
    } catch (RuntimeException exc) {
      state = State.FAILED;
      message = context.getText(com.android.internal.R.string.mmiError);
      phone.onMMIDone(this);
    }
  }
  void clearDisconnected() {
    internalClearDisconnected();

    updatePhoneState();
    phone.notifyPreciseCallStateChanged();
  }
  protected void handleBroadcastSms(AsyncResult ar) {
    try {
      byte[][] pdus = null;
      byte[] receivedPdu = (byte[]) ar.result;

      if (Config.LOGD) {
        for (int i = 0; i < receivedPdu.length; i += 8) {
          StringBuilder sb = new StringBuilder("SMS CB pdu data: ");
          for (int j = i; j < i + 8 && j < receivedPdu.length; j++) {
            int b = receivedPdu[j] & 0xff;
            if (b < 0x10) {
              sb.append("0");
            }
            sb.append(Integer.toHexString(b)).append(" ");
          }
          Log.d(TAG, sb.toString());
        }
      }

      SmsCbHeader header = new SmsCbHeader(receivedPdu);
      String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC);
      GsmCellLocation cellLocation = (GsmCellLocation) mGsmPhone.getCellLocation();
      int lac = cellLocation.getLac();
      int cid = cellLocation.getCid();

      if (header.nrOfPages > 1) {
        // Multi-page message
        SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, plmn, lac, cid);

        // Try to find other pages of the same message
        pdus = mSmsCbPageMap.get(concatInfo);

        if (pdus == null) {
          // This it the first page of this message, make room for all
          // pages and keep until complete
          pdus = new byte[header.nrOfPages][];

          mSmsCbPageMap.put(concatInfo, pdus);
        }

        // Page parameter is one-based
        pdus[header.pageIndex - 1] = receivedPdu;

        for (int i = 0; i < pdus.length; i++) {
          if (pdus[i] == null) {
            // Still missing pages, exit
            return;
          }
        }

        // Message complete, remove and dispatch
        mSmsCbPageMap.remove(concatInfo);
      } else {
        // Single page message
        pdus = new byte[1][];
        pdus[0] = receivedPdu;
      }

      dispatchBroadcastPdus(pdus);

      // Remove messages that are out of scope to prevent the map from
      // growing indefinitely, containing incomplete messages that were
      // never assembled
      Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator();

      while (iter.hasNext()) {
        SmsCbConcatInfo info = iter.next();

        if (!info.matchesLocation(plmn, lac, cid)) {
          iter.remove();
        }
      }
    } catch (RuntimeException e) {
      Log.e(TAG, "Error in decoding SMS CB pdu", e);
    }
  }
  private void onSetComplete(AsyncResult ar) {
    StringBuilder sb = new StringBuilder(getScString());
    sb.append("\n");

    if (ar.exception != null) {
      state = State.FAILED;
      if (ar.exception instanceof CommandException) {
        CommandException.Error err = ((CommandException) (ar.exception)).getCommandError();
        if (err == CommandException.Error.PASSWORD_INCORRECT) {
          if (isPinCommand()) {
            // look specifically for the PUK commands and adjust
            // the message accordingly.
            if (sc.equals(SC_PUK) || sc.equals(SC_PUK2)) {
              sb.append(context.getText(com.android.internal.R.string.badPuk));
            } else {
              sb.append(context.getText(com.android.internal.R.string.badPin));
            }
          } else {
            sb.append(context.getText(com.android.internal.R.string.passwordIncorrect));
          }
        } else if (err == CommandException.Error.SIM_PUK2) {
          sb.append(context.getText(com.android.internal.R.string.badPin));
          sb.append("\n");
          sb.append(context.getText(com.android.internal.R.string.needPuk2));
        } else if (err == CommandException.Error.FDN_CHECK_FAILURE) {
          Log.i(LOG_TAG, "FDN_CHECK_FAILURE");
          sb.append(context.getText(com.android.internal.R.string.mmiFdnError));
        } else {
          sb.append(context.getText(com.android.internal.R.string.mmiError));
        }
      } else {
        sb.append(context.getText(com.android.internal.R.string.mmiError));
      }
    } else if (isActivate()) {
      state = State.COMPLETE;
      if (isCallFwdRegister) {
        sb.append(context.getText(com.android.internal.R.string.serviceRegistered));
        isCallFwdRegister = false;
      } else {
        sb.append(context.getText(com.android.internal.R.string.serviceEnabled));
      }
      // Record CLIR setting
      if (sc.equals(SC_CLIR)) {
        phone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
      }
    } else if (isDeactivate()) {
      state = State.COMPLETE;
      sb.append(context.getText(com.android.internal.R.string.serviceDisabled));
      // Record CLIR setting
      if (sc.equals(SC_CLIR)) {
        phone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
      }
    } else if (isRegister()) {
      state = State.COMPLETE;
      sb.append(context.getText(com.android.internal.R.string.serviceRegistered));
    } else if (isErasure()) {
      state = State.COMPLETE;
      sb.append(context.getText(com.android.internal.R.string.serviceErased));
    } else {
      state = State.FAILED;
      sb.append(context.getText(com.android.internal.R.string.mmiError));
    }

    message = sb;
    phone.onMMIDone(this);
  }
  /** Called from GSMPhone.handleMessage; not a Handler subclass */
  public void handleMessage(Message msg) {
    AsyncResult ar;

    switch (msg.what) {
      case EVENT_SET_COMPLETE:
        ar = (AsyncResult) (msg.obj);

        onSetComplete(ar);
        break;

      case EVENT_SET_CFF_COMPLETE:
        ar = (AsyncResult) (msg.obj);

        /*
         * msg.arg1 = 1 means to set unconditional voice call forwarding
         * msg.arg2 = 1 means to enable voice call forwarding
         */
        if ((ar.exception == null) && (msg.arg1 == 1)) {
          boolean cffEnabled = (msg.arg2 == 1);
          if (phone.mSIMRecords != null) {
            phone.mSIMRecords.setVoiceCallForwardingFlag(1, cffEnabled);
            phone.setCallForwardingPreference(cffEnabled);
          } else {
            Log.w(LOG_TAG, "setVoiceCallForwardingFlag aborted. sim records is null.");
          }
        }

        onSetComplete(ar);
        break;

      case EVENT_GET_CLIR_COMPLETE:
        ar = (AsyncResult) (msg.obj);
        onGetClirComplete(ar);
        break;

      case EVENT_QUERY_CF_COMPLETE:
        ar = (AsyncResult) (msg.obj);
        onQueryCfComplete(ar);
        break;

      case EVENT_QUERY_COMPLETE:
        ar = (AsyncResult) (msg.obj);
        onQueryComplete(ar);
        break;

      case EVENT_USSD_COMPLETE:
        ar = (AsyncResult) (msg.obj);

        if (ar.exception != null) {
          state = State.FAILED;
          message = getErrorMessage(ar);

          phone.onMMIDone(this);
        }

        // Note that unlike most everything else, the USSD complete
        // response does not complete this MMI code...we wait for
        // an unsolicited USSD "Notify" or "Request".
        // The matching up of this is done in GSMPhone.

        break;

      case EVENT_USSD_CANCEL_COMPLETE:
        phone.onMMIDone(this);
        break;
    }
  }
  /** {@inheritDoc} */
  protected int dispatchMessage(SmsMessageBase smsb) {

    // If sms is null, means there was a parsing error.
    if (smsb == null) {
      return Intents.RESULT_SMS_GENERIC_ERROR;
    }
    SmsMessage sms = (SmsMessage) smsb;
    boolean handled = false;

    if (sms.isTypeZero()) {
      // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be
      // Displayed/Stored/Notified. They should only be acknowledged.
      Log.d(TAG, "Received short message type 0, Don't display or store it. Send Ack");
      return Intents.RESULT_SMS_HANDLED;
    }

    // Special case the message waiting indicator messages
    if (sms.isMWISetMessage()) {
      mGsmPhone.updateMessageWaitingIndicator(true);
      handled = sms.isMwiDontStore();
      if (Config.LOGD) {
        Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
      }
    } else if (sms.isMWIClearMessage()) {
      mGsmPhone.updateMessageWaitingIndicator(false);
      handled = sms.isMwiDontStore();
      if (Config.LOGD) {
        Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
      }
    }

    if (handled) {
      return Intents.RESULT_SMS_HANDLED;
    }

    if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) {
      // It's a storable message and there's no storage available.  Bail.
      // (See TS 23.038 for a description of class 0 messages.)
      return Intents.RESULT_SMS_OUT_OF_MEMORY;
    }

    SmsHeader smsHeader = sms.getUserDataHeader();
    // See if message is partial or port addressed.
    if ((smsHeader == null) || (smsHeader.concatRef == null)) {
      // Message is not partial (not part of concatenated sequence).
      byte[][] pdus = new byte[1][];
      pdus[0] = sms.getPdu();

      if (smsHeader != null && smsHeader.portAddrs != null) {
        if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
          return mWapPush.dispatchWapPdu(sms.getUserData());
        } else {
          // The message was sent to a port, so concoct a URI for it.
          dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
        }
      } else {
        // Normal short and non-port-addressed message, dispatch it.
        dispatchPdus(pdus);
      }
      return Activity.RESULT_OK;
    } else {
      // Process the message part.
      return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs);
    }
  }
  private void onGetClirComplete(AsyncResult ar) {
    StringBuilder sb = new StringBuilder(getScString());
    sb.append("\n");

    if (ar.exception != null) {
      state = State.FAILED;
      sb.append(getErrorMessage(ar));
    } else {
      int clirArgs[];

      clirArgs = (int[]) ar.result;

      // the 'm' parameter from TS 27.007 7.7
      switch (clirArgs[1]) {
        case 0: // CLIR not provisioned
          sb.append(context.getText(com.android.internal.R.string.serviceNotProvisioned));
          state = State.COMPLETE;
          break;

        case 1: // CLIR provisioned in permanent mode
          sb.append(context.getText(com.android.internal.R.string.CLIRPermanent));
          state = State.COMPLETE;
          break;

        case 2: // unknown (e.g. no network, etc.)
          sb.append(context.getText(com.android.internal.R.string.mmiError));
          state = State.FAILED;
          break;

        case 3: // CLIR temporary mode presentation restricted

          // the 'n' parameter from TS 27.007 7.7
          switch (clirArgs[0]) {
            default:
            case 0: // Default
              sb.append(context.getText(com.android.internal.R.string.CLIRDefaultOnNextCallOn));
              break;
            case 1: // CLIR invocation
              sb.append(context.getText(com.android.internal.R.string.CLIRDefaultOnNextCallOn));
              break;
            case 2: // CLIR suppression
              sb.append(context.getText(com.android.internal.R.string.CLIRDefaultOnNextCallOff));
              break;
          }
          state = State.COMPLETE;
          break;

        case 4: // CLIR temporary mode presentation allowed
          // the 'n' parameter from TS 27.007 7.7
          switch (clirArgs[0]) {
            default:
            case 0: // Default
              sb.append(context.getText(com.android.internal.R.string.CLIRDefaultOffNextCallOff));
              break;
            case 1: // CLIR invocation
              sb.append(context.getText(com.android.internal.R.string.CLIRDefaultOffNextCallOn));
              break;
            case 2: // CLIR suppression
              sb.append(context.getText(com.android.internal.R.string.CLIRDefaultOffNextCallOff));
              break;
          }

          state = State.COMPLETE;
          break;
      }
    }

    message = sb;
    phone.onMMIDone(this);
  }