@Override public void separate() throws CallStateException { synchronized (SipPhone.class) { SipCall call = (getPhone() == SipPhone.this) ? (SipCall) getBackgroundCall() : (SipCall) getForegroundCall(); if (call.getState() != Call.State.IDLE) { throw new CallStateException( "cannot put conn back to a call in non-idle state: " + call.getState()); } if (SCN_DBG) log("separate: conn=" + mPeer.getUriString() + " from " + mOwner + " back to " + call); // separate the AudioGroup and connection from the original call Phone originalPhone = getPhone(); AudioGroup audioGroup = call.getAudioGroup(); // may be null call.add(this); mSipAudioCall.setAudioGroup(audioGroup); // put the original call to bg; and the separated call becomes // fg if it was in bg originalPhone.switchHoldingAndActive(); // start audio and notify the phone app of the state change call = (SipCall) getForegroundCall(); mSipAudioCall.startAudio(); call.onConnectionStateChanged(this); } }
@Override public void switchHoldingAndActive() throws CallStateException { if (DBG) log("dialInternal: switch fg and bg"); synchronized (SipPhone.class) { mForegroundCall.switchWith(mBackgroundCall); if (mBackgroundCall.getState().isAlive()) mBackgroundCall.hold(); if (mForegroundCall.getState().isAlive()) mForegroundCall.unhold(); } }
@Override public void conference() throws CallStateException { synchronized (SipPhone.class) { if ((mForegroundCall.getState() != SipCall.State.ACTIVE) || (mForegroundCall.getState() != SipCall.State.ACTIVE)) { throw new CallStateException( "wrong state to merge calls: fg=" + mForegroundCall.getState() + ", bg=" + mBackgroundCall.getState()); } if (DBG) log("conference: merge fg & bg"); mForegroundCall.merge(mBackgroundCall); } }
public boolean canTake(Object incomingCall) { // FIXME: Is synchronizing on the class necessary, should we use a mLockObj? // Also there are many things not synchronized, of course // this may be true of CdmaPhone and GsmPhone too!!! synchronized (SipPhone.class) { if (!(incomingCall instanceof SipAudioCall)) { if (DBG) log("canTake: ret=false, not a SipAudioCall"); return false; } if (mRingingCall.getState().isAlive()) { if (DBG) log("canTake: ret=false, ringingCall not alive"); return false; } // FIXME: is it true that we cannot take any incoming call if // both foreground and background are active if (mForegroundCall.getState().isAlive() && mBackgroundCall.getState().isAlive()) { if (DBG) { log("canTake: ret=false," + " foreground and background both alive"); } return false; } try { SipAudioCall sipAudioCall = (SipAudioCall) incomingCall; if (DBG) log("canTake: taking call from: " + sipAudioCall.getPeerProfile().getUriString()); String localUri = sipAudioCall.getLocalProfile().getUriString(); if (localUri.equals(mProfile.getUriString())) { boolean makeCallWait = mForegroundCall.getState().isAlive(); mRingingCall.initIncomingCall(sipAudioCall, makeCallWait); if (sipAudioCall.getState() != SipSession.State.INCOMING_CALL) { // Peer cancelled the call! if (DBG) log(" canTake: call cancelled !!"); mRingingCall.reset(); } return true; } } catch (Exception e) { // Peer may cancel the call at any time during the time we hook // up ringingCall with sipAudioCall. Clean up ringingCall when // that happens. if (DBG) log(" canTake: exception e=" + e); mRingingCall.reset(); } if (DBG) log("canTake: NOT taking !!"); return false; } }
@Override public void onChanged(SipAudioCall call) { synchronized (SipPhone.class) { Call.State newState = getCallStateFrom(call); if (mState == newState) return; if (newState == Call.State.INCOMING) { setState(mOwner.getState()); // INCOMING or WAITING } else { if (mOwner == mRingingCall) { if (mRingingCall.getState() == Call.State.WAITING) { try { switchHoldingAndActive(); } catch (CallStateException e) { // disconnect the call. onCallEnded(DisconnectCause.LOCAL); return; } } mForegroundCall.switchWith(mRingingCall); } setState(newState); } mOwner.onConnectionStateChanged(SipConnection.this); if (SCN_DBG) log( "onChanged: " + mPeer.getUriString() + ": " + mState + " on phone " + getPhone()); } }
@Override public void acceptCall() throws CallStateException { synchronized (SipPhone.class) { if ((mRingingCall.getState() == Call.State.INCOMING) || (mRingingCall.getState() == Call.State.WAITING)) { if (DBG) log("acceptCall: accepting"); // Always unmute when answering a new call mRingingCall.setMute(false); mRingingCall.acceptCall(); } else { if (DBG) { log("acceptCall:" + " throw CallStateException(\"phone not ringing\")"); } throw new CallStateException("phone not ringing"); } } }
@Override public void sendDtmf(char c) { if (!PhoneNumberUtils.is12Key(c)) { loge("sendDtmf called with invalid character '" + c + "'"); } else if (mForegroundCall.getState().isAlive()) { synchronized (SipPhone.class) { mForegroundCall.sendDtmf(c); } } }
@Override public void rejectCall() throws CallStateException { synchronized (SipPhone.class) { if (mRingingCall.getState().isRinging()) { if (DBG) log("rejectCall: rejecting"); mRingingCall.rejectCall(); } else { if (DBG) { log("rejectCall:" + " throw CallStateException(\"phone not ringing\")"); } throw new CallStateException("phone not ringing"); } } }
private Connection dialInternal(String dialString) throws CallStateException { if (DBG) log("dialInternal: dialString=" + (VDBG ? dialString : "xxxxxx")); clearDisconnected(); if (!canDial()) { throw new CallStateException("dialInternal: cannot dial in current state"); } if (mForegroundCall.getState() == SipCall.State.ACTIVE) { switchHoldingAndActive(); } if (mForegroundCall.getState() != SipCall.State.IDLE) { // we should have failed in !canDial() above before we get here throw new CallStateException("cannot dial in current state"); } mForegroundCall.setMute(false); try { Connection c = mForegroundCall.dial(dialString); return c; } catch (SipException e) { loge("dialInternal: ", e); throw new CallStateException("dial error: " + e); } }
@Override public boolean getMute() { return (mForegroundCall.getState().isAlive() ? mForegroundCall.getMute() : mBackgroundCall.getMute()); }