@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 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(); } }
void switchWith(SipCall that) { if (SC_DBG) log("switchWith"); synchronized (SipPhone.class) { SipCall tmp = new SipCall(); tmp.takeOver(this); this.takeOver(that); that.takeOver(tmp); } }
@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 clearDisconnected() { synchronized (SipPhone.class) { mRingingCall.clearDisconnected(); mForegroundCall.clearDisconnected(); mBackgroundCall.clearDisconnected(); updatePhoneState(); notifyPreciseCallStateChanged(); } }
@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"); } } }
@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); } }
@Override protected void onCallEnded(DisconnectCause cause) { if (getDisconnectCause() != DisconnectCause.LOCAL) { setDisconnectCause(cause); } synchronized (SipPhone.class) { setState(Call.State.DISCONNECTED); SipAudioCall sipAudioCall = mSipAudioCall; // FIXME: This goes null and is synchronized, but many uses aren't sync'd mSipAudioCall = null; String sessionState = (sipAudioCall == null) ? "" : (sipAudioCall.getState() + ", "); if (SCN_DBG) log( "[SipAudioCallAdapter] onCallEnded: " + mPeer.getUriString() + ": " + sessionState + "cause: " + getDisconnectCause() + ", on phone " + getPhone()); if (sipAudioCall != null) { sipAudioCall.setListener(null); sipAudioCall.close(); } mOwner.onConnectionEnded(SipConnection.this); } }
@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"); } } }
public void conference(Call that) throws CallStateException { synchronized (SipPhone.class) { if (!(that instanceof SipCall)) { throw new CallStateException( "expect " + SipCall.class + ", cannot merge with " + that.getClass()); } mForegroundCall.merge((SipCall) that); } }
@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); } }
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 void setEchoSuppressionEnabled(boolean enabled) { // TODO: Remove the enabled argument. We should check the speakerphone // state with AudioManager instead of keeping a state here so the // method with a state argument is redundant. Also rename the method // to something like onSpeaerphoneStateChanged(). Echo suppression may // not be available on every device. synchronized (SipPhone.class) { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); String echoSuppression = audioManager.getParameters("ec_supported"); if (echoSuppression.contains("off")) { mForegroundCall.setAudioGroupMode(); } } }
void merge(SipCall that) throws CallStateException { if (SC_DBG) log("merge:"); AudioGroup audioGroup = getAudioGroup(); // copy to an array to avoid concurrent modification as connections // in that.connections will be removed in add(SipConnection). Connection[] cc = that.mConnections.toArray(new Connection[that.mConnections.size()]); for (Connection c : cc) { SipConnection conn = (SipConnection) c; add(conn); if (conn.getState() == Call.State.HOLDING) { conn.unhold(audioGroup); } } that.setState(Call.State.IDLE); }
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 setMute(boolean muted) { synchronized (SipPhone.class) { mForegroundCall.setMute(muted); } }
@Override protected Phone getPhone() { return mOwner.getPhone(); }
@Override public boolean getMute() { return (mForegroundCall.getState().isAlive() ? mForegroundCall.getMute() : mBackgroundCall.getMute()); }