Re #1395: Backport of PJSIP 1.x branch into PJSIP 2.0 trunk

* Backport of r3557:r3832

TODO: ticket #1268 (Option for automatic/manual sending of RTCP SDES/BYE for the stream) for video stream.



git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@3841 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c
index 9bcd1dd..226f5d1 100644
--- a/pjsip/src/pjsua-lib/pjsua_acc.c
+++ b/pjsip/src/pjsua-lib/pjsua_acc.c
@@ -470,9 +470,10 @@
 	      (int)cfg->id.slen, cfg->id.ptr, id));
 
     /* If accounts has registration enabled, start registration */
-    if (pjsua_var.acc[id].cfg.reg_uri.slen)
-	pjsua_acc_set_registration(id, PJ_TRUE);
-    else {
+    if (pjsua_var.acc[id].cfg.reg_uri.slen) {
+	if (pjsua_var.acc[id].cfg.register_on_acc_add)
+            pjsua_acc_set_registration(id, PJ_TRUE);
+    } else {
 	/* Otherwise subscribe to MWI, if it's enabled */
 	if (pjsua_var.acc[id].cfg.mwi_enabled)
 	    pjsua_start_mwi(&pjsua_var.acc[id]);
@@ -603,7 +604,7 @@
     }
 
     /* Delete server presence subscription */
-    pjsua_pres_delete_acc(acc_id);
+    pjsua_pres_delete_acc(acc_id, 0);
 
     /* Release account pool */
     if (pjsua_var.acc[acc_id].pool) {
@@ -833,7 +834,7 @@
     if (acc->cfg.publish_enabled != cfg->publish_enabled) {
 	acc->cfg.publish_enabled = cfg->publish_enabled;
 	if (!acc->cfg.publish_enabled)
-	    pjsua_pres_unpublish(acc);
+	    pjsua_pres_unpublish(acc, 0);
 	else
 	    update_reg = PJ_TRUE;
     }
@@ -992,6 +993,7 @@
     acc->cfg.unreg_timeout = cfg->unreg_timeout;
     acc->cfg.allow_contact_rewrite = cfg->allow_contact_rewrite;
     acc->cfg.reg_retry_interval = cfg->reg_retry_interval;
+    acc->cfg.reg_first_retry_interval = cfg->reg_first_retry_interval;
     acc->cfg.drop_calls_on_reg_fail = cfg->drop_calls_on_reg_fail;
     if (acc->cfg.reg_delay_before_refresh != cfg->reg_delay_before_refresh) {
         acc->cfg.reg_delay_before_refresh = cfg->reg_delay_before_refresh;
@@ -1393,7 +1395,7 @@
 			       tp->type_name,
 			       (int)acc->cfg.contact_uri_params.slen,
 			       acc->cfg.contact_uri_params.ptr,
-			       ob,
+			       (acc->cfg.use_rfc5626? ob: ""),
 			       (int)acc->cfg.contact_params.slen,
 			       acc->cfg.contact_params.ptr);
 	if (len < 1) {
@@ -1691,11 +1693,14 @@
 
     pjsua_acc *acc = (pjsua_acc*) param->token;
 
-    if (param->regc != acc->regc)
+    PJSUA_LOCK();
+
+    if (param->regc != acc->regc) {
+        PJSUA_UNLOCK();
 	return;
+    }
 
     pj_log_push_indent();
-    PJSUA_LOCK();
 
     /*
      * Print registration status.
@@ -2054,7 +2059,7 @@
 	    goto on_return;
 	}
 
-	pjsua_pres_unpublish(&pjsua_var.acc[acc_id]);
+	pjsua_pres_unpublish(&pjsua_var.acc[acc_id], 0);
 
 	status = pjsip_regc_unregister(pjsua_var.acc[acc_id].regc, &tdata);
     }
@@ -2070,6 +2075,10 @@
 
 	pjsip_regc_get_info(pjsua_var.acc[acc_id].regc, &reg_info);
 	pjsua_var.acc[acc_id].auto_rereg.reg_tp = reg_info.transport;
+        
+        if (pjsua_var.ua_cfg.cb.on_reg_started) {
+            (*pjsua_var.ua_cfg.cb.on_reg_started)(acc_id, renew);
+        }
     }
 
     if (status != PJ_SUCCESS) {
@@ -2529,10 +2538,11 @@
     /* Create the contact header */
     contact->ptr = (char*)pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
     contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE,
-				     "%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s%s>%.*s",
+				     "%s%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s%s>%.*s",
+				     (acc->display.slen?"\"" : ""),
 				     (int)acc->display.slen,
 				     acc->display.ptr,
-				     (acc->display.slen?" " : ""),
+				     (acc->display.slen?"\" " : ""),
 				     (secure ? PJSUA_SECURE_SCHEME : "sip"),
 				     (int)acc->user_part.slen,
 				     acc->user_part.ptr,
@@ -2545,7 +2555,7 @@
 				     transport_param,
 				     (int)acc->cfg.contact_uri_params.slen,
 				     acc->cfg.contact_uri_params.ptr,
-				     ob,
+				     (acc->cfg.use_rfc5626? ob: ""),
 				     (int)acc->cfg.contact_params.slen,
 				     acc->cfg.contact_params.ptr);
 
@@ -2687,10 +2697,11 @@
     /* Create the contact header */
     contact->ptr = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
     contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE,
-				     "%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s>%.*s",
+				     "%s%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s>%.*s",
+				     (acc->display.slen?"\"" : ""),
 				     (int)acc->display.slen,
 				     acc->display.ptr,
-				     (acc->display.slen?" " : ""),
+				     (acc->display.slen?"\" " : ""),
 				     (secure ? PJSUA_SECURE_SCHEME : "sip"),
 				     (int)acc->user_part.slen,
 				     acc->user_part.ptr,
@@ -2807,8 +2818,23 @@
     acc->auto_rereg.timer.user_data = acc;
 
     /* Reregistration attempt. The first attempt will be done immediately. */
-    delay.sec = acc->auto_rereg.attempt_cnt? acc->cfg.reg_retry_interval : 0;
+    delay.sec = acc->auto_rereg.attempt_cnt? acc->cfg.reg_retry_interval :
+					     acc->cfg.reg_first_retry_interval;
     delay.msec = 0;
+
+    /* Randomize interval by +/- 10 secs */
+    if (delay.sec >= 10) {
+	delay.msec = -10000 + (pj_rand() % 20000);
+    } else {
+	delay.sec = 0;
+	delay.msec = (pj_rand() % 10000);
+    }
+    pj_time_val_normalize(&delay);
+
+    PJ_LOG(4,(THIS_FILE,
+	      "Scheduling re-registration retry for acc %d in %u seconds..",
+	      acc->index, delay.sec));
+
     pjsua_schedule_timer(&acc->auto_rereg.timer, &delay);
 }
 
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index ad96a4d..02a78b9 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -971,7 +971,7 @@
     /* Verify that we can handle the request. */
     options |= PJSIP_INV_SUPPORT_100REL;
     options |= PJSIP_INV_SUPPORT_TIMER;
-    if (pjsua_var.acc[acc_id].cfg.require_100rel)
+    if (pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_MANDATORY)
 	options |= PJSIP_INV_REQUIRE_100REL;
     if (pjsua_var.media_cfg.enable_ice)
 	options |= PJSIP_INV_SUPPORT_ICE;
@@ -1047,6 +1047,19 @@
 	options &= ~(PJSIP_INV_SUPPORT_TIMER);
     }
 
+    /* If 100rel is optional and UAC supports it, use it. */
+    if ((options & PJSIP_INV_REQUIRE_100REL)==0 &&
+	pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_OPTIONAL)
+    {
+	const pj_str_t token = { "100rel", 6};
+	pjsip_dialog_cap_status cap_status;
+
+	cap_status = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_SUPPORTED, NULL,
+	                                      &token);
+	if (cap_status == PJSIP_DIALOG_CAP_SUPPORTED)
+	    options |= PJSIP_INV_REQUIRE_100REL;
+    }
+
     /* Create invite session: */
     status = pjsip_inv_create_uas( dlg, rdata, NULL, options, &inv);
     if (status != PJ_SUCCESS) {
@@ -1288,6 +1301,7 @@
     pj_time_val time_start, timeout;
 
     pj_gettimeofday(&time_start);
+    timeout.sec = 0;
     timeout.msec = PJSUA_ACQUIRE_CALL_TIMEOUT;
     pj_time_val_normalize(&timeout);
 
@@ -1356,20 +1370,24 @@
 PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id)
 {
     pjsua_call *call;
-    pjsua_conf_port_id port_id;
-    pjsip_dialog *dlg;
-    pj_status_t status;
+    pjsua_conf_port_id port_id = PJSUA_INVALID_ID;
 
     PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, 
 		     PJ_EINVAL);
 
-    status = acquire_call("pjsua_call_get_conf_port()", call_id, &call, &dlg);
-    if (status != PJ_SUCCESS)
-	return PJSUA_INVALID_ID;
+    /* Use PJSUA_LOCK() instead of acquire_call():
+     *  https://trac.pjsip.org/repos/ticket/1371
+     */
+    PJSUA_LOCK();
 
+    if (!pjsua_call_is_active(call_id))
+	goto on_return;
+
+    call = &pjsua_var.calls[call_id];
     port_id = call->media[call->audio_idx].strm.a.conf_slot;
 
-    pjsip_dlg_dec_lock(dlg);
+on_return:
+    PJSUA_UNLOCK();
 
     return port_id;
 }
@@ -1383,18 +1401,23 @@
 					 pjsua_call_info *info)
 {
     pjsua_call *call;
-    pjsip_dialog *dlg;
     unsigned mi;
-    pj_status_t status;
 
     PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
 		     PJ_EINVAL);
 
     pj_bzero(info, sizeof(*info));
 
-    status = acquire_call("pjsua_call_get_info()", call_id, &call, &dlg);
-    if (status != PJ_SUCCESS) {
-	return status;
+    /* Use PJSUA_LOCK() instead of acquire_call():
+     *  https://trac.pjsip.org/repos/ticket/1371
+     */
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    if (!call->inv) {
+	PJSUA_UNLOCK();
+	return PJSIP_ESESSIONTERMINATED;
     }
 
     /* id and role */
@@ -1520,7 +1543,7 @@
 	PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
     }
 
-    pjsip_dlg_dec_lock(dlg);
+    PJSUA_UNLOCK();
 
     return PJ_SUCCESS;
 }
@@ -1961,10 +1984,14 @@
     /* Add additional headers etc */
     pjsua_process_msg_data( tdata, msg_data);
 
+    /* Record the tx_data to keep track the operation */
+    call->hold_msg = (void*) tdata;
+
     /* Send the request */
     status = pjsip_inv_send_msg( call->inv, tdata);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
+	call->hold_msg = NULL;
 	goto on_return;
     }
 
@@ -2531,14 +2558,15 @@
     PJ_LOG(4,(THIS_FILE, "Hangup all calls.."));
     pj_log_push_indent();
 
-    PJSUA_LOCK();
+    // This may deadlock, see https://trac.pjsip.org/repos/ticket/1305
+    //PJSUA_LOCK();
 
     for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
 	if (pjsua_var.calls[i].inv)
 	    pjsua_call_hangup(i, 0, NULL, NULL);
     }
 
-    PJSUA_UNLOCK();
+    //PJSUA_UNLOCK();
     pj_log_pop_indent();
 }
 
@@ -3971,9 +3999,22 @@
 						    &tsx->status_text);
 	    }
 	}
+    } else if (tsx->role == PJSIP_ROLE_UAC &&
+	       tsx->last_tx == (pjsip_tx_data*)call->hold_msg &&
+	       tsx->state >= PJSIP_TSX_STATE_COMPLETED)
+    {
+	/* Monitor the status of call hold request */
+	call->hold_msg = NULL;
+	if (tsx->status_code/100 != 2) {
+	    /* Outgoing call hold failed */
+	    call->local_hold = PJ_FALSE;
+	    PJ_LOG(3,(THIS_FILE, "Error putting call %d on hold (reason=%d)",
+		      call->index, tsx->status_code));
+	}
     }
 
 on_return:
+
     PJSUA_UNLOCK();
     pj_log_pop_indent();
 }
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 31133b2..5205422 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -234,6 +234,7 @@
     pj_list_init(&cfg->reg_hdr_list);
     pj_list_init(&cfg->sub_hdr_list);
     cfg->call_hold_type = PJSUA_CALL_HOLD_TYPE_DEFAULT;
+    cfg->register_on_acc_add = PJ_TRUE;
 }
 
 PJ_DEF(void) pjsua_buddy_config_default(pjsua_buddy_config *cfg)
@@ -1340,7 +1341,7 @@
 /*
  * Destroy pjsua.
  */
-PJ_DEF(pj_status_t) pjsua_destroy(void)
+PJ_DEF(pj_status_t) pjsua_destroy2(unsigned flags)
 {
     int i;  /* Must be signed */
 
@@ -1365,12 +1366,14 @@
     if (pjsua_var.endpt) {
 	unsigned max_wait;
 
-	PJ_LOG(4,(THIS_FILE, "Shutting down..."));
+	PJ_LOG(4,(THIS_FILE, "Shutting down, flags=%d...", flags));
 
 	pj_log_push_indent();
 
 	/* Terminate all calls. */
-	pjsua_call_hangup_all();
+	if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
+	    pjsua_call_hangup_all();
+	}
 
 	/* Set all accounts to offline */
 	for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
@@ -1381,10 +1384,10 @@
 	}
 
 	/* Terminate all presence subscriptions. */
-	pjsua_pres_shutdown();
+	pjsua_pres_shutdown(flags);
 
 	/* Destroy media (to shutdown media transports etc) */
-	pjsua_media_subsys_destroy();
+	pjsua_media_subsys_destroy(flags);
 
 	/* Wait for sometime until all publish client sessions are done
 	 * (ticket #364)
@@ -1398,6 +1401,11 @@
 		max_wait = pjsua_var.acc[i].cfg.unpublish_max_wait_time_msec;
 	}
 	
+	/* No waiting if RX is disabled */
+	if (flags & PJSUA_DESTROY_NO_RX_MSG) {
+	    max_wait = 0;
+	}
+
 	/* Second stage, wait for unpublications to complete */
 	for (i=0; i<(int)(max_wait/50); ++i) {
 	    unsigned j;
@@ -1427,7 +1435,8 @@
 	    if (!pjsua_var.acc[i].valid)
 		continue;
 
-	    if (pjsua_var.acc[i].regc) {
+	    if (pjsua_var.acc[i].regc && (flags & PJSUA_DESTROY_NO_TX_MSG)==0)
+	    {
 		pjsua_acc_set_registration(i, PJ_FALSE);
 	    }
 	}
@@ -1452,6 +1461,11 @@
 		max_wait = pjsua_var.acc[i].cfg.unreg_timeout;
 	}
 	
+	/* No waiting if RX is disabled */
+	if (flags & PJSUA_DESTROY_NO_RX_MSG) {
+	    max_wait = 0;
+	}
+
 	/* Second stage, wait for unregistrations to complete */
 	for (i=0; i<(int)(max_wait/50); ++i) {
 	    unsigned j;
@@ -1472,8 +1486,9 @@
 	/* Wait for some time to allow unregistration and ICE/TURN
 	 * transports shutdown to complete: 
 	 */
-	if (i < 20)
+	if (i < 20 && (flags & PJSUA_DESTROY_NO_RX_MSG) == 0) {
 	    busy_sleep(1000 - i*50);
+	}
 
 	PJ_LOG(4,(THIS_FILE, "Destroying..."));
 
@@ -1560,6 +1575,12 @@
     return pjsua_var.state;
 }
 
+PJ_DEF(pj_status_t) pjsua_destroy(void)
+{
+    return pjsua_destroy2(0);
+}
+
+
 /**
  * Application is recommended to call this function after all initialization
  * is done, so that the library can do additional checking set up
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
index e774365..60aebbe 100644
--- a/pjsip/src/pjsua-lib/pjsua_media.c
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -396,7 +396,7 @@
 /*
  * Destroy pjsua media subsystem.
  */
-pj_status_t pjsua_media_subsys_destroy(void)
+pj_status_t pjsua_media_subsys_destroy(unsigned flags)
 {
     unsigned i;
 
@@ -441,6 +441,10 @@
 		pjsua_media_channel_deinit(i);
 	    }
 	    if (call_med->tp && call_med->tp_auto_del) {
+	        /* TODO: check if we're not allowed to send to network in the
+	         *       "flags", and if so do not do TURN allocation...
+	         */
+	        PJ_UNUSED_ARG(flags);
 		pjmedia_transport_close(call_med->tp);
 	    }
 	    call_med->tp = NULL;
@@ -1294,11 +1298,18 @@
     if (call_med->tp_st == PJSUA_MED_TP_CREATING)
         set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
 
+    if (!call_med->tp_orig &&
+        pjsua_var.ua_cfg.cb.on_create_media_transport)
+    {
+        call_med->use_custom_med_tp = PJ_TRUE;
+    } else
+        call_med->use_custom_med_tp = PJ_FALSE;
+
 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
     /* This function may be called when SRTP transport already exists
      * (e.g: in re-invite, update), don't need to destroy/re-create.
      */
-    if (!call_med->tp_orig || call_med->tp == call_med->tp_orig) {
+    if (!call_med->tp_orig) {
 	pjmedia_srtp_setting srtp_opt;
 	pjmedia_transport *srtp = NULL;
 
@@ -1314,7 +1325,7 @@
 	/* Always create SRTP adapter */
 	pjmedia_srtp_setting_default(&srtp_opt);
 	srtp_opt.close_member_tp = PJ_TRUE;
-	/* If media session has been ever established, let's use remote's
+	/* If media session has been ever established, let's use remote's 
 	 * preference in SRTP usage policy, especially when it is stricter.
 	 */
 	if (call_med->rem_srtp_use > acc->cfg.use_srtp)
@@ -1519,9 +1530,25 @@
                             call->async_call.dlg->pool);
             }
 
-	    status = pjmedia_transport_media_create(
-                         call_med->tp, tmp_pool,
-                         0, call->async_call.rem_sdp, mi);
+            if (call_med->use_custom_med_tp) {
+                unsigned custom_med_tp_flags = 0;
+
+                /* Use custom media transport returned by the application */
+                call_med->tp =
+                    (*pjsua_var.ua_cfg.cb.on_create_media_transport)
+                        (call_id, mi, call_med->tp,
+                         custom_med_tp_flags);
+                if (!call_med->tp) {
+                    status =
+                        PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
+                }
+            }
+
+            if (call_med->tp) {
+                status = pjmedia_transport_media_create(
+                             call_med->tp, tmp_pool,
+                             0, call->async_call.rem_sdp, mi);
+            }
 	    if (status != PJ_SUCCESS) {
                 call->med_ch_info.status = status;
                 call->med_ch_info.med_idx = mi;
@@ -2104,6 +2131,7 @@
 
 	PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
 			     call_id, mi));
+        call_med->prev_state = call_med->state;
 	call_med->state = PJSUA_CALL_MEDIA_NONE;
     }
 
@@ -2133,12 +2161,19 @@
     PJ_LOG(4,(THIS_FILE, "Call %d: deinitializing media..", call_id));
     pj_log_push_indent();
 
+    for (mi=0; mi<call->med_cnt; ++mi) {
+	pjsua_call_media *call_med = &call->media[mi];
+
+        if (call_med->type == PJMEDIA_TYPE_AUDIO && call_med->strm.a.stream)
+            pjmedia_stream_send_rtcp_bye(call_med->strm.a.stream);
+    }
+
     stop_media_session(call_id);
 
     for (mi=0; mi<call->med_cnt; ++mi) {
 	pjsua_call_media *call_med = &call->media[mi];
 
-	if (call_med->tp_st > PJSUA_MED_TP_IDLE) {
+        if (call_med->tp_st > PJSUA_MED_TP_IDLE) {
 	    pjmedia_transport_media_stop(call_med->tp);
 	    set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
 	}
@@ -2153,6 +2188,7 @@
 	    pjmedia_transport_close(call_med->tp);
 	    call_med->tp = call_med->tp_orig = NULL;
 	}
+        call_med->tp_orig = NULL;
     }
 
     check_snd_dev_idle();
@@ -2206,6 +2242,8 @@
     if (status != PJ_SUCCESS)
 	goto on_return;
 
+    si->rtcp_sdes_bye_disabled = PJ_TRUE;
+
     /* Check if no media is active */
     if (si->dir == PJMEDIA_DIR_NONE) {
 	/* Call media state */
@@ -2296,6 +2334,9 @@
 	    goto on_return;
 	}
 
+        if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
+            pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream);
+
 	/* If DTMF callback is installed by application, install our
 	 * callback to the session.
 	 */
diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c
index 88ea317..c78e8b5 100644
--- a/pjsip/src/pjsua-lib/pjsua_pres.c
+++ b/pjsip/src/pjsua-lib/pjsua_pres.c
@@ -1320,13 +1320,17 @@
 
 
 /* Unpublish presence publication */
-void pjsua_pres_unpublish(pjsua_acc *acc)
+void pjsua_pres_unpublish(pjsua_acc *acc, unsigned flags)
 {
     if (acc->publish_sess) {
 	pjsua_acc_config *acc_cfg = &acc->cfg;
 
 	acc->online_status = PJ_FALSE;
-	send_publish(acc->index, PJ_FALSE);
+
+	if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
+	    send_publish(acc->index, PJ_FALSE);
+	}
+
 	/* By ticket #364, don't destroy the session yet (let the callback
 	   destroy it)
 	if (acc->publish_sess) {
@@ -1339,7 +1343,7 @@
 }
 
 /* Terminate server subscription for the account */
-void pjsua_pres_delete_acc(int acc_id)
+void pjsua_pres_delete_acc(int acc_id, unsigned flags)
 {
     pjsua_acc *acc = &pjsua_var.acc[acc_id];
     pjsua_srv_pres *uapres;
@@ -1361,11 +1365,15 @@
 	pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status;
 	pjsip_pres_set_status(uapres->sub, &pres_status);
 
-	if (pjsip_pres_notify(uapres->sub, 
-			      PJSIP_EVSUB_STATE_TERMINATED, NULL,
-			      &reason, &tdata)==PJ_SUCCESS)
-	{
-	    pjsip_pres_send_request(uapres->sub, tdata);
+	if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
+	    if (pjsip_pres_notify(uapres->sub,
+				  PJSIP_EVSUB_STATE_TERMINATED, NULL,
+				  &reason, &tdata)==PJ_SUCCESS)
+	    {
+		pjsip_pres_send_request(uapres->sub, tdata);
+	    }
+	} else {
+	    pjsip_pres_terminate(uapres->sub, PJ_FALSE);
 	}
 
 	uapres = next;
@@ -1376,7 +1384,7 @@
     pj_list_init(&acc->pres_srv_list);
 
     /* Terminate presence publication, if any */
-    pjsua_pres_unpublish(acc);
+    pjsua_pres_unpublish(acc, flags);
 }
 
 
@@ -2251,6 +2259,10 @@
     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
 	pjsua_acc *acc = &pjsua_var.acc[i];
 
+	/* Acc may not be ready yet, otherwise assertion will happen */
+	if (!pjsua_acc_is_valid(i))
+	    continue;
+
 	/* Retry PUBLISH */
 	if (acc->cfg.publish_enabled && acc->publish_sess==NULL)
 	    pjsua_pres_init_publish_acc(acc->index);
@@ -2324,7 +2336,7 @@
 /*
  * Shutdown presence.
  */
-void pjsua_pres_shutdown(void)
+void pjsua_pres_shutdown(unsigned flags)
 {
     unsigned i;
 
@@ -2339,18 +2351,20 @@
     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
 	if (!pjsua_var.acc[i].valid)
 	    continue;
-	pjsua_pres_delete_acc(i);
+	pjsua_pres_delete_acc(i, flags);
     }
 
     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
 	pjsua_var.buddy[i].monitor = 0;
     }
 
-    refresh_client_subscriptions();
+    if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
+	refresh_client_subscriptions();
 
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
-	if (pjsua_var.acc[i].valid)
-	    pjsua_pres_update_acc(i, PJ_FALSE);
+	for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+	    if (pjsua_var.acc[i].valid)
+		pjsua_pres_update_acc(i, PJ_FALSE);
+	}
     }
 
     pj_log_pop_indent();