* #39101: updated pjsip stack, many android build enhancements.

The main issue here is due to the build system of the stack compared to a pure Android
NDK project. Generating arm and x86 ABI at the same time does not seem to be possible.
diff --git a/jni/pjproject-android/pjsip/src/pjsua2/account.cpp b/jni/pjproject-android/pjsip/src/pjsua2/account.cpp
new file mode 100644
index 0000000..cca0ad5
--- /dev/null
+++ b/jni/pjproject-android/pjsip/src/pjsua2/account.cpp
@@ -0,0 +1,799 @@
+/* $Id: account.cpp 4704 2014-01-16 05:30:46Z ming $ */
+/*
+ * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pjsua2/account.hpp>
+#include <pjsua2/endpoint.hpp>
+#include <pjsua2/presence.hpp>
+#include <pj/ctype.h>
+#include "util.hpp"
+
+using namespace pj;
+using namespace std;
+
+#define THIS_FILE		"account.cpp"
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AccountRegConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("AccountRegConfig");
+
+    NODE_READ_STRING	(this_node, registrarUri);
+    NODE_READ_BOOL	(this_node, registerOnAdd);
+    NODE_READ_UNSIGNED	(this_node, timeoutSec);
+    NODE_READ_UNSIGNED	(this_node, retryIntervalSec);
+    NODE_READ_UNSIGNED	(this_node, firstRetryIntervalSec);
+    NODE_READ_UNSIGNED	(this_node, delayBeforeRefreshSec);
+    NODE_READ_BOOL	(this_node, dropCallsOnFail);
+    NODE_READ_UNSIGNED	(this_node, unregWaitSec);
+    NODE_READ_UNSIGNED	(this_node, proxyUse);
+
+    readSipHeaders(this_node, "headers", headers);
+}
+
+void AccountRegConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("AccountRegConfig");
+
+    NODE_WRITE_STRING	(this_node, registrarUri);
+    NODE_WRITE_BOOL	(this_node, registerOnAdd);
+    NODE_WRITE_UNSIGNED	(this_node, timeoutSec);
+    NODE_WRITE_UNSIGNED	(this_node, retryIntervalSec);
+    NODE_WRITE_UNSIGNED	(this_node, firstRetryIntervalSec);
+    NODE_WRITE_UNSIGNED	(this_node, delayBeforeRefreshSec);
+    NODE_WRITE_BOOL	(this_node, dropCallsOnFail);
+    NODE_WRITE_UNSIGNED	(this_node, unregWaitSec);
+    NODE_WRITE_UNSIGNED	(this_node, proxyUse);
+
+    writeSipHeaders(this_node, "headers", headers);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AccountSipConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("AccountSipConfig");
+
+    NODE_READ_STRINGV	(this_node, proxies);
+    NODE_READ_STRING	(this_node, contactForced);
+    NODE_READ_STRING	(this_node, contactParams);
+    NODE_READ_STRING	(this_node, contactUriParams);
+    NODE_READ_BOOL	(this_node, authInitialEmpty);
+    NODE_READ_STRING	(this_node, authInitialAlgorithm);
+    NODE_READ_INT	(this_node, transportId);
+
+    ContainerNode creds_node = this_node.readArray("authCreds");
+    authCreds.resize(0);
+    while (creds_node.hasUnread()) {
+	AuthCredInfo cred;
+	cred.readObject(creds_node);
+	authCreds.push_back(cred);
+    }
+}
+
+void AccountSipConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("AccountSipConfig");
+
+    NODE_WRITE_STRINGV	(this_node, proxies);
+    NODE_WRITE_STRING	(this_node, contactForced);
+    NODE_WRITE_STRING	(this_node, contactParams);
+    NODE_WRITE_STRING	(this_node, contactUriParams);
+    NODE_WRITE_BOOL	(this_node, authInitialEmpty);
+    NODE_WRITE_STRING	(this_node, authInitialAlgorithm);
+    NODE_WRITE_INT	(this_node, transportId);
+
+    ContainerNode creds_node = this_node.writeNewArray("authCreds");
+    for (unsigned i=0; i<authCreds.size(); ++i) {
+	authCreds[i].writeObject(creds_node);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AccountCallConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("AccountCallConfig");
+
+    NODE_READ_NUM_T   ( this_node, pjsua_call_hold_type, holdType);
+    NODE_READ_NUM_T   ( this_node, pjsua_100rel_use, prackUse);
+    NODE_READ_NUM_T   ( this_node, pjsua_sip_timer_use, timerUse);
+    NODE_READ_UNSIGNED( this_node, timerMinSESec);
+    NODE_READ_UNSIGNED( this_node, timerSessExpiresSec);
+}
+
+void AccountCallConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("AccountCallConfig");
+
+    NODE_WRITE_NUM_T   ( this_node, pjsua_call_hold_type, holdType);
+    NODE_WRITE_NUM_T   ( this_node, pjsua_100rel_use, prackUse);
+    NODE_WRITE_NUM_T   ( this_node, pjsua_sip_timer_use, timerUse);
+    NODE_WRITE_UNSIGNED( this_node, timerMinSESec);
+    NODE_WRITE_UNSIGNED( this_node, timerSessExpiresSec);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AccountPresConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("AccountPresConfig");
+
+    NODE_READ_BOOL    ( this_node, publishEnabled);
+    NODE_READ_BOOL    ( this_node, publishQueue);
+    NODE_READ_UNSIGNED( this_node, publishShutdownWaitMsec);
+    NODE_READ_STRING  ( this_node, pidfTupleId);
+
+    readSipHeaders(this_node, "headers", headers);
+}
+
+void AccountPresConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("AccountPresConfig");
+
+    NODE_WRITE_BOOL    ( this_node, publishEnabled);
+    NODE_WRITE_BOOL    ( this_node, publishQueue);
+    NODE_WRITE_UNSIGNED( this_node, publishShutdownWaitMsec);
+    NODE_WRITE_STRING  ( this_node, pidfTupleId);
+
+    writeSipHeaders(this_node, "headers", headers);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AccountMwiConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("AccountMwiConfig");
+
+    NODE_READ_BOOL    ( this_node, enabled);
+    NODE_READ_UNSIGNED( this_node, expirationSec);
+}
+
+void AccountMwiConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("AccountMwiConfig");
+
+    NODE_WRITE_BOOL    ( this_node, enabled);
+    NODE_WRITE_UNSIGNED( this_node, expirationSec);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AccountNatConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("AccountNatConfig");
+
+    NODE_READ_NUM_T   ( this_node, pjsua_stun_use, sipStunUse);
+    NODE_READ_NUM_T   ( this_node, pjsua_stun_use, mediaStunUse);
+    NODE_READ_BOOL    ( this_node, iceEnabled);
+    NODE_READ_INT     ( this_node, iceMaxHostCands);
+    NODE_READ_BOOL    ( this_node, iceAggressiveNomination);
+    NODE_READ_UNSIGNED( this_node, iceNominatedCheckDelayMsec);
+    NODE_READ_INT     ( this_node, iceWaitNominationTimeoutMsec);
+    NODE_READ_BOOL    ( this_node, iceNoRtcp);
+    NODE_READ_BOOL    ( this_node, iceAlwaysUpdate);
+    NODE_READ_BOOL    ( this_node, turnEnabled);
+    NODE_READ_STRING  ( this_node, turnServer);
+    NODE_READ_NUM_T   ( this_node, pj_turn_tp_type, turnConnType);
+    NODE_READ_STRING  ( this_node, turnUserName);
+    NODE_READ_INT     ( this_node, turnPasswordType);
+    NODE_READ_STRING  ( this_node, turnPassword);
+    NODE_READ_INT     ( this_node, contactRewriteUse);
+    NODE_READ_INT     ( this_node, contactRewriteMethod);
+    NODE_READ_INT     ( this_node, viaRewriteUse);
+    NODE_READ_INT     ( this_node, sdpNatRewriteUse);
+    NODE_READ_INT     ( this_node, sipOutboundUse);
+    NODE_READ_STRING  ( this_node, sipOutboundInstanceId);
+    NODE_READ_STRING  ( this_node, sipOutboundRegId);
+    NODE_READ_UNSIGNED( this_node, udpKaIntervalSec);
+    NODE_READ_STRING  ( this_node, udpKaData);
+}
+
+void AccountNatConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("AccountNatConfig");
+
+    NODE_WRITE_NUM_T   ( this_node, pjsua_stun_use, sipStunUse);
+    NODE_WRITE_NUM_T   ( this_node, pjsua_stun_use, mediaStunUse);
+    NODE_WRITE_BOOL    ( this_node, iceEnabled);
+    NODE_WRITE_INT     ( this_node, iceMaxHostCands);
+    NODE_WRITE_BOOL    ( this_node, iceAggressiveNomination);
+    NODE_WRITE_UNSIGNED( this_node, iceNominatedCheckDelayMsec);
+    NODE_WRITE_INT     ( this_node, iceWaitNominationTimeoutMsec);
+    NODE_WRITE_BOOL    ( this_node, iceNoRtcp);
+    NODE_WRITE_BOOL    ( this_node, iceAlwaysUpdate);
+    NODE_WRITE_BOOL    ( this_node, turnEnabled);
+    NODE_WRITE_STRING  ( this_node, turnServer);
+    NODE_WRITE_NUM_T   ( this_node, pj_turn_tp_type, turnConnType);
+    NODE_WRITE_STRING  ( this_node, turnUserName);
+    NODE_WRITE_INT     ( this_node, turnPasswordType);
+    NODE_WRITE_STRING  ( this_node, turnPassword);
+    NODE_WRITE_INT     ( this_node, contactRewriteUse);
+    NODE_WRITE_INT     ( this_node, contactRewriteMethod);
+    NODE_WRITE_INT     ( this_node, viaRewriteUse);
+    NODE_WRITE_INT     ( this_node, sdpNatRewriteUse);
+    NODE_WRITE_INT     ( this_node, sipOutboundUse);
+    NODE_WRITE_STRING  ( this_node, sipOutboundInstanceId);
+    NODE_WRITE_STRING  ( this_node, sipOutboundRegId);
+    NODE_WRITE_UNSIGNED( this_node, udpKaIntervalSec);
+    NODE_WRITE_STRING  ( this_node, udpKaData);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AccountMediaConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("AccountMediaConfig");
+
+    NODE_READ_BOOL    ( this_node, lockCodecEnabled);
+    NODE_READ_BOOL    ( this_node, streamKaEnabled);
+    NODE_READ_NUM_T   ( this_node, pjmedia_srtp_use, srtpUse);
+    NODE_READ_INT     ( this_node, srtpSecureSignaling);
+    NODE_READ_NUM_T   ( this_node, pjsua_ipv6_use, ipv6Use);
+    NODE_READ_OBJ     ( this_node, transportConfig);
+}
+
+void AccountMediaConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("AccountMediaConfig");
+
+    NODE_WRITE_BOOL    ( this_node, lockCodecEnabled);
+    NODE_WRITE_BOOL    ( this_node, streamKaEnabled);
+    NODE_WRITE_NUM_T   ( this_node, pjmedia_srtp_use, srtpUse);
+    NODE_WRITE_INT     ( this_node, srtpSecureSignaling);
+    NODE_WRITE_NUM_T   ( this_node, pjsua_ipv6_use, ipv6Use);
+    NODE_WRITE_OBJ     ( this_node, transportConfig);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AccountVideoConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("AccountVideoConfig");
+
+    NODE_READ_BOOL    ( this_node, autoShowIncoming);
+    NODE_READ_BOOL    ( this_node, autoTransmitOutgoing);
+    NODE_READ_UNSIGNED( this_node, windowFlags);
+    NODE_READ_NUM_T   ( this_node, pjmedia_vid_dev_index, defaultCaptureDevice);
+    NODE_READ_NUM_T   ( this_node, pjmedia_vid_dev_index, defaultRenderDevice);
+    NODE_READ_NUM_T   ( this_node, pjmedia_vid_stream_rc_method, rateControlMethod);
+    NODE_READ_UNSIGNED( this_node, rateControlBandwidth);
+}
+
+void AccountVideoConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("AccountVideoConfig");
+
+    NODE_WRITE_BOOL    ( this_node, autoShowIncoming);
+    NODE_WRITE_BOOL    ( this_node, autoTransmitOutgoing);
+    NODE_WRITE_UNSIGNED( this_node, windowFlags);
+    NODE_WRITE_NUM_T   ( this_node, pjmedia_vid_dev_index, defaultCaptureDevice);
+    NODE_WRITE_NUM_T   ( this_node, pjmedia_vid_dev_index, defaultRenderDevice);
+    NODE_WRITE_NUM_T   ( this_node, pjmedia_vid_stream_rc_method, rateControlMethod);
+    NODE_WRITE_UNSIGNED( this_node, rateControlBandwidth);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AccountConfig::AccountConfig()
+{
+    pjsua_acc_config acc_cfg;
+    pjsua_acc_config_default(&acc_cfg);
+    pjsua_media_config med_cfg;
+    pjsua_media_config_default(&med_cfg);
+    fromPj(acc_cfg, &med_cfg);
+}
+
+/* Convert to pjsip. */
+void AccountConfig::toPj(pjsua_acc_config &ret) const
+{
+    unsigned i;
+
+    pjsua_acc_config_default(&ret);
+
+    // Global
+    ret.priority		= priority;
+    ret.id			= str2Pj(idUri);
+
+    // AccountRegConfig
+    ret.reg_uri			= str2Pj(regConfig.registrarUri);
+    ret.register_on_acc_add	= regConfig.registerOnAdd;
+    ret.reg_timeout		= regConfig.timeoutSec;
+    ret.reg_retry_interval	= regConfig.retryIntervalSec;
+    ret.reg_first_retry_interval= regConfig.firstRetryIntervalSec;
+    ret.reg_delay_before_refresh= regConfig.delayBeforeRefreshSec;
+    ret.drop_calls_on_reg_fail	= regConfig.dropCallsOnFail;
+    ret.unreg_timeout		= regConfig.unregWaitSec;
+    ret.reg_use_proxy		= regConfig.proxyUse;
+    for (i=0; i<regConfig.headers.size(); ++i) {
+	pj_list_push_back(&ret.reg_hdr_list, &regConfig.headers[i].toPj());
+    }
+
+    // AccountSipConfig
+    ret.cred_count = 0;
+    if (sipConfig.authCreds.size() > PJ_ARRAY_SIZE(ret.cred_info))
+	PJSUA2_RAISE_ERROR(PJ_ETOOMANY);
+    for (i=0; i<sipConfig.authCreds.size(); ++i) {
+	const AuthCredInfo &src = sipConfig.authCreds[i];
+	pjsip_cred_info *dst = &ret.cred_info[i];
+
+	dst->realm	= str2Pj(src.realm);
+	dst->scheme	= str2Pj(src.scheme);
+	dst->username	= str2Pj(src.username);
+	dst->data_type	= src.dataType;
+	dst->data	= str2Pj(src.data);
+	dst->ext.aka.k	= str2Pj(src.akaK);
+	dst->ext.aka.op	= str2Pj(src.akaOp);
+	dst->ext.aka.amf= str2Pj(src.akaAmf);
+
+	ret.cred_count++;
+    }
+    ret.proxy_cnt = 0;
+    if (sipConfig.proxies.size() > PJ_ARRAY_SIZE(ret.proxy))
+	PJSUA2_RAISE_ERROR(PJ_ETOOMANY);
+    for (i=0; i<sipConfig.proxies.size(); ++i) {
+	ret.proxy[ret.proxy_cnt++] = str2Pj(sipConfig.proxies[i]);
+    }
+    ret.force_contact		= str2Pj(sipConfig.contactForced);
+    ret.contact_params		= str2Pj(sipConfig.contactParams);
+    ret.contact_uri_params	= str2Pj(sipConfig.contactUriParams);
+    ret.auth_pref.initial_auth	= sipConfig.authInitialEmpty;
+    ret.auth_pref.algorithm	= str2Pj(sipConfig.authInitialAlgorithm);
+    ret.transport_id		= sipConfig.transportId;
+
+    // AccountCallConfig
+    ret.call_hold_type		= callConfig.holdType;
+    ret.require_100rel		= callConfig.prackUse;
+    ret.use_timer		= callConfig.timerUse;
+    ret.timer_setting.min_se	= callConfig.timerMinSESec;
+    ret.timer_setting.sess_expires = callConfig.timerSessExpiresSec;
+
+    // AccountPresConfig
+    for (i=0; i<presConfig.headers.size(); ++i) {
+	pj_list_push_back(&ret.sub_hdr_list, &presConfig.headers[i].toPj());
+    }
+    ret.publish_enabled		= presConfig.publishEnabled;
+    ret.publish_opt.queue_request= presConfig.publishQueue;
+    ret.unpublish_max_wait_time_msec = presConfig.publishShutdownWaitMsec;
+    ret.pidf_tuple_id		= str2Pj(presConfig.pidfTupleId);
+
+    // AccountNatConfig
+    ret.sip_stun_use		= natConfig.sipStunUse;
+    ret.media_stun_use		= natConfig.mediaStunUse;
+    ret.ice_cfg_use		= PJSUA_ICE_CONFIG_USE_CUSTOM;
+    ret.ice_cfg.enable_ice	= natConfig.iceEnabled;
+    ret.ice_cfg.ice_max_host_cands = natConfig.iceMaxHostCands;
+    ret.ice_cfg.ice_opt.aggressive = natConfig.iceAggressiveNomination;
+    ret.ice_cfg.ice_opt.nominated_check_delay = natConfig.iceNominatedCheckDelayMsec;
+    ret.ice_cfg.ice_opt.controlled_agent_want_nom_timeout = natConfig.iceWaitNominationTimeoutMsec;
+    ret.ice_cfg.ice_no_rtcp	= natConfig.iceNoRtcp;
+    ret.ice_cfg.ice_always_update = natConfig.iceAlwaysUpdate;
+
+    ret.turn_cfg_use 		= PJSUA_TURN_CONFIG_USE_CUSTOM;
+    ret.turn_cfg.enable_turn	= natConfig.turnEnabled;
+    ret.turn_cfg.turn_server	= str2Pj(natConfig.turnServer);
+    ret.turn_cfg.turn_conn_type	= natConfig.turnConnType;
+    ret.turn_cfg.turn_auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
+    ret.turn_cfg.turn_auth_cred.data.static_cred.username = str2Pj(natConfig.turnUserName);
+    ret.turn_cfg.turn_auth_cred.data.static_cred.data_type = (pj_stun_passwd_type)natConfig.turnPasswordType;
+    ret.turn_cfg.turn_auth_cred.data.static_cred.data = str2Pj(natConfig.turnPassword);
+    ret.turn_cfg.turn_auth_cred.data.static_cred.realm = pj_str((char*)"");
+    ret.turn_cfg.turn_auth_cred.data.static_cred.nonce = pj_str((char*)"");
+
+    ret.allow_contact_rewrite	= natConfig.contactRewriteUse;
+    ret.contact_rewrite_method	= natConfig.contactRewriteMethod;
+    ret.allow_via_rewrite	= natConfig.viaRewriteUse;
+    ret.allow_sdp_nat_rewrite	= natConfig.sdpNatRewriteUse;
+    ret.use_rfc5626		= natConfig.sipOutboundUse;
+    ret.rfc5626_instance_id	= str2Pj(natConfig.sipOutboundInstanceId);
+    ret.rfc5626_reg_id		= str2Pj(natConfig.sipOutboundRegId);
+    ret.ka_interval		= natConfig.udpKaIntervalSec;
+    ret.ka_data			= str2Pj(natConfig.udpKaData);
+
+    // AccountMediaConfig
+    ret.rtp_cfg			= mediaConfig.transportConfig.toPj();
+    ret.lock_codec		= mediaConfig.lockCodecEnabled;
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && (PJMEDIA_STREAM_ENABLE_KA != 0)
+    ret.use_stream_ka		= mediaConfig.streamKaEnabled;
+#endif
+    ret.use_srtp		= mediaConfig.srtpUse;
+    ret.srtp_secure_signaling	= mediaConfig.srtpSecureSignaling;
+    ret.ipv6_media_use		= mediaConfig.ipv6Use;
+
+    // AccountVideoConfig
+    ret.vid_in_auto_show	= videoConfig.autoShowIncoming;
+    ret.vid_out_auto_transmit	= videoConfig.autoTransmitOutgoing;
+    ret.vid_wnd_flags		= videoConfig.windowFlags;
+    ret.vid_cap_dev		= videoConfig.defaultCaptureDevice;
+    ret.vid_rend_dev		= videoConfig.defaultRenderDevice;
+    ret.vid_stream_rc_cfg.method= videoConfig.rateControlMethod;
+    ret.vid_stream_rc_cfg.bandwidth = videoConfig.rateControlBandwidth;
+}
+
+/* Initialize from pjsip. */
+void AccountConfig::fromPj(const pjsua_acc_config &prm,
+                           const pjsua_media_config *mcfg)
+{
+    const pjsip_hdr *hdr;
+    unsigned i;
+
+    // Global
+    priority			= prm.priority;
+    idUri			= pj2Str(prm.id);
+
+    // AccountRegConfig
+    regConfig.registrarUri	= pj2Str(prm.reg_uri);
+    regConfig.registerOnAdd	= (prm.register_on_acc_add != 0);
+    regConfig.timeoutSec	= prm.reg_timeout;
+    regConfig.retryIntervalSec	= prm.reg_retry_interval;
+    regConfig.firstRetryIntervalSec = prm.reg_first_retry_interval;
+    regConfig.delayBeforeRefreshSec = prm.reg_delay_before_refresh;
+    regConfig.dropCallsOnFail	= PJ2BOOL(prm.drop_calls_on_reg_fail);
+    regConfig.unregWaitSec	= prm.unreg_timeout;
+    regConfig.proxyUse		= prm.reg_use_proxy;
+    regConfig.headers.clear();
+    hdr = prm.reg_hdr_list.next;
+    while (hdr != &prm.reg_hdr_list) {
+	SipHeader new_hdr;
+	new_hdr.fromPj(hdr);
+
+	regConfig.headers.push_back(new_hdr);
+
+	hdr = hdr->next;
+    }
+
+    // AccountSipConfig
+    sipConfig.authCreds.clear();
+    for (i=0; i<prm.cred_count; ++i) {
+	AuthCredInfo cred;
+	const pjsip_cred_info &src = prm.cred_info[i];
+
+	cred.realm	= pj2Str(src.realm);
+	cred.scheme	= pj2Str(src.scheme);
+	cred.username	= pj2Str(src.username);
+	cred.dataType	= src.data_type;
+	cred.data	= pj2Str(src.data);
+	cred.akaK	= pj2Str(src.ext.aka.k);
+	cred.akaOp	= pj2Str(src.ext.aka.op);
+	cred.akaAmf	= pj2Str(src.ext.aka.amf);
+
+	sipConfig.authCreds.push_back(cred);
+    }
+    sipConfig.proxies.clear();
+    for (i=0; i<prm.proxy_cnt; ++i) {
+	sipConfig.proxies.push_back(pj2Str(prm.proxy[i]));
+    }
+    sipConfig.contactForced	= pj2Str(prm.force_contact);
+    sipConfig.contactParams	= pj2Str(prm.contact_params);
+    sipConfig.contactUriParams	= pj2Str(prm.contact_uri_params);
+    sipConfig.authInitialEmpty	= PJ2BOOL(prm.auth_pref.initial_auth);
+    sipConfig.authInitialAlgorithm = pj2Str(prm.auth_pref.algorithm);
+    sipConfig.transportId	= prm.transport_id;
+
+    // AccountCallConfig
+    callConfig.holdType		= prm.call_hold_type;
+    callConfig.prackUse		= prm.require_100rel;
+    callConfig.timerUse		= prm.use_timer;
+    callConfig.timerMinSESec	= prm.timer_setting.min_se;
+    callConfig.timerSessExpiresSec = prm.timer_setting.sess_expires;
+
+    // AccountPresConfig
+    presConfig.headers.clear();
+    hdr = prm.sub_hdr_list.next;
+    while (hdr != &prm.sub_hdr_list) {
+	SipHeader new_hdr;
+	new_hdr.fromPj(hdr);
+	presConfig.headers.push_back(new_hdr);
+	hdr = hdr->next;
+    }
+    presConfig.publishEnabled	= PJ2BOOL(prm.publish_enabled);
+    presConfig.publishQueue	= PJ2BOOL(prm.publish_opt.queue_request);
+    presConfig.publishShutdownWaitMsec = prm.unpublish_max_wait_time_msec;
+    presConfig.pidfTupleId	= pj2Str(prm.pidf_tuple_id);
+
+    // AccountMwiConfig
+    mwiConfig.enabled		= PJ2BOOL(prm.mwi_enabled);
+    mwiConfig.expirationSec	= prm.mwi_expires;
+
+    // AccountNatConfig
+    natConfig.sipStunUse	= prm.sip_stun_use;
+    natConfig.mediaStunUse	= prm.media_stun_use;
+    if (prm.ice_cfg_use == PJSUA_ICE_CONFIG_USE_CUSTOM) {
+	natConfig.iceEnabled = PJ2BOOL(prm.ice_cfg.enable_ice);
+	natConfig.iceMaxHostCands = prm.ice_cfg.ice_max_host_cands;
+	natConfig.iceAggressiveNomination = PJ2BOOL(prm.ice_cfg.ice_opt.aggressive);
+	natConfig.iceNominatedCheckDelayMsec = prm.ice_cfg.ice_opt.nominated_check_delay;
+	natConfig.iceWaitNominationTimeoutMsec = prm.ice_cfg.ice_opt.controlled_agent_want_nom_timeout;
+	natConfig.iceNoRtcp	= PJ2BOOL(prm.ice_cfg.ice_no_rtcp);
+	natConfig.iceAlwaysUpdate = PJ2BOOL(prm.ice_cfg.ice_always_update);
+    } else {
+	pjsua_media_config default_mcfg;
+	if (!mcfg) {
+	    pjsua_media_config_default(&default_mcfg);
+	    mcfg = &default_mcfg;
+	}
+	natConfig.iceEnabled	= PJ2BOOL(mcfg->enable_ice);
+	natConfig.iceMaxHostCands= mcfg->ice_max_host_cands;
+	natConfig.iceAggressiveNomination = PJ2BOOL(mcfg->ice_opt.aggressive);
+	natConfig.iceNominatedCheckDelayMsec = mcfg->ice_opt.nominated_check_delay;
+	natConfig.iceWaitNominationTimeoutMsec = mcfg->ice_opt.controlled_agent_want_nom_timeout;
+	natConfig.iceNoRtcp	= PJ2BOOL(mcfg->ice_no_rtcp);
+	natConfig.iceAlwaysUpdate = PJ2BOOL(mcfg->ice_always_update);
+    }
+
+    if (prm.turn_cfg_use == PJSUA_TURN_CONFIG_USE_CUSTOM) {
+	natConfig.turnEnabled	= PJ2BOOL(prm.turn_cfg.enable_turn);
+	natConfig.turnServer	= pj2Str(prm.turn_cfg.turn_server);
+	natConfig.turnConnType	= prm.turn_cfg.turn_conn_type;
+	natConfig.turnUserName	= pj2Str(prm.turn_cfg.turn_auth_cred.data.static_cred.username);
+	natConfig.turnPasswordType = prm.turn_cfg.turn_auth_cred.data.static_cred.data_type;
+	natConfig.turnPassword	= pj2Str(prm.turn_cfg.turn_auth_cred.data.static_cred.data);
+    } else {
+	pjsua_media_config default_mcfg;
+	if (!mcfg) {
+	    pjsua_media_config_default(&default_mcfg);
+	    mcfg = &default_mcfg;
+	}
+	natConfig.turnEnabled	= PJ2BOOL(mcfg->enable_turn);
+	natConfig.turnServer	= pj2Str(mcfg->turn_server);
+	natConfig.turnConnType	= mcfg->turn_conn_type;
+	natConfig.turnUserName	= pj2Str(mcfg->turn_auth_cred.data.static_cred.username);
+	natConfig.turnPasswordType = mcfg->turn_auth_cred.data.static_cred.data_type;
+	natConfig.turnPassword	= pj2Str(mcfg->turn_auth_cred.data.static_cred.data);
+    }
+    natConfig.contactRewriteUse	= prm.allow_contact_rewrite;
+    natConfig.contactRewriteMethod = prm.contact_rewrite_method;
+    natConfig.viaRewriteUse	= prm.allow_via_rewrite;
+    natConfig.sdpNatRewriteUse	= prm.allow_sdp_nat_rewrite;
+    natConfig.sipOutboundUse	= prm.use_rfc5626;
+    natConfig.sipOutboundInstanceId = pj2Str(prm.rfc5626_instance_id);
+    natConfig.sipOutboundRegId	= pj2Str(prm.rfc5626_reg_id);
+    natConfig.udpKaIntervalSec	= prm.ka_interval;
+    natConfig.udpKaData		= pj2Str(prm.ka_data);
+
+    // AccountMediaConfig
+    mediaConfig.transportConfig.fromPj(prm.rtp_cfg);
+    mediaConfig.lockCodecEnabled= PJ2BOOL(prm.lock_codec);
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && (PJMEDIA_STREAM_ENABLE_KA != 0)
+    mediaConfig.streamKaEnabled	= PJ2BOOL(prm.use_stream_ka);
+#else
+    mediaConfig.streamKaEnabled	= false;
+#endif
+    mediaConfig.srtpUse		= prm.use_srtp;
+    mediaConfig.srtpSecureSignaling = prm.srtp_secure_signaling;
+    mediaConfig.ipv6Use		= prm.ipv6_media_use;
+
+    // AccountVideoConfig
+    videoConfig.autoShowIncoming 	= PJ2BOOL(prm.vid_in_auto_show);
+    videoConfig.autoTransmitOutgoing	= PJ2BOOL(prm.vid_out_auto_transmit);
+    videoConfig.windowFlags		= prm.vid_wnd_flags;
+    videoConfig.defaultCaptureDevice	= prm.vid_cap_dev;
+    videoConfig.defaultRenderDevice	= prm.vid_rend_dev;
+    videoConfig.rateControlMethod	= prm.vid_stream_rc_cfg.method;
+    videoConfig.rateControlBandwidth	= prm.vid_stream_rc_cfg.bandwidth;
+}
+
+void AccountConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("AccountConfig");
+
+    NODE_READ_INT     ( this_node, priority);
+    NODE_READ_STRING  ( this_node, idUri);
+    NODE_READ_OBJ     ( this_node, regConfig);
+    NODE_READ_OBJ     ( this_node, sipConfig);
+    NODE_READ_OBJ     ( this_node, callConfig);
+    NODE_READ_OBJ     ( this_node, presConfig);
+    NODE_READ_OBJ     ( this_node, mwiConfig);
+    NODE_READ_OBJ     ( this_node, natConfig);
+    NODE_READ_OBJ     ( this_node, mediaConfig);
+    NODE_READ_OBJ     ( this_node, videoConfig);
+}
+
+void AccountConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("AccountConfig");
+
+    NODE_WRITE_INT     ( this_node, priority);
+    NODE_WRITE_STRING  ( this_node, idUri);
+    NODE_WRITE_OBJ     ( this_node, regConfig);
+    NODE_WRITE_OBJ     ( this_node, sipConfig);
+    NODE_WRITE_OBJ     ( this_node, callConfig);
+    NODE_WRITE_OBJ     ( this_node, presConfig);
+    NODE_WRITE_OBJ     ( this_node, mwiConfig);
+    NODE_WRITE_OBJ     ( this_node, natConfig);
+    NODE_WRITE_OBJ     ( this_node, mediaConfig);
+    NODE_WRITE_OBJ     ( this_node, videoConfig);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AccountInfo::fromPj(const pjsua_acc_info &pai)
+{
+    id 			= pai.id;
+    isDefault 		= pai.is_default != 0;
+    uri			= pj2Str(pai.acc_uri);
+    regIsConfigured	= pai.has_registration != 0;
+    regIsActive		= pai.has_registration && pai.expires > 0 &&
+			    (pai.status / 100 == 2);
+    regExpiresSec	= pai.expires;
+    regStatus		= pai.status;
+    regStatusText	= pj2Str(pai.status_text);
+    regLastErr		= pai.reg_last_err;
+    onlineStatus	= pai.online_status != 0;
+    onlineStatusText	= pj2Str(pai.online_status_text);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+Account::Account()
+: id(PJSUA_INVALID_ID)
+{
+}
+
+Account::~Account()
+{
+    /* If this instance is deleted, also delete the corresponding account in
+     * PJSUA library.
+     */
+    if (isValid() && pjsua_get_state() < PJSUA_STATE_CLOSING) {
+        // Cleanup buddies in the buddy list
+	while(buddyList.size() > 0) {
+	    Buddy *b = buddyList[0];
+	    delete b; /* this will remove itself from the list */
+	}
+
+	pjsua_acc_set_user_data(id, NULL);
+	pjsua_acc_del(id);
+    }
+}
+
+void Account::create(const AccountConfig &acc_cfg,
+                     bool make_default) throw(Error)
+{
+    pjsua_acc_config pj_acc_cfg;
+    
+    acc_cfg.toPj(pj_acc_cfg);
+    pj_acc_cfg.user_data = (void*)this;
+    PJSUA2_CHECK_EXPR( pjsua_acc_add(&pj_acc_cfg, make_default, &id) );
+}
+
+void Account::modify(const AccountConfig &acc_cfg) throw(Error)
+{
+    pjsua_acc_config pj_acc_cfg;
+    
+    acc_cfg.toPj(pj_acc_cfg);
+    pj_acc_cfg.user_data = (void*)this;
+    PJSUA2_CHECK_EXPR( pjsua_acc_modify(id, &pj_acc_cfg) );
+}
+
+bool Account::isValid() const
+{
+    return pjsua_acc_is_valid(id) != 0;
+}
+
+void Account::setDefault() throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_acc_set_default(id) );
+}
+
+bool Account::isDefault() const
+{
+    return pjsua_acc_get_default() == id;
+}
+
+int Account::getId() const
+{
+    return id;
+}
+
+Account *Account::lookup(int acc_id)
+{
+    return (Account*)pjsua_acc_get_user_data(acc_id);
+}
+
+AccountInfo Account::getInfo() const throw(Error)
+{
+    pjsua_acc_info pj_ai;
+    AccountInfo ai;
+
+    PJSUA2_CHECK_EXPR( pjsua_acc_get_info(id, &pj_ai) );
+    ai.fromPj(pj_ai);
+    return ai;
+}
+
+void Account::setRegistration(bool renew) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_acc_set_registration(id, renew) );
+}
+
+void
+Account::setOnlineStatus(const PresenceStatus &pres_st) throw(Error)
+{
+    pjrpid_element pj_rpid;
+
+    pj_bzero(&pj_rpid, sizeof(pj_rpid));
+    pj_rpid.type	= PJRPID_ELEMENT_TYPE_PERSON;
+    pj_rpid.activity	= pres_st.activity;
+    pj_rpid.id		= str2Pj(pres_st.rpidId);
+    pj_rpid.note	= str2Pj(pres_st.note);
+
+    PJSUA2_CHECK_EXPR( pjsua_acc_set_online_status2(
+			    id, pres_st.status == PJSUA_BUDDY_STATUS_ONLINE,
+			    &pj_rpid) );
+}
+
+void Account::setTransport(TransportId tp_id) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_acc_set_transport(id, tp_id) );
+}
+
+void Account::presNotify(const PresNotifyParam &prm) throw(Error)
+{
+    pj_str_t pj_state_str   = str2Pj(prm.stateStr);
+    pj_str_t pj_reason	    = str2Pj(prm.reason);
+    pjsua_msg_data msg_data;
+    prm.txOption.toPj(msg_data);
+
+    PJSUA2_CHECK_EXPR( pjsua_pres_notify(id, (pjsua_srv_pres*)prm.srvPres,
+					 prm.state, &pj_state_str,
+					 &pj_reason, prm.withBody,
+					 &msg_data) );
+}
+
+const BuddyVector& Account::enumBuddies() const throw(Error)
+{
+    return buddyList;
+}
+
+Buddy* Account::findBuddy(string uri, FindBuddyMatch *buddy_match) const
+		throw(Error)
+{
+    if (!buddy_match) {
+	static FindBuddyMatch def_bm;
+	buddy_match = &def_bm;
+    }
+
+    for (unsigned i = 0; i < buddyList.size(); i++) {
+	if (buddy_match->match(uri, *buddyList[i]))
+	    return buddyList[i];
+    }
+    PJSUA2_RAISE_ERROR(PJ_ENOTFOUND);
+}
+
+void Account::addBuddy(Buddy *buddy)
+{
+    pj_assert(buddy);
+
+    buddyList.push_back(buddy);
+}
+
+void Account::removeBuddy(Buddy *buddy)
+{
+    pj_assert(buddy);
+
+    BuddyVector::iterator it;
+    for (it = buddyList.begin(); it != buddyList.end(); it++) {
+	if (*it == buddy) {
+	    buddyList.erase(it);
+	    return;
+	}
+    }
+
+    pj_assert(!"Bug! Buddy to be removed is not in the buddy list!");
+}
diff --git a/jni/pjproject-android/pjsip/src/pjsua2/call.cpp b/jni/pjproject-android/pjsip/src/pjsua2/call.cpp
new file mode 100644
index 0000000..254d10c
--- /dev/null
+++ b/jni/pjproject-android/pjsip/src/pjsua2/call.cpp
@@ -0,0 +1,719 @@
+/* $Id: call.cpp 4704 2014-01-16 05:30:46Z ming $ */
+/*
+ * Copyright (C) 2012-2013 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pjsua2/account.hpp>
+#include <pjsua2/call.hpp>
+#include <pjsua2/endpoint.hpp>
+#include <pj/ctype.h>
+#include "util.hpp"
+
+using namespace pj;
+using namespace std;
+
+#define THIS_FILE		"call.cpp"
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define SDP_BUFFER_SIZE 1024
+
+MathStat::MathStat()
+: n(0), max(0), min(0), last(0), mean(0)
+{
+}
+
+void MathStat::fromPj(const pj_math_stat &prm)
+{
+    this->n    = prm.n;
+    this->max  = prm.max;
+    this->min  = prm.min;
+    this->last = prm.last;
+    this->mean = prm.mean;
+}
+
+void RtcpStreamStat::fromPj(const pjmedia_rtcp_stream_stat &prm)
+{
+    this->update.fromPj(prm.update);
+    this->updateCount     = prm.update_cnt;
+    this->pkt             = (unsigned)prm.pkt;
+    this->bytes           = (unsigned)prm.bytes;
+    this->discard         = prm.discard;
+    this->loss            = prm.loss;
+    this->reorder         = prm.loss;
+    this->dup             = prm.dup;
+    this->lossPeriodUsec.fromPj(prm.loss_period);
+    this->lossType.burst  = prm.loss_type.burst;
+    this->lossType.random = prm.loss_type.random;
+    this->jitterUsec.fromPj(prm.jitter);
+}
+
+void RtcpSdes::fromPj(const pjmedia_rtcp_sdes &prm)
+{
+    this->cname = pj2Str(prm.cname);
+    this->name  = pj2Str(prm.name);
+    this->email = pj2Str(prm.email);
+    this->phone = pj2Str(prm.phone);
+    this->loc   = pj2Str(prm.loc);
+    this->tool  = pj2Str(prm.tool);
+    this->note  = pj2Str(prm.note);
+}
+
+void RtcpStat::fromPj(const pjmedia_rtcp_stat &prm)
+{
+    this->start.fromPj(prm.start);
+    this->txStat.fromPj(prm.tx);
+    this->rxStat.fromPj(prm.rx);
+    this->rttUsec.fromPj(prm.rtt);
+    this->rtpTxLastTs  = prm.rtp_tx_last_ts;
+    this->rtpTxLastSeq = prm.rtp_tx_last_seq;
+#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
+    this->rxIpdvUsec.fromPj(prm.rx_ipdv);
+#endif
+#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && \
+    PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
+    this->rxRawJitterUsec.fromPj(prm.rx_raw_jitter);
+#endif
+    this->peerSdes.fromPj(prm.peer_sdes);
+}
+
+void JbufState::fromPj(const pjmedia_jb_state &prm)
+{
+    this->frameSize    = prm.frame_size;
+    this->minPrefetch  = prm.min_prefetch;
+    this->maxPrefetch  = prm.max_prefetch;
+    this->burst        = prm.burst;
+    this->prefetch     = prm.prefetch;
+    this->size         = prm.size;
+    this->avgDelayMsec = prm.avg_delay;
+    this->minDelayMsec = prm.min_delay;
+    this->maxDelayMsec = prm.max_delay;
+    this->devDelayMsec = prm.dev_delay;
+    this->avgBurst     = prm.avg_burst;
+    this->lost         = prm.lost;
+    this->discard      = prm.discard;
+    this->empty        = prm.empty;
+}
+
+void SdpSession::fromPj(const pjmedia_sdp_session &sdp)
+{
+    char buf[SDP_BUFFER_SIZE];
+    int len;
+
+    len = pjmedia_sdp_print(&sdp, buf, sizeof(buf));
+    wholeSdp = (len > -1? string(buf, len): "");
+    pjSdpSession = (void *)&sdp;
+}
+
+void MediaEvent::fromPj(const pjmedia_event &ev)
+{
+    type = ev.type;
+    if (type == PJMEDIA_EVENT_FMT_CHANGED) {
+        data.fmtChanged.newWidth = ev.data.fmt_changed.new_fmt.det.vid.size.w;
+        data.fmtChanged.newHeight = ev.data.fmt_changed.new_fmt.det.vid.size.h;
+    }
+    pjMediaEvent = (void *)&ev;
+}
+
+void MediaTransportInfo::fromPj(const pjmedia_transport_info &info)
+{
+    char straddr[PJ_INET6_ADDRSTRLEN+10];
+    
+    pj_sockaddr_print(&info.src_rtp_name, straddr, sizeof(straddr), 3);
+    srcRtpName = straddr;
+    pj_sockaddr_print(&info.src_rtcp_name, straddr, sizeof(straddr), 3);
+    srcRtcpName = straddr;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+/* Call Audio Media. */
+class CallAudioMedia : public AudioMedia
+{
+public:
+    /*
+     * Set the conference port identification associated with the
+     * call audio media.
+     */
+    void setPortId(int id);
+};
+
+
+void CallAudioMedia::setPortId(int id)
+{
+    this->id = id;
+}
+
+CallOpParam::CallOpParam(bool useDefaultCallSetting)
+: statusCode(pjsip_status_code(0)), reason(""), options(0)
+{
+    if (useDefaultCallSetting)
+        opt = CallSetting(true);
+}
+
+CallSendRequestParam::CallSendRequestParam()
+: method("")
+{
+}
+
+CallVidSetStreamParam::CallVidSetStreamParam()
+{
+#if PJSUA_HAS_VIDEO
+    pjsua_call_vid_strm_op_param prm;
+    
+    pjsua_call_vid_strm_op_param_default(&prm);
+    this->medIdx = prm.med_idx;
+    this->dir    = prm.dir;
+    this->capDev = prm.cap_dev;
+#endif
+}
+
+CallSetting::CallSetting(pj_bool_t useDefaultValues)
+{
+    if (useDefaultValues) {
+        pjsua_call_setting setting;
+    
+        pjsua_call_setting_default(&setting);
+        fromPj(setting);
+    } else {
+        flag                = 0;
+        reqKeyframeMethod   = 0;
+        audioCount          = 0;
+        videoCount          = 0;
+    }
+}
+
+bool CallSetting::isEmpty() const
+{
+    return (flag == 0 && reqKeyframeMethod == 0 && audioCount == 0 &&
+            videoCount == 0);
+}
+
+void CallSetting::fromPj(const pjsua_call_setting &prm)
+{
+    this->flag              = prm.flag;
+    this->reqKeyframeMethod = prm.req_keyframe_method;
+    this->audioCount        = prm.aud_cnt;
+    this->videoCount        = prm.vid_cnt;
+}
+
+pjsua_call_setting CallSetting::toPj() const
+{
+    pjsua_call_setting setting;
+
+    setting.flag                = this->flag;
+    setting.req_keyframe_method = this->reqKeyframeMethod;
+    setting.aud_cnt             = this->audioCount;
+    setting.vid_cnt             = this->videoCount;
+    
+    return setting;
+}
+
+
+CallMediaInfo::CallMediaInfo()
+{
+}
+
+void CallMediaInfo::fromPj(const pjsua_call_media_info &prm)
+{
+    this->index                     = prm.index;
+    this->type                      = prm.type;
+    this->dir                       = prm.dir;
+    this->status                    = prm.status;
+    if (this->type == PJMEDIA_TYPE_AUDIO) {
+        this->audioConfSlot         = (int)prm.stream.aud.conf_slot;
+    } else if (this->type == PJMEDIA_TYPE_VIDEO) {
+        this->videoIncomingWindowId = prm.stream.vid.win_in;
+        this->videoCapDev           = prm.stream.vid.cap_dev;
+    }
+}
+
+void CallInfo::fromPj(const pjsua_call_info &pci)
+{
+    unsigned mi;
+    
+    id 			= pci.id;
+    role                = pci.role;
+    accId               = pci.acc_id;
+    localUri            = pj2Str(pci.local_info);
+    localContact        = pj2Str(pci.local_contact);
+    remoteUri           = pj2Str(pci.remote_info);
+    remoteContact       = pj2Str(pci.remote_contact);
+    callIdString        = pj2Str(pci.call_id);
+    setting.fromPj(pci.setting);
+    state               = pci.state;
+    stateText           = pj2Str(pci.state_text);
+    lastStatusCode      = pci.last_status;
+    lastReason          = pj2Str(pci.last_status_text);
+    connectDuration.fromPj(pci.connect_duration);
+    totalDuration.fromPj(pci.total_duration);
+    remOfferer          = PJ2BOOL(pci.rem_offerer);
+    remAudioCount       = pci.rem_aud_cnt;
+    remVideoCount       = pci.rem_vid_cnt;
+    
+    for (mi = 0; mi < pci.media_cnt; mi++) {
+        CallMediaInfo med;
+        
+        med.fromPj(pci.media[mi]);
+        media.push_back(med);
+    }
+    for (mi = 0; mi < pci.prov_media_cnt; mi++) {
+        CallMediaInfo med;
+        
+        med.fromPj(pci.prov_media[mi]);
+        provMedia.push_back(med);
+    }
+}
+
+void StreamInfo::fromPj(const pjsua_stream_info &info)
+{
+    char straddr[PJ_INET6_ADDRSTRLEN+10];
+
+    type = info.type;
+    if (type == PJMEDIA_TYPE_AUDIO) {
+        proto = info.info.aud.proto;
+        dir = info.info.aud.dir;
+        pj_sockaddr_print(&info.info.aud.rem_addr, straddr, sizeof(straddr), 3);
+        remoteRtpAddress = straddr;
+        pj_sockaddr_print(&info.info.aud.rem_rtcp, straddr, sizeof(straddr), 3);
+        remoteRtcpAddress = straddr;
+        txPt = info.info.aud.tx_pt;
+        rxPt = info.info.aud.rx_pt;
+        codecName = pj2Str(info.info.aud.fmt.encoding_name);
+        codecClockRate = info.info.aud.fmt.clock_rate;
+        codecParam = info.info.aud.param;
+    } else if (type == PJMEDIA_TYPE_VIDEO) {
+        proto = info.info.vid.proto;
+        dir = info.info.vid.dir;
+        pj_sockaddr_print(&info.info.vid.rem_addr, straddr, sizeof(straddr), 3);
+        remoteRtpAddress = straddr;
+        pj_sockaddr_print(&info.info.vid.rem_rtcp, straddr, sizeof(straddr), 3);
+        remoteRtcpAddress = straddr;
+        txPt = info.info.vid.tx_pt;
+        rxPt = info.info.vid.rx_pt;
+        codecName = pj2Str(info.info.vid.codec_info.encoding_name);
+        codecClockRate = info.info.vid.codec_info.clock_rate;
+        codecParam = info.info.vid.codec_param;
+    }
+}
+
+void StreamStat::fromPj(const pjsua_stream_stat &prm)
+{
+    rtcp.fromPj(prm.rtcp);
+    jbuf.fromPj(prm.jbuf);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct call_param
+{
+    pjsua_msg_data      msg_data;
+    pjsua_msg_data     *p_msg_data;
+    pjsua_call_setting  opt;
+    pjsua_call_setting *p_opt;
+    pj_str_t            reason;
+    pj_str_t           *p_reason;
+
+public:
+    /**
+     * Default constructors with specified parameters.
+     */
+    call_param(const SipTxOption &tx_option);
+    call_param(const SipTxOption &tx_option, const CallSetting &setting,
+               const string &reason_str);
+};
+
+call_param::call_param(const SipTxOption &tx_option)
+{
+    if (tx_option.isEmpty()) {
+        p_msg_data = NULL;
+    } else {
+        tx_option.toPj(msg_data);
+        p_msg_data = &msg_data;
+    }
+    
+    p_opt = NULL;
+    p_reason = NULL;
+}
+
+call_param::call_param(const SipTxOption &tx_option, const CallSetting &setting,
+                       const string &reason_str)
+{
+    if (tx_option.isEmpty()) {
+        p_msg_data = NULL;
+    } else {
+        tx_option.toPj(msg_data);
+        p_msg_data = &msg_data;
+    }
+    
+    if (setting.isEmpty()) {
+        p_opt = NULL;
+    } else {
+        opt = setting.toPj();
+        p_opt = &opt;
+    }
+    
+    reason = str2Pj(reason_str);
+    p_reason = (reason.slen == 0? NULL: &reason);
+}
+
+Call::Call(Account& account, int call_id)
+: acc(account), id(call_id)
+{
+    if (call_id != PJSUA_INVALID_ID)
+        pjsua_call_set_user_data(call_id, this);
+}
+
+Call::~Call()
+{
+    /**
+     * If this instance is deleted, also hangup the corresponding call in
+     * PJSUA library.
+     */
+    if (id != PJSUA_INVALID_ID && pjsua_get_state() < PJSUA_STATE_CLOSING) {
+	pjsua_call_set_user_data(id, NULL);
+        if (isActive()) {
+            CallOpParam prm;
+            hangup(prm);
+        }
+    }
+}
+
+CallInfo Call::getInfo() const throw(Error)
+{
+    pjsua_call_info pj_ci;
+    CallInfo ci;
+    
+    PJSUA2_CHECK_EXPR( pjsua_call_get_info(id, &pj_ci) );
+    ci.fromPj(pj_ci);
+    return ci;
+}
+
+bool Call::isActive() const
+{
+    if (id == PJSUA_INVALID_ID)
+        return false;
+    
+    return (pjsua_call_is_active(id) != 0);
+}
+
+int Call::getId() const
+{
+    return id;
+}
+
+Call *Call::lookup(int call_id)
+{
+    Call *call = (Call*)pjsua_call_get_user_data(call_id);
+    if (call)
+        call->id = call_id;
+    return call;
+}
+
+bool Call::hasMedia() const
+{
+    return (pjsua_call_has_media(id) != 0);
+}
+
+Media *Call::getMedia(unsigned med_idx) const
+{
+    /* Check if the media index is valid and if the media has a valid port ID */
+    if (med_idx >= medias.size() ||
+        (medias[med_idx] && medias[med_idx]->getType() == PJMEDIA_TYPE_AUDIO &&
+         ((AudioMedia *)medias[med_idx])->getPortId() == PJSUA_INVALID_ID))
+    {
+        return NULL;
+    }
+    
+    return medias[med_idx];
+}
+
+pjsip_dialog_cap_status Call::remoteHasCap(int htype,
+                                           const string &hname,
+                                           const string &token) const
+{
+    pj_str_t pj_hname = str2Pj(hname);
+    pj_str_t pj_token = str2Pj(token);
+    
+    return pjsua_call_remote_has_cap(id, htype,
+                                     (htype == PJSIP_H_OTHER)? &pj_hname: NULL,
+                                     &pj_token);
+}
+
+void Call::setUserData(Token user_data)
+{
+    userData = user_data;
+}
+
+Token Call::getUserData() const
+{
+    return userData;
+}
+
+pj_stun_nat_type Call::getRemNatType() throw(Error)
+{
+    pj_stun_nat_type nat;
+    
+    PJSUA2_CHECK_EXPR( pjsua_call_get_rem_nat_type(id, &nat) );
+    
+    return nat;
+}
+
+void Call::makeCall(const string &dst_uri, const CallOpParam &prm) throw(Error)
+{
+    pj_str_t pj_dst_uri = str2Pj(dst_uri);
+    call_param param(prm.txOption, prm.opt, prm.reason);
+    
+    PJSUA2_CHECK_EXPR( pjsua_call_make_call(acc.getId(), &pj_dst_uri,
+                                            param.p_opt, this,
+                                            param.p_msg_data, &id) );
+}
+
+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) );
+}
+
+void Call::hangup(const CallOpParam &prm) throw(Error)
+{
+    call_param param(prm.txOption, prm.opt, prm.reason);
+    
+    PJSUA2_CHECK_EXPR( pjsua_call_hangup(id, prm.statusCode, param.p_reason,
+                                         param.p_msg_data) );
+}
+
+void Call::setHold(const CallOpParam &prm) throw(Error)
+{
+    call_param param(prm.txOption, prm.opt, prm.reason);
+    
+    PJSUA2_CHECK_EXPR( pjsua_call_set_hold2(id, prm.options, param.p_msg_data));
+}
+
+void Call::reinvite(const CallOpParam &prm) throw(Error)
+{
+    call_param param(prm.txOption, prm.opt, prm.reason);
+
+    PJSUA2_CHECK_EXPR( pjsua_call_reinvite2(id, param.p_opt, param.p_msg_data));
+}
+
+void Call::update(const CallOpParam &prm) throw(Error)
+{
+    call_param param(prm.txOption, prm.opt, prm.reason);
+    
+    PJSUA2_CHECK_EXPR( pjsua_call_update2(id, param.p_opt, param.p_msg_data) );
+}
+
+void Call::xfer(const string &dest, const CallOpParam &prm) throw(Error)
+{
+    call_param param(prm.txOption);
+    pj_str_t pj_dest = str2Pj(dest);
+    
+    PJSUA2_CHECK_EXPR( pjsua_call_xfer(id, &pj_dest, param.p_msg_data) );
+}
+
+void Call::xferReplaces(const Call& dest_call,
+                  const CallOpParam &prm) throw(Error)
+{
+    call_param param(prm.txOption);
+    
+    PJSUA2_CHECK_EXPR(pjsua_call_xfer_replaces(id, dest_call.getId(),
+                                               prm.options, param.p_msg_data) );
+}
+
+void Call::processRedirect(pjsip_redirect_op cmd) throw(Error)
+{
+    PJSUA2_CHECK_EXPR(pjsua_call_process_redirect(id, cmd));
+}
+
+void Call::dialDtmf(const string &digits) throw(Error)
+{
+    pj_str_t pj_digits = str2Pj(digits);
+    
+    PJSUA2_CHECK_EXPR(pjsua_call_dial_dtmf(id, &pj_digits));
+}
+
+void Call::sendInstantMessage(const SendInstantMessageParam& prm)
+    throw(Error)
+{
+    pj_str_t mime_type = str2Pj(prm.contentType);
+    pj_str_t content = str2Pj(prm.content);
+    call_param param(prm.txOption);
+
+    PJSUA2_CHECK_EXPR(pjsua_call_send_im(id, &mime_type, &content,
+                                         param.p_msg_data, prm.userData) );
+}
+
+void Call::sendTypingIndication(const SendTypingIndicationParam &prm)
+    throw(Error)
+{
+    call_param param(prm.txOption);
+    
+    PJSUA2_CHECK_EXPR(pjsua_call_send_typing_ind(id,
+                                                 (prm.isTyping?
+                                                  PJ_TRUE: PJ_FALSE),
+                                                 param.p_msg_data) );
+}
+
+void Call::sendRequest(const CallSendRequestParam &prm) throw(Error)
+{
+    pj_str_t method = str2Pj(prm.method);
+    call_param param(prm.txOption);
+    
+    PJSUA2_CHECK_EXPR(pjsua_call_send_request(id, &method, param.p_msg_data) );
+}
+
+string Call::dump(bool with_media, const string indent) throw(Error)
+{
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+    char buffer[1024 * 10];
+#else
+    char buffer[1024 * 3];
+#endif
+
+    PJSUA2_CHECK_EXPR(pjsua_call_dump(id, (with_media? PJ_TRUE: PJ_FALSE),
+                                      buffer, sizeof(buffer),
+                                      indent.c_str()));
+    
+    return buffer;
+}
+
+int Call::vidGetStreamIdx() const
+{
+#if PJSUA_HAS_VIDEO
+    return pjsua_call_get_vid_stream_idx(id);
+#else
+    return PJSUA_INVALID_ID;
+#endif
+}
+
+bool Call::vidStreamIsRunning(int med_idx, pjmedia_dir dir) const
+{
+#if PJSUA_HAS_VIDEO
+    return pjsua_call_vid_stream_is_running(id, med_idx, dir);
+#else
+    PJ_UNUSED_ARG(med_idx);
+    PJ_UNUSED_ARG(dir);
+    return false;
+#endif
+}
+
+void Call::vidSetStream(pjsua_call_vid_strm_op op,
+                        const CallVidSetStreamParam &param) throw(Error)
+{
+#if PJSUA_HAS_VIDEO
+    pjsua_call_vid_strm_op_param prm;
+    
+    prm.med_idx = param.medIdx;
+    prm.dir = param.dir;
+    prm.cap_dev = param.capDev;
+    PJSUA2_CHECK_EXPR( pjsua_call_set_vid_strm(id, op, &prm) );
+#else
+    PJ_UNUSED_ARG(op);
+    PJ_UNUSED_ARG(param);
+    PJSUA2_RAISE_ERROR(PJ_EINVALIDOP);
+#endif
+}
+
+StreamInfo Call::getStreamInfo(unsigned med_idx) const throw(Error)
+{
+    pjsua_stream_info pj_si;
+    StreamInfo si;
+    
+    PJSUA2_CHECK_EXPR( pjsua_call_get_stream_info(id, med_idx, &pj_si) );
+    si.fromPj(pj_si);
+    return si;
+}
+
+StreamStat Call::getStreamStat(unsigned med_idx) const throw(Error)
+{
+    pjsua_stream_stat pj_ss;
+    StreamStat ss;
+    
+    PJSUA2_CHECK_EXPR( pjsua_call_get_stream_stat(id, med_idx, &pj_ss) );
+    ss.fromPj(pj_ss);
+    return ss;
+}
+
+MediaTransportInfo Call::getMedTransportInfo(unsigned med_idx) const
+    throw(Error)
+{
+    pjmedia_transport_info pj_mti;
+    MediaTransportInfo mti;
+    
+    PJSUA2_CHECK_EXPR( pjsua_call_get_med_transport_info(id, med_idx,
+                                                         &pj_mti) );
+    mti.fromPj(pj_mti);
+    return mti;
+}
+
+void Call::processMediaUpdate(OnCallMediaStateParam &prm)
+{
+    pjsua_call_info pj_ci;
+    unsigned mi;
+    
+    if (pjsua_call_get_info(id, &pj_ci) == PJ_SUCCESS) {
+        for (mi = 0; mi < pj_ci.media_cnt; mi++) {
+            if (mi >= medias.size()) {
+                if (pj_ci.media[mi].type == PJMEDIA_TYPE_AUDIO) {
+                    medias.push_back(new CallAudioMedia);
+                } else {
+                    medias.push_back(NULL);
+                }
+            }
+            
+            if (pj_ci.media[mi].type == PJMEDIA_TYPE_AUDIO) {
+                CallAudioMedia *aud_med = (CallAudioMedia *)medias[mi];
+                
+                aud_med->setPortId(pj_ci.media[mi].stream.aud.conf_slot);
+                /* Add media if the conference slot ID is valid. */
+                if (pj_ci.media[mi].stream.aud.conf_slot != PJSUA_INVALID_ID)
+                {
+                    Endpoint::instance().mediaAdd((AudioMedia &)*aud_med);
+                } else {
+                    Endpoint::instance().mediaRemove((AudioMedia &)*aud_med);
+                }
+            }
+        }
+    }
+    
+    /* Call media state callback. */
+    onCallMediaState(prm);
+}
+
+void Call::processStateChange(OnCallStateParam &prm)
+{
+    pjsua_call_info pj_ci;
+    unsigned mi;
+    
+    if (pjsua_call_get_info(id, &pj_ci) == PJ_SUCCESS &&
+        pj_ci.state == PJSIP_INV_STATE_DISCONNECTED)
+    {
+        /* Clear medias. */
+        for (mi = 0; mi < medias.size(); mi++) {
+            if (medias[mi])
+                delete medias[mi];
+        }
+        medias.clear();
+    }
+    
+    onCallState(prm);
+    /* If the state is DISCONNECTED, this call may have already been deleted
+     * by the application in the callback, so do not access it anymore here.
+     */
+}
diff --git a/jni/pjproject-android/pjsip/src/pjsua2/endpoint.cpp b/jni/pjproject-android/pjsip/src/pjsua2/endpoint.cpp
new file mode 100644
index 0000000..4b11bda
--- /dev/null
+++ b/jni/pjproject-android/pjsip/src/pjsua2/endpoint.cpp
@@ -0,0 +1,1612 @@
+/* $Id: endpoint.cpp 4704 2014-01-16 05:30:46Z ming $ */
+/* 
+ * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjsua2/endpoint.hpp>
+#include <pjsua2/account.hpp>
+#include <pjsua2/call.hpp>
+#include <pjsua2/presence.hpp>
+#include <algorithm>
+#include "util.hpp"
+
+using namespace pj;
+using namespace std;
+
+#include <pjsua2/account.hpp>
+#include <pjsua2/call.hpp>
+
+#define THIS_FILE		"endpoint.cpp"
+#define MAX_STUN_SERVERS	32
+#define TIMER_SIGNATURE		0x600D878A
+#define MAX_CODEC_NUM 		64
+
+struct UserTimer
+{
+    pj_uint32_t		signature;
+    OnTimerParam	prm;
+    pj_timer_entry	entry;
+};
+
+Endpoint *Endpoint::instance_;
+
+///////////////////////////////////////////////////////////////////////////////
+
+UaConfig::UaConfig()
+{
+    pjsua_config ua_cfg;
+
+    pjsua_config_default(&ua_cfg);
+    fromPj(ua_cfg);
+}
+
+void UaConfig::fromPj(const pjsua_config &ua_cfg)
+{
+    unsigned i;
+
+    this->maxCalls = ua_cfg.max_calls;
+    this->threadCnt = ua_cfg.thread_cnt;
+    this->userAgent = pj2Str(ua_cfg.user_agent);
+
+    for (i=0; i<ua_cfg.nameserver_count; ++i) {
+	this->nameserver.push_back(pj2Str(ua_cfg.nameserver[i]));
+    }
+
+    for (i=0; i<ua_cfg.stun_srv_cnt; ++i) {
+	this->stunServer.push_back(pj2Str(ua_cfg.stun_srv[i]));
+    }
+
+    this->stunIgnoreFailure = PJ2BOOL(ua_cfg.stun_ignore_failure);
+    this->natTypeInSdp = ua_cfg.nat_type_in_sdp;
+    this->mwiUnsolicitedEnabled = PJ2BOOL(ua_cfg.enable_unsolicited_mwi);
+}
+
+pjsua_config UaConfig::toPj() const
+{
+    unsigned i;
+    pjsua_config pua_cfg;
+
+    pjsua_config_default(&pua_cfg);
+
+    pua_cfg.max_calls = this->maxCalls;
+    pua_cfg.thread_cnt = this->threadCnt;
+    pua_cfg.user_agent = str2Pj(this->userAgent);
+
+    for (i=0; i<this->nameserver.size() && i<PJ_ARRAY_SIZE(pua_cfg.nameserver);
+	 ++i)
+    {
+	pua_cfg.nameserver[i] = str2Pj(this->nameserver[i]);
+    }
+    pua_cfg.nameserver_count = i;
+
+    for (i=0; i<this->stunServer.size() && i<PJ_ARRAY_SIZE(pua_cfg.stun_srv);
+	 ++i)
+    {
+	pua_cfg.stun_srv[i] = str2Pj(this->stunServer[i]);
+    }
+    pua_cfg.stun_srv_cnt = i;
+
+    pua_cfg.nat_type_in_sdp = this->natTypeInSdp;
+    pua_cfg.enable_unsolicited_mwi = this->mwiUnsolicitedEnabled;
+
+    return pua_cfg;
+}
+
+void UaConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("UaConfig");
+
+    NODE_READ_UNSIGNED( this_node, maxCalls);
+    NODE_READ_UNSIGNED( this_node, threadCnt);
+    NODE_READ_BOOL    ( this_node, mainThreadOnly);
+    NODE_READ_STRINGV ( this_node, nameserver);
+    NODE_READ_STRING  ( this_node, userAgent);
+    NODE_READ_STRINGV ( this_node, stunServer);
+    NODE_READ_BOOL    ( this_node, stunIgnoreFailure);
+    NODE_READ_INT     ( this_node, natTypeInSdp);
+    NODE_READ_BOOL    ( this_node, mwiUnsolicitedEnabled);
+}
+
+void UaConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("UaConfig");
+
+    NODE_WRITE_UNSIGNED( this_node, maxCalls);
+    NODE_WRITE_UNSIGNED( this_node, threadCnt);
+    NODE_WRITE_BOOL    ( this_node, mainThreadOnly);
+    NODE_WRITE_STRINGV ( this_node, nameserver);
+    NODE_WRITE_STRING  ( this_node, userAgent);
+    NODE_WRITE_STRINGV ( this_node, stunServer);
+    NODE_WRITE_BOOL    ( this_node, stunIgnoreFailure);
+    NODE_WRITE_INT     ( this_node, natTypeInSdp);
+    NODE_WRITE_BOOL    ( this_node, mwiUnsolicitedEnabled);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+LogConfig::LogConfig()
+{
+    pjsua_logging_config lc;
+
+    pjsua_logging_config_default(&lc);
+    fromPj(lc);
+}
+
+void LogConfig::fromPj(const pjsua_logging_config &lc)
+{
+    this->msgLogging = lc.msg_logging;
+    this->level = lc.level;
+    this->consoleLevel = lc.console_level;
+    this->decor = lc.decor;
+    this->filename = pj2Str(lc.log_filename);
+    this->fileFlags = lc.log_file_flags;
+    this->writer = NULL;
+}
+
+pjsua_logging_config LogConfig::toPj() const
+{
+    pjsua_logging_config lc;
+
+    pjsua_logging_config_default(&lc);
+
+    lc.msg_logging = this->msgLogging;
+    lc.level = this->level;
+    lc.console_level = this->consoleLevel;
+    lc.decor = this->decor;
+    lc.log_file_flags = this->fileFlags;
+    lc.log_filename = str2Pj(this->filename);
+
+    return lc;
+}
+
+void LogConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("LogConfig");
+
+    NODE_READ_UNSIGNED( this_node, msgLogging);
+    NODE_READ_UNSIGNED( this_node, level);
+    NODE_READ_UNSIGNED( this_node, consoleLevel);
+    NODE_READ_UNSIGNED( this_node, decor);
+    NODE_READ_STRING  ( this_node, filename);
+    NODE_READ_UNSIGNED( this_node, fileFlags);
+}
+
+void LogConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("LogConfig");
+
+    NODE_WRITE_UNSIGNED( this_node, msgLogging);
+    NODE_WRITE_UNSIGNED( this_node, level);
+    NODE_WRITE_UNSIGNED( this_node, consoleLevel);
+    NODE_WRITE_UNSIGNED( this_node, decor);
+    NODE_WRITE_STRING  ( this_node, filename);
+    NODE_WRITE_UNSIGNED( this_node, fileFlags);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MediaConfig::MediaConfig()
+{
+    pjsua_media_config mc;
+
+    pjsua_media_config_default(&mc);
+    fromPj(mc);
+}
+
+void MediaConfig::fromPj(const pjsua_media_config &mc)
+{
+    this->clockRate = mc.clock_rate;
+    this->sndClockRate = mc.snd_clock_rate;
+    this->channelCount = mc.channel_count;
+    this->audioFramePtime = mc.audio_frame_ptime;
+    this->maxMediaPorts = mc.max_media_ports;
+    this->hasIoqueue = PJ2BOOL(mc.has_ioqueue);
+    this->threadCnt = mc.thread_cnt;
+    this->quality = mc.quality;
+    this->ptime = mc.ptime;
+    this->noVad = PJ2BOOL(mc.no_vad);
+    this->ilbcMode = mc.ilbc_mode;
+    this->txDropPct = mc.tx_drop_pct;
+    this->rxDropPct = mc.rx_drop_pct;
+    this->ecOptions = mc.ec_options;
+    this->ecTailLen = mc.ec_tail_len;
+    this->sndRecLatency = mc.snd_rec_latency;
+    this->sndPlayLatency = mc.snd_play_latency;
+    this->jbInit = mc.jb_init;
+    this->jbMinPre = mc.jb_min_pre;
+    this->jbMaxPre = mc.jb_max_pre;
+    this->jbMax = mc.jb_max;
+    this->sndAutoCloseTime = mc.snd_auto_close_time;
+    this->vidPreviewEnableNative = PJ2BOOL(mc.vid_preview_enable_native);
+}
+
+pjsua_media_config MediaConfig::toPj() const
+{
+    pjsua_media_config mcfg;
+
+    pjsua_media_config_default(&mcfg);
+
+    mcfg.clock_rate = this->clockRate;
+    mcfg.snd_clock_rate = this->sndClockRate;
+    mcfg.channel_count = this->channelCount;
+    mcfg.audio_frame_ptime = this->audioFramePtime;
+    mcfg.max_media_ports = this->maxMediaPorts;
+    mcfg.has_ioqueue = this->hasIoqueue;
+    mcfg.thread_cnt = this->threadCnt;
+    mcfg.quality = this->quality;
+    mcfg.ptime = this->ptime;
+    mcfg.no_vad = this->noVad;
+    mcfg.ilbc_mode = this->ilbcMode;
+    mcfg.tx_drop_pct = this->txDropPct;
+    mcfg.rx_drop_pct = this->rxDropPct;
+    mcfg.ec_options = this->ecOptions;
+    mcfg.ec_tail_len = this->ecTailLen;
+    mcfg.snd_rec_latency = this->sndRecLatency;
+    mcfg.snd_play_latency = this->sndPlayLatency;
+    mcfg.jb_init = this->jbInit;
+    mcfg.jb_min_pre = this->jbMinPre;
+    mcfg.jb_max_pre = this->jbMaxPre;
+    mcfg.jb_max = this->jbMax;
+    mcfg.snd_auto_close_time = this->sndAutoCloseTime;
+    mcfg.vid_preview_enable_native = this->vidPreviewEnableNative;
+
+    return mcfg;
+}
+
+void MediaConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("MediaConfig");
+
+    NODE_READ_UNSIGNED( this_node, clockRate);
+    NODE_READ_UNSIGNED( this_node, sndClockRate);
+    NODE_READ_UNSIGNED( this_node, channelCount);
+    NODE_READ_UNSIGNED( this_node, audioFramePtime);
+    NODE_READ_UNSIGNED( this_node, maxMediaPorts);
+    NODE_READ_BOOL    ( this_node, hasIoqueue);
+    NODE_READ_UNSIGNED( this_node, threadCnt);
+    NODE_READ_UNSIGNED( this_node, quality);
+    NODE_READ_UNSIGNED( this_node, ptime);
+    NODE_READ_BOOL    ( this_node, noVad);
+    NODE_READ_UNSIGNED( this_node, ilbcMode);
+    NODE_READ_UNSIGNED( this_node, txDropPct);
+    NODE_READ_UNSIGNED( this_node, rxDropPct);
+    NODE_READ_UNSIGNED( this_node, ecOptions);
+    NODE_READ_UNSIGNED( this_node, ecTailLen);
+    NODE_READ_UNSIGNED( this_node, sndRecLatency);
+    NODE_READ_UNSIGNED( this_node, sndPlayLatency);
+    NODE_READ_INT     ( this_node, jbInit);
+    NODE_READ_INT     ( this_node, jbMinPre);
+    NODE_READ_INT     ( this_node, jbMaxPre);
+    NODE_READ_INT     ( this_node, jbMax);
+    NODE_READ_INT     ( this_node, sndAutoCloseTime);
+    NODE_READ_BOOL    ( this_node, vidPreviewEnableNative);
+}
+
+void MediaConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("MediaConfig");
+
+    NODE_WRITE_UNSIGNED( this_node, clockRate);
+    NODE_WRITE_UNSIGNED( this_node, sndClockRate);
+    NODE_WRITE_UNSIGNED( this_node, channelCount);
+    NODE_WRITE_UNSIGNED( this_node, audioFramePtime);
+    NODE_WRITE_UNSIGNED( this_node, maxMediaPorts);
+    NODE_WRITE_BOOL    ( this_node, hasIoqueue);
+    NODE_WRITE_UNSIGNED( this_node, threadCnt);
+    NODE_WRITE_UNSIGNED( this_node, quality);
+    NODE_WRITE_UNSIGNED( this_node, ptime);
+    NODE_WRITE_BOOL    ( this_node, noVad);
+    NODE_WRITE_UNSIGNED( this_node, ilbcMode);
+    NODE_WRITE_UNSIGNED( this_node, txDropPct);
+    NODE_WRITE_UNSIGNED( this_node, rxDropPct);
+    NODE_WRITE_UNSIGNED( this_node, ecOptions);
+    NODE_WRITE_UNSIGNED( this_node, ecTailLen);
+    NODE_WRITE_UNSIGNED( this_node, sndRecLatency);
+    NODE_WRITE_UNSIGNED( this_node, sndPlayLatency);
+    NODE_WRITE_INT     ( this_node, jbInit);
+    NODE_WRITE_INT     ( this_node, jbMinPre);
+    NODE_WRITE_INT     ( this_node, jbMaxPre);
+    NODE_WRITE_INT     ( this_node, jbMax);
+    NODE_WRITE_INT     ( this_node, sndAutoCloseTime);
+    NODE_WRITE_BOOL    ( this_node, vidPreviewEnableNative);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void EpConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("EpConfig");
+    NODE_READ_OBJ( this_node, uaConfig);
+    NODE_READ_OBJ( this_node, logConfig);
+    NODE_READ_OBJ( this_node, medConfig);
+}
+
+void EpConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("EpConfig");
+    NODE_WRITE_OBJ( this_node, uaConfig);
+    NODE_WRITE_OBJ( this_node, logConfig);
+    NODE_WRITE_OBJ( this_node, medConfig);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/* Class to post log to main thread */
+struct PendingLog : public PendingJob
+{
+    LogEntry entry;
+    virtual void execute(bool is_pending)
+    {
+	PJ_UNUSED_ARG(is_pending);
+	Endpoint::instance().utilLogWrite(entry);
+    }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Endpoint instance
+ */
+Endpoint::Endpoint()
+: writer(NULL), mainThreadOnly(false), mainThread(NULL), pendingJobSize(0)
+{
+    if (instance_) {
+	PJSUA2_RAISE_ERROR(PJ_EEXISTS);
+    }
+
+    instance_ = this;
+}
+
+Endpoint& Endpoint::instance() throw(Error)
+{
+    if (!instance_) {
+	PJSUA2_RAISE_ERROR(PJ_ENOTFOUND);
+    }
+    return *instance_;
+}
+
+Endpoint::~Endpoint()
+{
+    while (!pendingJobs.empty()) {
+	delete pendingJobs.front();
+	pendingJobs.pop_front();
+    }
+
+    while(mediaList.size() > 0) {
+	AudioMedia *cur_media = mediaList[0];
+	delete cur_media; /* this will remove itself from the list */
+    }
+
+    clearCodecInfoList();
+
+    try {
+	libDestroy();
+    } catch (Error &err) {
+	// Ignore
+	PJ_UNUSED_ARG(err);
+    }
+
+    instance_ = NULL;
+}
+
+void Endpoint::utilAddPendingJob(PendingJob *job)
+{
+    enum {
+	MAX_PENDING_JOBS = 1024
+    };
+
+    /* See if we can execute immediately */
+    if (!mainThreadOnly || pj_thread_this()==mainThread) {
+	job->execute(false);
+	delete job;
+	return;
+    }
+
+    if (pendingJobSize > MAX_PENDING_JOBS) {
+	enum { NUMBER_TO_DISCARD = 5 };
+
+	pj_enter_critical_section();
+	for (unsigned i=0; i<NUMBER_TO_DISCARD; ++i) {
+	    delete pendingJobs.back();
+	    pendingJobs.pop_back();
+	}
+
+	pendingJobSize -= NUMBER_TO_DISCARD;
+	pj_leave_critical_section();
+
+	utilLogWrite(1, THIS_FILE,
+	             "*** ERROR: Job queue full!! Jobs discarded!!! ***");
+    }
+
+    pj_enter_critical_section();
+    pendingJobs.push_back(job);
+    pendingJobSize++;
+    pj_leave_critical_section();
+}
+
+/* Handle log callback */
+void Endpoint::utilLogWrite(LogEntry &entry)
+{
+    if (mainThreadOnly && pj_thread_this() != mainThread) {
+	PendingLog *job = new PendingLog;
+	job->entry = entry;
+	utilAddPendingJob(job);
+    } else {
+	writer->write(entry);
+    }
+}
+
+/* Run pending jobs only in main thread */
+void Endpoint::performPendingJobs()
+{
+    if (pj_thread_this() != mainThread)
+	return;
+
+    if (pendingJobSize == 0)
+	return;
+
+    for (;;) {
+	PendingJob *job = NULL;
+
+	pj_enter_critical_section();
+	if (pendingJobSize != 0) {
+	    job = pendingJobs.front();
+	    pendingJobs.pop_front();
+	    pendingJobSize--;
+	}
+	pj_leave_critical_section();
+
+	if (job) {
+	    job->execute(true);
+	    delete job;
+	} else
+	    break;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Endpoint static callbacks
+ */
+void Endpoint::logFunc(int level, const char *data, int len)
+{
+    Endpoint &ep = Endpoint::instance();
+
+    if (!ep.writer)
+	return;
+
+    LogEntry entry;
+    entry.level = level;
+    entry.msg = string(data, len);
+    entry.threadId = (long)pj_thread_this();
+    entry.threadName = string(pj_thread_get_name(pj_thread_this()));
+
+    ep.utilLogWrite(entry);
+}
+
+void Endpoint::stun_resolve_cb(const pj_stun_resolve_result *res)
+{
+    Endpoint &ep = Endpoint::instance();
+
+    if (!res)
+	return;
+
+    OnNatCheckStunServersCompleteParam prm;
+
+    prm.userData = res->token;
+    prm.status = res->status;
+    if (res->status == PJ_SUCCESS) {
+	char straddr[PJ_INET6_ADDRSTRLEN+10];
+
+	prm.name = string(res->name.ptr, res->name.slen);
+	pj_sockaddr_print(&res->addr, straddr, sizeof(straddr), 3);
+	prm.addr = straddr;
+    }
+
+    ep.onNatCheckStunServersComplete(prm);
+}
+
+void Endpoint::on_timer(pj_timer_heap_t *timer_heap,
+                        pj_timer_entry *entry)
+{
+    PJ_UNUSED_ARG(timer_heap);
+
+    Endpoint &ep = Endpoint::instance();
+    UserTimer *ut = (UserTimer*) entry->user_data;
+
+    if (ut->signature != TIMER_SIGNATURE)
+	return;
+
+    ep.onTimer(ut->prm);
+}
+
+void Endpoint::on_nat_detect(const pj_stun_nat_detect_result *res)
+{
+    Endpoint &ep = Endpoint::instance();
+
+    if (!res)
+	return;
+
+    OnNatDetectionCompleteParam prm;
+
+    prm.status = res->status;
+    prm.reason = res->status_text;
+    prm.natType = res->nat_type;
+    prm.natTypeName = res->nat_type_name;
+
+    ep.onNatDetectionComplete(prm);
+}
+
+void Endpoint::on_transport_state( pjsip_transport *tp,
+				   pjsip_transport_state state,
+				   const pjsip_transport_state_info *info)
+{
+    Endpoint &ep = Endpoint::instance();
+
+    OnTransportStateParam prm;
+
+    prm.hnd = (TransportHandle)tp;
+    prm.state = state;
+    prm.lastError = info ? info->status : PJ_SUCCESS;
+
+    ep.onTransportState(prm);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Account static callbacks
+ */
+
+Account *Endpoint::lookupAcc(int acc_id, const char *op)
+{
+    Account *acc = Account::lookup(acc_id);
+    if (!acc) {
+	PJ_LOG(1,(THIS_FILE,
+		  "Error: cannot find Account instance for account id %d in "
+		  "%s", acc_id, op));
+    }
+
+    return acc;
+}
+
+Call *Endpoint::lookupCall(int call_id, const char *op)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	PJ_LOG(1,(THIS_FILE,
+		  "Error: cannot find Call instance for call id %d in "
+		  "%s", call_id, op));
+    }
+
+    return call;
+}
+
+void Endpoint::on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
+                                pjsip_rx_data *rdata)
+{
+    Account *acc = lookupAcc(acc_id, "on_incoming_call()");
+    if (!acc) {
+	pjsua_call_hangup(call_id, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL);
+	return;
+    }
+
+    /* call callback */
+    OnIncomingCallParam prm;
+    prm.callId = call_id;
+    prm.rdata.fromPj(*rdata);
+
+    acc->onIncomingCall(prm);
+
+    /* disconnect if callback doesn't handle the call */
+    pjsua_call_info ci;
+
+    pjsua_call_get_info(call_id, &ci);
+    if (!pjsua_call_get_user_data(call_id) &&
+	ci.state != PJSIP_INV_STATE_DISCONNECTED)
+    {
+	pjsua_call_hangup(call_id, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL);
+    }
+}
+
+void Endpoint::on_reg_started(pjsua_acc_id acc_id, pj_bool_t renew)
+{
+    Account *acc = lookupAcc(acc_id, "on_reg_started()");
+    if (!acc) {
+	return;
+    }
+
+    OnRegStartedParam prm;
+    prm.renew = PJ2BOOL(renew);
+    acc->onRegStarted(prm);
+}
+
+void Endpoint::on_reg_state2(pjsua_acc_id acc_id, pjsua_reg_info *info)
+{
+    Account *acc = lookupAcc(acc_id, "on_reg_state2()");
+    if (!acc) {
+	return;
+    }
+
+    OnRegStateParam prm;
+    prm.status		= info->cbparam->status;
+    prm.code 		= (pjsip_status_code) info->cbparam->code;
+    prm.reason		= pj2Str(info->cbparam->reason);
+    if (info->cbparam->rdata)
+	prm.rdata.fromPj(*info->cbparam->rdata);
+    prm.expiration	= info->cbparam->expiration;
+
+    acc->onRegState(prm);
+}
+
+void Endpoint::on_incoming_subscribe(pjsua_acc_id acc_id,
+                                     pjsua_srv_pres *srv_pres,
+                                     pjsua_buddy_id buddy_id,
+                                     const pj_str_t *from,
+                                     pjsip_rx_data *rdata,
+                                     pjsip_status_code *code,
+                                     pj_str_t *reason,
+                                     pjsua_msg_data *msg_data)
+{
+    PJ_UNUSED_ARG(buddy_id);
+    PJ_UNUSED_ARG(srv_pres);
+
+    Account *acc = lookupAcc(acc_id, "on_incoming_subscribe()");
+    if (!acc) {
+	/* default behavior should apply */
+	return;
+    }
+
+    OnIncomingSubscribeParam prm;
+    prm.srvPres		= srv_pres;
+    prm.fromUri 	= pj2Str(*from);
+    prm.rdata.fromPj(*rdata);
+    prm.code		= *code;
+    prm.reason		= pj2Str(*reason);
+    prm.txOption.fromPj(*msg_data);
+
+    acc->onIncomingSubscribe(prm);
+
+    *code = prm.code;
+    acc->tmpReason = prm.reason;
+    *reason = str2Pj(acc->tmpReason);
+    prm.txOption.toPj(*msg_data);
+}
+
+void Endpoint::on_pager2(pjsua_call_id call_id,
+                         const pj_str_t *from,
+                         const pj_str_t *to,
+                         const pj_str_t *contact,
+                         const pj_str_t *mime_type,
+                         const pj_str_t *body,
+                         pjsip_rx_data *rdata,
+                         pjsua_acc_id acc_id)
+{
+    OnInstantMessageParam prm;
+    prm.fromUri		= pj2Str(*from);
+    prm.toUri		= pj2Str(*to);
+    prm.contactUri	= pj2Str(*contact);
+    prm.contentType	= pj2Str(*mime_type);
+    prm.msgBody		= pj2Str(*body);
+    prm.rdata.fromPj(*rdata);
+
+    if (call_id != PJSUA_INVALID_ID) {
+	Call *call = lookupCall(call_id, "on_pager2()");
+	if (!call) {
+	    /* Ignored */
+	    return;
+	}
+
+	call->onInstantMessage(prm);
+    } else {
+	Account *acc = lookupAcc(acc_id, "on_pager2()");
+	if (!acc) {
+	    /* Ignored */
+	    return;
+	}
+
+	acc->onInstantMessage(prm);
+    }
+}
+
+void Endpoint::on_pager_status2( pjsua_call_id call_id,
+				 const pj_str_t *to,
+				 const pj_str_t *body,
+				 void *user_data,
+				 pjsip_status_code status,
+				 const pj_str_t *reason,
+				 pjsip_tx_data *tdata,
+				 pjsip_rx_data *rdata,
+				 pjsua_acc_id acc_id)
+{
+    PJ_UNUSED_ARG(tdata);
+
+    OnInstantMessageStatusParam prm;
+    prm.userData	= user_data;
+    prm.toUri		= pj2Str(*to);
+    prm.msgBody		= pj2Str(*body);
+    prm.code		= status;
+    prm.reason		= pj2Str(*reason);
+    if (rdata)
+	prm.rdata.fromPj(*rdata);
+
+    if (call_id != PJSUA_INVALID_ID) {
+	Call *call = lookupCall(call_id, "on_pager_status2()");
+	if (!call) {
+	    /* Ignored */
+	    return;
+	}
+
+	call->onInstantMessageStatus(prm);
+    } else {
+	Account *acc = lookupAcc(acc_id, "on_pager_status2()");
+	if (!acc) {
+	    /* Ignored */
+	    return;
+	}
+
+	acc->onInstantMessageStatus(prm);
+    }
+}
+
+void Endpoint::on_typing2( pjsua_call_id call_id,
+			   const pj_str_t *from,
+			   const pj_str_t *to,
+			   const pj_str_t *contact,
+			   pj_bool_t is_typing,
+			   pjsip_rx_data *rdata,
+			   pjsua_acc_id acc_id)
+{
+    OnTypingIndicationParam prm;
+    prm.fromUri		= pj2Str(*from);
+    prm.toUri		= pj2Str(*to);
+    prm.contactUri	= pj2Str(*contact);
+    prm.isTyping	= is_typing != 0;
+    prm.rdata.fromPj(*rdata);
+
+    if (call_id != PJSUA_INVALID_ID) {
+	Call *call = lookupCall(call_id, "on_typing2()");
+	if (!call) {
+	    /* Ignored */
+	    return;
+	}
+
+	call->onTypingIndication(prm);
+    } else {
+	Account *acc = lookupAcc(acc_id, "on_typing2()");
+	if (!acc) {
+	    /* Ignored */
+	    return;
+	}
+
+	acc->onTypingIndication(prm);
+    }
+}
+
+void Endpoint::on_mwi_info(pjsua_acc_id acc_id,
+                           pjsua_mwi_info *mwi_info)
+{
+    OnMwiInfoParam prm;
+    prm.state	= pjsip_evsub_get_state(mwi_info->evsub);
+    prm.rdata.fromPj(*mwi_info->rdata);
+
+    Account *acc = lookupAcc(acc_id, "on_mwi_info()");
+    if (!acc) {
+	/* Ignored */
+	return;
+    }
+
+    acc->onMwiInfo(prm);
+}
+
+void Endpoint::on_buddy_state(pjsua_buddy_id buddy_id)
+{
+    Buddy *buddy = (Buddy*)pjsua_buddy_get_user_data(buddy_id);
+    if (!buddy || !buddy->isValid()) {
+	/* Ignored */
+	return;
+    }
+
+    buddy->onBuddyState();
+}
+
+// Call callbacks
+void Endpoint::on_call_state(pjsua_call_id call_id, pjsip_event *e)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallStateParam prm;
+    prm.e.fromPj(*e);
+    
+    call->processStateChange(prm);
+    /* If the state is DISCONNECTED, call may have already been deleted
+     * by the application in the callback, so do not access it anymore here.
+     */
+}
+
+void Endpoint::on_call_tsx_state(pjsua_call_id call_id,
+                                 pjsip_transaction *tsx,
+                                 pjsip_event *e)
+{
+    PJ_UNUSED_ARG(tsx);
+
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallTsxStateParam prm;
+    prm.e.fromPj(*e);
+    
+    call->onCallTsxState(prm);
+}
+
+void Endpoint::on_call_media_state(pjsua_call_id call_id)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+
+    OnCallMediaStateParam prm;
+    call->processMediaUpdate(prm);
+}
+
+void Endpoint::on_call_sdp_created(pjsua_call_id call_id,
+                                   pjmedia_sdp_session *sdp,
+                                   pj_pool_t *pool,
+                                   const pjmedia_sdp_session *rem_sdp)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallSdpCreatedParam prm;
+    string orig_sdp;
+    
+    prm.sdp.fromPj(*sdp);
+    orig_sdp = prm.sdp.wholeSdp;
+    if (rem_sdp)
+        prm.remSdp.fromPj(*rem_sdp);
+    
+    call->onCallSdpCreated(prm);
+    
+    /* Check if application modifies the SDP */
+    if (orig_sdp != prm.sdp.wholeSdp) {
+        pjmedia_sdp_parse(pool, (char*)prm.sdp.wholeSdp.c_str(),
+                          prm.sdp.wholeSdp.size(), &sdp);
+    }
+}
+
+void Endpoint::on_stream_created(pjsua_call_id call_id,
+                                 pjmedia_stream *strm,
+                                 unsigned stream_idx,
+                                 pjmedia_port **p_port)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnStreamCreatedParam prm;
+    prm.stream = strm;
+    prm.streamIdx = stream_idx;
+    prm.pPort = (void *)*p_port;
+    
+    call->onStreamCreated(prm);
+    
+    if (prm.pPort != (void *)*p_port)
+        *p_port = (pjmedia_port *)prm.pPort;
+}
+
+void Endpoint::on_stream_destroyed(pjsua_call_id call_id,
+                                   pjmedia_stream *strm,
+                                   unsigned stream_idx)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnStreamDestroyedParam prm;
+    prm.stream = strm;
+    prm.streamIdx = stream_idx;
+    
+    call->onStreamDestroyed(prm);
+}
+
+struct PendingOnDtmfDigitCallback : public PendingJob
+{
+    int call_id;
+    OnDtmfDigitParam prm;
+
+    virtual void execute(bool is_pending)
+    {
+	PJ_UNUSED_ARG(is_pending);
+
+	Call *call = Call::lookup(call_id);
+	if (!call)
+	    return;
+
+	call->onDtmfDigit(prm);
+    }
+};
+
+void Endpoint::on_dtmf_digit(pjsua_call_id call_id, int digit)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    PendingOnDtmfDigitCallback *job = new PendingOnDtmfDigitCallback;
+    job->call_id = call_id;
+    char buf[10];
+    pj_ansi_sprintf(buf, "%c", digit);
+    job->prm.digit = (string)buf;
+    
+    Endpoint::instance().utilAddPendingJob(job);
+}
+
+void Endpoint::on_call_transfer_request2(pjsua_call_id call_id,
+                                         const pj_str_t *dst,
+                                         pjsip_status_code *code,
+                                         pjsua_call_setting *opt)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallTransferRequestParam prm;
+    prm.dstUri = pj2Str(*dst);
+    prm.statusCode = *code;
+    prm.opt.fromPj(*opt);
+    
+    call->onCallTransferRequest(prm);
+    
+    *code = prm.statusCode;
+    *opt = prm.opt.toPj();
+}
+
+void Endpoint::on_call_transfer_status(pjsua_call_id call_id,
+                                       int st_code,
+                                       const pj_str_t *st_text,
+                                       pj_bool_t final,
+                                       pj_bool_t *p_cont)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallTransferStatusParam prm;
+    prm.statusCode = (pjsip_status_code)st_code;
+    prm.reason = pj2Str(*st_text);
+    prm.finalNotify = PJ2BOOL(final);
+    prm.cont = PJ2BOOL(*p_cont);
+    
+    call->onCallTransferStatus(prm);
+    
+    *p_cont = prm.cont;
+}
+
+void Endpoint::on_call_replace_request2(pjsua_call_id call_id,
+                                        pjsip_rx_data *rdata,
+                                        int *st_code,
+                                        pj_str_t *st_text,
+                                        pjsua_call_setting *opt)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallReplaceRequestParam prm;
+    prm.rdata.fromPj(*rdata);
+    prm.statusCode = (pjsip_status_code)*st_code;
+    prm.reason = pj2Str(*st_text);
+    prm.opt.fromPj(*opt);
+    
+    call->onCallReplaceRequest(prm);
+    
+    *st_code = prm.statusCode;
+    *st_text = str2Pj(prm.reason);
+    *opt = prm.opt.toPj();
+}
+
+void Endpoint::on_call_replaced(pjsua_call_id old_call_id,
+                                pjsua_call_id new_call_id)
+{
+    Call *call = Call::lookup(old_call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallReplacedParam prm;
+    prm.newCallId = new_call_id;
+    
+    call->onCallReplaced(prm);
+}
+
+void Endpoint::on_call_rx_offer(pjsua_call_id call_id,
+                                const pjmedia_sdp_session *offer,
+                                void *reserved,
+                                pjsip_status_code *code,
+                                pjsua_call_setting *opt)
+{
+    PJ_UNUSED_ARG(reserved);
+
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallRxOfferParam prm;
+    prm.offer.fromPj(*offer);
+    prm.statusCode = *code;
+    prm.opt.fromPj(*opt);
+    
+    call->onCallRxOffer(prm);
+    
+    *code = prm.statusCode;
+    *opt = prm.opt.toPj();
+}
+
+pjsip_redirect_op Endpoint::on_call_redirected(pjsua_call_id call_id,
+                                               const pjsip_uri *target,
+                                               const pjsip_event *e)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return PJSIP_REDIRECT_STOP;
+    }
+    
+    OnCallRedirectedParam prm;
+    char uristr[PJSIP_MAX_URL_SIZE];
+    int len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, target, uristr,
+                              sizeof(uristr));
+    if (len < 1) {
+        pj_ansi_strcpy(uristr, "--URI too long--");
+    }
+    prm.targetUri = string(uristr);
+    if (e)
+        prm.e.fromPj(*e);
+    else
+        prm.e.type = PJSIP_EVENT_UNKNOWN;
+    
+    return call->onCallRedirected(prm);
+}
+
+
+struct PendingOnMediaTransportCallback : public PendingJob
+{
+    int call_id;
+    OnCallMediaTransportStateParam prm;
+
+    virtual void execute(bool is_pending)
+    {
+	PJ_UNUSED_ARG(is_pending);
+
+	Call *call = Call::lookup(call_id);
+	if (!call)
+	    return;
+
+	call->onCallMediaTransportState(prm);
+    }
+};
+
+pj_status_t
+Endpoint::on_call_media_transport_state(pjsua_call_id call_id,
+                                        const pjsua_med_tp_state_info *info)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return PJ_SUCCESS;
+    }
+
+    PendingOnMediaTransportCallback *job = new PendingOnMediaTransportCallback;
+    
+    job->call_id = call_id;
+    job->prm.medIdx = info->med_idx;
+    job->prm.state = info->state;
+    job->prm.status = info->status;
+    job->prm.sipErrorCode = info->sip_err_code;
+    
+    Endpoint::instance().utilAddPendingJob(job);
+
+    return PJ_SUCCESS;
+}
+
+struct PendingOnMediaEventCallback : public PendingJob
+{
+    int call_id;
+    OnCallMediaEventParam prm;
+
+    virtual void execute(bool is_pending)
+    {
+	Call *call = Call::lookup(call_id);
+	if (!call)
+	    return;
+
+	if (is_pending) {
+	    /* Can't do this anymore, pointer is invalid */
+	    prm.ev.pjMediaEvent = NULL;
+	}
+
+	call->onCallMediaEvent(prm);
+    }
+};
+
+void Endpoint::on_call_media_event(pjsua_call_id call_id,
+                                   unsigned med_idx,
+                                   pjmedia_event *event)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    PendingOnMediaEventCallback *job = new PendingOnMediaEventCallback;
+
+    job->call_id = call_id;
+    job->prm.medIdx = med_idx;
+    job->prm.ev.fromPj(*event);
+    
+    Endpoint::instance().utilAddPendingJob(job);
+}
+
+pjmedia_transport*
+Endpoint::on_create_media_transport(pjsua_call_id call_id,
+                                    unsigned media_idx,
+                                    pjmedia_transport *base_tp,
+                                    unsigned flags)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return base_tp;
+    }
+    
+    OnCreateMediaTransportParam prm;
+    prm.mediaIdx = media_idx;
+    prm.mediaTp = base_tp;
+    prm.flags = flags;
+    
+    call->onCreateMediaTransport(prm);
+    
+    return (pjmedia_transport *)prm.mediaTp;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Endpoint library operations
+ */
+Version Endpoint::libVersion() const
+{
+    Version ver;
+    ver.major = PJ_VERSION_NUM_MAJOR;
+    ver.minor = PJ_VERSION_NUM_MINOR;
+    ver.rev = PJ_VERSION_NUM_REV;
+    ver.suffix = PJ_VERSION_NUM_EXTRA;
+    ver.full = pj_get_version();
+    ver.numeric = PJ_VERSION_NUM;
+    return ver;
+}
+
+void Endpoint::libCreate() throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_create() );
+    mainThread = pj_thread_this();
+}
+
+pjsua_state Endpoint::libGetState() const
+{
+    return pjsua_get_state();
+}
+
+void Endpoint::libInit(const EpConfig &prmEpConfig) throw(Error)
+{
+    pjsua_config ua_cfg;
+    pjsua_logging_config log_cfg;
+    pjsua_media_config med_cfg;
+
+    ua_cfg = prmEpConfig.uaConfig.toPj();
+    log_cfg = prmEpConfig.logConfig.toPj();
+    med_cfg = prmEpConfig.medConfig.toPj();
+
+    /* Setup log callback */
+    if (prmEpConfig.logConfig.writer) {
+	this->writer = prmEpConfig.logConfig.writer;
+	log_cfg.cb = &Endpoint::logFunc;
+    }
+    mainThreadOnly = prmEpConfig.uaConfig.mainThreadOnly;
+
+    /* Setup UA callbacks */
+    pj_bzero(&ua_cfg.cb, sizeof(ua_cfg.cb));
+    ua_cfg.cb.on_nat_detect 	= &Endpoint::on_nat_detect;
+    ua_cfg.cb.on_transport_state = &Endpoint::on_transport_state;
+
+    ua_cfg.cb.on_incoming_call	= &Endpoint::on_incoming_call;
+    ua_cfg.cb.on_reg_started	= &Endpoint::on_reg_started;
+    ua_cfg.cb.on_reg_state2	= &Endpoint::on_reg_state2;
+    ua_cfg.cb.on_incoming_subscribe = &Endpoint::on_incoming_subscribe;
+    ua_cfg.cb.on_pager2		= &Endpoint::on_pager2;
+    ua_cfg.cb.on_pager_status2	= &Endpoint::on_pager_status2;
+    ua_cfg.cb.on_typing2	= &Endpoint::on_typing2;
+    ua_cfg.cb.on_mwi_info	= &Endpoint::on_mwi_info;
+    ua_cfg.cb.on_buddy_state	= &Endpoint::on_buddy_state;
+
+    /* Call callbacks */
+    ua_cfg.cb.on_call_state             = &Endpoint::on_call_state;
+    ua_cfg.cb.on_call_tsx_state         = &Endpoint::on_call_tsx_state;
+    ua_cfg.cb.on_call_media_state       = &Endpoint::on_call_media_state;
+    ua_cfg.cb.on_call_sdp_created       = &Endpoint::on_call_sdp_created;
+    ua_cfg.cb.on_stream_created         = &Endpoint::on_stream_created;
+    ua_cfg.cb.on_stream_destroyed       = &Endpoint::on_stream_destroyed;
+    ua_cfg.cb.on_dtmf_digit             = &Endpoint::on_dtmf_digit;
+    ua_cfg.cb.on_call_transfer_request2 = &Endpoint::on_call_transfer_request2;
+    ua_cfg.cb.on_call_transfer_status   = &Endpoint::on_call_transfer_status;
+    ua_cfg.cb.on_call_replace_request2  = &Endpoint::on_call_replace_request2;
+    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_redirected        = &Endpoint::on_call_redirected;
+    ua_cfg.cb.on_call_media_transport_state =
+        &Endpoint::on_call_media_transport_state;
+    ua_cfg.cb.on_call_media_event       = &Endpoint::on_call_media_event;
+    ua_cfg.cb.on_create_media_transport = &Endpoint::on_create_media_transport;
+
+    /* Init! */
+    PJSUA2_CHECK_EXPR( pjsua_init(&ua_cfg, &log_cfg, &med_cfg) );
+}
+
+void Endpoint::libStart() throw(Error)
+{
+    PJSUA2_CHECK_EXPR(pjsua_start());
+}
+
+void Endpoint::libRegisterWorkerThread(const string &name) throw(Error)
+{
+    PJSUA2_CHECK_EXPR(pjsua_register_worker_thread(name.c_str()));
+}
+
+void Endpoint::libStopWorkerThreads()
+{
+    pjsua_stop_worker_threads();
+}
+
+int Endpoint::libHandleEvents(unsigned msec_timeout)
+{
+    performPendingJobs();
+    return pjsua_handle_events(msec_timeout);
+}
+
+void Endpoint::libDestroy(unsigned flags) throw(Error)
+{
+    pj_status_t status;
+
+    status = pjsua_destroy2(flags);
+
+    delete this->writer;
+    this->writer = NULL;
+
+    if (pj_log_get_log_func() == &Endpoint::logFunc) {
+	pj_log_set_log_func(NULL);
+    }
+
+    PJSUA2_CHECK_RAISE_ERROR(status);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Endpoint Utilities
+ */
+string Endpoint::utilStrError(pj_status_t prmErr)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+    pj_strerror(prmErr, errmsg, sizeof(errmsg));
+    return errmsg;
+}
+
+static void ept_log_write(int level, const char *sender,
+                          const char *format, ...)
+{
+    va_list arg;
+    va_start(arg, format);
+    pj_log(sender, level, format, arg );
+    va_end(arg);
+}
+
+void Endpoint::utilLogWrite(int prmLevel,
+			    const string &prmSender,
+			    const string &prmMsg)
+{
+    ept_log_write(prmLevel, prmSender.c_str(), "%s", prmMsg.c_str());
+}
+
+pj_status_t Endpoint::utilVerifySipUri(const string &prmUri)
+{
+    return pjsua_verify_sip_url(prmUri.c_str());
+}
+
+pj_status_t Endpoint::utilVerifyUri(const string &prmUri)
+{
+    return pjsua_verify_url(prmUri.c_str());
+}
+
+Token Endpoint::utilTimerSchedule(unsigned prmMsecDelay,
+                                  Token prmUserData) throw (Error)
+{
+    UserTimer *ut;
+    pj_time_val delay;
+    pj_status_t status;
+
+    ut = new UserTimer;
+    ut->signature = TIMER_SIGNATURE;
+    ut->prm.msecDelay = prmMsecDelay;
+    ut->prm.userData = prmUserData;
+    pj_timer_entry_init(&ut->entry, 1, ut, &Endpoint::on_timer);
+
+    delay.sec = 0;
+    delay.msec = prmMsecDelay;
+    pj_time_val_normalize(&delay);
+
+    status = pjsua_schedule_timer(&ut->entry, &delay);
+    if (status != PJ_SUCCESS) {
+	delete ut;
+	PJSUA2_CHECK_RAISE_ERROR(status);
+    }
+
+    return (Token)ut;
+}
+
+void Endpoint::utilTimerCancel(Token prmTimerToken)
+{
+    UserTimer *ut = (UserTimer*)(void*)prmTimerToken;
+
+    if (ut->signature != TIMER_SIGNATURE) {
+	PJ_LOG(1,(THIS_FILE,
+		  "Invalid timer token in Endpoint::utilTimerCancel()"));
+	return;
+    }
+
+    ut->entry.id = 0;
+    ut->signature = 0xFFFFFFFE;
+    pjsua_cancel_timer(&ut->entry);
+
+    delete ut;
+}
+
+IntVector Endpoint::utilSslGetAvailableCiphers() throw (Error)
+{
+#if PJ_HAS_SSL_SOCK
+    pj_ssl_cipher ciphers[64];
+    unsigned count = PJ_ARRAY_SIZE(ciphers);
+
+    PJSUA2_CHECK_EXPR( pj_ssl_cipher_get_availables(ciphers, &count) );
+
+    return IntVector(ciphers, ciphers + count);
+#else
+    return IntVector();
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Endpoint NAT operations
+ */
+void Endpoint::natDetectType(void) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_detect_nat_type() );
+}
+
+pj_stun_nat_type Endpoint::natGetType() throw(Error)
+{
+    pj_stun_nat_type type;
+
+    PJSUA2_CHECK_EXPR( pjsua_get_nat_type(&type) );
+
+    return type;
+}
+
+void Endpoint::natCheckStunServers(const StringVector &servers,
+				   bool wait,
+				   Token token) throw(Error)
+{
+    pj_str_t srv[MAX_STUN_SERVERS];
+    unsigned i, count = 0;
+
+    for (i=0; i<servers.size() && i<MAX_STUN_SERVERS; ++i) {
+	srv[count].ptr = (char*)servers[i].c_str();
+	srv[count].slen = servers[i].size();
+	++count;
+    }
+
+    PJSUA2_CHECK_EXPR(pjsua_resolve_stun_servers(count, srv, wait, token,
+                                                 &Endpoint::stun_resolve_cb) );
+}
+
+void Endpoint::natCancelCheckStunServers(Token token,
+                                         bool notify_cb) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_cancel_stun_resolution(token, notify_cb) );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Transport API
+ */
+TransportId Endpoint::transportCreate(pjsip_transport_type_e type,
+                                      const TransportConfig &cfg) throw(Error)
+{
+    pjsua_transport_config tcfg;
+    pjsua_transport_id tid;
+
+    tcfg = cfg.toPj();
+    PJSUA2_CHECK_EXPR( pjsua_transport_create(type,
+                                              &tcfg, &tid) );
+
+    return tid;
+}
+
+IntVector Endpoint::transportEnum() throw(Error)
+{
+    pjsua_transport_id tids[32];
+    unsigned count = PJ_ARRAY_SIZE(tids);
+
+    PJSUA2_CHECK_EXPR( pjsua_enum_transports(tids, &count) );
+
+    return IntVector(tids, tids+count);
+}
+
+TransportInfo Endpoint::transportGetInfo(TransportId id) throw(Error)
+{
+    pjsua_transport_info pj_tinfo;
+    TransportInfo tinfo;
+
+    PJSUA2_CHECK_EXPR( pjsua_transport_get_info(id, &pj_tinfo) );
+    tinfo.fromPj(pj_tinfo);
+
+    return tinfo;
+}
+
+void Endpoint::transportSetEnable(TransportId id, bool enabled) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_transport_set_enable(id, enabled) );
+}
+
+void Endpoint::transportClose(TransportId id) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_transport_close(id, PJ_FALSE) );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Call operations
+ */
+
+void Endpoint::hangupAllCalls(void)
+{
+    pjsua_call_hangup_all();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Media API
+ */
+unsigned Endpoint::mediaMaxPorts() const
+{
+    return pjsua_conf_get_max_ports();
+}
+
+unsigned Endpoint::mediaActivePorts() const
+{
+    return pjsua_conf_get_active_ports();
+}
+
+const AudioMediaVector &Endpoint::mediaEnumPorts() const throw(Error)
+{
+    return mediaList;
+}
+
+void Endpoint::mediaAdd(AudioMedia &media)
+{
+    if (mediaExists(media))
+	return;
+
+    mediaList.push_back(&media);
+}
+
+void Endpoint::mediaRemove(AudioMedia &media)
+{
+    AudioMediaVector::iterator it = std::find(mediaList.begin(),
+					      mediaList.end(),
+					      &media);
+
+    if (it != mediaList.end())
+	mediaList.erase(it);
+
+}
+
+bool Endpoint::mediaExists(const AudioMedia &media) const
+{
+    AudioMediaVector::const_iterator it = std::find(mediaList.begin(),
+						    mediaList.end(),
+						    &media);
+
+    return (it != mediaList.end());
+}
+
+AudDevManager &Endpoint::audDevManager()
+{
+    return audioDevMgr;
+}
+
+/*
+ * Codec operations.
+ */
+const CodecInfoVector &Endpoint::codecEnum() throw(Error)
+{
+    pjsua_codec_info pj_codec[MAX_CODEC_NUM];
+    unsigned count = 0;
+
+    PJSUA2_CHECK_EXPR( pjsua_enum_codecs(pj_codec, &count) );
+
+    pj_enter_critical_section();
+    clearCodecInfoList();
+    for (unsigned i=0;(i<count && i<MAX_CODEC_NUM);++i) {
+	CodecInfo *codec_info = new CodecInfo;
+
+	codec_info->fromPj(pj_codec[i]);
+	codecInfoList.push_back(codec_info);
+    }
+    pj_leave_critical_section();
+    return codecInfoList;
+}
+
+void Endpoint::codecSetPriority(const string &codec_id,
+			        pj_uint8_t priority) throw(Error)
+{
+    pj_str_t codec_str = str2Pj(codec_id);
+    PJSUA2_CHECK_EXPR( pjsua_codec_set_priority(&codec_str, priority) );
+}
+
+CodecParam Endpoint::codecGetParam(const string &codec_id) const throw(Error)
+{
+    pjmedia_codec_param *pj_param = NULL;
+    pj_str_t codec_str = str2Pj(codec_id);
+
+    PJSUA2_CHECK_EXPR( pjsua_codec_get_param(&codec_str, pj_param) );
+
+    return pj_param;
+}
+
+void Endpoint::codecSetParam(const string &codec_id,
+			     const CodecParam param) throw(Error)
+{
+    pj_str_t codec_str = str2Pj(codec_id);
+    pjmedia_codec_param *pj_param = (pjmedia_codec_param*)param;
+
+    PJSUA2_CHECK_EXPR( pjsua_codec_set_param(&codec_str, pj_param) );
+}
+
+void Endpoint::clearCodecInfoList()
+{
+    for (unsigned i=0;i<codecInfoList.size();++i) {
+	delete codecInfoList[i];
+    }
+    codecInfoList.clear();
+}
diff --git a/jni/pjproject-android/pjsip/src/pjsua2/json.cpp b/jni/pjproject-android/pjsip/src/pjsua2/json.cpp
new file mode 100644
index 0000000..12bef12
--- /dev/null
+++ b/jni/pjproject-android/pjsip/src/pjsua2/json.cpp
@@ -0,0 +1,544 @@
+/* $Id: json.cpp 4704 2014-01-16 05:30:46Z ming $ */
+/*
+ * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pjsua2/json.hpp>
+#include <pjlib-util/errno.h>
+#include <pj/file_io.h>
+#include "util.hpp"
+
+#define THIS_FILE	"json.cpp"
+
+using namespace pj;
+using namespace std;
+
+/* Json node operations */
+static bool		jsonNode_hasUnread(const ContainerNode*);
+static string		jsonNode_unreadName(const ContainerNode*n)
+				            throw(Error);
+static float		jsonNode_readNumber(const ContainerNode*,
+            		                    const string&)
+					      throw(Error);
+static bool		jsonNode_readBool(const ContainerNode*,
+           		                  const string&)
+					  throw(Error);
+static string		jsonNode_readString(const ContainerNode*,
+             		                    const string&)
+					    throw(Error);
+static StringVector	jsonNode_readStringVector(const ContainerNode*,
+                   	                          const string&)
+						  throw(Error);
+static ContainerNode	jsonNode_readContainer(const ContainerNode*,
+                    	                       const string &)
+					       throw(Error);
+static ContainerNode	jsonNode_readArray(const ContainerNode*,
+                    	                   const string &)
+					   throw(Error);
+static void		jsonNode_writeNumber(ContainerNode*,
+           		                     const string &name,
+           		                     float num)
+           		                     throw(Error);
+static void		jsonNode_writeBool(ContainerNode*,
+           		                   const string &name,
+           		                   bool value)
+					   throw(Error);
+static void		jsonNode_writeString(ContainerNode*,
+           		                     const string &name,
+           		                     const string &value)
+					     throw(Error);
+static void		jsonNode_writeStringVector(ContainerNode*,
+           		                           const string &name,
+           		                           const StringVector &value)
+					           throw(Error);
+static ContainerNode 	jsonNode_writeNewContainer(ContainerNode*,
+                     	                           const string &name)
+					           throw(Error);
+static ContainerNode 	jsonNode_writeNewArray(ContainerNode*,
+                     	                       const string &name)
+					       throw(Error);
+
+static container_node_op json_op =
+{
+    &jsonNode_hasUnread,
+    &jsonNode_unreadName,
+    &jsonNode_readNumber,
+    &jsonNode_readBool,
+    &jsonNode_readString,
+    &jsonNode_readStringVector,
+    &jsonNode_readContainer,
+    &jsonNode_readArray,
+    &jsonNode_writeNumber,
+    &jsonNode_writeBool,
+    &jsonNode_writeString,
+    &jsonNode_writeStringVector,
+    &jsonNode_writeNewContainer,
+    &jsonNode_writeNewArray
+};
+
+///////////////////////////////////////////////////////////////////////////////
+JsonDocument::JsonDocument()
+: root(NULL)
+{
+    pj_caching_pool_init(&cp, NULL, 0);
+    pool = pj_pool_create(&cp.factory, "jsondoc", 512, 512, NULL);
+    if (!pool)
+	PJSUA2_RAISE_ERROR(PJ_ENOMEM);
+}
+
+JsonDocument::~JsonDocument()
+{
+    if (pool)
+	pj_pool_release(pool);
+    pj_caching_pool_destroy(&cp);
+}
+
+void JsonDocument::initRoot() const
+{
+    rootNode.op = &json_op;
+    rootNode.data.doc = (void*)this;
+    rootNode.data.data1 = (void*)root;
+    rootNode.data.data2 = root->value.children.next;
+}
+
+void JsonDocument::loadFile(const string &filename) throw(Error)
+{
+    if (root)
+	PJSUA2_RAISE_ERROR3(PJ_EINVALIDOP, "JsonDocument.loadString()",
+	                    "Document already initialized");
+
+    if (!pj_file_exists(filename.c_str()))
+	PJSUA2_RAISE_ERROR(PJ_ENOTFOUND);
+
+    pj_ssize_t size = (pj_ssize_t)pj_file_size(filename.c_str());
+    pj_status_t status;
+
+    char *buffer = (char*)pj_pool_alloc(pool, size+1);
+    pj_oshandle_t fd = 0;
+    unsigned parse_size;
+    char err_msg[120];
+    pj_json_err_info err_info;
+
+    err_msg[0] = '\0';
+
+    status = pj_file_open(pool, filename.c_str(), PJ_O_RDONLY, &fd);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    status = pj_file_read(fd, buffer, &size);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    pj_file_close(fd);
+    fd = NULL;
+
+    if (size <= 0) {
+	status = PJ_EEOF;
+	goto on_error;
+    }
+
+    parse_size = (unsigned)size;
+    root = pj_json_parse(pool, buffer, &parse_size, &err_info);
+    if (root == NULL) {
+	pj_ansi_snprintf(err_msg, sizeof(err_msg),
+	                 "JSON parsing failed: syntax error in file '%s' at "
+	                 "line %d column %d",
+	                 filename.c_str(), err_info.line, err_info.col);
+	PJ_LOG(1,(THIS_FILE, err_msg));
+	status = PJLIB_UTIL_EINJSON;
+	goto on_error;
+    }
+
+    initRoot();
+    return;
+
+on_error:
+    if (fd)
+	pj_file_close(fd);
+    if (err_msg[0])
+	PJSUA2_RAISE_ERROR3(status, "loadFile()", err_msg);
+    else
+	PJSUA2_RAISE_ERROR(status);
+}
+
+void JsonDocument::loadString(const string &input) throw(Error)
+{
+    if (root)
+	PJSUA2_RAISE_ERROR3(PJ_EINVALIDOP, "JsonDocument.loadString()",
+	                    "Document already initialized");
+
+    unsigned size = input.size();
+    char *buffer = (char*)pj_pool_alloc(pool, size+1);
+    unsigned parse_size = (unsigned)size;
+    pj_json_err_info err_info;
+
+    pj_memcpy(buffer, input.c_str(), size);
+
+    root = pj_json_parse(pool, buffer, &parse_size, &err_info);
+    if (root == NULL) {
+	char err_msg[80];
+
+	pj_ansi_snprintf(err_msg, sizeof(err_msg),
+	                 "JSON parsing failed at line %d column %d",
+	                 err_info.line, err_info.col);
+	PJ_LOG(1,(THIS_FILE, err_msg));
+	PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, "loadString()", err_msg);
+    }
+    initRoot();
+}
+
+struct save_file_data
+{
+    pj_oshandle_t fd;
+};
+
+static pj_status_t json_file_writer(const char *s,
+				    unsigned size,
+				    void *user_data)
+{
+    save_file_data *sd = (save_file_data*)user_data;
+    pj_ssize_t ssize = (pj_ssize_t)size;
+    return pj_file_write(sd->fd, s, &ssize);
+}
+
+void JsonDocument::saveFile(const string &filename) throw(Error)
+{
+    struct save_file_data sd;
+    pj_status_t status;
+
+    /* Make sure root container has been created */
+    getRootContainer();
+
+    status = pj_file_open(pool, filename.c_str(), PJ_O_WRONLY, &sd.fd);
+    if (status != PJ_SUCCESS)
+	PJSUA2_RAISE_ERROR(status);
+
+    status = pj_json_writef(root, &json_file_writer, &sd.fd);
+    pj_file_close(sd.fd);
+
+    if (status != PJ_SUCCESS)
+	PJSUA2_RAISE_ERROR(status);
+}
+
+struct save_string_data
+{
+    string	output;
+};
+
+static pj_status_t json_string_writer(const char *s,
+                                      unsigned size,
+                                      void *user_data)
+{
+    save_string_data *sd = (save_string_data*)user_data;
+    sd->output.append(s, size);
+    return PJ_SUCCESS;
+}
+
+string JsonDocument::saveString() throw(Error)
+{
+    struct save_string_data sd;
+    pj_status_t status;
+
+    /* Make sure root container has been created */
+    getRootContainer();
+
+    status = pj_json_writef(root, &json_string_writer, &sd);
+    if (status != PJ_SUCCESS)
+	PJSUA2_RAISE_ERROR(status);
+
+    return sd.output;
+}
+
+ContainerNode & JsonDocument::getRootContainer() const
+{
+    if (!root) {
+	root = allocElement();
+	pj_json_elem_obj(root, NULL);
+	initRoot();
+    }
+
+    return rootNode;
+}
+
+pj_json_elem* JsonDocument::allocElement() const
+{
+    return (pj_json_elem*)pj_pool_alloc(pool, sizeof(pj_json_elem));
+}
+
+pj_pool_t *JsonDocument::getPool()
+{
+    return pool;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+struct json_node_data
+{
+    JsonDocument	*doc;
+    pj_json_elem	*jnode;
+    pj_json_elem	*childPtr;
+};
+
+static bool jsonNode_hasUnread(const ContainerNode *node)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    return jdat->childPtr != (pj_json_elem*)&jdat->jnode->value.children;
+}
+
+static void json_verify(struct json_node_data *jdat,
+                        const char *op,
+                        const string &name,
+                        pj_json_val_type type)
+{
+    if (jdat->childPtr == (pj_json_elem*)&jdat->jnode->value.children)
+	PJSUA2_RAISE_ERROR3(PJ_EEOF, op, "No unread element");
+
+    /* If name is specified, then check if the names match, except
+     * when the node name itself is empty and the parent node is
+     * an array, then ignore the checking (JSON doesn't allow array
+     * elements to have name).
+     */
+    if (name.size() && name.compare(0, name.size(),
+                                    jdat->childPtr->name.ptr,
+                                    jdat->childPtr->name.slen) &&
+        jdat->childPtr->name.slen &&
+        jdat->jnode->type != PJ_JSON_VAL_ARRAY)
+    {
+	char err_msg[80];
+	pj_ansi_snprintf(err_msg, sizeof(err_msg),
+	                 "Name mismatch: expecting '%s' got '%.*s'",
+	                 name.c_str(), (int)jdat->childPtr->name.slen,
+	                 jdat->childPtr->name.ptr);
+	PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, op, err_msg);
+    }
+
+    if (type != PJ_JSON_VAL_NULL && jdat->childPtr->type != type) {
+	char err_msg[80];
+	pj_ansi_snprintf(err_msg, sizeof(err_msg),
+	                 "Type mismatch: expecting %d got %d",
+	                 type, jdat->childPtr->type);
+	PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, op, err_msg);
+    }
+}
+
+static string jsonNode_unreadName(const ContainerNode *node)
+				  throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    json_verify(jdat, "unreadName()", "", PJ_JSON_VAL_NULL);
+    return pj2Str(jdat->childPtr->name);
+}
+
+static float jsonNode_readNumber(const ContainerNode *node,
+            		         const string &name)
+				 throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    json_verify(jdat, "readNumber()", name, PJ_JSON_VAL_NUMBER);
+    jdat->childPtr = jdat->childPtr->next;
+    return jdat->childPtr->prev->value.num;
+}
+
+static bool jsonNode_readBool(const ContainerNode *node,
+			      const string &name)
+			      throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    json_verify(jdat, "readBool()", name, PJ_JSON_VAL_BOOL);
+    jdat->childPtr = jdat->childPtr->next;
+    return PJ2BOOL(jdat->childPtr->prev->value.is_true);
+}
+
+static string jsonNode_readString(const ContainerNode *node,
+				  const string &name)
+				  throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    json_verify(jdat, "readString()", name, PJ_JSON_VAL_STRING);
+    jdat->childPtr = jdat->childPtr->next;
+    return pj2Str(jdat->childPtr->prev->value.str);
+}
+
+static StringVector jsonNode_readStringVector(const ContainerNode *node,
+                   	                      const string &name)
+					      throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    json_verify(jdat, "readStringVector()", name, PJ_JSON_VAL_ARRAY);
+
+    StringVector result;
+    pj_json_elem *child = jdat->childPtr->value.children.next;
+    while (child != (pj_json_elem*)&jdat->childPtr->value.children) {
+	if (child->type != PJ_JSON_VAL_STRING) {
+	    char err_msg[80];
+	    pj_ansi_snprintf(err_msg, sizeof(err_msg),
+			     "Elements not string but type %d",
+			     child->type);
+	    PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, "readStringVector()",
+	                        err_msg);
+	}
+	result.push_back(pj2Str(child->value.str));
+	child = child->next;
+    }
+
+    jdat->childPtr = jdat->childPtr->next;
+    return result;
+}
+
+static ContainerNode jsonNode_readContainer(const ContainerNode *node,
+                    	                    const string &name)
+					    throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    json_verify(jdat, "readContainer()", name, PJ_JSON_VAL_OBJ);
+
+    ContainerNode json_node;
+
+    json_node.op = &json_op;
+    json_node.data.doc = (void*)jdat->doc;
+    json_node.data.data1 = (void*)jdat->childPtr;
+    json_node.data.data2 = (void*)jdat->childPtr->value.children.next;
+
+    jdat->childPtr = jdat->childPtr->next;
+    return json_node;
+}
+
+static ContainerNode jsonNode_readArray(const ContainerNode *node,
+                    	                const string &name)
+					throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    json_verify(jdat, "readArray()", name, PJ_JSON_VAL_ARRAY);
+
+    ContainerNode json_node;
+
+    json_node.op = &json_op;
+    json_node.data.doc = (void*)jdat->doc;
+    json_node.data.data1 = (void*)jdat->childPtr;
+    json_node.data.data2 = (void*)jdat->childPtr->value.children.next;
+
+    jdat->childPtr = jdat->childPtr->next;
+    return json_node;
+}
+
+static pj_str_t alloc_name(JsonDocument *doc, const string &name)
+{
+    pj_str_t new_name;
+    pj_strdup2(doc->getPool(), &new_name, name.c_str());
+    return new_name;
+}
+
+static void jsonNode_writeNumber(ContainerNode *node,
+           		         const string &name,
+           		         float num)
+           		         throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    pj_json_elem *el = jdat->doc->allocElement();
+    pj_str_t nm = alloc_name(jdat->doc, name);
+    pj_json_elem_number(el, &nm, num);
+    pj_json_elem_add(jdat->jnode, el);
+}
+
+static void jsonNode_writeBool(ContainerNode *node,
+           		       const string &name,
+           		       bool value)
+			       throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    pj_json_elem *el = jdat->doc->allocElement();
+    pj_str_t nm = alloc_name(jdat->doc, name);
+    pj_json_elem_bool(el, &nm, value);
+    pj_json_elem_add(jdat->jnode, el);
+}
+
+static void jsonNode_writeString(ContainerNode *node,
+           		         const string &name,
+           		         const string &value)
+				 throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    pj_json_elem *el = jdat->doc->allocElement();
+    pj_str_t nm = alloc_name(jdat->doc, name);
+    pj_str_t new_val;
+    pj_strdup2(jdat->doc->getPool(), &new_val, value.c_str());
+    pj_json_elem_string(el, &nm, &new_val);
+
+    pj_json_elem_add(jdat->jnode, el);
+}
+
+static void jsonNode_writeStringVector(ContainerNode *node,
+           		               const string &name,
+           		               const StringVector &value)
+				       throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    pj_json_elem *el = jdat->doc->allocElement();
+    pj_str_t nm = alloc_name(jdat->doc, name);
+
+    pj_json_elem_array(el, &nm);
+    for (unsigned i=0; i<value.size(); ++i) {
+	pj_str_t new_val;
+
+	pj_strdup2(jdat->doc->getPool(), &new_val, value[i].c_str());
+	pj_json_elem *child = jdat->doc->allocElement();
+	pj_json_elem_string(child, NULL, &new_val);
+	pj_json_elem_add(el, child);
+    }
+
+    pj_json_elem_add(jdat->jnode, el);
+}
+
+static ContainerNode jsonNode_writeNewContainer(ContainerNode *node,
+                     	                        const string &name)
+					        throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    pj_json_elem *el = jdat->doc->allocElement();
+    pj_str_t nm = alloc_name(jdat->doc, name);
+
+    pj_json_elem_obj(el, &nm);
+    pj_json_elem_add(jdat->jnode, el);
+
+    ContainerNode json_node;
+
+    json_node.op = &json_op;
+    json_node.data.doc = (void*)jdat->doc;
+    json_node.data.data1 = (void*)el;
+    json_node.data.data2 = (void*)el->value.children.next;
+
+    return json_node;
+}
+
+static ContainerNode jsonNode_writeNewArray(ContainerNode *node,
+                     	                    const string &name)
+					    throw(Error)
+{
+    json_node_data *jdat = (json_node_data*)&node->data;
+    pj_json_elem *el = jdat->doc->allocElement();
+    pj_str_t nm = alloc_name(jdat->doc, name);
+
+    pj_json_elem_array(el, &nm);
+    pj_json_elem_add(jdat->jnode, el);
+
+    ContainerNode json_node;
+
+    json_node.op = &json_op;
+    json_node.data.doc = (void*)jdat->doc;
+    json_node.data.data1 = (void*)el;
+    json_node.data.data2 = (void*)el->value.children.next;
+
+    return json_node;
+}
diff --git a/jni/pjproject-android/pjsip/src/pjsua2/media.cpp b/jni/pjproject-android/pjsip/src/pjsua2/media.cpp
new file mode 100644
index 0000000..d9989b5
--- /dev/null
+++ b/jni/pjproject-android/pjsip/src/pjsua2/media.cpp
@@ -0,0 +1,795 @@
+/* $Id: media.cpp 4708 2014-01-21 10:59:25Z nanang $ */
+/*
+ * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pj/ctype.h>
+#include <pjsua2/media.hpp>
+#include <pjsua2/types.hpp>
+#include <pjsua2/endpoint.hpp>
+#include "util.hpp"
+
+using namespace pj;
+using namespace std;
+
+#define THIS_FILE		"media.cpp"
+#define MAX_FILE_NAMES 		64
+#define MAX_DEV_COUNT		64
+
+///////////////////////////////////////////////////////////////////////////////
+void MediaFormatAudio::fromPj(const pjmedia_format &format)
+{
+    if ((format.type != PJMEDIA_TYPE_AUDIO) &&
+	(format.detail_type != PJMEDIA_FORMAT_DETAIL_AUDIO))
+    {
+	type = PJMEDIA_TYPE_UNKNOWN;
+	return;
+    }
+
+    id = format.id;
+    type = format.type;
+
+    /* Detail. */
+    clockRate = format.det.aud.clock_rate;
+    channelCount = format.det.aud.channel_count;
+    frameTimeUsec = format.det.aud.frame_time_usec;
+    bitsPerSample = format.det.aud.bits_per_sample;
+    avgBps = format.det.aud.avg_bps;
+    maxBps = format.det.aud.max_bps;
+}
+
+pjmedia_format MediaFormatAudio::toPj() const
+{
+    pjmedia_format pj_format;
+
+    pj_format.id = id;
+    pj_format.type = type;
+
+    pj_format.detail_type = PJMEDIA_FORMAT_DETAIL_AUDIO;
+    pj_format.det.aud.clock_rate = clockRate;
+    pj_format.det.aud.channel_count = channelCount;
+    pj_format.det.aud.frame_time_usec = frameTimeUsec;
+    pj_format.det.aud.bits_per_sample = bitsPerSample;
+    pj_format.det.aud.avg_bps = avgBps;
+    pj_format.det.aud.max_bps = maxBps;
+
+    return pj_format;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/* Audio Media operations. */
+void ConfPortInfo::fromPj(const pjsua_conf_port_info &port_info)
+{
+    portId = port_info.slot_id;
+    name = pj2Str(port_info.name);
+    format.fromPj(port_info.format);
+    txLevelAdj = port_info.tx_level_adj;
+    rxLevelAdj = port_info.rx_level_adj;
+
+    /*
+    format.id = PJMEDIA_FORMAT_PCM;
+    format.type = PJMEDIA_TYPE_AUDIO;
+    format.clockRate = port_info.clock_rate;
+    format.channelCount = port_info.channel_count;
+    format.bitsPerSample = port_info.bits_per_sample;
+    format.frameTimeUsec = (port_info.samples_per_frame *
+			   1000000) /
+			   (port_info.clock_rate *
+			   port_info.channel_count);
+
+    format.avgBps = format.maxBps = port_info.clock_rate *
+				    port_info.channel_count *
+				    port_info.bits_per_sample;
+    */
+    listeners.clear();
+    for (unsigned i=0; i<port_info.listener_cnt; ++i) {
+	listeners.push_back(port_info.listeners[i]);
+    }
+}
+///////////////////////////////////////////////////////////////////////////////
+Media::Media(pjmedia_type med_type)
+: type(med_type)
+{
+
+}
+
+Media::~Media()
+{
+
+}
+
+pjmedia_type Media::getType() const
+{
+    return type;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+AudioMedia::AudioMedia() 
+: Media(PJMEDIA_TYPE_AUDIO), id(PJSUA_INVALID_ID), mediaPool(NULL)
+{
+
+}
+
+void AudioMedia::registerMediaPort(MediaPort port) throw(Error)
+{
+    /* Check if media already added to Conf bridge. */
+    pj_assert(!Endpoint::instance().mediaExists(*this));
+
+    if (port != NULL) {
+	pj_assert(id == PJSUA_INVALID_ID);
+
+	pj_caching_pool_init(&mediaCachingPool, NULL, 0);
+
+	mediaPool = pj_pool_create(&mediaCachingPool.factory,
+				   "media",
+				   512,
+				   512,
+				   NULL);
+
+	if (!mediaPool) {
+	    pj_caching_pool_destroy(&mediaCachingPool);
+	    PJSUA2_RAISE_ERROR(PJ_ENOMEM);
+	}
+
+	PJSUA2_CHECK_EXPR( pjsua_conf_add_port(mediaPool,
+					       (pjmedia_port *)port,
+					       &id) );
+    }
+
+    Endpoint::instance().mediaAdd(*this);
+}
+
+void AudioMedia::unregisterMediaPort()
+{
+    if (id != PJSUA_INVALID_ID) {
+	pjsua_conf_remove_port(id);
+	id = PJSUA_INVALID_ID;
+    }
+
+    if (mediaPool) {
+	pj_pool_release(mediaPool);
+	mediaPool = NULL;
+	pj_caching_pool_destroy(&mediaCachingPool);
+    }
+
+    Endpoint::instance().mediaRemove(*this);
+}
+
+AudioMedia::~AudioMedia() 
+{
+    unregisterMediaPort();
+}
+
+ConfPortInfo AudioMedia::getPortInfo() const throw(Error)
+{
+    return AudioMedia::getPortInfoFromId(id);
+}
+
+int AudioMedia::getPortId() const
+{
+    return id;
+}
+
+ConfPortInfo AudioMedia::getPortInfoFromId(int port_id) throw(Error)
+{
+    pjsua_conf_port_info pj_info;
+    ConfPortInfo pi;
+
+    PJSUA2_CHECK_EXPR( pjsua_conf_get_port_info(port_id, &pj_info) );
+    pi.fromPj(pj_info);
+    return pi;
+}
+
+void AudioMedia::startTransmit(const AudioMedia &sink) const throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_conf_connect(id, sink.id) );
+}
+
+void AudioMedia::stopTransmit(const AudioMedia &sink) const throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_conf_disconnect(id, sink.id) );
+}
+
+void AudioMedia::adjustRxLevel(float level) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_conf_adjust_rx_level(id, level) );
+}
+
+void AudioMedia::adjustTxLevel(float level) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_conf_adjust_tx_level(id, level) );
+}
+
+unsigned AudioMedia::getRxLevel() const throw(Error)
+{
+    return getSignalLevel(true);
+}
+
+unsigned AudioMedia::getTxLevel() const throw(Error)
+{
+    return getSignalLevel(false);
+}
+
+unsigned AudioMedia::getSignalLevel(bool is_rx) const throw(Error)
+{    
+    unsigned rx_level;
+    unsigned tx_level;
+    
+    PJSUA2_CHECK_EXPR( pjsua_conf_get_signal_level(id, &tx_level, &rx_level) );
+    return is_rx?rx_level:tx_level;
+}
+
+AudioMedia* AudioMedia::typecastFromMedia(Media *media)
+{
+    return static_cast<AudioMedia*>(media);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AudioMediaPlayer::AudioMediaPlayer()
+: playerId(PJSUA_INVALID_ID)
+{
+
+}
+
+AudioMediaPlayer::~AudioMediaPlayer()
+{
+    if (playerId != PJSUA_INVALID_ID) {
+	unregisterMediaPort();
+	pjsua_player_destroy(playerId);
+    }
+}
+
+void AudioMediaPlayer::createPlayer(const string &file_name,
+				    unsigned options)
+				    throw(Error)
+{
+    if (playerId != PJSUA_INVALID_ID) {
+	PJSUA2_RAISE_ERROR(PJ_EEXISTS);
+    }
+
+    pj_str_t pj_name = str2Pj(file_name);
+
+    PJSUA2_CHECK_EXPR( pjsua_player_create(&pj_name,
+					   options, 
+					   &playerId) );
+
+    /* Get media port id. */
+    id = pjsua_player_get_conf_port(playerId);
+
+    registerMediaPort(NULL);
+}
+
+void AudioMediaPlayer::createPlaylist(const StringVector &file_names,
+				      const string &label,
+				      unsigned options)
+				      throw(Error)
+{
+    if (playerId != PJSUA_INVALID_ID) {
+	PJSUA2_RAISE_ERROR(PJ_EEXISTS);
+    }
+
+    pj_str_t pj_files[MAX_FILE_NAMES];
+    unsigned i, count = 0;
+    pj_str_t pj_lbl = str2Pj(label);
+
+    count = PJ_ARRAY_SIZE(pj_files);
+
+    for(i=0; i<file_names.size() && i<count;++i)
+    {
+	const string &file_name = file_names[i];
+	
+	pj_files[i] = str2Pj(file_name);
+    }
+
+    PJSUA2_CHECK_EXPR( pjsua_playlist_create(pj_files,
+					     i,
+					     &pj_lbl,
+					     options, 
+					     &playerId) );
+
+    /* Get media port id. */
+    id = pjsua_player_get_conf_port(playerId);
+
+    registerMediaPort(NULL);
+}
+
+void AudioMediaPlayer::setPos(pj_uint32_t samples) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_player_set_pos(playerId, samples) );
+}
+
+AudioMediaPlayer* AudioMediaPlayer::typecastFromAudioMedia(
+						AudioMedia *media)
+{
+    return static_cast<AudioMediaPlayer*>(media);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+AudioMediaRecorder::AudioMediaRecorder()
+: recorderId(PJSUA_INVALID_ID)
+{
+
+}
+
+AudioMediaRecorder::~AudioMediaRecorder()
+{
+    if (recorderId != PJSUA_INVALID_ID) {
+	unregisterMediaPort();
+	pjsua_recorder_destroy(recorderId);
+    }
+}
+
+void AudioMediaRecorder::createRecorder(const string &file_name,
+				        unsigned enc_type,
+				        pj_ssize_t max_size,
+				        unsigned options)
+				        throw(Error)
+{
+    PJ_UNUSED_ARG(max_size);
+
+    if (recorderId != PJSUA_INVALID_ID) {
+	PJSUA2_RAISE_ERROR(PJ_EEXISTS);
+    }
+
+    pj_str_t pj_name = str2Pj(file_name);
+
+    PJSUA2_CHECK_EXPR( pjsua_recorder_create(&pj_name,
+					     enc_type,
+					     NULL,
+					     -1,
+					     options,
+					     &recorderId) );
+
+    /* Get media port id. */
+    id = pjsua_recorder_get_conf_port(recorderId);
+
+    registerMediaPort(NULL);
+}
+
+AudioMediaRecorder* AudioMediaRecorder::typecastFromAudioMedia(
+						AudioMedia *media)
+{
+    return static_cast<AudioMediaRecorder*>(media);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+void AudioDevInfo::fromPj(const pjmedia_aud_dev_info &dev_info)
+{
+    name = dev_info.name;
+    inputCount = dev_info.input_count;
+    outputCount = dev_info.output_count;
+    defaultSamplesPerSec = dev_info.default_samples_per_sec;
+    driver = dev_info.driver;
+    caps = dev_info.caps;
+    routes = dev_info.routes;
+
+    for (unsigned i=0; i<dev_info.ext_fmt_cnt;++i) {
+	MediaFormatAudio *format = new MediaFormatAudio;
+
+	format->fromPj(dev_info.ext_fmt[i]);
+	if (format->type == PJMEDIA_TYPE_AUDIO)
+	    extFmt.push_back(format);
+    }
+}
+
+AudioDevInfo::~AudioDevInfo()
+{
+    for(unsigned i=0;i<extFmt.size();++i) {
+	delete extFmt[i];
+    }
+    extFmt.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Simple AudioMedia class for sound device.
+ */
+class DevAudioMedia : public AudioMedia
+{
+public:
+    DevAudioMedia();
+    ~DevAudioMedia();
+};
+
+DevAudioMedia::DevAudioMedia()
+{
+    this->id = 0;
+    registerMediaPort(NULL);
+}
+
+DevAudioMedia::~DevAudioMedia()
+{
+    /* Avoid removing this port (conf port id=0) from conference */
+    this->id = PJSUA_INVALID_ID;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/* Audio device operations. */
+
+AudDevManager::AudDevManager()
+: devMedia(NULL)
+{
+}
+
+AudDevManager::~AudDevManager()
+{
+    // At this point, devMedia should have been cleaned up by Endpoint,
+    // as AudDevManager destructor is called after Endpoint destructor.
+    //delete devMedia;
+    
+    clearAudioDevList();
+}
+
+int AudDevManager::getCaptureDev() const throw(Error)
+{
+    return getActiveDev(true);
+}
+
+AudioMedia &AudDevManager::getCaptureDevMedia() throw(Error)
+{
+    if (!devMedia)
+	devMedia = new DevAudioMedia;
+    return *devMedia;
+}
+
+int AudDevManager::getPlaybackDev() const throw(Error)
+{
+    return getActiveDev(false);
+}
+
+AudioMedia &AudDevManager::getPlaybackDevMedia() throw(Error)
+{
+    if (!devMedia)
+    	devMedia = new DevAudioMedia;
+    return *devMedia;
+}
+
+void AudDevManager::setCaptureDev(int capture_dev) const throw(Error)
+{
+    int playback_dev = getPlaybackDev();
+
+    PJSUA2_CHECK_EXPR( pjsua_set_snd_dev(capture_dev, playback_dev) );
+}
+
+void AudDevManager::setPlaybackDev(int playback_dev) const throw(Error)
+{
+    int capture_dev = getCaptureDev();
+
+    PJSUA2_CHECK_EXPR( pjsua_set_snd_dev(capture_dev, playback_dev) );
+}
+
+const AudioDevInfoVector &AudDevManager::enumDev() throw(Error)
+{
+    pjmedia_aud_dev_info pj_info[MAX_DEV_COUNT];
+    unsigned count;
+
+    PJSUA2_CHECK_EXPR( pjsua_enum_aud_devs(pj_info, &count) );
+
+    pj_enter_critical_section();
+    clearAudioDevList();
+    for (unsigned i = 0; (i<count && i<MAX_DEV_COUNT) ;++i) {
+	AudioDevInfo *dev_info = new AudioDevInfo;
+	dev_info->fromPj(pj_info[i]);
+	audioDevList.push_back(dev_info);
+    }
+    pj_leave_critical_section();
+    return audioDevList;
+}
+
+void AudDevManager::setNullDev() throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_set_null_snd_dev() );
+}
+
+MediaPort *AudDevManager::setNoDev()
+{
+    return (MediaPort*)pjsua_set_no_snd_dev();
+}
+
+void AudDevManager::setEcOptions(unsigned tail_msec,
+				 unsigned options) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_set_ec(tail_msec, options) );
+}
+
+unsigned AudDevManager::getEcTail() const throw(Error)
+{
+    unsigned tail_msec = 0;
+
+    PJSUA2_CHECK_EXPR( pjsua_get_ec_tail(&tail_msec) );
+
+    return tail_msec;
+}
+
+bool AudDevManager::sndIsActive() const
+{
+    return PJ2BOOL(pjsua_snd_is_active());
+}
+
+void AudDevManager::refreshDevs() throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjmedia_aud_dev_refresh() );
+}
+
+unsigned AudDevManager::getDevCount() const
+{
+    return pjmedia_aud_dev_count();
+}
+
+AudioDevInfo
+AudDevManager::getDevInfo(int id) const throw(Error)
+{
+    AudioDevInfo dev_info;
+    pjmedia_aud_dev_info pj_info;
+
+    PJSUA2_CHECK_EXPR( pjmedia_aud_dev_get_info(id, &pj_info) );
+
+    dev_info.fromPj(pj_info);
+    return dev_info;
+}
+
+int AudDevManager::lookupDev(const string &drv_name,
+			     const string &dev_name) const throw(Error)
+{
+    pjmedia_aud_dev_index pj_idx = 0;
+
+    PJSUA2_CHECK_EXPR( pjmedia_aud_dev_lookup(drv_name.c_str(),
+					      dev_name.c_str(),
+					      &pj_idx) );
+
+    return pj_idx;
+}
+
+
+string AudDevManager::capName(pjmedia_aud_dev_cap cap) const
+{
+    return pjmedia_aud_dev_cap_name(cap, NULL);
+}
+
+void
+AudDevManager::setExtFormat(const MediaFormatAudio &format,
+			    bool keep) throw(Error)
+{
+    pjmedia_format pj_format = format.toPj();
+
+    PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT,
+					     &pj_format,
+					     keep) );
+}
+
+MediaFormatAudio AudDevManager::getExtFormat() const throw(Error)
+{
+    pjmedia_format pj_format;
+    MediaFormatAudio format;
+
+    PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT,
+					     &pj_format) );
+
+    format.fromPj(pj_format);
+
+    return format;
+}
+
+void AudDevManager::setInputLatency(unsigned latency_msec,
+				    bool keep) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY,
+					     &latency_msec,
+					     keep) );
+}
+
+unsigned AudDevManager::getInputLatency() const throw(Error)
+{
+    unsigned latency_msec = 0;
+
+    PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY,
+					     &latency_msec) );
+
+    return latency_msec;
+}
+
+void
+AudDevManager::setOutputLatency(unsigned latency_msec,
+				bool keep) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY,
+					     &latency_msec,
+					     keep) );
+}
+
+unsigned AudDevManager::getOutputLatency() const throw(Error)
+{
+    unsigned latency_msec = 0;
+
+    PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY,
+					     &latency_msec) );
+
+    return latency_msec;
+}
+
+void AudDevManager::setInputVolume(unsigned volume, bool keep) throw(Error)
+{
+    PJSUA2_CHECK_EXPR(
+	    pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING,
+				  &volume,
+				  keep) );
+}
+
+unsigned AudDevManager::getInputVolume() const throw(Error)
+{
+    unsigned volume = 0;
+
+    PJSUA2_CHECK_EXPR(
+	    pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING,
+				  &volume) );
+
+    return volume;
+}
+
+void AudDevManager::setOutputVolume(unsigned volume, bool keep) throw(Error)
+{
+    PJSUA2_CHECK_EXPR(
+	    pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+				  &volume,
+				  keep) );
+}
+
+unsigned AudDevManager::getOutputVolume() const throw(Error)
+{
+    unsigned volume = 0;
+
+    PJSUA2_CHECK_EXPR(
+	    pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
+				  &volume) );
+
+    return volume;
+}
+
+unsigned AudDevManager::getInputSignal() const throw(Error)
+{
+    unsigned signal = 0;
+
+    PJSUA2_CHECK_EXPR(
+	    pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER,
+				  &signal) );
+
+    return signal;
+}
+
+unsigned AudDevManager::getOutputSignal() const throw(Error)
+{
+    unsigned signal = 0;
+
+    PJSUA2_CHECK_EXPR(
+	    pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER,
+				  &signal) );
+
+    return signal;
+}
+
+void
+AudDevManager::setInputRoute(pjmedia_aud_dev_route route,
+			     bool keep) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE,
+					     &route,
+					     keep) );
+}
+
+pjmedia_aud_dev_route AudDevManager::getInputRoute() const throw(Error)
+{
+    pjmedia_aud_dev_route route = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
+
+    PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE,
+					     &route) );
+
+    return route;
+}
+
+void
+AudDevManager::setOutputRoute(pjmedia_aud_dev_route route,
+			      bool keep) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
+					     &route,
+					     keep) );
+}
+
+pjmedia_aud_dev_route AudDevManager::getOutputRoute() const throw(Error)
+{
+    pjmedia_aud_dev_route route = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
+
+    PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
+					     &route) );
+
+    return route;
+}
+
+void AudDevManager::setVad(bool enable, bool keep) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_VAD,
+					     &enable,
+					     keep) );
+}
+
+bool AudDevManager::getVad() const throw(Error)
+{
+    bool enable = false;
+
+    PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_VAD,
+					     &enable) );
+
+    return enable;
+}
+
+void AudDevManager::setCng(bool enable, bool keep) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_CNG,
+					     &enable,
+					     keep) );
+}
+
+bool AudDevManager::getCng() const throw(Error)
+{
+    bool enable = false;
+
+    PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_CNG,
+					     &enable) );
+
+    return enable;
+}
+
+void AudDevManager::setPlc(bool enable, bool keep) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_PLC,
+					     &enable,
+					     keep) );
+}
+
+bool AudDevManager::getPlc() const throw(Error)
+{
+    bool enable = false;
+
+    PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_PLC,
+					     &enable) );
+
+    return enable;
+}
+
+void AudDevManager::clearAudioDevList()
+{
+    for(unsigned i=0;i<audioDevList.size();++i) {
+	delete audioDevList[i];
+    }
+    audioDevList.clear();
+}
+
+int AudDevManager::getActiveDev(bool is_capture) const throw(Error)
+{
+    int capture_dev = 0, playback_dev = 0;
+    PJSUA2_CHECK_EXPR( pjsua_get_snd_dev(&capture_dev, &playback_dev) );
+
+    return is_capture?capture_dev:playback_dev;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+void CodecInfo::fromPj(const pjsua_codec_info &codec_info)
+{
+    codecId = pj2Str(codec_info.codec_id);
+    priority = codec_info.priority;
+    desc = pj2Str(codec_info.desc);
+}
diff --git a/jni/pjproject-android/pjsip/src/pjsua2/persistent.cpp b/jni/pjproject-android/pjsip/src/pjsua2/persistent.cpp
new file mode 100644
index 0000000..f6febac
--- /dev/null
+++ b/jni/pjproject-android/pjsip/src/pjsua2/persistent.cpp
@@ -0,0 +1,227 @@
+/* $Id: persistent.cpp 4704 2014-01-16 05:30:46Z ming $ */
+/*
+ * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pjsua2/persistent.hpp>
+
+using namespace pj;
+using namespace std;
+
+
+bool PersistentDocument::hasUnread() const
+{
+    return getRootContainer().hasUnread();
+}
+
+string PersistentDocument::unreadName() const throw(Error)
+{
+    return getRootContainer().unreadName();
+}
+
+int PersistentDocument::readInt(const string &name) const throw(Error)
+{
+    return (int)getRootContainer().readNumber(name);
+}
+
+float PersistentDocument::readNumber(const string &name) const throw(Error)
+{
+    return getRootContainer().readNumber(name);
+}
+
+bool PersistentDocument::readBool(const string &name) const throw(Error)
+{
+    return getRootContainer().readBool(name);
+}
+
+string PersistentDocument::readString(const string &name) const throw(Error)
+{
+    return getRootContainer().readString(name);
+}
+
+StringVector PersistentDocument::readStringVector(const string &name) const
+						  throw(Error)
+{
+    return getRootContainer().readStringVector(name);
+}
+
+void PersistentDocument::readObject(PersistentObject &obj) const throw(Error)
+{
+    getRootContainer().readObject(obj);
+}
+
+ContainerNode PersistentDocument::readContainer(const string &name) const
+					        throw(Error)
+{
+    return getRootContainer().readContainer(name);
+}
+
+ContainerNode PersistentDocument::readArray(const string &name) const
+					    throw(Error)
+{
+    return getRootContainer().readArray(name);
+}
+
+void PersistentDocument::writeNumber(const string &name,
+				     float num) throw(Error)
+{
+    getRootContainer().writeNumber(name, num);
+}
+
+void PersistentDocument::writeInt(const string &name,
+				  int num) throw(Error)
+{
+    getRootContainer().writeNumber(name, (float)num);
+}
+
+void PersistentDocument::writeBool(const string &name,
+				   bool value) throw(Error)
+{
+    getRootContainer().writeBool(name, value);
+}
+
+void PersistentDocument::writeString(const string &name,
+                                     const string &value) throw(Error)
+{
+    getRootContainer().writeString(name, value);
+}
+
+void PersistentDocument::writeStringVector(const string &name,
+                                           const StringVector &value)
+					   throw(Error)
+{
+    getRootContainer().writeStringVector(name, value);
+}
+
+void PersistentDocument::writeObject(const PersistentObject &obj) throw(Error)
+{
+    getRootContainer().writeObject(obj);
+}
+
+ContainerNode PersistentDocument::writeNewContainer(const string &name)
+						    throw(Error)
+{
+    return getRootContainer().writeNewContainer(name);
+}
+
+ContainerNode PersistentDocument::writeNewArray(const string &name)
+						    throw(Error)
+{
+    return getRootContainer().writeNewArray(name);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool ContainerNode::hasUnread() const
+{
+    return op->hasUnread(this);
+}
+
+string ContainerNode::unreadName() const throw(Error)
+{
+    return op->unreadName(this);
+}
+
+int ContainerNode::readInt(const string &name) const throw(Error)
+{
+    return (int)op->readNumber(this, name);
+}
+
+float ContainerNode::readNumber(const string &name) const throw(Error)
+{
+    return op->readNumber(this, name);
+}
+
+bool ContainerNode::readBool(const string &name) const throw(Error)
+{
+    return op->readBool(this, name);
+}
+
+string ContainerNode::readString(const string &name) const throw(Error)
+{
+    return op->readString(this, name);
+}
+
+StringVector ContainerNode::readStringVector(const string &name) const
+					     throw(Error)
+{
+    return op->readStringVector(this, name);
+}
+
+void ContainerNode::readObject(PersistentObject &obj) const throw(Error)
+{
+    obj.readObject(*this);
+}
+
+ContainerNode ContainerNode::readContainer(const string &name) const
+					   throw(Error)
+{
+    return op->readContainer(this, name);
+}
+
+ContainerNode ContainerNode::readArray(const string &name) const
+					   throw(Error)
+{
+    return op->readArray(this, name);
+}
+
+void ContainerNode::writeNumber(const string &name,
+				float num) throw(Error)
+{
+    return op->writeNumber(this, name, num);
+}
+
+void ContainerNode::writeInt(const string &name,
+			     int num) throw(Error)
+{
+    return op->writeNumber(this, name, (float)num);
+}
+
+void ContainerNode::writeBool(const string &name,
+			      bool value) throw(Error)
+{
+    return op->writeBool(this, name, value);
+}
+
+void ContainerNode::writeString(const string &name,
+				const string &value) throw(Error)
+{
+    return op->writeString(this, name, value);
+}
+
+void ContainerNode::writeStringVector(const string &name,
+				      const StringVector &value)
+				      throw(Error)
+{
+    return op->writeStringVector(this, name, value);
+}
+
+void ContainerNode::writeObject(const PersistentObject &obj) throw(Error)
+{
+    obj.writeObject(*this);
+}
+
+ContainerNode ContainerNode::writeNewContainer(const string &name)
+					       throw(Error)
+{
+    return op->writeNewContainer(this, name);
+}
+
+ContainerNode ContainerNode::writeNewArray(const string &name)
+					   throw(Error)
+{
+    return op->writeNewArray(this, name);
+}
diff --git a/jni/pjproject-android/pjsip/src/pjsua2/presence.cpp b/jni/pjproject-android/pjsip/src/pjsua2/presence.cpp
new file mode 100644
index 0000000..4cdc525
--- /dev/null
+++ b/jni/pjproject-android/pjsip/src/pjsua2/presence.cpp
@@ -0,0 +1,190 @@
+/* $Id: presence.cpp 4704 2014-01-16 05:30:46Z ming $ */
+/*
+ * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pjsua2/presence.hpp>
+#include <pjsua2/account.hpp>
+#include "util.hpp"
+
+using namespace pj;
+using namespace std;
+
+#define THIS_FILE		"presence.cpp"
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+PresenceStatus::PresenceStatus()
+: status(PJSUA_BUDDY_STATUS_UNKNOWN), activity(PJRPID_ACTIVITY_UNKNOWN)
+{
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+void BuddyConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("BuddyConfig");
+
+    NODE_READ_STRING   ( this_node, uri);
+    NODE_READ_BOOL     ( this_node, subscribe);
+}
+
+void BuddyConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("BuddyConfig");
+
+    NODE_WRITE_STRING  ( this_node, uri);
+    NODE_WRITE_BOOL    ( this_node, subscribe);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void BuddyInfo::fromPj(const pjsua_buddy_info &pbi)
+{
+    uri 		= pj2Str(pbi.uri);
+    contact 		= pj2Str(pbi.contact);
+    presMonitorEnabled 	= PJ2BOOL(pbi.monitor_pres);
+    subState 		= pbi.sub_state;
+    subStateName 	= string(pbi.sub_state_name);
+    subTermCode 	= (pjsip_status_code)pbi.sub_term_code;
+    subTermReason 	= pj2Str(pbi.sub_term_reason);
+    
+    /* Presence status */
+    presStatus.status	= pbi.status;
+    presStatus.statusText = pj2Str(pbi.status_text);
+    presStatus.activity = pbi.rpid.activity;
+    presStatus.note	= pj2Str(pbi.rpid.note);
+    presStatus.rpidId	= pj2Str(pbi.rpid.id);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+/*
+ * Constructor.
+ */
+Buddy::Buddy()
+: id(PJSUA_INVALID_ID)
+{
+}
+ 
+/*
+ * Destructor.
+ */
+Buddy::~Buddy()
+{
+    if (isValid()) {
+	pjsua_buddy_set_user_data(id, NULL);
+	pjsua_buddy_del(id);
+
+	/* Remove from account buddy list */
+	acc->removeBuddy(this);
+    }
+}
+    
+/*
+ * Create buddy and register the buddy to PJSUA-LIB.
+ */
+void Buddy::create(Account &account, const BuddyConfig &cfg) throw(Error)
+{
+    pjsua_buddy_config pj_cfg;
+    pjsua_buddy_config_default(&pj_cfg);
+    
+    if (!account.isValid())
+	PJSUA2_RAISE_ERROR3(PJ_EINVAL, "Buddy::create()", "Invalid account");
+    
+    pj_cfg.uri = str2Pj(cfg.uri);
+    pj_cfg.subscribe = cfg.subscribe;
+    pj_cfg.user_data = (void*)this;
+    PJSUA2_CHECK_EXPR( pjsua_buddy_add(&pj_cfg, &id) );
+    
+    acc = &account;
+    acc->addBuddy(this);
+}
+    
+/*
+ * Check if this buddy is valid.
+ */
+bool Buddy::isValid() const
+{
+    return PJ2BOOL( pjsua_buddy_is_valid(id) );
+}
+
+/*
+ * Get detailed buddy info.
+ */
+BuddyInfo Buddy::getInfo() const throw(Error)
+{
+    pjsua_buddy_info pj_bi;
+    BuddyInfo bi;
+
+    PJSUA2_CHECK_EXPR( pjsua_buddy_get_info(id, &pj_bi) );
+    bi.fromPj(pj_bi);
+    return bi;
+}
+
+/*
+ * Enable/disable buddy's presence monitoring.
+ */
+void Buddy::subscribePresence(bool subscribe) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_buddy_subscribe_pres(id, subscribe) );
+}
+
+    
+/*
+ * Update the presence information for the buddy.
+ */
+void Buddy::updatePresence(void) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_buddy_update_pres(id) );
+}
+     
+/*
+ * Send instant messaging outside dialog.
+ */
+void Buddy::sendInstantMessage(const SendInstantMessageParam &prm) throw(Error)
+{
+    BuddyInfo bi = getInfo();
+
+    pj_str_t to = str2Pj(bi.contact.empty()? bi.uri : bi.contact);
+    pj_str_t mime_type = str2Pj(prm.contentType);
+    pj_str_t content = str2Pj(prm.content);
+    void *user_data = (void*)prm.userData;
+    pjsua_msg_data msg_data;
+    prm.txOption.toPj(msg_data);
+    
+    PJSUA2_CHECK_EXPR( pjsua_im_send(acc->getId(), &to, &mime_type, &content,
+				     &msg_data, user_data) );
+}
+
+/*
+ * Send typing indication outside dialog.
+ */
+void Buddy::sendTypingIndication(const SendTypingIndicationParam &prm)
+     throw(Error)
+{
+    BuddyInfo bi = getInfo();
+
+    pj_str_t to = str2Pj(bi.contact.empty()? bi.uri : bi.contact);
+    pjsua_msg_data msg_data;
+    prm.txOption.toPj(msg_data);
+    
+    PJSUA2_CHECK_EXPR( pjsua_im_typing(acc->getId(), &to, prm.isTyping,
+				       &msg_data) );
+}
+
diff --git a/jni/pjproject-android/pjsip/src/pjsua2/siptypes.cpp b/jni/pjproject-android/pjsip/src/pjsua2/siptypes.cpp
new file mode 100644
index 0000000..775c17b
--- /dev/null
+++ b/jni/pjproject-android/pjsip/src/pjsua2/siptypes.cpp
@@ -0,0 +1,590 @@
+/* $Id: siptypes.cpp 4704 2014-01-16 05:30:46Z ming $ */
+/*
+ * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pjsua2/types.hpp>
+#include <pjsua2/siptypes.hpp>
+#include "util.hpp"
+
+using namespace pj;
+using namespace std;
+
+#define THIS_FILE	"siptypes.cpp"
+
+///////////////////////////////////////////////////////////////////////////////
+namespace pj
+{
+void readIntVector( ContainerNode &node,
+                    const string &array_name,
+                    IntVector &v) throw(Error)
+{
+    ContainerNode array_node = node.readArray(array_name);
+    v.resize(0);
+    while (array_node.hasUnread()) {
+	v.push_back((int)array_node.readNumber());
+    }
+}
+
+void writeIntVector(ContainerNode &node,
+                    const string &array_name,
+                    const IntVector &v) throw(Error)
+{
+    ContainerNode array_node = node.writeNewArray(array_name);
+    for (unsigned i=0; i<v.size(); ++i) {
+	array_node.writeNumber("", (float)v[i]);
+    }
+}
+
+void readQosParams( ContainerNode &node,
+                    pj_qos_params &qos) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("qosParams");
+
+    NODE_READ_NUM_T( this_node, pj_uint8_t, qos.flags);
+    NODE_READ_NUM_T( this_node, pj_uint8_t, qos.dscp_val);
+    NODE_READ_NUM_T( this_node, pj_uint8_t, qos.so_prio);
+    NODE_READ_NUM_T( this_node, pj_qos_wmm_prio, qos.wmm_prio);
+}
+
+void writeQosParams( ContainerNode &node,
+                     const pj_qos_params &qos) throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("qosParams");
+
+    NODE_WRITE_NUM_T( this_node, pj_uint8_t, qos.flags);
+    NODE_WRITE_NUM_T( this_node, pj_uint8_t, qos.dscp_val);
+    NODE_WRITE_NUM_T( this_node, pj_uint8_t, qos.so_prio);
+    NODE_WRITE_NUM_T( this_node, pj_qos_wmm_prio, qos.wmm_prio);
+}
+
+void readSipHeaders( const ContainerNode &node,
+                     const string &array_name,
+                     SipHeaderVector &headers) throw(Error)
+{
+    ContainerNode headers_node = node.readArray(array_name);
+    headers.resize(0);
+    while (headers_node.hasUnread()) {
+	SipHeader hdr;
+
+	ContainerNode header_node = headers_node.readContainer("header");
+	hdr.hName = header_node.readString("hname");
+	hdr.hValue = header_node.readString("hvalue");
+	headers.push_back(hdr);
+    }
+}
+
+void writeSipHeaders(ContainerNode &node,
+                     const string &array_name,
+                     const SipHeaderVector &headers) throw(Error)
+{
+    ContainerNode headers_node = node.writeNewArray(array_name);
+    for (unsigned i=0; i<headers.size(); ++i) {
+	ContainerNode header_node = headers_node.writeNewContainer("header");
+	header_node.writeString("hname", headers[i].hName);
+	header_node.writeString("hvalue", headers[i].hValue);
+    }
+}
+
+} // namespace
+///////////////////////////////////////////////////////////////////////////////
+
+AuthCredInfo::AuthCredInfo()
+: scheme("digest"), realm("*"), dataType(0)
+{
+}
+
+AuthCredInfo::AuthCredInfo(const string &param_scheme,
+			   const string &param_realm,
+			   const string &param_user_name,
+			   const int param_data_type,
+			   const string param_data)
+: scheme(param_scheme), realm(param_realm), username(param_user_name),
+  dataType(param_data_type), data(param_data)
+{
+}
+
+void AuthCredInfo::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("AuthCredInfo");
+
+    NODE_READ_STRING( this_node, scheme);
+    NODE_READ_STRING( this_node, realm);
+    NODE_READ_STRING( this_node, username);
+    NODE_READ_INT   ( this_node, dataType);
+    NODE_READ_STRING( this_node, data);
+    NODE_READ_STRING( this_node, akaK);
+    NODE_READ_STRING( this_node, akaOp);
+    NODE_READ_STRING( this_node, akaAmf);
+}
+
+void AuthCredInfo::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("AuthCredInfo");
+
+    NODE_WRITE_STRING( this_node, scheme);
+    NODE_WRITE_STRING( this_node, realm);
+    NODE_WRITE_STRING( this_node, username);
+    NODE_WRITE_INT   ( this_node, dataType);
+    NODE_WRITE_STRING( this_node, data);
+    NODE_WRITE_STRING( this_node, akaK);
+    NODE_WRITE_STRING( this_node, akaOp);
+    NODE_WRITE_STRING( this_node, akaAmf);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TlsConfig::TlsConfig()
+{
+    pjsip_tls_setting ts;
+    pjsip_tls_setting_default(&ts);
+    this->fromPj(ts);
+}
+
+pjsip_tls_setting TlsConfig::toPj() const
+{
+    pjsip_tls_setting ts;
+
+    ts.ca_list_file	= str2Pj(this->CaListFile);
+    ts.cert_file	= str2Pj(this->certFile);
+    ts.privkey_file	= str2Pj(this->privKeyFile);
+    ts.password		= str2Pj(this->password);
+    ts.method		= this->method;
+    ts.ciphers_num	= this->ciphers.size();
+    // The following will only work if sizeof(enum)==sizeof(int)
+    pj_assert(sizeof(ts.ciphers[0]) == sizeof(int));
+    ts.ciphers		= ts.ciphers_num? 
+			    (pj_ssl_cipher*)&this->ciphers[0] : NULL;
+    ts.verify_server	= this->verifyServer;
+    ts.verify_client	= this->verifyClient;
+    ts.require_client_cert = this->requireClientCert;
+    ts.timeout.sec 	= this->msecTimeout / 1000;
+    ts.timeout.msec	= this->msecTimeout % 1000;
+    ts.qos_type		= this->qosType;
+    ts.qos_params	= this->qosParams;
+    ts.qos_ignore_error	= this->qosIgnoreError;
+
+    return ts;
+}
+
+void TlsConfig::fromPj(const pjsip_tls_setting &prm)
+{
+    this->CaListFile 	= pj2Str(prm.ca_list_file);
+    this->certFile 	= pj2Str(prm.cert_file);
+    this->privKeyFile 	= pj2Str(prm.privkey_file);
+    this->password 	= pj2Str(prm.password);
+    this->method 	= (pjsip_ssl_method)prm.method;
+    // The following will only work if sizeof(enum)==sizeof(int)
+    pj_assert(sizeof(prm.ciphers[0]) == sizeof(int));
+    this->ciphers 	= IntVector(prm.ciphers, prm.ciphers+prm.ciphers_num);
+    this->verifyServer 	= PJ2BOOL(prm.verify_server);
+    this->verifyClient 	= PJ2BOOL(prm.verify_client);
+    this->requireClientCert = PJ2BOOL(prm.require_client_cert);
+    this->msecTimeout 	= PJ_TIME_VAL_MSEC(prm.timeout);
+    this->qosType 	= prm.qos_type;
+    this->qosParams 	= prm.qos_params;
+    this->qosIgnoreError = PJ2BOOL(prm.qos_ignore_error);
+}
+
+void TlsConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("TlsConfig");
+
+    NODE_READ_STRING  ( this_node, CaListFile);
+    NODE_READ_STRING  ( this_node, certFile);
+    NODE_READ_STRING  ( this_node, privKeyFile);
+    NODE_READ_STRING  ( this_node, password);
+    NODE_READ_NUM_T   ( this_node, pjsip_ssl_method, method);
+    readIntVector     ( this_node, "ciphers", ciphers);
+    NODE_READ_BOOL    ( this_node, verifyServer);
+    NODE_READ_BOOL    ( this_node, verifyClient);
+    NODE_READ_BOOL    ( this_node, requireClientCert);
+    NODE_READ_UNSIGNED( this_node, msecTimeout);
+    NODE_READ_NUM_T   ( this_node, pj_qos_type, qosType);
+    readQosParams     ( this_node, qosParams);
+    NODE_READ_BOOL    ( this_node, qosIgnoreError);
+}
+
+void TlsConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("TlsConfig");
+
+    NODE_WRITE_STRING  ( this_node, CaListFile);
+    NODE_WRITE_STRING  ( this_node, certFile);
+    NODE_WRITE_STRING  ( this_node, privKeyFile);
+    NODE_WRITE_STRING  ( this_node, password);
+    NODE_WRITE_NUM_T   ( this_node, pjsip_ssl_method, method);
+    writeIntVector     ( this_node, "ciphers", ciphers);
+    NODE_WRITE_BOOL    ( this_node, verifyServer);
+    NODE_WRITE_BOOL    ( this_node, verifyClient);
+    NODE_WRITE_BOOL    ( this_node, requireClientCert);
+    NODE_WRITE_UNSIGNED( this_node, msecTimeout);
+    NODE_WRITE_NUM_T   ( this_node, pj_qos_type, qosType);
+    writeQosParams     ( this_node, qosParams);
+    NODE_WRITE_BOOL    ( this_node, qosIgnoreError);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TransportConfig::TransportConfig()
+{
+    pjsua_transport_config tc;
+    pjsua_transport_config_default(&tc);
+    this->fromPj(tc);
+}
+
+void TransportConfig::fromPj(const pjsua_transport_config &prm)
+{
+    this->port 		= prm.port;
+    this->portRange	= prm.port_range;
+    this->publicAddress = pj2Str(prm.public_addr);
+    this->boundAddress	= pj2Str(prm.bound_addr);
+    this->tlsConfig.fromPj(prm.tls_setting);
+    this->qosType	= prm.qos_type;
+    this->qosParams	= prm.qos_params;
+}
+
+pjsua_transport_config TransportConfig::toPj() const
+{
+    pjsua_transport_config tc;
+
+    tc.port		= this->port;
+    tc.port_range	= this->portRange;
+    tc.public_addr	= str2Pj(this->publicAddress);
+    tc.bound_addr	= str2Pj(this->boundAddress);
+    tc.tls_setting	= this->tlsConfig.toPj();
+    tc.qos_type		= this->qosType;
+    tc.qos_params	= this->qosParams;
+
+    return tc;
+}
+
+void TransportConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("TransportConfig");
+
+    NODE_READ_UNSIGNED  ( this_node, port);
+    NODE_READ_UNSIGNED  ( this_node, portRange);
+    NODE_READ_STRING    ( this_node, publicAddress);
+    NODE_READ_STRING    ( this_node, boundAddress);
+    NODE_READ_NUM_T     ( this_node, pj_qos_type, qosType);
+    readQosParams       ( this_node, qosParams);
+    NODE_READ_OBJ       ( this_node, tlsConfig);
+}
+
+void TransportConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("TransportConfig");
+
+    NODE_WRITE_UNSIGNED  ( this_node, port);
+    NODE_WRITE_UNSIGNED  ( this_node, portRange);
+    NODE_WRITE_STRING    ( this_node, publicAddress);
+    NODE_WRITE_STRING    ( this_node, boundAddress);
+    NODE_WRITE_NUM_T     ( this_node, pj_qos_type, qosType);
+    writeQosParams       ( this_node, qosParams);
+    NODE_WRITE_OBJ       ( this_node, tlsConfig);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void TransportInfo::fromPj(const pjsua_transport_info &info)
+{
+    this->id = info.id;
+    this->type = info.type;
+    this->typeName = pj2Str(info.type_name);
+    this->info = pj2Str(info.info);
+    this->flags = info.flag;
+
+    char straddr[PJ_INET6_ADDRSTRLEN+10];
+    pj_sockaddr_print(&info.local_addr, straddr, sizeof(straddr), 3);
+    this->localAddress = straddr;
+
+    pj_ansi_snprintf(straddr, sizeof(straddr), "%.*s:%d",
+                     (int)info.local_name.host.slen,
+                     info.local_name.host.ptr,
+                     info.local_name.port);
+    this->localName = straddr;
+    this->usageCount = info.usage_count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SipRxData::SipRxData()
+: pjRxData(NULL)
+{
+}
+
+void SipRxData::fromPj(pjsip_rx_data &rdata)
+{
+    char straddr[PJ_INET6_ADDRSTRLEN+10];
+
+    info	= pjsip_rx_data_get_info(&rdata);
+    wholeMsg	= string(rdata.msg_info.msg_buf, rdata.msg_info.len);
+    pj_sockaddr_print(&rdata.pkt_info.src_addr, straddr, sizeof(straddr), 3);
+    srcAddress  = straddr;
+    pjRxData    = (void *)&rdata;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SipMediaType::fromPj(const pjsip_media_type &prm)
+{
+    type	= pj2Str(prm.type);
+    subType	= pj2Str(prm.subtype);
+}
+
+pjsip_media_type SipMediaType::toPj() const
+{
+    pjsip_media_type pj_mt;
+    pj_bzero(&pj_mt, sizeof(pj_mt));
+    pj_mt.type	    = str2Pj(type);
+    pj_mt.subtype   = str2Pj(subType);
+    return pj_mt;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SipHeader::fromPj(const pjsip_hdr *hdr) throw(Error)
+{
+    char buf[256];
+
+    int len = pjsip_hdr_print_on((void*)hdr, buf, sizeof(buf)-1);
+    if (len <= 0)
+	PJSUA2_RAISE_ERROR(PJ_ETOOSMALL);
+    buf[len] = '\0';
+
+    char *pos = strchr(buf, ':');
+    if (!pos)
+	PJSUA2_RAISE_ERROR(PJSIP_EINVALIDHDR);
+
+    // Trim white space after header name
+    char *end_name = pos;
+    while (end_name>buf && pj_isspace(*(end_name-1))) --end_name;
+
+    // Trim whitespaces after colon
+    char *start_val = pos+1;
+    while (*start_val && pj_isspace(*start_val)) ++start_val;
+
+    hName = string(buf, end_name);
+    hValue = string(start_val);
+}
+
+pjsip_generic_string_hdr &SipHeader::toPj() const
+{
+    pj_str_t hname  = str2Pj(hName);
+    pj_str_t hvalue = str2Pj(hValue);
+
+    pjsip_generic_string_hdr_init2(&pjHdr, &hname, &hvalue);
+    return pjHdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SipMultipartPart::fromPj(const pjsip_multipart_part &prm) throw(Error)
+{
+    headers.clear();
+    pjsip_hdr* pj_hdr = prm.hdr.next;
+    while (pj_hdr != &prm.hdr) {
+	SipHeader sh;
+	sh.fromPj(pj_hdr);
+	headers.push_back(sh);
+	pj_hdr = pj_hdr->next;
+    }
+
+    if (!prm.body)
+	PJSUA2_RAISE_ERROR(PJ_EINVAL);
+    
+    contentType.fromPj(prm.body->content_type);
+    body = string((char*)prm.body->data, prm.body->len);
+}
+
+pjsip_multipart_part& SipMultipartPart::toPj() const
+{
+    pj_list_init(&pjMpp.hdr);
+    for (unsigned i = 0; i < headers.size(); i++) {
+	pjsip_generic_string_hdr& pj_hdr = headers[i].toPj();
+	pj_list_push_back(&pjMpp.hdr, &pj_hdr);
+    }
+
+    pj_bzero(&pjMsgBody, sizeof(pjMsgBody));
+    pjMsgBody.content_type  = contentType.toPj();
+    pjMsgBody.print_body    = &pjsip_print_text_body;
+    pjMsgBody.clone_data    = &pjsip_clone_text_data;
+    pjMsgBody.data	    = (void*)body.c_str();
+    pjMsgBody.len	    = body.size();
+    pjMpp.body = &pjMsgBody;
+
+    return pjMpp;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SipEvent::SipEvent()
+: type(PJSIP_EVENT_UNKNOWN), pjEvent(NULL)
+{
+}
+
+void SipEvent::fromPj(const pjsip_event &ev)
+{
+    type = ev.type;
+    if (type == PJSIP_EVENT_TIMER) {
+        body.timer.entry = ev.body.timer.entry;
+    } else if (type == PJSIP_EVENT_TSX_STATE) {
+        body.tsxState.prevState = (pjsip_tsx_state_e)
+        ev.body.tsx_state.prev_state;
+        body.tsxState.tsx.fromPj(*ev.body.tsx_state.tsx);
+        if (body.tsxState.type == PJSIP_EVENT_TX_MSG) {
+            if (ev.body.tsx_state.src.tdata)
+        	body.tsxState.src.tdata.fromPj(*ev.body.tsx_state.src.tdata);
+        } else if (body.tsxState.type == PJSIP_EVENT_RX_MSG) {
+            if (ev.body.tsx_state.src.rdata)
+        	body.tsxState.src.rdata.fromPj(*ev.body.tsx_state.src.rdata);
+        } else if (body.tsxState.type == PJSIP_EVENT_TRANSPORT_ERROR) {
+            body.tsxState.src.status = ev.body.tsx_state.src.status;
+        } else if (body.tsxState.type == PJSIP_EVENT_TIMER) {
+            body.tsxState.src.timer = ev.body.tsx_state.src.timer;
+        } else if (body.tsxState.type == PJSIP_EVENT_USER) {
+            body.tsxState.src.data = ev.body.tsx_state.src.data;
+        }
+    } else if (type == PJSIP_EVENT_TX_MSG) {
+	if (ev.body.tx_msg.tdata)
+	    body.txMsg.tdata.fromPj(*ev.body.tx_msg.tdata);
+    } else if (type == PJSIP_EVENT_RX_MSG) {
+	if (ev.body.rx_msg.rdata)
+	    body.rxMsg.rdata.fromPj(*ev.body.rx_msg.rdata);
+    } else if (type == PJSIP_EVENT_TRANSPORT_ERROR) {
+	if (ev.body.tx_error.tdata)
+	    body.txError.tdata.fromPj(*ev.body.tx_error.tdata);
+	if (ev.body.tx_error.tsx)
+	    body.txError.tsx.fromPj(*ev.body.tx_error.tsx);
+    } else if (type == PJSIP_EVENT_USER) {
+        body.user.user1 = ev.body.user.user1;
+        body.user.user2 = ev.body.user.user2;
+        body.user.user3 = ev.body.user.user3;
+        body.user.user4 = ev.body.user.user4;
+    }
+    pjEvent = (void *)&ev;
+}
+
+SipTxData::SipTxData()
+: pjTxData(NULL)
+{
+}
+
+void SipTxData::fromPj(pjsip_tx_data &tdata)
+{
+    char straddr[PJ_INET6_ADDRSTRLEN+10];
+    
+    info	= pjsip_tx_data_get_info(&tdata);
+    pjsip_tx_data_encode(&tdata);
+    wholeMsg	= string(tdata.buf.start, tdata.buf.end - tdata.buf.start);
+    if (pj_sockaddr_has_addr(&tdata.tp_info.dst_addr)) {
+	pj_sockaddr_print(&tdata.tp_info.dst_addr, straddr, sizeof(straddr), 3);
+	dstAddress  = straddr;
+    } else {
+	dstAddress = "";
+    }
+    pjTxData    = (void *)&tdata;
+}
+
+SipTransaction::SipTransaction()
+: role(PJSIP_ROLE_UAC), statusCode(0), pjTransaction(NULL)
+{
+}
+
+void SipTransaction::fromPj(pjsip_transaction &tsx)
+{
+    this->role          = tsx.role;
+    this->method        = pj2Str(tsx.method.name);
+    this->statusCode    = tsx.status_code;
+    this->statusText    = pj2Str(tsx.status_text);
+    if (tsx.last_tx)
+	this->lastTx.fromPj(*tsx.last_tx);
+    else
+	this->lastTx.pjTxData = NULL;
+    this->pjTransaction = (void *)&tsx;
+}
+
+bool SipTxOption::isEmpty() const
+{
+    return (targetUri == "" && headers.size() == 0 && contentType == "" &&
+            msgBody == "" && multipartContentType.type == "" &&
+            multipartContentType.subType == "" && multipartParts.size() == 0);
+}
+
+void SipTxOption::fromPj(const pjsua_msg_data &prm) throw(Error)
+{
+    targetUri = pj2Str(prm.target_uri);
+
+    headers.clear();
+    pjsip_hdr* pj_hdr = prm.hdr_list.next;
+    while (pj_hdr != &prm.hdr_list) {
+	SipHeader sh;
+	sh.fromPj(pj_hdr);
+	headers.push_back(sh);
+	pj_hdr = pj_hdr->next;
+    }
+
+    contentType = pj2Str(prm.content_type);
+    msgBody = pj2Str(prm.msg_body);
+    multipartContentType.fromPj(prm.multipart_ctype);
+
+    multipartParts.clear();
+    pjsip_multipart_part* pj_mp = prm.multipart_parts.next;
+    while (pj_mp != &prm.multipart_parts) {
+	SipMultipartPart smp;
+	smp.fromPj(*pj_mp);
+	multipartParts.push_back(smp);
+	pj_mp = pj_mp->next;
+    }
+}
+
+void SipTxOption::toPj(pjsua_msg_data &msg_data) const
+{
+    unsigned i;
+
+    pjsua_msg_data_init(&msg_data);
+
+    msg_data.target_uri = str2Pj(targetUri);
+
+    pj_list_init(&msg_data.hdr_list);
+    for (i = 0; i < headers.size(); i++) {
+	pjsip_generic_string_hdr& pj_hdr = headers[i].toPj();
+	pj_list_push_back(&msg_data.hdr_list, &pj_hdr);
+    }
+
+    msg_data.content_type = str2Pj(contentType);
+    msg_data.msg_body = str2Pj(msgBody);
+    msg_data.multipart_ctype = multipartContentType.toPj();
+
+    pj_list_init(&msg_data.multipart_parts);
+    for (i = 0; i < multipartParts.size(); i++) {
+	pjsip_multipart_part& pj_part = multipartParts[i].toPj();
+	pj_list_push_back(&msg_data.multipart_parts, &pj_part);
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+SendInstantMessageParam::SendInstantMessageParam()
+: contentType("text/plain"), content(""), userData(NULL)
+{
+}
+
+SendTypingIndicationParam::SendTypingIndicationParam()
+: isTyping(false)
+{
+}
diff --git a/jni/pjproject-android/pjsip/src/pjsua2/types.cpp b/jni/pjproject-android/pjsip/src/pjsua2/types.cpp
new file mode 100644
index 0000000..26c19a9
--- /dev/null
+++ b/jni/pjproject-android/pjsip/src/pjsua2/types.cpp
@@ -0,0 +1,95 @@
+/* $Id: types.cpp 4704 2014-01-16 05:30:46Z ming $ */
+/*
+ * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <pjsua2/types.hpp>
+#include "util.hpp"
+
+using namespace pj;
+using namespace std;
+
+#define THIS_FILE	"types.cpp"
+
+///////////////////////////////////////////////////////////////////////////////
+
+Error::Error()
+: status(PJ_SUCCESS), srcLine(0)
+{
+}
+
+Error::Error( pj_status_t prm_status,
+	      const string &prm_title,
+	      const string &prm_reason,
+	      const string &prm_src_file,
+	      int prm_src_line)
+: status(prm_status), title(prm_title), reason(prm_reason),
+  srcFile(prm_src_file), srcLine(prm_src_line)
+{
+    if (this->status != PJ_SUCCESS && prm_reason.empty()) {
+	char errmsg[PJ_ERR_MSG_SIZE];
+	pj_strerror(this->status, errmsg, sizeof(errmsg));
+	this->reason = errmsg;
+    }
+}
+
+string Error::info(bool multi_line) const
+{
+    string output;
+
+    if (status==PJ_SUCCESS) {
+	output = "No error";
+    } else if (!multi_line) {
+	char temp[80];
+
+	if (!title.empty()) {
+	    output += title + " error: ";
+	}
+	snprintf(temp, sizeof(temp), " (status=%d)", status);
+	output += reason + temp;
+	if (!srcFile.empty()) {
+	    output += " [";
+	    output += srcFile;
+	    snprintf(temp, sizeof(temp), ":%d]", srcLine);
+	    output += temp;
+	}
+    } else {
+	char temp[80];
+
+	if (!title.empty()) {
+	    output += string("Title:       ") + title + "\n";
+	}
+
+	snprintf(temp, sizeof(temp), "%d\n", status);
+	output += string("Code:        ") + temp;
+	output += string("Description: ") + reason + "\n";
+	if (!srcFile.empty()) {
+	    snprintf(temp, sizeof(temp), ":%d\n", srcLine);
+	    output += string("Location:    ") + srcFile + temp;
+	}
+    }
+
+    return output;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void TimeValue::fromPj(const pj_time_val &prm)
+{
+    this->sec  = prm.sec;
+    this->msec = prm.msec;
+}
+
diff --git a/jni/pjproject-android/pjsip/src/pjsua2/util.hpp b/jni/pjproject-android/pjsip/src/pjsua2/util.hpp
new file mode 100644
index 0000000..cc99a0e
--- /dev/null
+++ b/jni/pjproject-android/pjsip/src/pjsua2/util.hpp
@@ -0,0 +1,45 @@
+/* $Id: util.hpp 4704 2014-01-16 05:30:46Z ming $ */
+/*
+ * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <pjsua2/types.hpp>
+#include <string>
+
+#define PJ2BOOL(var) ((var) != PJ_FALSE)
+
+namespace pj
+{
+using std::string;
+
+inline pj_str_t str2Pj(const string &input_str)
+{
+    pj_str_t output_str;
+    output_str.ptr = (char*)input_str.c_str();
+    output_str.slen = input_str.size();
+    return output_str;
+}
+
+inline string pj2Str(const pj_str_t &input_str)
+{
+    if (input_str.ptr)
+	return string(input_str.ptr, input_str.slen);
+    return string();
+}
+
+
+}	// namespace