diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java index 0822350978016..baf9a8e96c10f 100644 --- a/services/java/com/android/server/sip/SipSessionGroup.java +++ b/services/java/com/android/server/sip/SipSessionGroup.java @@ -84,7 +84,8 @@ class SipSessionGroup implements SipListener { private static final boolean DEBUG_PING = DEBUG && false; private static final String ANONYMOUS = "anonymous"; private static final String SERVER_ERROR_PREFIX = "Response: "; - private static final int EXPIRY_TIME = 3600; + private static final int EXPIRY_TIME = 3600; // in seconds + private static final int CANCEL_CALL_TIMER = 5; // in seconds private static final EventObject DEREGISTER = new EventObject("Deregister"); private static final EventObject END_CALL = new EventObject("End call"); @@ -363,6 +364,40 @@ class SipSessionGroup implements SipListener { String mPeerSessionDescription; boolean mInCall; boolean mReRegisterFlag = false; + SessionTimer mTimer; + + // lightweight timer + class SessionTimer { + private boolean mRunning = true; + + void start(final int timeout) { + new Thread(new Runnable() { + public void run() { + sleep(timeout); + if (mRunning) timeout(); + } + }).start(); + } + + synchronized void cancel() { + mRunning = false; + this.notify(); + } + + private void timeout() { + synchronized (SipSessionGroup.this) { + onError(SipErrorCode.TIME_OUT, "Session timed out!"); + } + } + + private synchronized void sleep(int timeout) { + try { + this.wait(timeout * 1000); + } catch (InterruptedException e) { + Log.e(TAG, "session timer interrupted!"); + } + } + } public SipSessionImpl(ISipSessionListener listener) { setListener(listener); @@ -382,6 +417,8 @@ class SipSessionGroup implements SipListener { mServerTransaction = null; mClientTransaction = null; mPeerSessionDescription = null; + + cancelSessionTimer(); } public boolean isInCall() { @@ -434,16 +471,16 @@ class SipSessionGroup implements SipListener { }).start(); } - public void makeCall(SipProfile peerProfile, - String sessionDescription) { - doCommandAsync( - new MakeCallCommand(peerProfile, sessionDescription)); + public void makeCall(SipProfile peerProfile, String sessionDescription, + int timeout) { + doCommandAsync(new MakeCallCommand(peerProfile, sessionDescription, + timeout)); } - public void answerCall(String sessionDescription) { + public void answerCall(String sessionDescription, int timeout) { try { - processCommand( - new MakeCallCommand(mPeerProfile, sessionDescription)); + processCommand(new MakeCallCommand(mPeerProfile, + sessionDescription, timeout)); } catch (SipException e) { onError(e); } @@ -453,9 +490,15 @@ class SipSessionGroup implements SipListener { doCommandAsync(END_CALL); } - public void changeCall(String sessionDescription) { - doCommandAsync( - new MakeCallCommand(mPeerProfile, sessionDescription)); + public void changeCall(String sessionDescription, int timeout) { + doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, + timeout)); + } + + public void changeCallWithTimeout( + String sessionDescription, int timeout) { + doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, + timeout)); } public void register(int duration) { @@ -800,6 +843,7 @@ class SipSessionGroup implements SipListener { addSipSession(this); mState = SipSessionState.OUTGOING_CALL; mProxy.onCalling(this); + startSessionTimer(cmd.getTimeout()); return true; } else if (evt instanceof RegisterCommand) { int duration = ((RegisterCommand) evt).getDuration(); @@ -831,6 +875,7 @@ class SipSessionGroup implements SipListener { ((MakeCallCommand) evt).getSessionDescription(), mServerTransaction); mState = SipSessionState.INCOMING_CALL_ANSWERING; + startSessionTimer(((MakeCallCommand) evt).getTimeout()); return true; } else if (END_CALL == evt) { mSipHelper.sendInviteBusyHere(mInviteReceived, @@ -873,6 +918,7 @@ class SipSessionGroup implements SipListener { if (mState == SipSessionState.OUTGOING_CALL) { mState = SipSessionState.OUTGOING_CALL_RING_BACK; mProxy.onRingingBack(this); + cancelSessionTimer(); } return true; case Response.OK: @@ -885,10 +931,10 @@ class SipSessionGroup implements SipListener { if (handleAuthentication(event)) { addSipSession(this); } else if (mLastNonce == null) { - endCallOnError(SipErrorCode.SERVER_ERROR, + onError(SipErrorCode.SERVER_ERROR, "server does not provide challenge"); } else { - endCallOnError(SipErrorCode.INVALID_CREDENTIALS, + onError(SipErrorCode.INVALID_CREDENTIALS, "incorrect username or password"); } return true; @@ -914,6 +960,7 @@ class SipSessionGroup implements SipListener { // response. mSipHelper.sendCancel(mClientTransaction); mState = SipSessionState.OUTGOING_CALL_CANCELING; + startSessionTimer(CANCEL_CALL_TIMER); return true; } return false; @@ -926,9 +973,13 @@ class SipSessionGroup implements SipListener { Response response = event.getResponse(); int statusCode = response.getStatusCode(); if (expectResponse(Request.CANCEL, evt)) { - if (statusCode == Response.OK) { - // do nothing; wait for REQUEST_TERMINATED - return true; + switch (statusCode) { + case Response.OK: + // do nothing; wait for REQUEST_TERMINATED + return true; + case Response.REQUEST_TERMINATED: + endCallNormally(); + return true; } } else if (expectResponse(Request.INVITE, evt)) { if (statusCode == Response.OK) { @@ -978,11 +1029,27 @@ class SipSessionGroup implements SipListener { mClientTransaction = mSipHelper.sendReinvite(mDialog, ((MakeCallCommand) evt).getSessionDescription()); mState = SipSessionState.OUTGOING_CALL; + startSessionTimer(((MakeCallCommand) evt).getTimeout()); return true; } return false; } + // timeout in seconds + private void startSessionTimer(int timeout) { + if (timeout > 0) { + mTimer = new SessionTimer(); + mTimer.start(timeout); + } + } + + private void cancelSessionTimer() { + if (mTimer != null) { + mTimer.cancel(); + mTimer = null; + } + } + private String createErrorMessage(Response response) { return String.format(SERVER_ERROR_PREFIX + "%s (%d)", response.getReasonPhrase(), response.getStatusCode()); @@ -991,15 +1058,10 @@ class SipSessionGroup implements SipListener { private void establishCall() { mState = SipSessionState.IN_CALL; mInCall = true; + cancelSessionTimer(); mProxy.onCallEstablished(this, mPeerSessionDescription); } - private void fallbackToPreviousInCall(Throwable exception) { - exception = getRootCause(exception); - fallbackToPreviousInCall(getErrorCode(exception), - exception.toString()); - } - private void fallbackToPreviousInCall(SipErrorCode errorCode, String message) { mState = SipSessionState.IN_CALL; @@ -1022,6 +1084,7 @@ class SipSessionGroup implements SipListener { } private void onError(SipErrorCode errorCode, String message) { + cancelSessionTimer(); switch (mState) { case REGISTERING: case DEREGISTERING: @@ -1255,11 +1318,18 @@ class SipSessionGroup implements SipListener { private class MakeCallCommand extends EventObject { private String mSessionDescription; + private int mTimeout; // in seconds public MakeCallCommand(SipProfile peerProfile, String sessionDescription) { + this(peerProfile, sessionDescription, -1); + } + + public MakeCallCommand(SipProfile peerProfile, + String sessionDescription, int timeout) { super(peerProfile); mSessionDescription = sessionDescription; + mTimeout = timeout; } public SipProfile getPeerProfile() { @@ -1269,6 +1339,9 @@ class SipSessionGroup implements SipListener { public String getSessionDescription() { return mSessionDescription; } - } + public int getTimeout() { + return mTimeout; + } + } } diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java index 07dd35d896dea..d7dc4abcd1d45 100755 --- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java +++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java @@ -74,6 +74,7 @@ import java.util.List; public class SipPhone extends SipPhoneBase { private static final String LOG_TAG = "SipPhone"; private static final boolean LOCAL_DEBUG = true; + private static final int SESSION_TIMEOUT = 8; // in seconds // A call that is ringing or (call) waiting private SipCall ringingCall = new SipCall(); @@ -675,7 +676,7 @@ public class SipPhone extends SipPhoneBase { void acceptCall() throws CallStateException { try { - mSipAudioCall.answerCall(); + mSipAudioCall.answerCall(SESSION_TIMEOUT); } catch (SipException e) { throw new CallStateException("acceptCall(): " + e); } @@ -693,7 +694,7 @@ public class SipPhone extends SipPhoneBase { void dial() throws SipException { setState(Call.State.DIALING); mSipAudioCall = mSipManager.makeAudioCall(mContext, mProfile, - mPeer, null); + mPeer, null, SESSION_TIMEOUT); mSipAudioCall.setRingbackToneEnabled(false); mSipAudioCall.setListener(mAdapter); } @@ -701,7 +702,7 @@ public class SipPhone extends SipPhoneBase { void hold() throws CallStateException { setState(Call.State.HOLDING); try { - mSipAudioCall.holdCall(); + mSipAudioCall.holdCall(SESSION_TIMEOUT); } catch (SipException e) { throw new CallStateException("hold(): " + e); } @@ -711,7 +712,7 @@ public class SipPhone extends SipPhoneBase { mSipAudioCall.setAudioGroup(audioGroup); setState(Call.State.ACTIVE); try { - mSipAudioCall.continueCall(); + mSipAudioCall.continueCall(SESSION_TIMEOUT); } catch (SipException e) { throw new CallStateException("unhold(): " + e); } diff --git a/voip/java/android/net/sip/ISipSession.aidl b/voip/java/android/net/sip/ISipSession.aidl index cd8bd2cc30aec..5661b8fdf07a3 100644 --- a/voip/java/android/net/sip/ISipSession.aidl +++ b/voip/java/android/net/sip/ISipSession.aidl @@ -112,9 +112,11 @@ interface ISipSession { * * @param callee the SIP profile to make the call to * @param sessionDescription the session description of this call + * @param timeout the session will be timed out if the call is not + * established within {@code timeout} seconds * @see ISipSessionListener */ - void makeCall(in SipProfile callee, String sessionDescription); + void makeCall(in SipProfile callee, String sessionDescription, int timeout); /** * Answers an incoming call with the specified session description. The @@ -122,8 +124,10 @@ interface ISipSession { * {@link SipSessionState#INCOMING_CALL}. * * @param sessionDescription the session description to answer this call + * @param timeout the session will be timed out if the call is not + * established within {@code timeout} seconds */ - void answerCall(String sessionDescription); + void answerCall(String sessionDescription, int timeout); /** * Ends an established call, terminates an outgoing call or rejects an @@ -140,6 +144,8 @@ interface ISipSession { * to call when the session state is in {@link SipSessionState#IN_CALL}. * * @param sessionDescription the new session description + * @param timeout the session will be timed out if the call is not + * established within {@code timeout} seconds */ - void changeCall(String sessionDescription); + void changeCall(String sessionDescription, int timeout); } diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java index 573760ee8924a..2a9a65b50d160 100644 --- a/voip/java/android/net/sip/SipAudioCall.java +++ b/voip/java/android/net/sip/SipAudioCall.java @@ -158,12 +158,18 @@ public interface SipAudioCall { void close(); /** - * Initiates an audio call to the specified profile. + * Initiates an audio call to the specified profile. The attempt will be + * timed out if the call is not established within {@code timeout} seconds + * and {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} + * will be called. * * @param callee the SIP profile to make the call to * @param sipManager the {@link SipManager} object to help make call with + * @param timeout the timeout value in seconds + * @see Listener.onError */ - void makeCall(SipProfile callee, SipManager sipManager) throws SipException; + void makeCall(SipProfile callee, SipManager sipManager, int timeout) + throws SipException; /** * Attaches an incoming call to this call object. @@ -179,18 +185,38 @@ public interface SipAudioCall { /** * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is - * called. + * called. The attempt will be timed out if the call is not established + * within {@code timeout} seconds and + * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} + * will be called. + * + * @param timeout the timeout value in seconds + * @see Listener.onError */ - void holdCall() throws SipException; + void holdCall(int timeout) throws SipException; - /** Answers a call. */ - void answerCall() throws SipException; + /** + * Answers a call. The attempt will be timed out if the call is not + * established within {@code timeout} seconds and + * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} + * will be called. + * + * @param timeout the timeout value in seconds + * @see Listener.onError + */ + void answerCall(int timeout) throws SipException; /** * Continues a call that's on hold. When succeeds, - * {@link Listener#onCallEstablished} is called. + * {@link Listener#onCallEstablished} is called. The attempt will be timed + * out if the call is not established within {@code timeout} seconds and + * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} + * will be called. + * + * @param timeout the timeout value in seconds + * @see Listener.onError */ - void continueCall() throws SipException; + void continueCall(int timeout) throws SipException; /** Puts the device to speaker mode. */ void setSpeakerMode(boolean speakerMode); diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java index 8bf486a9a7700..fcabcc4d2274b 100644 --- a/voip/java/android/net/sip/SipAudioCallImpl.java +++ b/voip/java/android/net/sip/SipAudioCallImpl.java @@ -55,6 +55,7 @@ public class SipAudioCallImpl extends SipSessionAdapter private static final boolean DONT_RELEASE_SOCKET = false; private static final String AUDIO = "audio"; private static final int DTMF = 101; + private static final int SESSION_TIMEOUT = 5; // in seconds private Context mContext; private SipProfile mLocalProfile; @@ -144,12 +145,21 @@ public class SipAudioCallImpl extends SipSessionAdapter if (closeRtp) stopCall(RELEASE_SOCKET); stopRingbackTone(); stopRinging(); - mSipSession = null; + mInCall = false; mHold = false; mSessionId = -1L; mErrorCode = null; mErrorMessage = null; + + if (mSipSession != null) { + try { + mSipSession.setListener(null); + } catch (RemoteException e) { + // don't care + } + mSipSession = null; + } } public synchronized SipProfile getLocalProfile() { @@ -219,7 +229,7 @@ public class SipAudioCallImpl extends SipSessionAdapter // session changing request try { mPeerSd = new SdpSessionDescription(sessionDescription); - answerCall(); + answerCall(SESSION_TIMEOUT); } catch (Throwable e) { Log.e(TAG, "onRinging()", e); session.endCall(); @@ -346,14 +356,15 @@ public class SipAudioCallImpl extends SipSessionAdapter } public synchronized void makeCall(SipProfile peerProfile, - SipManager sipManager) throws SipException { + SipManager sipManager, int timeout) throws SipException { try { mSipSession = sipManager.createSipSession(mLocalProfile, this); if (mSipSession == null) { throw new SipException( "Failed to create SipSession; network available?"); } - mSipSession.makeCall(peerProfile, createOfferSessionDescription()); + mSipSession.makeCall(peerProfile, createOfferSessionDescription(), + timeout); } catch (Throwable e) { if (e instanceof SipException) { throw (SipException) e; @@ -376,10 +387,10 @@ public class SipAudioCallImpl extends SipSessionAdapter } } - public synchronized void holdCall() throws SipException { + public synchronized void holdCall(int timeout) throws SipException { if (mHold) return; try { - mSipSession.changeCall(createHoldSessionDescription()); + mSipSession.changeCall(createHoldSessionDescription(), timeout); mHold = true; } catch (Throwable e) { throwSipException(e); @@ -389,21 +400,21 @@ public class SipAudioCallImpl extends SipSessionAdapter if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD); } - public synchronized void answerCall() throws SipException { + public synchronized void answerCall(int timeout) throws SipException { try { stopRinging(); - mSipSession.answerCall(createAnswerSessionDescription()); + mSipSession.answerCall(createAnswerSessionDescription(), timeout); } catch (Throwable e) { Log.e(TAG, "answerCall()", e); throwSipException(e); } } - public synchronized void continueCall() throws SipException { + public synchronized void continueCall(int timeout) throws SipException { if (!mHold) return; try { mHold = false; - mSipSession.changeCall(createContinueSessionDescription()); + mSipSession.changeCall(createContinueSessionDescription(), timeout); } catch (Throwable e) { throwSipException(e); } diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java index 5b1767da814d2..36895cd66ae2c 100644 --- a/voip/java/android/net/sip/SipManager.java +++ b/voip/java/android/net/sip/SipManager.java @@ -218,44 +218,55 @@ public class SipManager { } /** - * Creates a {@link SipAudioCall} to make a call. + * Creates a {@link SipAudioCall} to make a call. The attempt will be timed + * out if the call is not established within {@code timeout} seconds and + * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} + * will be called. * * @param context context to create a {@link SipAudioCall} object * @param localProfile the SIP profile to make the call from * @param peerProfile the SIP profile to make the call to * @param listener to listen to the call events from {@link SipAudioCall}; * can be null + * @param timeout the timeout value in seconds * @return a {@link SipAudioCall} object * @throws SipException if calling the SIP service results in an error + * @see SipAudioCall.Listener.onError */ public SipAudioCall makeAudioCall(Context context, SipProfile localProfile, - SipProfile peerProfile, SipAudioCall.Listener listener) + SipProfile peerProfile, SipAudioCall.Listener listener, int timeout) throws SipException { SipAudioCall call = new SipAudioCallImpl(context, localProfile); call.setListener(listener); - call.makeCall(peerProfile, this); + call.makeCall(peerProfile, this, timeout); return call; } /** * Creates a {@link SipAudioCall} to make a call. To use this method, one - * must call {@link #open(SipProfile)} first. + * must call {@link #open(SipProfile)} first. The attempt will be timed out + * if the call is not established within {@code timeout} seconds and + * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} + * will be called. * * @param context context to create a {@link SipAudioCall} object * @param localProfileUri URI of the SIP profile to make the call from * @param peerProfileUri URI of the SIP profile to make the call to * @param listener to listen to the call events from {@link SipAudioCall}; * can be null + * @param timeout the timeout value in seconds * @return a {@link SipAudioCall} object * @throws SipException if calling the SIP service results in an error + * @see SipAudioCall.Listener.onError */ public SipAudioCall makeAudioCall(Context context, String localProfileUri, - String peerProfileUri, SipAudioCall.Listener listener) + String peerProfileUri, SipAudioCall.Listener listener, int timeout) throws SipException { try { return makeAudioCall(context, new SipProfile.Builder(localProfileUri).build(), - new SipProfile.Builder(peerProfileUri).build(), listener); + new SipProfile.Builder(peerProfileUri).build(), listener, + timeout); } catch (ParseException e) { throw new SipException("build SipProfile", e); }