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();
    }
  }
  void clearDisconnected() {
    if (DBG) log("clearDisconnected");

    internalClearDisconnected();

    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 onCallMerged(ImsCall call, boolean swapCalls) {
          if (DBG) log("onCallMerged");

          mForegroundCall.merge(mBackgroundCall, mForegroundCall.getState());
          if (swapCalls) {
            try {
              switchWaitingOrHoldingAndActive();
            } catch (CallStateException e) {
              if (Phone.DEBUG_PHONE) {
                loge("Failed swap fg and bg calls on merge exception=" + e);
              }
            }
          }
          updatePhoneState();
          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;
    }
  }
  /** oirMode is one of the CLIR_ constants */
  synchronized Connection dial(String dialString, int clirMode, int videoState)
      throws CallStateException {
    boolean isPhoneInEcmMode =
        SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false);
    boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(dialString);

    if (DBG) log("dial clirMode=" + clirMode);

    // note that this triggers call state changed notif
    clearDisconnected();

    if (mImsManager == null) {
      throw new CallStateException("service not available");
    }

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

    if (isPhoneInEcmMode && isEmergencyNumber) {
      handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER);
    }

    boolean holdBeforeDial = false;

    // The new call must be assigned to the foreground call.
    // That call must be idle, so place anything that's
    // there on hold
    if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
      if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
        // we should have failed in !canDial() above before we get here
        throw new CallStateException("cannot dial in current state");
      }
      // foreground call is empty for the newly dialed connection
      holdBeforeDial = true;
      switchWaitingOrHoldingAndActive();
    }

    ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
    ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;

    mClirMode = clirMode;

    synchronized (mSyncHold) {
      if (holdBeforeDial) {
        fgState = mForegroundCall.getState();
        bgState = mBackgroundCall.getState();

        // holding foreground call failed
        if (fgState == ImsPhoneCall.State.ACTIVE) {
          throw new CallStateException("cannot dial in current state");
        }

        // holding foreground call succeeded
        if (bgState == ImsPhoneCall.State.HOLDING) {
          holdBeforeDial = false;
        }
      }

      mPendingMO =
          new ImsPhoneConnection(
              mPhone.getContext(), checkForTestEmergencyNumber(dialString), this, mForegroundCall);
    }
    addConnection(mPendingMO);

    if (!holdBeforeDial) {
      if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
        dialInternal(mPendingMO, clirMode, videoState);
      } else {
        try {
          getEcbmInterface().exitEmergencyCallbackMode();
        } catch (ImsException e) {
          e.printStackTrace();
          throw new CallStateException("service not available");
        }
        mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
        pendingCallClirMode = clirMode;
        pendingCallVideoState = videoState;
        pendingCallInEcm = true;
      }
    }

    updatePhoneState();
    mPhone.notifyPreciseCallStateChanged();

    return mPendingMO;
  }