private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
    if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);

    if (imsCall == null) return;

    boolean changed = false;
    ImsPhoneConnection conn = findConnection(imsCall);

    if (conn == null) {
      // TODO : what should be done?
      return;
    }

    changed = conn.update(imsCall, state);

    if (state == ImsPhoneCall.State.DISCONNECTED) {
      changed = conn.onDisconnect(cause) || changed;
      // detach the disconnected connections
      conn.getCall().detach(conn);
      removeConnection(conn);
    }

    if (changed) {
      if (conn.getCall() == mHandoverCall) return;
      updatePhoneState();
      mPhone.notifyPreciseCallStateChanged();
    }
  }
  /* 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();
  }
  @Override
  public void handleMessage(Message msg) {
    AsyncResult ar;
    if (DBG) log("handleMessage what=" + msg.what);

    switch (msg.what) {
      case EVENT_HANGUP_PENDINGMO:
        if (mPendingMO != null) {
          mPendingMO.onDisconnect();
          removeConnection(mPendingMO);
          mPendingMO = null;
        }

        updatePhoneState();
        mPhone.notifyPreciseCallStateChanged();
        break;
      case EVENT_RESUME_BACKGROUND:
        try {
          resumeWaitingOrHolding();
        } catch (CallStateException e) {
          if (Phone.DEBUG_PHONE) {
            loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
          }
        }
        break;
      case EVENT_DIAL_PENDINGMO:
        dialInternal(mPendingMO, mClirMode, VideoProfile.VideoState.AUDIO_ONLY);
        break;

      case EVENT_EXIT_ECM_RESPONSE_CDMA:
        // no matter the result, we still do the same here
        if (pendingCallInEcm) {
          dialInternal(mPendingMO, pendingCallClirMode, pendingCallVideoState);
          pendingCallInEcm = false;
        }
        mPhone.unsetOnEcbModeExitResponse(this);
        break;
    }
  }