Merge "SipAudioCallImpl: deliver call change failure and don't end call when getting an error in a call."
diff --git a/java/gov/nist/javax/sip/header/Via.java b/java/gov/nist/javax/sip/header/Via.java
index 2253394..be40962 100644
--- a/java/gov/nist/javax/sip/header/Via.java
+++ b/java/gov/nist/javax/sip/header/Via.java
@@ -96,6 +96,8 @@
      */
     protected String comment;
 
+    private boolean rPortFlag = false;
+
     /** Default constructor
     */
     public Via() {
@@ -265,6 +267,7 @@
         if (comment != null) {
             buffer.append(SP).append(LPAREN).append(comment).append(RPAREN);
         }
+        if (rPortFlag) buffer.append(";rport");
         return buffer;
     }
 
@@ -324,11 +327,7 @@
      * Set the RPort flag parameter
      */
     public void setRPort(){
-        try {
-            this.setParameter(Via.RPORT,"");
-        } catch (ParseException e) {
-            e.printStackTrace();    // should not occur
-        }
+        rPortFlag = true;
     }
 
     /**
diff --git a/src/android/net/sip/SipSessionState.java b/src/android/net/sip/SipSessionState.java
index c43b011..5bab112 100644
--- a/src/android/net/sip/SipSessionState.java
+++ b/src/android/net/sip/SipSessionState.java
@@ -49,7 +49,10 @@
     IN_CALL,
 
     /** Some error occurs when making a remote call to {@link ISipSession}. */
-    REMOTE_ERROR;
+    REMOTE_ERROR,
+
+    /** When an OPTIONS request is sent. */
+    PINGING;
 
     /**
      * Checks if the specified string represents the same state as this object.
diff --git a/src/com/android/sip/SipHelper.java b/src/com/android/sip/SipHelper.java
index 0e2280c..594857c 100644
--- a/src/com/android/sip/SipHelper.java
+++ b/src/com/android/sip/SipHelper.java
@@ -138,8 +138,10 @@
             throws ParseException, SipException {
         List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(1);
         ListeningPoint lp = getListeningPoint();
-        viaHeaders.add(mHeaderFactory.createViaHeader(lp.getIPAddress(),
-                lp.getPort(), lp.getTransport(), null));
+        ViaHeader viaHeader = mHeaderFactory.createViaHeader(lp.getIPAddress(),
+                lp.getPort(), lp.getTransport(), null);
+        viaHeader.setRPort();
+        viaHeaders.add(viaHeader);
         return viaHeaders;
     }
 
@@ -173,12 +175,15 @@
         return uri;
     }
 
-    public void sendKeepAlive(SipProfile profile)
+    public ClientTransaction sendKeepAlive(SipProfile userProfile, String tag)
             throws SipException {
         try {
-            String proxy = profile.getProxyAddress();
-            if (proxy == null) proxy = profile.getSipDomain();
-            getListeningPoint().sendHeartbeat(proxy, profile.getPort());
+            Request request = createRequest(Request.OPTIONS, userProfile, tag);
+
+            ClientTransaction clientTransaction =
+                    mSipProvider.getNewClientTransaction(request);
+            clientTransaction.sendRequest();
+            return clientTransaction;
         } catch (Exception e) {
             throw new SipException("sendKeepAlive()", e);
         }
@@ -187,19 +192,7 @@
     public ClientTransaction sendRegister(SipProfile userProfile, String tag,
             int expiry) throws SipException {
         try {
-            FromHeader fromHeader = createFromHeader(userProfile, tag);
-            ToHeader toHeader = createToHeader(userProfile);
-            SipURI requestURI = mAddressFactory.createSipURI("sip:"
-                    + userProfile.getSipDomain());
-            List<ViaHeader> viaHeaders = createViaHeaders();
-            CallIdHeader callIdHeader = createCallIdHeader();
-            CSeqHeader cSeqHeader = createCSeqHeader(Request.REGISTER);
-            MaxForwardsHeader maxForwards = createMaxForwardsHeader();
-
-            Request request = mMessageFactory.createRequest(requestURI,
-                    Request.REGISTER, callIdHeader, cSeqHeader, fromHeader,
-                    toHeader, viaHeaders, maxForwards);
-
+            Request request = createRequest(Request.REGISTER, userProfile, tag);
             if (expiry == 0) {
                 // remove all previous registrations by wildcard
                 // rfc3261#section-10.2.2
@@ -208,9 +201,6 @@
                 request.addHeader(createContactHeader(userProfile));
             }
             request.addHeader(mHeaderFactory.createExpiresHeader(expiry));
-            Header userAgentHeader = mHeaderFactory.createHeader("User-Agent",
-                    "SIPAUA/0.1.001");
-            request.addHeader(userAgentHeader);
 
             ClientTransaction clientTransaction =
                     mSipProvider.getNewClientTransaction(request);
@@ -221,6 +211,25 @@
         }
     }
 
+    private Request createRequest(String requestType, SipProfile userProfile,
+            String tag) throws ParseException, SipException {
+        FromHeader fromHeader = createFromHeader(userProfile, tag);
+        ToHeader toHeader = createToHeader(userProfile);
+        SipURI requestURI = mAddressFactory.createSipURI("sip:"
+                + userProfile.getSipDomain());
+        List<ViaHeader> viaHeaders = createViaHeaders();
+        CallIdHeader callIdHeader = createCallIdHeader();
+        CSeqHeader cSeqHeader = createCSeqHeader(requestType);
+        MaxForwardsHeader maxForwards = createMaxForwardsHeader();
+        Request request = mMessageFactory.createRequest(requestURI,
+                requestType, callIdHeader, cSeqHeader, fromHeader,
+                toHeader, viaHeaders, maxForwards);
+        Header userAgentHeader = mHeaderFactory.createHeader("User-Agent",
+                "SIPAUA/0.1.001");
+        request.addHeader(userAgentHeader);
+        return request;
+    }
+
     public ClientTransaction handleChallenge(ResponseEvent responseEvent,
             AccountManager accountManager) throws SipException {
         AuthenticationHelper authenticationHelper =
diff --git a/src/com/android/sip/SipServiceImpl.java b/src/com/android/sip/SipServiceImpl.java
index f926905..55ae2a7 100644
--- a/src/com/android/sip/SipServiceImpl.java
+++ b/src/com/android/sip/SipServiceImpl.java
@@ -479,22 +479,46 @@
 
     private class KeepAliveProcess implements Runnable {
         private SipSessionGroup.SipSessionImpl mSession;
-        private static final int DURATION = 15;
+        private static final int INCREMENT = 15;
+        private static final int MAX_RETRY = 4;
+        private int interval = INCREMENT;
+        private boolean maxIntervalMeasured = false;
 
         public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
             mSession = session;
         }
 
         public void start() {
-            mTimer.set(DURATION * 1000, this);
+            mTimer.set(interval * 1000, this);
         }
 
         public void run() {
+            int retry = 0;
             Log.d(TAG, "  ~~~ keepalive");
-            try {
+            mTimer.cancel(this);
+            for (retry = 0; retry < MAX_RETRY; ++retry) {
                 mSession.sendKeepAlive();
-            } catch (SipException e) {
-                Log.e(TAG, "Cannot send keepalive", e);
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "keepalive interrupted", e);
+                    return;
+                }
+                if (SipSessionState.READY_TO_CALL.equals(mSession.getState())) {
+                    break;
+                }
+            }
+            if (retry == MAX_RETRY) {
+                Log.w(TAG, "Server didn't respond SIP OPTIONS req:" + mSession);
+                return;
+            }
+            if (mSession.isReRegisterRequired()) {
+                mSession.register(EXPIRY_TIME);
+                interval -= INCREMENT;
+                maxIntervalMeasured = true;
+            } else {
+                if (!maxIntervalMeasured) interval += INCREMENT;
+                mTimer.set(interval * 1000, this);
             }
         }
 
@@ -616,6 +640,7 @@
             FLog.d(TAG, "onRegistering(): " + session + ": " + mSession);
             synchronized (SipServiceImpl.this) {
                 if (!isStopped() && (session != mSession)) return;
+                mRegistered = false;
                 try {
                     mProxy.onRegistering(session);
                 } catch (Throwable t) {
diff --git a/src/com/android/sip/SipSessionGroup.java b/src/com/android/sip/SipSessionGroup.java
index d05d5bd..5d16bcf 100644
--- a/src/com/android/sip/SipSessionGroup.java
+++ b/src/com/android/sip/SipSessionGroup.java
@@ -63,6 +63,7 @@
 import javax.sip.header.CSeqHeader;
 import javax.sip.header.ExpiresHeader;
 import javax.sip.header.FromHeader;
+import javax.sip.header.ViaHeader;
 import javax.sip.message.Message;
 import javax.sip.message.Request;
 import javax.sip.message.Response;
@@ -87,6 +88,7 @@
     private SipStack mSipStack;
     private SipHelper mSipHelper;
     private String mLastNonce;
+    private int mRPort;
 
     // session that processes INVITE requests
     private SipSessionImpl mCallReceiverSession;
@@ -320,6 +322,7 @@
         ClientTransaction mClientTransaction;
         byte[] mPeerSessionDescription;
         boolean mInCall;
+        boolean mReRegisterFlag = false;
 
         public SipSessionImpl(ISipSessionListener listener) {
             setListener(listener);
@@ -422,9 +425,15 @@
             }
         }
 
-        public void sendKeepAlive() throws SipException {
-            synchronized (SipSessionGroup.this) {
-                mSipHelper.sendKeepAlive(mLocalProfile);
+        public boolean isReRegisterRequired() {
+            return mReRegisterFlag;
+        }
+
+        public void sendKeepAlive() {
+            try {
+                process(new OptionsCommand());
+            } catch (SipException e) {
+                Log.e(TAG, "sendKeepAlive failed", e);
             }
         }
 
@@ -463,6 +472,9 @@
                 case DEREGISTERING:
                     processed = registeringToReady(evt);
                     break;
+                case PINGING:
+                    processed = parseOptionsResult(evt);
+                    break;
                 case READY_TO_CALL:
                     processed = readyForCall(evt);
                     break;
@@ -565,6 +577,31 @@
             return expires;
         }
 
+        private boolean parseOptionsResult(EventObject evt) {
+            if (expectResponse(Request.OPTIONS, evt)) {
+                ResponseEvent event = (ResponseEvent) evt;
+                int rPort = getRPortFromResponse(event.getResponse());
+                if (rPort != -1) {
+                    if (mRPort == 0) mRPort = rPort;
+                    if (mRPort != rPort) {
+                        mReRegisterFlag = true;
+                        Log.w(TAG, "The rport is changed, we need to re-register now!");
+                    }
+                } else {
+                    Log.w(TAG, "Remote side did not respect our rport request");
+                }
+                reset();
+                return true;
+            }
+            return false;
+        }
+
+        private int getRPortFromResponse(Response response) {
+            ViaHeader viaHeader = (ViaHeader)(response.getHeader(
+                    SIPHeaderNames.VIA));
+            return (viaHeader == null) ? -1 : viaHeader.getRPort();
+        }
+
         private boolean registeringToReady(EventObject evt)
                 throws SipException {
             if (expectResponse(Request.REGISTER, evt)) {
@@ -663,6 +700,13 @@
                 mState = SipSessionState.DEREGISTERING;
                 mProxy.onRegistering(this);
                 return true;
+            } else if (evt instanceof OptionsCommand) {
+                mClientTransaction = mSipHelper.sendKeepAlive(mLocalProfile,
+                        generateTag());
+                mDialog = mClientTransaction.getDialog();
+                mState = SipSessionState.PINGING;
+                addSipSession(this);
+                return true;
             }
             return false;
         }
@@ -982,6 +1026,12 @@
         }
     }
 
+    private class OptionsCommand extends EventObject {
+        public OptionsCommand() {
+            super(SipSessionGroup.this);
+        }
+    }
+
     private class RegisterCommand extends EventObject {
         private int mDuration;
 
diff --git a/src/com/android/sip/WakeupTimer.java b/src/com/android/sip/WakeupTimer.java
index e0cc61d..6739081 100644
--- a/src/com/android/sip/WakeupTimer.java
+++ b/src/com/android/sip/WakeupTimer.java
@@ -90,8 +90,9 @@
         for (MyEvent e : mEventQueue) {
             int remainingTime = (int) (e.mTriggerTime - now);
             remainingTime = remainingTime / minPeriod * minPeriod;
-            e.mTriggerTime = now + remainingTime;
-
+            if (remainingTime != 0) {
+                e.mTriggerTime = now + remainingTime;
+            }
             e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod;
         }
         TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(