Index: /pjproject/trunk/pjmedia/src/pjmedia/sdp_neg.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/sdp_neg.c	(revision 5827)
+++ /pjproject/trunk/pjmedia/src/pjmedia/sdp_neg.c	(revision 5828)
@@ -511,9 +511,10 @@
     PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);
 
-    /* Can only do this in STATE_REMOTE_OFFER.
-     * If we already provide local offer, then rx_remote_answer() should
+    /* Can only do this in STATE_REMOTE_OFFER or WAIT_NEGO.
+     * If we already provide local offer, then set_remote_answer() should
      * be called instead of this function.
      */
-    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, 
+    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER ||
+    		     neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, 
 		     PJMEDIA_SDPNEG_EINSTATE);
 
Index: /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 5827)
+++ /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 5828)
@@ -1092,4 +1092,7 @@
      * current call setting.
      *
+     * Note: this callback may not be called if \a on_call_rx_reinvite()
+     * is implemented.
+     *
      * @param call_id	The call index.
      * @param offer	The new offer received.
@@ -1106,4 +1109,45 @@
 			     pjsip_status_code *code,
 			     pjsua_call_setting *opt);
+
+
+    /**
+     * Notify application when call has received a re-INVITE with offer
+     * from the peer. It allows more fine-grained control over the response
+     * to a re-INVITE. If application sets async to PJ_TRUE, it can send
+     * the reply manually using the function #pjsua_call_answer_with_sdp().
+     * Otherwise, by default the re-INVITE will be answered automatically
+     * after the callback returns.
+     *
+     * Currently, this callback is only called for re-INVITE with
+     * SDP, but app should be prepared to handle the case of re-INVITE
+     * without SDP.
+     *
+     * Remarks: If manually answering at a later timing, application may
+     * need to monitor on_call_tsx_state() callback to check whether
+     * the re-INVITE is already answered automatically with 487 due to
+     * being cancelled.
+     *
+     * Note: on_call_rx_offer() will still be called after this callback,
+     * but only if async is PJ_FALSE and code is 200. 
+     *
+     * @param call_id	The call index.
+     * @param offer	Remote offer.
+     * @param rdata     The received re-INVITE request.
+     * @param reserved	Reserved param, currently not used.
+     * @param async	On input, it is PJ_FALSE. Set to PJ_TRUE if
+     *			app wants to manually answer the re-INVITE.
+     * @param code	Status code to be returned for answering the
+     *			offer. On input, it contains status code 200.
+     *			Currently, valid values are only 200 and 488.
+     * @param opt	The current call setting, application can update
+     *			this setting for answering the offer.
+     */
+    void (*on_call_rx_reinvite)(pjsua_call_id call_id,
+    		                const pjmedia_sdp_session *offer,
+                                pjsip_rx_data *rdata,
+			     	void *reserved,
+			     	pj_bool_t *async,
+			     	pjsip_status_code *code,
+			     	pjsua_call_setting *opt);
 
 
@@ -5123,4 +5167,30 @@
 
 /**
+ * Same as #pjsua_call_answer2() but this function will set the SDP
+ * answer first before sending the response.
+ *
+ * @param call_id	Incoming call identification.
+ * @param sdp		SDP answer. 
+ * @param opt		Optional call setting.
+ * @param code		Status code, (100-699).
+ * @param reason	Optional reason phrase. If NULL, default text
+ *			will be used.
+ * @param msg_data	Optional list of headers etc to be added to outgoing
+ *			response message. Note that this message data will
+ *			be persistent in all next answers/responses for this
+ *			INVITE request.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t)
+pjsua_call_answer_with_sdp(pjsua_call_id call_id,
+			   const pjmedia_sdp_session *sdp, 
+			   const pjsua_call_setting *opt,
+			   unsigned code,
+			   const pj_str_t *reason,
+			   const pjsua_msg_data *msg_data);
+
+
+/**
  * Hangup call by using method that is appropriate according to the
  * call state. This function is different than answering the call with
Index: /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 5827)
+++ /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 5828)
@@ -193,4 +193,5 @@
 					    offer.			    */
     
+    pj_bool_t		 rx_reinv_async;/**< on_call_rx_reinvite() async.   */
     pj_timer_entry	 reinv_timer;  /**< Reinvite retry timer.	    */
     pj_bool_t	 	 reinv_pending;/**< Pending until CONFIRMED state.  */
Index: /pjproject/trunk/pjsip/include/pjsua2/call.hpp
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua2/call.hpp	(revision 5827)
+++ /pjproject/trunk/pjsip/include/pjsua2/call.hpp	(revision 5828)
@@ -867,4 +867,39 @@
 
 /**
+ * This structure contains parameters for Call::onCallRxReinvite() callback.
+ */
+struct OnCallRxReinviteParam
+{
+    /**
+     * The new offer received.
+     */
+    SdpSession          offer;
+
+    /**
+     * The incoming re-INVITE.
+     */
+    SipRxData           rdata;
+    
+    /**
+     * On input, it is false. Set to true if app wants to manually answer
+     * the re-INVITE.
+     */
+    bool		async;
+    
+    /**
+     * Status code to be returned for answering the offer. On input,
+     * it contains status code 200. Currently, valid values are only
+     * 200 and 488.
+     */
+    pjsip_status_code   statusCode;
+    
+    /**
+     * The current call setting, application can update this setting for
+     * answering the offer.
+     */
+    CallSetting         opt;
+};
+
+/**
  * This structure contains parameters for Call::onCallTxOffer() callback.
  */
@@ -1036,4 +1071,9 @@
      */
     SipTxOption         txOption;
+
+    /**
+     * SDP answer. Currently only used for Call::answer().
+     */
+    SdpSession		sdp;
     
 public:
@@ -1658,4 +1698,28 @@
     { PJ_UNUSED_ARG(prm); }
     
+    /**
+     * Notify application when call has received a re-INVITE offer from
+     * the peer. It allows more fine-grained control over the response to
+     * a re-INVITE. If application sets async to PJ_TRUE, it can send
+     * the reply manually using the function #Call::answer() and setting
+     * the SDP answer. Otherwise, by default the re-INVITE will be
+     * answered automatically after the callback returns.
+     *
+     * Currently, this callback is only called for re-INVITE with
+     * SDP, but app should be prepared to handle the case of re-INVITE
+     * without SDP.
+     *
+     * Remarks: If manually answering at a later timing, application may
+     * need to monitor onCallTsxState() callback to check whether
+     * the re-INVITE is already answered automatically with 487 due to
+     * being cancelled.
+     *
+     * Note: onCallRxOffer() will still be called after this callback,
+     * but only if prm.async is false and prm.code is 200. 
+     */
+    virtual void onCallRxReinvite(OnCallRxReinviteParam &prm)
+    { PJ_UNUSED_ARG(prm); }
+
+
     /**
      * Notify application when call has received INVITE with no SDP offer.
@@ -1806,8 +1870,11 @@
 
 private:
+    friend class Endpoint;
+
     Account             &acc;
     pjsua_call_id 	 id;
     Token                userData;
     std::vector<Media *> medias;
+    pj_pool_t		*sdp_pool;
 };
 
Index: /pjproject/trunk/pjsip/include/pjsua2/endpoint.hpp
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua2/endpoint.hpp	(revision 5827)
+++ /pjproject/trunk/pjsip/include/pjsua2/endpoint.hpp	(revision 5828)
@@ -1715,4 +1715,11 @@
                                  pjsip_status_code *code,
                                  pjsua_call_setting *opt);
+    static void on_call_rx_reinvite(pjsua_call_id call_id,
+                                    const pjmedia_sdp_session *offer,
+                                    pjsip_rx_data *rdata,
+                                    void *reserved,
+                                    pj_bool_t *async,
+                                    pjsip_status_code *code,
+                                    pjsua_call_setting *opt);
     static void on_call_tx_offer(pjsua_call_id call_id,
 				 void *reserved,
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 5827)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 5828)
@@ -80,5 +80,12 @@
  */
 static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
-				   const pjmedia_sdp_session *offer);
+				struct pjsip_inv_on_rx_offer_cb_param *param);
+
+/*
+ * Called when receiving re-INVITE.
+ */
+static pj_status_t pjsua_call_on_rx_reinvite(pjsip_inv_session *inv,
+    		                  	     const pjmedia_sdp_session *offer,
+                                  	     pjsip_rx_data *rdata);
 
 /*
@@ -191,8 +198,11 @@
     inv_cb.on_new_session = &pjsua_call_on_forked;
     inv_cb.on_media_update = &pjsua_call_on_media_update;
-    inv_cb.on_rx_offer = &pjsua_call_on_rx_offer;
+    inv_cb.on_rx_offer2 = &pjsua_call_on_rx_offer;
     inv_cb.on_create_offer = &pjsua_call_on_create_offer;
     inv_cb.on_tsx_state_changed = &pjsua_call_on_tsx_state_changed;
     inv_cb.on_redirected = &pjsua_call_on_redirected;
+    if (pjsua_var.ua_cfg.cb.on_call_rx_reinvite) {
+    	inv_cb.on_rx_reinvite = &pjsua_call_on_rx_reinvite;
+    }
 
     /* Initialize invite session module: */
@@ -2404,4 +2414,38 @@
 
 /*
+ * Send response to incoming INVITE request.
+ */
+PJ_DEF(pj_status_t)
+pjsua_call_answer_with_sdp(pjsua_call_id call_id,
+			   const pjmedia_sdp_session *sdp, 
+			   const pjsua_call_setting *opt,
+			   unsigned code,
+			   const pj_str_t *reason,
+			   const pjsua_msg_data *msg_data)
+{
+    pjsua_call *call;
+    pjsip_dialog *dlg = NULL;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    status = acquire_call("pjsua_call_answer_with_sdp()",
+    			  call_id, &call, &dlg);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pjsip_inv_set_sdp_answer(call->inv, sdp);
+
+    pjsip_dlg_dec_lock(dlg);
+    
+    if (status != PJ_SUCCESS)
+    	return status;
+    
+    return pjsua_call_answer2(call_id, opt, code, reason, msg_data);
+}
+
+
+/*
  * Hangup call by using method that is appropriate according to the
  * call state.
@@ -4264,5 +4308,5 @@
  */
 static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
-				   const pjmedia_sdp_session *offer)
+				struct pjsip_inv_on_rx_offer_cb_param *param)
 {
     pjsua_call *call;
@@ -4270,4 +4314,7 @@
     unsigned i;
     pj_status_t status;
+    const pjmedia_sdp_session *offer = param->offer;
+    pjsua_call_setting opt;
+    pj_bool_t async = PJ_FALSE;
 
     call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
@@ -4285,10 +4332,51 @@
 
     cleanup_call_setting_flag(&call->opt);
-
-    if (pjsua_var.ua_cfg.cb.on_call_rx_offer) {
+    opt = call->opt;
+
+    if (pjsua_var.ua_cfg.cb.on_call_rx_reinvite &&
+        param->rdata->msg_info.msg->type == PJSIP_REQUEST_MSG &&
+        param->rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD)
+    {
+        pjsip_status_code code = PJSIP_SC_OK;
+
+    	/* If on_call_rx_reinvite() callback is implemented,
+    	 * call it first.
+    	 */
+	(*pjsua_var.ua_cfg.cb.on_call_rx_reinvite)(
+						call->index, offer,
+						(pjsip_rx_data *)param->rdata,
+						NULL, &async, &code, &opt);
+	if (async) {
+    	    pjsip_tx_data *response;
+
+    	    status = pjsip_inv_initial_answer(inv,
+    	    				      (pjsip_rx_data *)param->rdata,
+				      	      100, NULL, NULL, &response);
+    	    if (status != PJ_SUCCESS) {
+		PJ_LOG(3, (THIS_FILE, "Failed to create initial answer")); 
+    	    	goto on_return;
+    	    }
+
+	    status = pjsip_inv_send_msg(inv, response);
+    	    if (status != PJ_SUCCESS) {
+		PJ_LOG(3, (THIS_FILE, "Failed to send initial answer")); 
+    	    	goto on_return;
+    	    }
+
+	    PJ_LOG(4,(THIS_FILE, "App will manually answer the re-INVITE "
+	    			 "on call %d", call->index));
+	}
+	if (code != PJSIP_SC_OK) {
+	    PJ_LOG(4,(THIS_FILE, "Rejecting re-INVITE updated media offer "
+	    			 "on call %d", call->index));
+	    goto on_return;
+	}
+
+	call->opt = opt;
+    }
+
+    if (pjsua_var.ua_cfg.cb.on_call_rx_offer && !async) {
 	pjsip_status_code code = PJSIP_SC_OK;
-	pjsua_call_setting opt;
-
-	opt = call->opt;
+
 	(*pjsua_var.ua_cfg.cb.on_call_rx_offer)(call->index, offer, NULL,
 						&code, &opt);
@@ -4313,4 +4401,9 @@
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
+	goto on_return;
+    }
+
+    if (async) {
+	call->rx_reinv_async = async;
 	goto on_return;
     }
@@ -4361,4 +4454,25 @@
 on_return:
     pj_log_pop_indent();
+}
+
+
+/*
+ * Called when receiving re-INVITE.
+ */
+static pj_status_t pjsua_call_on_rx_reinvite(pjsip_inv_session *inv,
+    		                  	     const pjmedia_sdp_session *offer,
+                                  	     pjsip_rx_data *rdata)
+{
+    pjsua_call *call;
+    pj_bool_t async;
+
+    PJ_UNUSED_ARG(offer);
+    PJ_UNUSED_ARG(rdata);
+
+    call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
+    async = call->rx_reinv_async;
+    call->rx_reinv_async = PJ_FALSE;
+
+    return (async? PJ_SUCCESS: !PJ_SUCCESS);
 }
 
Index: /pjproject/trunk/pjsip/src/pjsua2/call.cpp
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua2/call.cpp	(revision 5827)
+++ /pjproject/trunk/pjsip/src/pjsua2/call.cpp	(revision 5828)
@@ -161,4 +161,5 @@
 : statusCode(pjsip_status_code(0)), reason(""), options(0)
 {
+    sdp.wholeSdp = "";
     if (useDefaultCallSetting)
         opt = CallSetting(true);
@@ -329,4 +330,5 @@
     pj_str_t            reason;
     pj_str_t           *p_reason;
+    pjmedia_sdp_session *sdp;
 
 public:
@@ -336,5 +338,6 @@
     call_param(const SipTxOption &tx_option);
     call_param(const SipTxOption &tx_option, const CallSetting &setting,
-               const string &reason_str);
+               const string &reason_str, pj_pool_t *pool = NULL,
+               const string &sdp_str = "");
 };
 
@@ -350,8 +353,10 @@
     p_opt = NULL;
     p_reason = NULL;
+    sdp = NULL;
 }
 
 call_param::call_param(const SipTxOption &tx_option, const CallSetting &setting,
-                       const string &reason_str)
+                       const string &reason_str, pj_pool_t *pool,
+                       const string &sdp_str)
 {
     if (tx_option.isEmpty()) {
@@ -371,4 +376,16 @@
     reason = str2Pj(reason_str);
     p_reason = (reason.slen == 0? NULL: &reason);
+
+    if (sdp_str == "") {
+    	sdp = NULL;
+    } else {
+        pj_str_t dup_pj_sdp;
+        pj_str_t pj_sdp_str = {(char*)sdp_str.c_str(),
+        		       (pj_ssize_t)sdp_str.size()};
+
+        pj_strdup(pool, &dup_pj_sdp, &pj_sdp_str);        
+        pjmedia_sdp_parse(pool, dup_pj_sdp.ptr,
+                          dup_pj_sdp.slen, &sdp);
+    }
 }
 
@@ -493,8 +510,17 @@
 void Call::answer(const CallOpParam &prm) throw(Error)
 {
-    call_param param(prm.txOption, prm.opt, prm.reason);
-    
-    PJSUA2_CHECK_EXPR( pjsua_call_answer2(id, param.p_opt, prm.statusCode,
-                                          param.p_reason, param.p_msg_data) );
+    call_param param(prm.txOption, prm.opt, prm.reason,
+    		     sdp_pool, prm.sdp.wholeSdp);
+    
+    if (param.sdp) {
+    	PJSUA2_CHECK_EXPR( pjsua_call_answer_with_sdp(id, param.sdp, param.p_opt,
+    						      prm.statusCode,
+                                              	      param.p_reason,
+                                              	      param.p_msg_data) );
+    } else {
+    	PJSUA2_CHECK_EXPR( pjsua_call_answer2(id, param.p_opt, prm.statusCode,
+                                              param.p_reason,
+                                              param.p_msg_data) );
+    }
 }
 
Index: /pjproject/trunk/pjsip/src/pjsua2/endpoint.cpp
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua2/endpoint.cpp	(revision 5827)
+++ /pjproject/trunk/pjsip/src/pjsua2/endpoint.cpp	(revision 5828)
@@ -1067,4 +1067,5 @@
         prm.remSdp.fromPj(*rem_sdp);
     
+    call->sdp_pool = pool;
     call->onCallSdpCreated(prm);
     
@@ -1253,4 +1254,33 @@
     call->onCallRxOffer(prm);
     
+    *code = prm.statusCode;
+    *opt = prm.opt.toPj();
+}
+
+void Endpoint::on_call_rx_reinvite(pjsua_call_id call_id,
+                                   const pjmedia_sdp_session *offer,
+                                   pjsip_rx_data *rdata,
+			     	   void *reserved,
+			     	   pj_bool_t *async,
+                                   pjsip_status_code *code,
+                                   pjsua_call_setting *opt)
+{
+    PJ_UNUSED_ARG(reserved);
+
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallRxReinviteParam prm;
+    prm.offer.fromPj(*offer);
+    prm.rdata.fromPj(*rdata);
+    prm.async = PJ2BOOL(*async);
+    prm.statusCode = *code;
+    prm.opt.fromPj(*opt);
+    
+    call->onCallRxReinvite(prm);
+    
+    *async = prm.async;
     *code = prm.statusCode;
     *opt = prm.opt.toPj();
@@ -1577,4 +1607,5 @@
     ua_cfg.cb.on_call_replaced          = &Endpoint::on_call_replaced;
     ua_cfg.cb.on_call_rx_offer          = &Endpoint::on_call_rx_offer;
+    ua_cfg.cb.on_call_rx_reinvite       = &Endpoint::on_call_rx_reinvite;
     ua_cfg.cb.on_call_tx_offer          = &Endpoint::on_call_tx_offer;
     ua_cfg.cb.on_call_redirected        = &Endpoint::on_call_redirected;
