/* 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();
  }
  /** 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;
  }
  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;
  }
 void switchWaitingOrHoldingAndActive() throws CallStateException {
   // Should we bother with this check?
   if (ringingCall.getState() == GsmCall.State.INCOMING) {
     throw new CallStateException("cannot be in the incoming state");
   } else {
     cm.switchWaitingOrHoldingAndActive(obtainCompleteMessage(EVENT_SWITCH_RESULT));
   }
 }
  void acceptCall() throws CallStateException {
    // FIXME if SWITCH fails, should retry with ANSWER
    // in case the active/holding call disappeared and this
    // is no longer call waiting

    if (ringingCall.getState() == GsmCall.State.INCOMING) {
      Rlog.i("phone", "acceptCall: incoming...");
      // Always unmute when answering a new call
      setMute(false);
      cm.acceptCall(obtainCompleteMessage());
    } else if (ringingCall.getState() == GsmCall.State.WAITING) {
      setMute(false);
      switchWaitingOrHoldingAndActive();
    } else {
      throw new CallStateException("phone not ringing");
    }
  }
 void rejectCall() throws CallStateException {
   // AT+CHLD=0 means "release held or UDUB"
   // so if the phone isn't ringing, this could hang up held
   if (ringingCall.getState().isRinging()) {
     cm.rejectCall(obtainCompleteMessage());
   } else {
     throw new CallStateException("phone not ringing");
   }
 }
  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();
    }
  }
  private void dumpState() {
    List l;

    Rlog.i(LOG_TAG, "Phone State:" + state);

    Rlog.i(LOG_TAG, "Ringing call: " + ringingCall.toString());

    l = ringingCall.getConnections();
    for (int i = 0, s = l.size(); i < s; i++) {
      Rlog.i(LOG_TAG, l.get(i).toString());
    }

    Rlog.i(LOG_TAG, "Foreground call: " + foregroundCall.toString());

    l = foregroundCall.getConnections();
    for (int i = 0, s = l.size(); i < s; i++) {
      Rlog.i(LOG_TAG, l.get(i).toString());
    }

    Rlog.i(LOG_TAG, "Background call: " + backgroundCall.toString());

    l = backgroundCall.getConnections();
    for (int i = 0, s = l.size(); i < s; i++) {
      Rlog.i(LOG_TAG, l.get(i).toString());
    }
  }
  private boolean handleCallWaitingIncallSupplementaryService(String dialString)
      throws CallStateException {
    int len = dialString.length();

    if (len > 2) {
      return false;
    }

    GsmCall call = (GsmCall) getForegroundCall();

    try {
      if (len > 1) {
        char ch = dialString.charAt(1);
        int callIndex = ch - '0';

        if (callIndex >= 1 && callIndex <= GsmCallTracker.MAX_CONNECTIONS) {
          if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 1: hangupConnectionByIndex " + callIndex);
          mCT.hangupConnectionByIndex(call, callIndex);
        }
      } else {
        if (call.getState() != GsmCall.State.IDLE) {
          if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 1: hangup foreground");
          // mCT.hangupForegroundResumeBackground();
          mCT.hangup(call);
        } else {
          if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 1: switchWaitingOrHoldingAndActive");
          mCT.switchWaitingOrHoldingAndActive();
        }
      }
    } catch (CallStateException e) {
      if (LOCAL_DEBUG) Log.d(LOG_TAG, "hangup failed", e);
      notifySuppServiceFailed(Phone.SuppService.HANGUP);
    }

    return true;
  }
  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();
  }
 private void internalClearDisconnected() {
   ringingCall.clearDisconnected();
   foregroundCall.clearDisconnected();
   backgroundCall.clearDisconnected();
 }
 boolean canTransfer() {
   return foregroundCall.getState() == GsmCall.State.ACTIVE
       && backgroundCall.getState() == GsmCall.State.HOLDING;
 }
 boolean canConference() {
   return foregroundCall.getState() == GsmCall.State.ACTIVE
       && backgroundCall.getState() == GsmCall.State.HOLDING
       && !backgroundCall.isFull()
       && !foregroundCall.isFull();
 }