/* package */
  void resumeWaitingOrHolding() throws CallStateException {
    if (DBG) log("resumeWaitingOrHolding");

    try {
      if (mForegroundCall.getState().isAlive()) {
        // resume foreground call after holding background call
        // they were switched before holding
        ImsCall imsCall = mForegroundCall.getImsCall();
        if (imsCall != null) imsCall.resume();
      } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
        // accept waiting call after holding background call
        ImsCall imsCall = mRingingCall.getImsCall();
        if (imsCall != null) imsCall.accept(ImsCallProfile.CALL_TYPE_VOICE);
      } else {
        // Just resume background call.
        // To distinguish resuming call with swapping calls
        // we do not switch calls.here
        // ImsPhoneConnection.update will chnage the parent when completed
        ImsCall imsCall = mBackgroundCall.getImsCall();
        if (imsCall != null) imsCall.resume();
      }
    } catch (ImsException e) {
      throw new CallStateException(e.getMessage());
    }
  }
  void conference() {
    if (DBG) log("conference");

    ImsCall fgImsCall = mForegroundCall.getImsCall();
    if (fgImsCall == null) {
      log("conference no foreground ims call");
      return;
    }

    ImsCall bgImsCall = mBackgroundCall.getImsCall();
    if (bgImsCall == null) {
      log("conference no background ims call");
      return;
    }

    // Keep track of the connect time of the earliest call so that it can be set on the
    // {@code ImsConference} when it is created.
    long conferenceConnectTime =
        Math.min(
            mForegroundCall.getEarliestConnectTime(), mBackgroundCall.getEarliestConnectTime());
    ImsPhoneConnection foregroundConnection = mForegroundCall.getFirstConnection();
    if (foregroundConnection != null) {
      foregroundConnection.setConferenceConnectTime(conferenceConnectTime);
    }

    try {
      fgImsCall.merge(bgImsCall);
    } catch (ImsException e) {
      log("conference " + e.getMessage());
    }
  }
  void switchWaitingOrHoldingAndActive() throws CallStateException {
    if (DBG) log("switchWaitingOrHoldingAndActive");

    if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
      throw new CallStateException("cannot be in the incoming state");
    }

    if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
      ImsCall imsCall = mForegroundCall.getImsCall();
      if (imsCall == null) {
        throw new CallStateException("no ims call");
      }

      // Swap the ImsCalls pointed to by the foreground and background ImsPhoneCalls.
      // If hold or resume later fails, we will swap them back.
      mSwitchingFgAndBgCalls = true;
      mCallExpectedToResume = mBackgroundCall.getImsCall();
      mForegroundCall.switchWith(mBackgroundCall);

      // Hold the foreground call; once the foreground call is held, the background call will
      // be resumed.
      try {
        imsCall.hold();
      } catch (ImsException e) {
        mForegroundCall.switchWith(mBackgroundCall);
        throw new CallStateException(e.getMessage());
      }
    } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
      resumeWaitingOrHolding();
    }
  }
  /* package */ void hangup(ImsPhoneCall call) throws CallStateException {
    if (DBG) log("hangup call");

    if (call.getConnections().size() == 0) {
      throw new CallStateException("no connections");
    }

    ImsCall imsCall = call.getImsCall();
    boolean rejectCall = false;

    if (call == mRingingCall) {
      if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
      rejectCall = true;
    } else if (call == mForegroundCall) {
      if (call.isDialingOrAlerting()) {
        if (Phone.DEBUG_PHONE) {
          log("(foregnd) hangup dialing or alerting...");
        }
      } else {
        if (Phone.DEBUG_PHONE) {
          log("(foregnd) hangup foreground");
        }
        // held call will be resumed by onCallTerminated
      }
    } else if (call == mBackgroundCall) {
      if (Phone.DEBUG_PHONE) {
        log("(backgnd) hangup waiting or background");
      }
    } else {
      throw new CallStateException(
          "ImsPhoneCall " + call + "does not belong to ImsPhoneCallTracker " + this);
    }

    call.onHangupLocal();

    try {
      if (imsCall != null) {
        if (rejectCall) imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
        else imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
      } else if (mPendingMO != null && call == mForegroundCall) {
        // is holding a foreground call
        mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
        mPendingMO.onDisconnect();
        removeConnection(mPendingMO);
        mPendingMO = null;
        updatePhoneState();
        removeMessages(EVENT_DIAL_PENDINGMO);
      }
    } catch (ImsException e) {
      throw new CallStateException(e.getMessage());
    }

    mPhone.notifyPreciseCallStateChanged();
  }