Browse Source

切换linphone sdk版本,修改sip使用方式,增加opus音频格式

weizhengliang 1 year ago
parent
commit
a5d9a17674
27 changed files with 2045 additions and 1727 deletions
  1. 34 0
      app/src/main/assets/assistant_default_values
  2. 40 0
      app/src/main/assets/assistant_linphone_default_values
  3. 42 0
      app/src/main/assets/linphonerc_default
  4. 43 0
      app/src/main/assets/linphonerc_factory
  5. 1 1
      build.gradle
  6. 7 2
      conversion_box/src/main/AndroidManifest.xml
  7. 58 47
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/activity/MainActivity.kt
  8. 0 143
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/BaseCallFragment.kt.bak
  9. 8 21
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/SipCallFragment.kt
  10. 0 504
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/SkyCallFragment.kt.bak
  11. 0 7
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/helper/AppUpdateHelper.java
  12. 0 329
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/service/WdklSipService.java
  13. 0 579
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/SipHelper.java.bak
  14. 0 17
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/SipStatus.java.bak
  15. 45 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/callback/PhoneCallback.java
  16. 13 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/callback/RegistrationCallback.java
  17. 540 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/core/CorePreferences.kt
  18. 248 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/core/LinCoreService.java
  19. 517 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/core/LinphoneManager.kt
  20. 23 22
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/helper/AudioRouteUtils.kt
  21. 171 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/utils/FileUtils.kt
  22. 106 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/utils/LinphoneUtils.kt
  23. 148 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/utils/VideoZoomHelper.kt
  24. 0 20
      conversion_box/src/main/res/raw/linphonerc_default
  25. 0 34
      conversion_box/src/main/res/raw/linphonerc_factory
  26. 1 1
      middleware/build.gradle
  27. BIN
      middleware/libs/linphone-sdk-android-5.2.10.aar

+ 34 - 0
app/src/main/assets/assistant_default_values

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config xmlns="http://www.linphone.org/xsds/lpconfig.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.linphone.org/xsds/lpconfig.xsd lpconfig.xsd">
+  <section name="proxy_default_values">
+    <entry name="avpf" overwrite="true">0</entry>
+    <entry name="dial_escape_plus" overwrite="true">0</entry>
+    <entry name="publish" overwrite="true">0</entry>
+    <entry name="quality_reporting_collector" overwrite="true"></entry>
+    <entry name="quality_reporting_enabled" overwrite="true">0</entry>
+    <entry name="quality_reporting_interval" overwrite="true">0</entry>
+    <entry name="reg_expires" overwrite="true">3600</entry>
+    <entry name="reg_identity" overwrite="true"></entry>
+    <entry name="reg_proxy" overwrite="true"></entry>
+    <entry name="reg_route" overwrite="true"></entry>
+    <entry name="reg_sendregister" overwrite="true">1</entry>
+    <entry name="nat_policy_ref" overwrite="true"></entry>
+    <entry name="realm" overwrite="true"></entry>
+    <entry name="conference_factory_uri" overwrite="true"></entry>
+    <entry name="push_notification_allowed" overwrite="true">0</entry>
+  </section>
+  <section name="nat_policy_default_values">
+    <entry name="stun_server" overwrite="true"></entry>
+    <entry name="protocols" overwrite="true"></entry>
+  </section>
+  <section name="assistant">
+    <entry name="domain" overwrite="true"></entry>
+    <entry name="algorithm" overwrite="true">MD5</entry>
+    <entry name="password_max_length" overwrite="true">-1</entry>
+    <entry name="password_min_length" overwrite="true">0</entry>
+    <entry name="username_length" overwrite="true">-1</entry>
+    <entry name="username_max_length" overwrite="true">128</entry>
+    <entry name="username_min_length" overwrite="true">1</entry>
+    <entry name="username_regex" overwrite="true">^[a-zA-Z0-9+_.\-]*$</entry>
+  </section>
+</config>

+ 40 - 0
app/src/main/assets/assistant_linphone_default_values

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config xmlns="http://www.linphone.org/xsds/lpconfig.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.linphone.org/xsds/lpconfig.xsd lpconfig.xsd">
+  <section name="proxy_default_values">
+    <entry name="avpf" overwrite="true">1</entry>
+    <entry name="dial_escape_plus" overwrite="true">0</entry>
+    <entry name="publish" overwrite="true">0</entry>
+    <entry name="quality_reporting_collector" overwrite="true">sip:voip-metrics@sip.linphone.org;transport=tls</entry>
+    <entry name="quality_reporting_enabled" overwrite="true">1</entry>
+    <entry name="quality_reporting_interval" overwrite="true">180</entry>
+    <entry name="reg_expires" overwrite="true">31536000</entry>
+    <entry name="reg_identity" overwrite="true">sip:?@sip.linphone.org</entry>
+    <entry name="reg_proxy" overwrite="true">&lt;sip:sip.linphone.org;transport=tls&gt;</entry>
+    <entry name="reg_route" overwrite="true">&lt;sip:sip.linphone.org;transport=tls&gt;</entry>
+    <entry name="reg_sendregister" overwrite="true">1</entry>
+    <entry name="nat_policy_ref" overwrite="true">nat_policy_default_values</entry>
+    <entry name="realm" overwrite="true">sip.linphone.org</entry>
+    <entry name="conference_factory_uri" overwrite="true">sip:conference-factory@sip.linphone.org</entry>
+    <entry name="push_notification_allowed" overwrite="true">1</entry>
+  </section>
+  <section name="nat_policy_default_values">
+    <entry name="stun_server" overwrite="true">stun.linphone.org</entry>
+    <entry name="protocols" overwrite="true">stun,ice</entry>
+  </section>
+  <section name="sip">
+    <entry name="rls_uri" overwrite="true">sips:rls@sip.linphone.org</entry>
+  </section>
+  <section name="lime">
+    <entry name="x3dh_server_url" overwrite="true">https://lime.linphone.org/lime-server/lime-server.php</entry>
+  </section>
+  <section name="assistant">
+    <entry name="domain" overwrite="true">sip.linphone.org</entry>
+    <entry name="algorithm" overwrite="true">SHA-256</entry>
+    <entry name="password_max_length" overwrite="true">-1</entry>
+    <entry name="password_min_length" overwrite="true">1</entry>
+    <entry name="username_length" overwrite="true">-1</entry>
+    <entry name="username_max_length" overwrite="true">64</entry>
+    <entry name="username_min_length" overwrite="true">1</entry>
+    <entry name="username_regex" overwrite="true">^[a-z0-9+_.\-]*$</entry>
+  </section>
+</config>

+ 42 - 0
app/src/main/assets/linphonerc_default

@@ -0,0 +1,42 @@
+
+## Start of default rc
+
+[sip]
+contact="Linphone Android" <sip:linphone.android@unknown-host>
+use_info=0
+use_ipv6=1
+keepalive_period=30000
+sip_port=-1
+sip_tcp_port=-1
+sip_tls_port=-1
+media_encryption=none
+
+[net]
+#Because dynamic bitrate adaption can increase bitrate, we must allow "no limit"
+download_bw=0
+upload_bw=0
+
+[video]
+size=vga
+
+[app]
+tunnel=disabled
+push_notification=1
+auto_start=1
+
+[tunnel]
+host=
+port=443
+
+[misc]
+log_collection_upload_server_url=https://www.linphone.org:444/lft.php
+file_transfer_server_url=https://www.linphone.org:444/lft.php
+version_check_url_root=https://www.linphone.org/releases
+max_calls=10
+history_max_size=100
+
+[in-app-purchase]
+server_url=https://subscribe.linphone.org:444/inapp.php
+purchasable_items_ids=test_account_subscription
+
+## End of default rc

+ 43 - 0
app/src/main/assets/linphonerc_factory

@@ -0,0 +1,43 @@
+
+## Start of factory rc
+
+# This file shall not contain path referencing package name, in order to be portable when app is renamed.
+# Paths to resources must be set from LinphoneManager, after creating LinphoneCore.
+
+[net]
+mtu=1300
+force_ice_disablement=0
+
+[sip]
+guess_hostname=1
+register_only_when_network_is_up=1
+auto_net_state_mon=1
+auto_answer_replacing_calls=1
+ping_with_options=0
+use_cpim=1
+
+[sound]
+#remove this property for any application that is not Linphone public version itself
+ec_calibrator_cool_tones=1
+
+[video]
+displaytype=MSAndroidTextureDisplay
+auto_resize_preview_to_keep_ratio=1
+
+[misc]
+enable_basic_to_client_group_chat_room_migration=0
+enable_simple_group_chat_message_state=0
+aggregate_imdn=1
+notify_each_friend_individually_when_presence_received=0
+
+[app]
+activation_code_length=4
+prefer_basic_chat_room=1
+
+[assistant]
+xmlrpc_url=https://subscribe.linphone.org:444/wizard.php
+
+[lime]
+lime_update_threshold=-1
+
+## End of factory rc

+ 1 - 1
build.gradle

@@ -32,7 +32,7 @@ buildscript {
     /**
      * SDK目标支持版本
      */
-    ext.target_sdk_version = 28
+    ext.target_sdk_version = 30
 
     /**
      * SDK编译版本

+ 7 - 2
conversion_box/src/main/AndroidManifest.xml

@@ -8,6 +8,9 @@
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
 
+    <!-- Needed for full screen intent in incoming call notifications -->
+    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
+
     <application
         android:allowBackup="true"
         android:label="@string/app_name"
@@ -22,7 +25,9 @@
             android:launchMode="singleTask"/>
 
         <service
-            android:name="com.wdkl.app.ncs.conversion_box.service.WdklSipService"
-            android:label="@string/app_name" />
+            android:name="com.wdkl.app.ncs.conversion_box.sip.core.LinCoreService"
+            android:foregroundServiceType="phoneCall|camera|microphone"
+            android:label="@string/app_name"
+            android:stopWithTask="false" />
     </application>
 </manifest>

+ 58 - 47
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/activity/MainActivity.kt

@@ -25,8 +25,8 @@ import com.wdkl.app.ncs.conversion_box.databinding.MainActivityLayoutBinding
 import com.wdkl.app.ncs.conversion_box.fragment.*
 import com.wdkl.app.ncs.conversion_box.helper.*
 import com.wdkl.app.ncs.conversion_box.launch.MainLaunch
-import com.wdkl.app.ncs.conversion_box.service.WdklSipService
 import com.wdkl.app.ncs.conversion_box.settings.SettingConfig
+import com.wdkl.app.ncs.conversion_box.sip.callback.PhoneCallback
 import com.wdkl.ncs.android.lib.base.BaseActivity
 import com.wdkl.ncs.android.lib.base.BaseApplication
 import com.wdkl.ncs.android.lib.utils.*
@@ -57,6 +57,7 @@ import com.wdkl.ncs.android.middleware.tcp.enums.TcpType
 import com.wdkl.ncs.android.middleware.udp.ServerInfoUtil
 import com.wdkl.ncs.android.middleware.utils.AppUtil
 import com.wdkl.ncs.android.middleware.utils.CommonUtils
+import com.wdkl.ncs.host.sip.core.LinphoneManager
 import com.wdkl.ncs.janus.client.JanusClient
 import com.wdkl.ncs.janus.client.StreamingCallback
 import com.wdkl.ncs.janus.rtc.WebRTCEngine
@@ -70,7 +71,7 @@ import okhttp3.Request
 import org.greenrobot.eventbus.EventBus
 import org.greenrobot.eventbus.Subscribe
 import org.greenrobot.eventbus.ThreadMode
-import org.linphone.core.AccountCreator
+import org.linphone.core.Call
 import org.linphone.core.RegistrationState
 import org.linphone.core.TransportType
 import serialporttest.utils.SerialPort485Util
@@ -104,7 +105,7 @@ class MainActivity :BaseActivity<MainActivityPresenter, MainActivityLayoutBindin
     private var clickTime : Long = 0
     private var clickSosTime : Long = 0
 
-    private var mAccountCreator: AccountCreator? = null
+    private var linphoneManager: LinphoneManager? = null
 
     //网络异常计数
     private var netErrCount : Int = 0
@@ -221,14 +222,54 @@ class MainActivity :BaseActivity<MainActivityPresenter, MainActivityLayoutBindin
         SoundPoolManager.getInstance().init()
 
         if (SettingConfig.getSipEnabled(activity)) {
-            //启动sip服务
-            val serviceIntent = Intent(BaseApplication.appContext, WdklSipService::class.java)
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                //android8.0以上通过startForegroundService启动service
-                startForegroundService(serviceIntent)
-            } else {
-                startService(serviceIntent)
+            linphoneManager = LinphoneManager.getInstance(BaseApplication.appContext)
+            linphoneManager?.start()
+            linphoneManager?.phoneCallback = object : PhoneCallback() {
+                //通话状态回调监听
+                override fun incomingCall(call: Call) {
+                    //如果当前在通话界面则说明接听了通话,此时将自动接听,否则挂断
+                    linphoneManager?.answerCall(call, false)
+                }
+
+                override fun callConnected(call: Call) {
+                        /*if (SettingConfig.getRecordEnable(BaseApplication.appContext)) {
+                            //通话录音
+                            call.params.recordFile = Environment.getExternalStorageDirectory().path +
+                                    "/" + Environment.DIRECTORY_DOWNLOADS +
+                                    "/" + TimeHandle.getRecTimeFilename() + ".wav"
+
+                            if (Constants.showCall && !call.isRecording) {
+                                call.startRecording();
+                            }
+
+                            Log.e("SipCall", "call recording: " + call.isRecording + ", path: " + call.params.recordFile)
+                        }*/
+
+                    EventBus.getDefault().post(MessageEvent(1, Constant.SIP_CALL_CONNECTED))
+                }
+
+                override fun callEnd(call: Call) {
+                    /*if (SettingConfig.getRecordEnable(BaseApplication.appContext)) {
+                        Log.e("SipCall", "call recording: " + call.isRecording + ", record path: " + call.params.recordFile)
+                        if (call.isRecording) {
+                            call.stopRecording()
+                        }
+                    }*/
+
+                    EventBus.getDefault().post(MessageEvent("endcall", Constant.EVENT_END_CALL))
+                }
+
+                override fun callReleased(call: Call) {
+                    //
+                }
+
+                override fun error(string: String) {
+                    runOnUiThread {
+                        showMessage(string)
+                    }
+                }
             }
+
             view_title_layout_tv_point.text = "sip"
         } else {
             view_title_layout_tv_point.text = "rtc"
@@ -791,33 +832,10 @@ class MainActivity :BaseActivity<MainActivityPresenter, MainActivityLayoutBindin
 
 
         if (SettingConfig.getSipEnabled(activity)) {
-            //配置sip账户
-            if (WdklSipService.getCore() != null) {
-                mAccountCreator = WdklSipService.getCore().createAccountCreator(null)
-                // 以下三项必须
-                if (!TextUtils.isEmpty(Constant.SIP_ID) && !TextUtils.isEmpty(Constant.sip_ip)) {
-                    Log.e(TAG, "sip connect: ${Constant.SIP_ID}@${Constant.sip_ip}:${Constant.sip_port}")
-                    mAccountCreator!!.setDomain(Constant.sip_ip)
-                    mAccountCreator!!.setUsername(Constant.SIP_ID)
-                    mAccountCreator!!.setPassword(Constant.SIP_ID)
-                    //默认使用udp
-                    mAccountCreator!!.transport = TransportType.Udp
-
-                    // 这里会自动创建代理配置、认证信息到 SIP核心
-                    val cfg = mAccountCreator!!.createProxyConfig()
-                    // 确保新创建的是最新
-                    WdklSipService.getCore().defaultProxyConfig = cfg
-
-                    /*if (Constants.sip_port != null) {
-                        var transports = WdklSipService.getCore().transports
-                        transports.udpPort = Constants.sip_port!!
-                        transports.tcpPort = Constants.sip_port!!
-                        transports.tlsPort = -1
-                        WdklSipService.getCore().transports = transports
-                    }*/
-                } else {
-                    showMessage("SIP empty")
-                }
+            if (!TextUtils.isEmpty(Constant.SIP_ID) && !TextUtils.isEmpty(Constant.sip_ip)) {
+                linphoneManager?.createProxyConfig(Constant.SIP_ID!!, Constant.SIP_ID!!, "${Constant.sip_ip}:5060", TransportType.Udp)
+            } else {
+                showMessage("SIP empty")
             }
         }
     }
@@ -1524,19 +1542,12 @@ class MainActivity :BaseActivity<MainActivityPresenter, MainActivityLayoutBindin
                             }
 
                             if (SettingConfig.getSipEnabled(activity)) {
-                                val sipCore = WdklSipService.getCore()
-                                if (sipCore == null || TextUtils.isEmpty(targetSip)) {
+                                if (TextUtils.isEmpty(targetSip)) {
                                     //通话失败,重置并返回主界面
-                                    showMessage("Core或targetSipId为空!")
+                                    showMessage("targetSipId为空!")
                                     handoffCall()
                                 } else {
-                                    val addressToCall = sipCore.interpretUrl(targetSip)
-                                    val params = sipCore.createCallParams(null)
-                                    params?.isVideoEnabled = false
-                                    if (addressToCall != null) {
-                                        sipCore.inviteAddressWithParams(addressToCall, params!!)
-                                        Log.d(TAG, ">>>>>>>>>>> invite address: " + addressToCall.asString())
-                                    }
+                                    linphoneManager?.startCall(targetSip, false)
                                 }
                             }
                         } else if (tcpModel.getAction() == TcpAction.VoiceAction.SUCCESS) {

+ 0 - 143
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/BaseCallFragment.kt.bak

@@ -1,143 +0,0 @@
-package com.wdkl.app.ncs.conversion_box.fragment
-
-import android.os.Bundle
-import android.os.CountDownTimer
-import android.support.v4.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import com.enation.javashop.utils.base.tool.BaseToolActivity
-import com.wdkl.app.ncs.conversion_box.helper.RingPlayHelper
-import com.wdkl.app.ncs.conversion_box.settings.SettingConfig
-import com.wdkl.core.voip.VoipEvent
-import com.wdkl.ncs.android.lib.utils.showMessage
-import com.wdkl.ncs.android.middleware.common.Constant
-import com.wdkl.ncs.android.middleware.common.MessageEvent
-import com.wdkl.ncs.android.middleware.tcp.channel.VoiceUtil
-import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel
-import com.wdkl.skywebrtc.SkyEngineKit
-import com.wdkl.skywebrtc.except.NotInitializedException
-import org.greenrobot.eventbus.EventBus
-
-abstract class BaseCallFragment: Fragment() {
-
-    private var layout: View? = null
-
-    protected lateinit var baseActivity: BaseToolActivity
-
-    //通话状态:0-去电, 1-来电, 2-探视
-    protected var callState : Int = 0
-    protected var tcpModel: TcpModel? = null
-
-    //计时器
-    lateinit var countDownTimer: CountDownTimer
-
-    protected var gEngineKit: SkyEngineKit? = null
-
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        retainInstance = true
-        callState = arguments.getInt("call_state")
-        if (arguments.getSerializable("tcp_model") != null) {
-            tcpModel = arguments.getSerializable("tcp_model") as TcpModel
-        }
-    }
-
-    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
-        if (layout == null) {
-            layout = inflater.inflate(getLayId(), null)
-        }
-
-        /**初始化宿主Activity*/
-        baseActivity = getActivity() as BaseToolActivity
-
-        return layout
-    }
-
-    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-
-        try {
-            SkyEngineKit.init(VoipEvent())
-            gEngineKit = SkyEngineKit.Instance()
-        } catch (e: NotInitializedException) {
-            SkyEngineKit.init(VoipEvent())
-            try {
-                gEngineKit = SkyEngineKit.Instance()
-            } catch (ex: NotInitializedException) {
-                ex.printStackTrace()
-                baseActivity.finish()
-            }
-        }
-
-        init()
-        bindEvent()
-    }
-
-    override fun onDestroyView() {
-        super.onDestroyView()
-        destroy()
-    }
-
-    override fun onStart() {
-        EventBus.getDefault().register(this)
-        super.onStart()
-    }
-
-    override fun onStop() {
-        EventBus.getDefault().unregister(this)
-        super.onStop()
-    }
-
-    protected abstract fun getLayId(): Int
-
-    protected abstract fun init()
-
-    protected abstract fun bindEvent()
-
-    protected abstract fun destroy()
-
-    //初始化计时器
-    protected fun initCountDownTimer(view: TextView) {
-        val overTime = SettingConfig.getSipOverTime(baseActivity) * 1000L
-        countDownTimer = object: CountDownTimer(overTime, 1000) {
-            override fun onTick(millisUntilFinished: Long) {
-                if (view != null) {
-                    val time = millisUntilFinished/1000
-                    view.setText("倒计时: " + time + "秒")
-                }
-            }
-
-            override fun onFinish() {
-                //呼叫超时,返回到主界面
-                RingPlayHelper.stopRingTone()
-                showMessage("无人应答...")
-                Constant.CALL_STATE = Constant.CALL_STANDBY
-                VoiceUtil.cancelAudioCall(Constant.DEVICE_ID)
-                backToMain()
-            }
-        }
-    }
-
-    //开始计时
-    protected fun startTimer() {
-        if (countDownTimer != null) {
-            countDownTimer.start()
-        }
-    }
-
-    //取消计时器
-    protected fun cancelTimer() {
-        if (countDownTimer != null) {
-            countDownTimer.cancel()
-        }
-    }
-
-    //返回主界面
-    protected fun backToMain() {
-        EventBus.getDefault().post(MessageEvent("BackCall", Constant.EVENT_REMOVE_CALL_FRAGMENT))
-    }
-
-}

+ 8 - 21
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/SipCallFragment.kt

@@ -9,11 +9,11 @@ import android.view.ViewGroup
 import com.enation.javashop.utils.base.tool.BaseToolActivity
 import com.wdkl.app.ncs.conversion_box.R
 import com.wdkl.app.ncs.conversion_box.activity.MainActivity
-import com.wdkl.app.ncs.conversion_box.helper.AudioRouteUtils
 import com.wdkl.app.ncs.conversion_box.helper.RingPlayHelper
-import com.wdkl.app.ncs.conversion_box.service.WdklSipService
+import com.wdkl.ncs.android.lib.base.BaseApplication
 import com.wdkl.ncs.android.middleware.common.Constant
 import com.wdkl.ncs.android.middleware.common.MessageEvent
+import com.wdkl.ncs.host.sip.core.LinphoneManager
 import kotlinx.android.synthetic.main.call_fragment_layout.*
 import org.greenrobot.eventbus.EventBus
 import org.greenrobot.eventbus.Subscribe
@@ -32,8 +32,7 @@ class SipCallFragment: Fragment() {
     protected var callState : Int = 0
     protected var onlyAudio: Boolean = true
 
-    //呼叫倒计时
-    private var sipCore: Core? = null
+    private var linphoneManager: LinphoneManager? = null
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -59,7 +58,8 @@ class SipCallFragment: Fragment() {
     fun init() {
         RingPlayHelper.stopRingTone()
 
-        sipCore = WdklSipService.getCore()
+        linphoneManager = LinphoneManager.getInstance(BaseApplication.appContext)
+        linphoneManager?.enableMic(true)
         toggleSpeaker(true)
 
         //Log.e(TAG, "udpPort: ${sipCore!!.transports.udpPort}, tcpPort: ${sipCore!!.transports.tcpPort}")
@@ -90,13 +90,7 @@ class SipCallFragment: Fragment() {
     }
 
     private fun callTerminate() {
-        if (sipCore != null && sipCore!!.callsNb > 0) {
-            var call = sipCore!!.currentCall
-            if (call == null) {
-                call = sipCore!!.calls[0]
-            }
-            call!!.terminate()
-        }
+        linphoneManager?.terminateCall()
     }
 
     //通话结束
@@ -118,16 +112,9 @@ class SipCallFragment: Fragment() {
     }
 
     private fun toggleSpeaker(enable: Boolean) {
-        Log.d(TAG, "toggle speaker: $enable, sipCore: $sipCore")
-        if ( sipCore == null) {
-            return
-        }
+        Log.d(TAG, "toggle speaker: $enable")
 
-        if (enable) {
-            AudioRouteUtils.routeAudioToSpeaker(sipCore!!)
-        } else {
-            AudioRouteUtils.routeAudioToEarpiece(sipCore!!)
-        }
+        linphoneManager?.enableSpeaker(enable)
     }
 
     @Subscribe(threadMode = ThreadMode.MAIN)

+ 0 - 504
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/SkyCallFragment.kt.bak

@@ -1,504 +0,0 @@
-package com.wdkl.app.ncs.conversion_box.fragment
-
-import android.os.Handler
-import android.os.Looper
-import android.os.SystemClock
-import android.util.Log
-import android.view.View
-import android.view.ViewGroup
-import com.google.gson.Gson
-import com.wdkl.app.ncs.conversion_box.R
-import com.wdkl.app.ncs.conversion_box.helper.RingPlayHelper
-import com.wdkl.ncs.android.lib.utils.AppTool
-import com.wdkl.ncs.android.lib.utils.showMessage
-import com.wdkl.ncs.android.middleware.common.Constant
-import com.wdkl.ncs.android.middleware.common.MessageEvent
-import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
-import com.wdkl.ncs.android.middleware.tcp.channel.VideoUtil
-import com.wdkl.ncs.android.middleware.tcp.channel.VoiceUtil
-import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel
-import com.wdkl.ncs.android.middleware.tcp.enums.TcpAction
-import com.wdkl.ncs.android.middleware.tcp.enums.TcpType
-import com.wdkl.skywebrtc.CallSession
-import com.wdkl.skywebrtc.EnumType
-import kotlinx.android.synthetic.main.sky_voice_call_layout.*
-import org.greenrobot.eventbus.Subscribe
-import org.greenrobot.eventbus.ThreadMode
-import org.webrtc.SurfaceViewRenderer
-import java.util.*
-
-class SkyCallFragment: BaseCallFragment(), CallSession.CallSessionCallback {
-
-    //来电设备id
-    var fromId: Int = -1
-
-    private var interactionVO: InteractionVO? = null
-
-    private var localSurfaceView: SurfaceViewRenderer? = null
-    private var remoteSurfaceView: SurfaceViewRenderer? = null
-
-    private val handler = Handler(Looper.getMainLooper())
-
-    private var visiting: Boolean = false
-
-    private var audioCall: Boolean = false
-
-    override fun getLayId(): Int {
-        return R.layout.sky_voice_call_layout
-    }
-
-    override fun init() {
-        //初始化计时器
-        initCountDownTimer(sky_voice_call_timeout)
-        //tcp参数
-        if (tcpModel != null) {
-            fromId = tcpModel!!.fromId
-            interactionVO = Gson().fromJson(tcpModel!!.data.toString(), InteractionVO::class.java)
-        }
-
-        when (callState) {
-            0 -> {
-                //去电
-                startOutgoing()
-                RingPlayHelper.playRingTone(baseActivity, R.raw.ring_back2, true)
-            }
-
-            1 -> {
-                //来电
-                showIncomingCall()
-                RingPlayHelper.playRingTone(baseActivity, R.raw.ring_tone, true)
-            }
-        }
-    }
-
-    override fun bindEvent() {
-        //去电取消或通话挂断
-        sky_voice_call_hangup.setOnClickListener {
-            RingPlayHelper.stopRingTone()
-            if (Constant.CALL_STATE == Constant.CALL_CALLING) {
-                //结束sip通话
-                val session = gEngineKit?.getCurrentSession()
-                if (session != null) {
-                    session.leave()
-                }
-
-                Constant.CALL_STATE = Constant.CALL_STANDBY
-                sky_voice_call_timer.stop()
-                backToMain()
-            } else {
-                Constant.CALL_STATE = Constant.CALL_STANDBY
-                VoiceUtil.cancelAudioCall(Constant.DEVICE_ID)
-                cancelCall()
-            }
-        }
-
-        //来电拒绝
-        sky_voice_call_ring_reject.setOnClickListener {
-            RingPlayHelper.stopRingTone()
-            Constant.CALL_STATE = Constant.CALL_STANDBY
-            VoiceUtil.rejectAudioCall(Constant.DEVICE_ID, fromId, interactionVO?.id)
-            backToMain()
-        }
-
-        //来电接听
-        sky_voice_call_ring_pickup_audio.setOnClickListener {
-            acceptCall()
-            RingPlayHelper.stopRingTone()
-            Constant.CALL_STATE = Constant.CALL_INCOMING
-            VoiceUtil.acceptAudioCall(Constant.DEVICE_ID, fromId, interactionVO?.id)
-        }
-    }
-
-    override fun destroy() {
-        cancelTimer()
-        Constant.CALL_STATE = Constant.CALL_STANDBY
-        if (sky_voice_call_timer != null) {
-            sky_voice_call_timer.stop()
-        }
-        RingPlayHelper.stopRingTone()
-    }
-
-    private fun startOutgoing() {
-        VoiceUtil.startAudioCall(Constant.DEVICE_ID)
-        Constant.CALL_STATE = Constant.CALL_OUTGOING
-        sky_voice_call_timeout.visibility = View.VISIBLE
-        sky_voice_call_timer.visibility = View.GONE
-        startTimer()
-    }
-
-    //去电界面
-    private fun showOutgoingCall() {
-        Constant.CALL_STATE = Constant.CALL_OUTGOING
-        sky_voice_call_calling_text.text = "呼叫成功,等待接听..."
-        sky_voice_call_outgoing.visibility = View.VISIBLE
-        sky_voice_call_incoming.visibility = View.GONE
-        sky_voice_call_timeout.visibility = View.VISIBLE
-        sky_voice_call_timer.visibility = View.GONE
-        startTimer()
-    }
-
-    //来电界面
-    private fun showIncomingCall() {
-        Constant.CALL_STATE = Constant.CALL_INCOMING
-        sky_voice_call_calling_text.text = "有新来电..."
-        sky_voice_call_outgoing.visibility = View.GONE
-        sky_voice_call_incoming.visibility = View.VISIBLE
-        sky_voice_call_timeout.visibility = View.GONE
-        sky_voice_call_timer.visibility = View.GONE
-        cancelTimer()
-    }
-
-    //开始接听
-    private fun acceptCall() {
-        sky_voice_call_calling_text.text = "连接中..."
-        sky_voice_call_outgoing.visibility = View.VISIBLE
-        sky_voice_call_incoming.visibility = View.GONE
-        sky_voice_call_timeout.visibility = View.GONE
-        sky_voice_call_timer.visibility = View.GONE
-        cancelTimer()
-    }
-
-    //呼叫取消
-    private fun cancelCall() {
-        cancelTimer()
-        Constant.CALL_STATE = Constant.CALL_STANDBY
-        sky_voice_call_timer.stop()
-        backToMain()
-    }
-
-    //语音接通
-    private fun joinAudioCall() {
-        val session = gEngineKit?.getCurrentSession()
-        if (session != null) {
-            Log.e("dds", "audio call session state: " + session.state)
-            session.setSessionCallback(this)
-        }
-        if (session != null && session.state == EnumType.CallState.Incoming) {
-            session.joinHome(session.roomId)
-            session.toggleSpeaker(true)
-        }
-    }
-
-    //视频接通
-    private fun joinVideoCall() {
-        val session = gEngineKit?.getCurrentSession()
-        if (session != null) {
-            Log.e("dds", "video call session state: " + session.state)
-            session.setSessionCallback(this)
-        }
-        if (session != null && session.state == EnumType.CallState.Incoming) {
-            val surfaceView = gEngineKit!!.currentSession.setupLocalVideo(false)
-            if (surfaceView != null) {
-                localSurfaceView = surfaceView as SurfaceViewRenderer
-                localSurfaceView!!.setZOrderMediaOverlay(false)
-                fullscreen_video_frame.addView(localSurfaceView)
-            }
-
-            session.joinHome(session.roomId)
-            session.toggleSpeaker(true)
-        }
-    }
-
-    private fun showCalling(audioOnly: Boolean) {
-        if (audioOnly) {
-            //移除视频画面
-            fullscreen_video_frame.visibility = View.GONE
-            pip_video_frame.visibility = View.GONE
-            ll_voice_call.visibility = View.VISIBLE
-        } else {
-            //显示视频画面
-            fullscreen_video_frame.visibility = View.VISIBLE
-            pip_video_frame.visibility = View.VISIBLE
-            ll_voice_call.visibility = View.GONE
-        }
-
-        Constant.CALL_STATE = Constant.CALL_CALLING
-        sky_voice_call_calling_text.text = "通话中..."
-        sky_voice_call_timeout.visibility = View.GONE
-        cancelTimer()
-        sky_voice_call_timer.visibility = View.VISIBLE
-        sky_voice_call_timer.base = SystemClock.elapsedRealtime()
-        sky_voice_call_timer.start()
-    }
-
-    //创建会话
-    private fun startCall(targetId: String, audioOnly: Boolean): Boolean {
-        audioCall = audioOnly
-        val room = UUID.randomUUID().toString() + System.currentTimeMillis()
-        val b = gEngineKit!!.startOutCall(baseActivity, room, targetId, audioOnly)
-        if (b) {
-            val session = gEngineKit!!.currentSession
-            if (session == null) {
-                return false
-            } else {
-                session.setSessionCallback(this)
-                session.toggleSpeaker(true)
-            }
-        }
-        return b
-    }
-
-    //通话结束
-    private fun callEnd() {
-        Log.e("dds", "call end !!!!!!!!!!!!!!!!!!")
-        if (visiting) {
-            VideoUtil.handoffVideoCall(Constant.DEVICE_ID, fromId, interactionVO?.id)
-        } else {
-            VoiceUtil.handoffAudioCall(Constant.DEVICE_ID, fromId, interactionVO?.id)
-        }
-
-        if (sky_voice_call_timer != null) {
-            sky_voice_call_timer.stop()
-        }
-        if (gEngineKit != null && gEngineKit!!.currentSession != null && gEngineKit!!.currentSession.state != EnumType.CallState.Idle) {
-            gEngineKit!!.endCall()
-        }
-        backToMain()
-    }
-
-    //探视
-    private fun startVisiting(targetId: String) {
-        if (!startCall(targetId, false)) {
-            //通话失败,重置并返回主界面
-            Constant.CALL_STATE = Constant.CALL_STANDBY
-            VideoUtil.rejectVideoCall(Constant.DEVICE_ID, fromId, interactionVO!!.id)
-            if (sky_voice_call_timer != null) {
-                sky_voice_call_timer.stop()
-            }
-            backToMain()
-        }
-    }
-
-
-    /********************************************************
-     ********************* webrtc通话回调 ********************
-     * 注意: 如涉及到UI更新的需要在主线程处理,务必注意
-     *******************************************************/
-    override fun didChangeState(state: EnumType.CallState?) {
-        Log.e("dds", "didChangeState: " + state)
-        handler.post {
-            if (state == EnumType.CallState.Connected) {
-                //更新界面显示
-                showCalling(audioCall)
-            }
-        }
-    }
-
-    override fun didDisconnected(userId: String?) {
-        handler.post {
-            callEnd()
-        }
-    }
-
-    override fun didError(error: String?) {
-        handler.post {
-            callEnd()
-        }
-    }
-
-    //处理本地视频画面
-    override fun didCreateLocalVideoTrack() {
-        Log.e("dds", "didCreateLocalVideoTrack")
-        handler.post {
-            if (localSurfaceView == null) {
-                val surfaceView = gEngineKit!!.currentSession.setupLocalVideo(true)
-                Log.e("dds", "didCreateLocalVideoTrack surfaceView: " + surfaceView)
-                if (surfaceView != null) {
-                    localSurfaceView = surfaceView as SurfaceViewRenderer
-                }
-            }
-        }
-    }
-
-    //处理远端视频画面
-    override fun didReceiveRemoteVideoTrack(userId: String?) {
-        Log.e("dds", "didReceiveRemoteVideoTrack  userId: " + userId)
-        handler.post {
-            //本地画面
-            if (localSurfaceView != null) {
-                localSurfaceView!!.setZOrderMediaOverlay(true)
-                if (localSurfaceView!!.parent != null) {
-                    (localSurfaceView!!.parent as ViewGroup).removeView(localSurfaceView)
-                }
-                pip_video_frame!!.addView(localSurfaceView)
-            }
-
-            //远端画面
-            val surfaceView = gEngineKit!!.currentSession.setupRemoteVideo(userId, false)
-            Log.e("dds", "didReceiveRemoteVideoTrack,surfaceView = $surfaceView")
-            if (surfaceView != null) {
-                remoteSurfaceView = surfaceView as SurfaceViewRenderer
-                fullscreen_video_frame.removeAllViews()
-                if (remoteSurfaceView!!.parent != null) {
-                    (remoteSurfaceView!!.parent as ViewGroup).removeView(remoteSurfaceView)
-                }
-                fullscreen_video_frame.addView(remoteSurfaceView)
-            }
-        }
-    }
-
-    override fun didCallEndWithReason(callEndReason: EnumType.CallEndReason?) {
-        handler.post {
-            when (callEndReason) {
-                EnumType.CallEndReason.Busy -> {
-                    showMessage("对方忙线中")
-                }
-                EnumType.CallEndReason.AcceptByOtherClient -> {
-                    showMessage("通话中")
-                }
-                EnumType.CallEndReason.Hangup -> {
-                    //showMessage("挂断")
-                }
-                EnumType.CallEndReason.MediaError -> {
-                    showMessage("媒体错误")
-                }
-                EnumType.CallEndReason.OpenCameraFailure -> {
-                    showMessage("打开摄像头错误")
-                }
-                EnumType.CallEndReason.RemoteHangup -> {
-                    showMessage("对方挂断")
-                }
-                EnumType.CallEndReason.RemoteSignalError -> {
-                    showMessage("对方网络断开")
-                }
-                EnumType.CallEndReason.SignalError -> {
-                    showMessage("连接断开")
-                }
-                EnumType.CallEndReason.Timeout -> {
-                    showMessage("对方未接听")
-                }
-            }
-
-            callEnd()
-        }
-    }
-
-    override fun didChangeMode(isAudioOnly: Boolean) {
-        handler.post {
-            //
-        }
-    }
-
-    override fun didUserLeave(userId: String?) {
-        handler.post {
-            callEnd()
-        }
-    }
-
-
-
-    @Subscribe(threadMode = ThreadMode.MAIN)
-    fun onMoonEvent(messageEvent: MessageEvent) {
-        when (messageEvent.type) {
-            Constant.EVENT_SIP_CALL_STATUS -> {
-                //收到sip通话邀请,加入通话
-                Log.e("dds", "EVENT_SIP_CALL_STATUS: " + messageEvent.message)
-                if (messageEvent.message is String) {
-                    val call = messageEvent.message as String
-                    if (call.equals("video_call")) {
-                        audioCall = false
-                        Handler().postDelayed({
-                            joinVideoCall()
-                        }, 2000)
-                    } else {
-                        audioCall = true
-                        Handler().postDelayed({
-                            joinAudioCall()
-                        }, 1500)
-                    }
-                }
-            }
-
-            Constant.EVENT_TCP_MSG -> {
-                if (messageEvent.message is TcpModel) {
-                    val curTcpModel = messageEvent.message as TcpModel
-                    if (curTcpModel.getType() == TcpType.VOICE) {
-                        val curInteractionVO = Gson().fromJson(curTcpModel.data.toString(), InteractionVO::class.java)
-                        if (curTcpModel.getAction() == TcpAction.VoiceAction.ACCEPT) {
-                            //我方呼出,对方接受
-                            RingPlayHelper.stopRingTone()
-                            Constant.interactionId = curInteractionVO.id
-                            fromId = curTcpModel.fromId
-                            acceptCall()
-                            if (!startCall(curInteractionVO.toSipId, true)) {
-                                //通话失败,重置并返回主界面
-                                Constant.CALL_STATE = Constant.CALL_STANDBY
-                                VoiceUtil.handoffAudioCall(Constant.DEVICE_ID, fromId, Constant.interactionId)
-                                if (sky_voice_call_timer != null) {
-                                    sky_voice_call_timer.stop()
-                                }
-                                backToMain()
-                            }
-                        } else if (curTcpModel.getAction() == TcpAction.VoiceAction.REJECT) {
-                            //我方呼出,对方拒绝
-                            showMessage("对方已拒绝!")
-                            RingPlayHelper.stopRingTone()
-                            cancelCall()
-                        } else if (curTcpModel.getAction() == TcpAction.VoiceAction.CALLING) {
-                            //我方呼出,对方通话中
-                            showMessage("对方正在忙线中,暂时无法接听!")
-                            AppTool.Time.delay(2000) {
-                                RingPlayHelper.stopRingTone()
-                                cancelCall()
-                            }
-                        } else if (curTcpModel.getAction() == TcpAction.VoiceAction.SUCCESS) {
-                            //呼叫成功
-                            //本机呼叫的时候tcpmodel为空,只有呼叫成功的时候才能获得对应tcp相关数据
-                            interactionVO = curInteractionVO
-                            Constant.interactionId = curInteractionVO.id
-                            showOutgoingCall()
-                        } else if (curTcpModel.getAction() == TcpAction.VoiceAction.FAILED) {
-                            //我方呼出,对方不在线,设备离线或其它错误
-                            showMessage("呼叫失败,找不到设备或对方不在线!")
-                            RingPlayHelper.stopRingTone()
-                            cancelCall()
-                        } else if (curTcpModel.getAction() == TcpAction.VoiceAction.HANDOFF) {
-                            //对方挂断,不论我方呼出或呼入
-                            if (Constant.interactionId == curInteractionVO.id) {
-                                RingPlayHelper.stopRingTone()
-                                cancelCall()
-                            }
-                        } else if (curTcpModel.getAction() == TcpAction.VoiceAction.CANCEL) {
-                            //对方呼叫时取消
-                            RingPlayHelper.stopRingTone()
-                            cancelCall()
-                        }
-                    }
-                }
-            }
-
-            //外部呼叫按键
-            Constant.EVENT_SERIAL_EVENT -> {
-                if (messageEvent.message is String) {
-                    val serialAction = messageEvent.message as String
-                    if (serialAction.equals("cancel")) {
-                        RingPlayHelper.stopRingTone()
-                        Constant.CALL_STATE = Constant.CALL_STANDBY
-                        VoiceUtil.cancelAudioCall(Constant.DEVICE_ID)
-                        cancelCall()
-                    } else if (serialAction.equals("accept")) {
-                        RingPlayHelper.stopRingTone()
-                        Constant.CALL_STATE = Constant.CALL_INCOMING
-                        VoiceUtil.acceptAudioCall(Constant.DEVICE_ID, fromId, interactionVO?.id)
-                        acceptCall()
-                    } else if (serialAction.equals("handoff")) {
-                        val session = gEngineKit?.getCurrentSession()
-                        if (session != null) {
-                            session.leave()
-                        }
-
-                        Constant.CALL_STATE = Constant.CALL_STANDBY
-                        sky_voice_call_timer.stop()
-                        backToMain()
-                    } else if (serialAction.equals("reject")) {
-                        RingPlayHelper.stopRingTone()
-                        Constant.CALL_STATE = Constant.CALL_STANDBY
-                        VoiceUtil.rejectAudioCall(Constant.DEVICE_ID, fromId, interactionVO?.id)
-                        backToMain()
-                    }
-                }
-            }
-        }
-    }
-
-}

+ 0 - 7
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/helper/AppUpdateHelper.java

@@ -12,7 +12,6 @@ import android.os.Build;
 import android.os.Environment;
 import android.util.Log;
 
-import com.wdkl.app.ncs.conversion_box.service.WdklSipService;
 import com.wdkl.app.ncs.conversion_box.settings.SettingConfig;
 import com.wdkl.ncs.android.component.welcome.activity.WelcomeActivity;
 
@@ -252,12 +251,6 @@ public class AppUpdateHelper {
     }
 
     public static void restartApp(Context context) {
-        if (SettingConfig.getSipEnabled(context)) {
-            //停止服务
-            Intent serviceIntent = new Intent(context, WdklSipService.class);
-            context.stopService(serviceIntent);
-        }
-
         //重新启动app
         Intent mStartActivity = new Intent(context.getApplicationContext(), WelcomeActivity.class);
         mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

+ 0 - 329
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/service/WdklSipService.java

@@ -1,329 +0,0 @@
-package com.wdkl.app.ncs.conversion_box.service;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.support.annotation.Nullable;
-import android.util.Log;
-import android.widget.Toast;
-
-
-import com.wdkl.app.ncs.conversion_box.R;
-import com.wdkl.app.ncs.conversion_box.settings.SettingConfig;
-import com.wdkl.ncs.android.lib.base.BaseApplication;
-import com.wdkl.ncs.android.middleware.common.Constant;
-import com.wdkl.ncs.android.middleware.common.MessageEvent;
-
-import org.greenrobot.eventbus.EventBus;
-import org.linphone.core.Call;
-import org.linphone.core.CallParams;
-import org.linphone.core.Core;
-import org.linphone.core.CoreListenerStub;
-import org.linphone.core.Factory;
-import org.linphone.core.LogCollectionState;
-import org.linphone.core.PayloadType;
-import org.linphone.core.ProxyConfig;
-import org.linphone.core.RegistrationState;
-import org.linphone.mediastream.Version;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Timer;
-import java.util.TimerTask;
-
-public class WdklSipService extends Service {
-    private static final String START_SIPPHONE_LOGS = " ==== Device information dump ====";
-
-    private static final String PREFER_PAYLOAD = "PUMU";
-
-    //单例化服务,以便全局调用
-    private static WdklSipService sInstance;
-
-    private NotificationManager notificationManager = null;
-    private String notificationId = "channelId0";
-    private String notificationName = "sip_service";
-
-    private Handler mHandler;
-    private Timer mTimer;
-
-    private Core mCore;
-    private CoreListenerStub mCoreListener;
-
-    public static boolean sipTesting = false;
-
-    public static boolean isReady() {
-        return sInstance != null;
-    }
-
-    public static WdklSipService getInstance() {
-        return sInstance;
-    }
-
-    public static Core getCore() {
-        return sInstance.mCore;
-    }
-
-    @Nullable
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-
-        notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            NotificationChannel channel = new NotificationChannel(
-                    notificationId,
-                    notificationName,
-                    NotificationManager.IMPORTANCE_HIGH
-            );
-            channel.enableVibration(true);
-            channel.enableLights(true);
-            channel.setBypassDnd(true);
-            channel.setShowBadge(true);
-            channel.setSound(null, null);
-            notificationManager.createNotificationChannel(channel);
-        }
-
-        //首次调用必须使用 Factory相关方法
-        //这里开户调试日志及设置路径
-        String basePath = getFilesDir().getAbsolutePath();
-        Factory.instance().setLogCollectionPath(basePath);
-        Factory.instance().enableLogCollection(LogCollectionState.Enabled);
-        Factory.instance().setDebugMode(false, getString(R.string.app_name));
-
-        //收集一些设备信息
-        Log.i("sipCall", START_SIPPHONE_LOGS);
-        dumpDeviceInformation();
-        dumpInstalledLinphoneInformation();
-
-        mHandler = new Handler();
-        //主监听器,根据事件调用界面
-        mCoreListener = new CoreListenerStub() {
-            @Override
-            public void onCallStateChanged(Core core, Call call, Call.State state, String message) {
-                if (!SettingConfig.getSipEnabled(BaseApplication.appContext)) {
-                    return;
-                }
-
-                Toast.makeText(WdklSipService.this, message, Toast.LENGTH_SHORT).show();
-                Log.d("sipCall", ">>>>>>>>>>>> call state: " + state + ", " + call.getRemoteAddress().asString());
-
-                if (state == Call.State.IncomingReceived || state == Call.State.IncomingEarlyMedia) {
-                    //Toast.makeText(WdklSipService.this, "Incoming call", Toast.LENGTH_LONG).show();
-                    //来电时将自动接听
-                    CallParams params = getCore().createCallParams(call);
-                    call.acceptWithParams(params);
-                } else if (state == Call.State.Connected) {
-                    if (sipTesting) {
-                        //通话已建立完成,打开通话界面
-                        //Intent intent = new Intent(WdklSipService.this, CallActivity.class);
-                        //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                        //startActivity(intent);
-                    } else {
-                        EventBus.getDefault().post(new MessageEvent(1, Constant.SIP_CALL_CONNECTED));
-                    }
-                } else if (state == Call.State.End || state == Call.State.Released){
-                    EventBus.getDefault().post(new MessageEvent("endcall", Constant.EVENT_END_CALL));
-                }
-            }
-
-            @Override
-            public void onRegistrationStateChanged(Core core, ProxyConfig cfg, RegistrationState state, String message) {
-                if (!SettingConfig.getSipEnabled(BaseApplication.appContext)) {
-                    return;
-                }
-
-                EventBus.getDefault().post(new MessageEvent(state, Constant.EVENT_SIP_REGISTER_STATUS));
-            }
-        };
-
-        try {
-            //复制一些源资源
-            //默认配置只能在首次时安装一次
-            copyIfNotExist(R.raw.linphonerc_default, basePath + "/.wdkl_sip_rc");
-            //用户配置,每次复制
-            copyFromPackage(R.raw.linphonerc_factory, "wdkl_sip_rc");
-        } catch (IOException ioe) {
-            Log.e("sipCall",ioe.getMessage());
-        }
-
-        //创建SIP核心并加载监听器
-        mCore = Factory.instance()
-                .createCore(basePath + "/.wdkl_sip_rc", basePath + "/wdkl_sip_rc", this);
-        mCore.addListener(mCoreListener);
-        //SIP核心配置完成
-        configureCore();
-    }
-
-    private Notification getNotification() {
-        Notification.Builder builder = new Notification.Builder(this)
-                .setSmallIcon(R.mipmap.ic_launcher)
-                .setContentTitle("sip service")
-                .setContentText("running...")
-                .setOnlyAlertOnce(true);
-
-        //设置Notification的ChannelID,否则不能正常显示
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            builder.setChannelId(notificationId);
-        }
-
-        return builder.build();
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            startForeground(11, getNotification()); //开前台服务
-        }
-
-        super.onStartCommand(intent, flags, startId);
-        //Toast.makeText(WdklSipService.this, "sip服务已启动", Toast.LENGTH_SHORT).show();
-
-        //如果服务已经在运行,则返回
-        if (sInstance != null) {
-            return START_STICKY;
-        }
-
-        //一旦服务启动,则一直保持
-        sInstance = this;
-
-        //SIP核心在创建和配置完成后,开启
-        mCore.start();
-        //必须定时运行 SIP核心 iterate()
-        TimerTask lTask = new TimerTask() {
-            @Override
-            public void run() {
-                mHandler.post(
-                        new Runnable() {
-                            @Override
-                            public void run() {
-                                if (mCore != null) {
-                                    mCore.iterate();
-                                }
-                            }
-                        });
-            }
-        };
-        mTimer = new Timer("wdkl sip scheduler");
-        mTimer.schedule(lTask, 0, 20);
-
-        return START_STICKY;
-    }
-
-    @Override
-    public void onDestroy() {
-        mCore.removeListener(mCoreListener);
-        mTimer.cancel();
-        mCore.stop();
-        // A stopped Core can be started again
-        // To ensure resources are freed, we must ensure it will be garbage collected
-        mCore = null;
-        // Don't forget to free the singleton as well
-        sInstance = null;
-
-        super.onDestroy();
-    }
-
-    @Override
-    public void onTaskRemoved(Intent rootIntent) {
-        // For this sample we will kill the Service at the same time we kill the app
-        stopSelf();
-
-        super.onTaskRemoved(rootIntent);
-    }
-
-    private void configureCore() {
-        // We will create a directory for user signed certificates if needed
-        String basePath = getFilesDir().getAbsolutePath();
-        String userCerts = basePath + "/user-certs";
-        File f = new File(userCerts);
-        if (!f.exists()) {
-            if (!f.mkdir()) {
-                Log.e("sipCall",userCerts + " can't be created.");
-            }
-        }
-        mCore.setUserCertificatesPath(userCerts);
-
-        //音频部分, 这里增加了一个遍历, 用于设置指定的音频格式.
-        PayloadType[] payloads = mCore.getAudioPayloadTypes();
-        for(int i = 0; i < payloads.length; i ++){
-            PayloadType pt = payloads[i];
-            //Log.i("sipCall", ">>>>>>>>>>>>>>>>>1 " + pt.getMimeType() + " = " + pt.enabled());
-            if (pt.getMimeType().equals("PCMU")
-//                    || pt.getMimeType().equals("PUMA")
-//                    || pt.getMimeType().equals("GSM")
-            ){
-                pt.enable(true);
-            } else {
-                pt.enable(false);
-            }
-        }
-        mCore.setAudioPayloadTypes(payloads);
-    }
-
-    private void dumpDeviceInformation() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("DEVICE=").append(Build.DEVICE).append("\n");
-        sb.append("MODEL=").append(Build.MODEL).append("\n");
-        sb.append("MANUFACTURER=").append(Build.MANUFACTURER).append("\n");
-        sb.append("SDK=").append(Build.VERSION.SDK_INT).append("\n");
-        sb.append("Supported ABIs=");
-        for (String abi : Version.getCpuAbis()) {
-            sb.append(abi).append(", ");
-        }
-        sb.append("\n");
-        Log.i("sipCall",sb.toString());
-    }
-
-    private void dumpInstalledLinphoneInformation() {
-        PackageInfo info = null;
-        try {
-            info = getPackageManager().getPackageInfo(getPackageName(), 0);
-        } catch (PackageManager.NameNotFoundException nnfe) {
-            Log.e("sipCall",nnfe.getMessage());
-        }
-
-        if (info != null) {
-            Log.i(
-                    "[Service] sipphone version is ",
-                    info.versionName + " (" + info.versionCode + ")");
-        } else {
-            Log.i("sipCall","[Service] sipphone version is unknown");
-        }
-    }
-
-    private void copyIfNotExist(int ressourceId, String target) throws IOException {
-        File lFileToCopy = new File(target);
-        if (!lFileToCopy.exists()) {
-            copyFromPackage(ressourceId, lFileToCopy.getName());
-        }
-    }
-
-    private void copyFromPackage(int ressourceId, String target) throws IOException {
-        FileOutputStream lOutputStream = openFileOutput(target, 0);
-        InputStream lInputStream = getResources().openRawResource(ressourceId);
-        int readByte;
-        byte[] buff = new byte[8048];
-        while ((readByte = lInputStream.read(buff)) != -1) {
-            lOutputStream.write(buff, 0, readByte);
-        }
-        lOutputStream.flush();
-        lOutputStream.close();
-        lInputStream.close();
-    }
-}

+ 0 - 579
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/SipHelper.java.bak

@@ -1,579 +0,0 @@
-package com.wdkl.app.ncs.callingbed2.sip;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.CountDownTimer;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.util.Log;
-
-import com.vvsip.ansip.IVvsipService;
-import com.vvsip.ansip.IVvsipServiceListener;
-import com.vvsip.ansip.VvsipCall;
-import com.vvsip.ansip.VvsipService;
-import com.vvsip.ansip.VvsipServiceBinder;
-import com.vvsip.ansip.VvsipTask;
-import com.wdkl.app.ncs.callingbed2.common.MessageEvent;
-import com.wdkl.app.ncs.callingbed2.helper.NetHelper;
-
-
-import org.greenrobot.eventbus.EventBus;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.wdkl.app.ncs.callingbed2.common.Constant.EVENT_SIP_REGISTER_STATUS;
-import static com.wdkl.app.ncs.callingbed2.sip.SipStatus.REGISTERCOM;
-import static com.wdkl.app.ncs.callingbed2.sip.SipStatus.REGISTERFAIL;
-import static com.wdkl.app.ncs.callingbed2.sip.SipStatus.REGISTERING;
-import static com.vvsip.ansip.VvsipTask.EXOSIP_CALL_CLOSED;
-
-
-public class SipHelper {
-
-    private final static String SIP_IP_END = ":5060";
-
-    private static String sipIP = "";
-    private static String sipID = "";
-    private static String sipPWD = "";
-
-    /**
-     * Sip启动注册.
-     */
-    protected int mSipRegisterTime = 5000;
-    private Handler sipRegisterHandler = null;
-    private Runnable sipRegisterRunnable = null;
-
-    /**
-     * SIP信息
-     */
-    public static final String SipInfoTag = "SipInfo";
-    /**
-     * 电话呼叫对象
-     */
-    private List<VvsipCall> mVvsipCalls = null;
-
-
-    private static SipHelper sipHelper;
-
-    public Handler getSipRegisterHandler() {
-        return sipRegisterHandler;
-    }
-
-    public Runnable getSipRegisterRunnable() {
-        return sipRegisterRunnable;
-    }
-
-    public List<VvsipCall> getmVvsipCalls() {
-        return mVvsipCalls;
-    }
-
-    private IVvsipServiceListener sipListner;
-
-
-    private Context mContext;
-
-    public static SipHelper getInstance() {
-        if (sipHelper == null) {
-            synchronized (SipHelper.class) {
-                if (sipHelper == null) {
-                    sipHelper = new SipHelper();
-                }
-            }
-        }
-        return sipHelper;
-    }
-
-    /**
-     * Instantiates a new Sip register util.
-     */
-    private SipHelper() {
-        if (mVvsipCalls == null) {
-            mVvsipCalls = new ArrayList<VvsipCall>();
-        }
-
-        // Runnable exiting the splash screen and launching the menu
-        sipRegisterRunnable = new Runnable() {
-            public void run() {
-                isSuccessRegisterSip();
-            }
-        };
-
-        // Run the exitRunnable in in mSipRegisterTime ms
-        sipRegisterHandler = new Handler();
-
-        IVvsipService sipservice = VvsipService.getService();
-        if (sipservice != null) {
-            sipRegisterHandler.postDelayed(sipRegisterRunnable, 3000);
-            return;
-        }
-        sipRegisterHandler.postDelayed(sipRegisterRunnable, mSipRegisterTime);
-    }
-
-    public void initSip(Context context, String ip, String id, String pwd) {
-        mContext = context;
-        sipIP = ip;
-        sipID = id;
-        sipPWD = pwd;
-    }
-
-    public void setSipListner(IVvsipServiceListener listner) {
-        sipListner = listner;
-    }
-
-    /*public void addSipListner(IVvsipServiceListener listner) {
-        IVvsipService sipService = VvsipService.getService();
-        if (sipService != null && listner != null) {
-            sipService.addListener(listner);
-            Log.d("sip", "add sip listner");
-        }
-    }
-
-    public void removeSipListner(IVvsipServiceListener listner) {
-        IVvsipService sipService = VvsipService.getService();
-        if (sipService != null && listner != null) {
-            sipService.removeListener(listner);
-            Log.d("sip", "remove sip listner");
-        }
-    }*/
-
-    /**
-     * 检测Sip服务是否注册成功
-     */
-    public void isSuccessRegisterSip() {
-        VvsipTask vvsipTask = VvsipTask.getVvsipTask();
-        if (vvsipTask != null && VvsipTask.global_failure != 0) {
-            /**
-             * ==================================sip服务启动失败 ================================
-             */
-        }
-    }
-
-    /**
-     * 注销Sip服务
-     */
-    public void unRegisterSip() {
-        //LogUtil.i(SipInfoTag, "lifecycle // onDestroy");
-
-        IVvsipService sipservice = VvsipService.getService();
-        if (sipservice != null && sipListner != null) {
-            sipservice.removeListener(sipListner);
-        }
-
-        getSipServiceStartHandler().removeCallbacks(getSipServiceStartRunnable());
-        sipRegisterHandler.removeCallbacks(sipRegisterRunnable);
-        if (getSipServiceConnection() != null && isRegister) {
-            try {
-                mContext.unbindService(getSipServiceConnection());
-                setSipServiceConnection(null);
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        }
-        if (mVvsipCalls != null) {
-            mVvsipCalls.clear();
-            mVvsipCalls = null;
-        }
-
-        //Log.i(SipInfoTag, "lifecycle // onDestroy");
-    }
-
-    public static String sipStatus = "";
-
-    /**
-     * Sip信息获取
-     */
-    public void obtainSipInfo() {
-        if (sipStatus.equals(REGISTERCOM) && NetHelper.NetConn) {//sip注册成功并且以太网连上
-            return;
-        }
-        IVvsipService sipService = VvsipService.getService();
-        if (sipService != null && sipListner != null) {
-            //sipService.addListener(sipListner);
-            sipService.setMessageHandler(messageHandler);
-        } else {
-            //LogUtil.i(SipInfoTag, "lifecycle // _service==null");
-        }
-        sipRegister();
-        failUiRefreshSip();
-    }
-
-    private void failUiRefreshSip() {
-        if (!NetHelper.NetConn) {
-            sipStatus = REGISTERFAIL;
-            EventBus.getDefault().post(new MessageEvent(REGISTERFAIL, EVENT_SIP_REGISTER_STATUS));
-            if (mSipThread != null) {
-                mSipThread.interrupt();
-                mSipThread = null;
-            }
-            //LogUtil.e(SipInfoTag, "以太网断开,SIP UI状态刷新为失败");
-        }
-    }
-
-
-    /**
-     * Sip信息
-     */
-    private String sipinfo = "";
-    private static int handleCount = 0;
-    //Sip註冊次數
-    private CountDownTimer mCountDownAutoTimer;
-    @SuppressLint("HandlerLeak")
-    private Handler messageHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            //LogUtil.i("QASE", "handleMessage==" + " msg.obj==" + msg.obj.toString() + " msg.what==" + msg.what);
-            //LogUtil.i(SipInfoTag, "#" + msg.obj);
-            sipinfo = "" + msg.obj + sipinfo;
-            //LogUtil.i(SipInfoTag, "Sip信息" + sipinfo);
-
-            if (msg.what == 22) {//释放资源
-                //EventBus.getDefault().post(new MessageEvent(msg.what, EVENT_SIP_REGISTER_STATUS));
-            }
-
-            if (sipinfo.contains("200 OK")) {//注册成功
-                sipStatus = REGISTERCOM;
-                EventBus.getDefault().post(new MessageEvent(REGISTERCOM, EVENT_SIP_REGISTER_STATUS));
-                if (mSipThread != null) {
-                    mSipThread.interrupt();
-                    mSipThread = null;
-                }
-                if (msg.obj.toString().contains("408")) {//超时
-                    sipStatus = REGISTERFAIL;
-                    EventBus.getDefault().post(new MessageEvent(REGISTERFAIL, EVENT_SIP_REGISTER_STATUS));
-                    sipRegister();
-                }
-            } else {//注册失败
-                sipStatus = REGISTERFAIL;
-                EventBus.getDefault().post(new MessageEvent(REGISTERFAIL, EVENT_SIP_REGISTER_STATUS));
-                if (mSipThread != null) {
-                    mSipThread.interrupt();
-                    mSipThread = null;
-                }
-                sipRegister();
-            }
-            failUiRefreshSip();
-
-            if (msg.obj.toString().contains("autocall")) {
-                VvsipCall pCall = null;
-                //LogUtil.e(SipInfoTag, "onClick1");
-                for (VvsipCall _pCall : mVvsipCalls) {
-                    if (_pCall.cid > 0)
-                        //LogUtil.e(SipInfoTag, "state#" + _pCall.mState);
-                        if (_pCall.cid > 0 && _pCall.mState <= 2) {
-                            pCall = _pCall;
-                            break;
-                        }
-                }
-                //LogUtil.e(SipInfoTag, "onClick2");
-                if (pCall == null)
-                    return;
-                //LogUtil.e(SipInfoTag, "onClick3#" + pCall.mState);
-                IVvsipService _service = VvsipService.getService();
-                if (_service == null)
-                    return;
-                VvsipTask _vvsipTask = _service.getVvsipTask();
-                if (_vvsipTask == null)
-                    return;
-                pCall.stop();
-                _service.setSpeakerModeOff();
-            }
-        }
-    };
-
-    /**
-     * ====================Sip注册======================
-     */
-    private Thread mSipThread;
-
-    private static class SipThread extends Thread {
-        WeakReference<Activity> mThreadCallingBedActivity;
-
-        private SipThread(Activity activity) {
-            mThreadCallingBedActivity = new WeakReference<Activity>(activity);
-        }
-
-        @Override
-        public void run() {
-            super.run();
-            if (mThreadCallingBedActivity == null)
-                return;
-            if (mThreadCallingBedActivity.get() != null) {
-                IVvsipService sipService = VvsipService.getService();
-                try {
-                    if (sipService != null && !SipHelper.getInstance().sipinfo.contains("200 OK") && NetHelper.NetConn) {
-                        sipStatus = REGISTERING;
-                        EventBus.getDefault().post(new MessageEvent(REGISTERING, EVENT_SIP_REGISTER_STATUS));
-                        sipService.register(sipIP + SIP_IP_END, sipID, sipPWD);
-                        handleCount++;
-                        Log.e(SipInfoTag, "以太网连接,SIP UI状态刷新为注册中");
-                    } else if (sipService != null && SipHelper.getInstance().sipinfo.contains("200 OK")) {
-                        sipStatus = REGISTERCOM;
-                        EventBus.getDefault().post(new MessageEvent(REGISTERCOM, EVENT_SIP_REGISTER_STATUS));
-                    }
-                } catch (NullPointerException e) {
-                    e.printStackTrace();
-                }
-            }
-        }
-    }
-
-    private void sipRegister() {
-        synchronized (this) {
-            mSipThread = new SipThread((Activity) mContext);
-            if (handleCount < 3) {
-                if (mCountDownAutoTimer == null) {
-                    mCountDownAutoTimer = new CountDownTimer(10000, 1000) {
-                        @Override
-                        public void onTick(long l) {
-                        }
-
-                        @Override
-                        public void onFinish() {
-                            handleCount = 0;
-                            if (mSipThread != null) {
-                                mSipThread.start();
-                            }
-                            if (mCountDownAutoTimer != null) {
-                                mCountDownAutoTimer.cancel();
-                                mCountDownAutoTimer = null;
-                            }
-                        }
-                    };
-                    mCountDownAutoTimer.start();
-                }
-                return;
-            } else {
-                if (mCountDownAutoTimer != null) {
-                    mCountDownAutoTimer.cancel();
-                    mCountDownAutoTimer = null;
-                }
-            }
-            if (handleCount == 0) {
-                mSipThread.start();
-            }
-        }
-    }
-
-
-    public void setmSipThread(Thread mSipThread) {
-        this.mSipThread = mSipThread;
-    }
-
-    public Thread getmSipThread() {
-        return mSipThread;
-    }
-
-    /**
-     * 开始通话
-     */
-    public void startCall(String sipUseName) {
-        IVvsipService sipService = VvsipService.getService();
-        if (sipService == null) return;
-        //----------------------------------------------携带呼叫列表转接床头机的Mac地址--------------------------------------------------//
-        sipService.initiateOutgoingCall(sipUseName, "");
-    }
-
-    /**
-     * 结束通话
-     */
-    public void endCall() {
-        VvsipCall call = null;
-        for (VvsipCall pCall : mVvsipCalls) {
-            if (pCall.cid > 0 && pCall.mState <= 2) {
-                call = pCall;
-                break;
-            }
-        }
-        if (call == null) return;
-        IVvsipService sipService = VvsipService.getService();
-        if (sipService == null) return;
-        VvsipTask sipTask = sipService.getVvsipTask();
-        if (sipTask == null) return;
-        VvsipService.getService().mainEndCall(EXOSIP_CALL_CLOSED);
-        call.stop();
-        sipService.setSpeakerModeOff();
-        sipService.stopPlayer();
-        sipService.setAudioNormalMode();
-    }
-
-    /**
-     * 添加一个电话呼叫对象
-     *
-     * @param call
-     */
-    public void addCallObject(final VvsipCall call) {
-        try {
-            if (call == null) {
-                return;
-            }
-
-            if (mVvsipCalls == null)
-                return;
-            mVvsipCalls.add(call);
-        } catch (Exception e) {
-            //LogUtil.e(SipInfoTag, "onNewVvsipCallEvent: " + e);
-        }
-    }
-
-    /**
-     * 移除一个电话呼叫对象
-     *
-     * @param call
-     */
-    public void removeCallObject(final VvsipCall call) {
-        try {
-            if (call == null) {
-                return;
-            }
-
-            // 4 crash detected here for 4.0.9 with mVvsipCalls=NULL
-            if (mVvsipCalls == null)
-                return;
-            mVvsipCalls.remove(call);
-        } catch (Exception e) {
-            //Log.e(SipInfoTag, "onRemoveVvsipCallEvent: " + e);
-        }
-    }
-
-    /**
-     * 自动接电话
-     */
-    public void autoTalking() {
-        if (mVvsipCalls == null) {
-            mVvsipCalls = new ArrayList<VvsipCall>();
-        }
-        for (VvsipCall _pCall : mVvsipCalls) {
-            if (_pCall.cid > 0 && _pCall.mState < 2 && _pCall.mIncomingCall) {
-                // ANSWER EXISTING CALL
-                int i = _pCall.answer(200, 1);
-              //LogUtil.d(SipInfoTag, "ANSWER EXISTING CALL");
-                IVvsipService _service = VvsipService.getService();
-                if (_service != null) {
-                    if (i >= 0) {
-                        _service.stopPlayer();
-                        _service.setSpeakerModeOff();
-                        _service.setAudioInCallMode();
-                    }
-                }
-                break;
-            }
-        }
-    }
-
-    public static boolean isServiceRunning(Context context, String className) {
-        boolean isRunning = false;
-        ActivityManager activityManager = (ActivityManager) context
-                .getSystemService(Context.ACTIVITY_SERVICE);
-        List<ActivityManager.RunningServiceInfo> serviceList = activityManager
-                .getRunningServices(30);
-
-        if (!(serviceList.size() > 0)) {
-            return false;
-        }
-
-        for (int i = 0; i < serviceList.size(); i++) {
-            if (serviceList.get(i).service.getClassName().equals(className) == true) {
-                isRunning = true;
-                break;
-            }
-        }
-        return isRunning;
-    }
-
-    /**
-     * #############################
-     * <p>
-     * Sip启动服务.
-     * <p>
-     * #############################
-     */
-    private static Handler sipServiceStartHandler = null;
-    private static Runnable sipServiceStartRunnable = null;
-    private static ServiceConnection sipServiceConnection;
-
-    public static Runnable getSipServiceStartRunnable() {
-        return sipServiceStartRunnable;
-    }
-
-    public static Handler getSipServiceStartHandler() {
-        return sipServiceStartHandler;
-    }
-
-    public static ServiceConnection getSipServiceConnection() {
-        return sipServiceConnection;
-    }
-
-    public static void setSipServiceConnection(ServiceConnection sipServiceConnections) {
-        sipServiceConnection = sipServiceConnections;
-    }
-
-    /**
-     * 启动服务
-     */
-    public static Boolean isRegister = false;//是否注册
-
-    public void sipStartService() {
-        sipServiceStartHandler = new Handler();
-
-        sipServiceStartRunnable = new Runnable() {
-            public void run() {
-                if (mContext == null) {
-                    sipServiceStartHandler.postDelayed(sipServiceStartRunnable, 1000);
-                    return;
-                }
-                Intent intent = new Intent(mContext.getApplicationContext(), VvsipService.class);
-                mContext.startService(intent);
-
-                sipServiceConnection = new ServiceConnection() {
-                    public void onServiceConnected(ComponentName name, IBinder service) {
-                        IVvsipService sipservice = ((VvsipServiceBinder) service).getService();
-                        if (sipservice != null && sipListner != null) {
-                            //LogUtil.i(SipInfoTag, "Connected!");
-                            sipservice.addListener(sipListner);
-                            SipHelper.getInstance().obtainSipInfo();//Sip信息获取
-                        }
-                    }
-
-                    public void onServiceDisconnected(ComponentName name) {
-                        //LogUtil.i(SipInfoTag, "Disconnected!");
-                    }
-                };
-
-                isRegister = mContext.bindService(intent, sipServiceConnection, Context.BIND_AUTO_CREATE);
-            }
-        };
-
-        sipServiceStartHandler.postDelayed(sipServiceStartRunnable, 0);
-    }
-
-    /**
-     * 用来解析错误代码
-     */
-    public static String analyseErrorCode(String errorCode) {
-        switch (errorCode) {
-            case "200":
-                return "成功";
-            case "408":
-                return "请求超时";
-            case "400":
-                return "服务器不支持请求的语法";
-            case "401":
-                return "未授权";
-            case "403":
-                return "服务器禁止请求";
-            case "404":
-                return "服务器找不到";
-            default:
-                return "未知错误";
-        }
-    }
-
-}

+ 0 - 17
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/SipStatus.java.bak

@@ -1,17 +0,0 @@
-package com.wdkl.app.ncs.callingbed2.sip;
-
-public class SipStatus {
-    /**
-     * 注册中
-     */
-    public static final String REGISTERING = "register_ing";
-    /**
-     * 注册失败
-     */
-    public static final String REGISTERFAIL = "register_fail";
-    /**
-     * 注册完成
-     */
-    public static final String REGISTERCOM = "register_com";
-
-}

+ 45 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/callback/PhoneCallback.java

@@ -0,0 +1,45 @@
+package com.wdkl.app.ncs.conversion_box.sip.callback;
+
+import org.linphone.core.Call;
+
+public abstract class PhoneCallback {
+    /**
+     * 来电状态
+     *
+     * @param call
+     */
+    public void incomingCall(Call call) {
+    }
+
+    /**
+     * 呼叫初始化
+     */
+    public void outgoingInit(Call call) {
+    }
+
+    /**
+     * 电话接通
+     */
+    public void callConnected(Call call) {
+    }
+
+    /**
+     * 电话挂断
+     */
+    public void callEnd(Call call) {
+    }
+
+    /**
+     * 释放通话
+     */
+    public void callReleased(Call call) {
+    }
+
+    /**
+     * 连接失败
+     */
+    public void error(String string) {
+    }
+
+
+}

+ 13 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/callback/RegistrationCallback.java

@@ -0,0 +1,13 @@
+package com.wdkl.app.ncs.conversion_box.sip.callback;
+
+public abstract class RegistrationCallback {
+    public void registrationNone() {}
+
+    public void registrationProgress() {}
+
+    public void registrationOk() {}
+
+    public void registrationCleared() {}
+
+    public void registrationFailed() {}
+}

+ 540 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/core/CorePreferences.kt

@@ -0,0 +1,540 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+package com.wdkl.app.ncs.conversion_box.sip.core
+
+import android.content.Context
+import android.util.Log
+import com.wdkl.app.ncs.conversion_box.R
+import org.linphone.core.BuildConfig
+import org.linphone.core.Config
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+
+class CorePreferences constructor(private val context: Context) {
+    private var _config: Config? = null
+    var config: Config
+        get() = _config!!
+        set(value) {
+            _config = value
+        }
+
+    /* VFS encryption */
+
+    companion object {
+        private const val encryptedSharedPreferencesFile = "encrypted.pref"
+    }
+
+
+
+    /* App settings */
+
+    var debugLogs: Boolean
+        get() = config.getBool("app", "debug", BuildConfig.DEBUG)
+        set(value) {
+            config.setBool("app", "debug", value)
+        }
+
+    var autoStart: Boolean
+        get() = config.getBool("app", "auto_start", true)
+        set(value) {
+            config.setBool("app", "auto_start", value)
+        }
+
+    var keepServiceAlive: Boolean
+        get() = config.getBool("app", "keep_service_alive", false)
+        set(value) {
+            config.setBool("app", "keep_service_alive", value)
+        }
+
+    var readAndAgreeTermsAndPrivacy: Boolean
+        get() = config.getBool("app", "read_and_agree_terms_and_privacy", false)
+        set(value) {
+            config.setBool("app", "read_and_agree_terms_and_privacy", value)
+        }
+
+    /* UI */
+
+    var forcePortrait: Boolean
+        get() = config.getBool("app", "force_portrait_orientation", false)
+        set(value) {
+            config.setBool("app", "force_portrait_orientation", value)
+        }
+
+    var replaceSipUriByUsername: Boolean
+        get() = config.getBool("app", "replace_sip_uri_by_username", false)
+        set(value) {
+            config.setBool("app", "replace_sip_uri_by_username", value)
+        }
+
+    var enableAnimations: Boolean
+        get() = config.getBool("app", "enable_animations", false)
+        set(value) {
+            config.setBool("app", "enable_animations", value)
+        }
+
+    /** -1 means auto, 0 no, 1 yes */
+    var darkMode: Int
+        get() {
+            if (!darkModeAllowed) return 0
+            return config.getInt("app", "dark_mode", -1)
+        }
+        set(value) {
+            config.setInt("app", "dark_mode", value)
+        }
+
+    /* Audio */
+
+    /* Video */
+
+    var videoPreview: Boolean
+        get() = config.getBool("app", "video_preview", false)
+        set(value) = config.setBool("app", "video_preview", value)
+
+    /* Chat */
+
+    // iOS and Android 4.4.x releases currently can't display more than 1 file per message
+    // TODO: Remove for the release, this won't be necessary anymore
+    var preventMoreThanOneFilePerMessage: Boolean
+        get() = config.getBool("app", "prevent_more_than_one_file_per_message", true)
+        set(value) {
+            config.setBool("app", "prevent_more_than_one_file_per_message", value)
+        }
+
+    var markAsReadUponChatMessageNotificationDismissal: Boolean
+        get() = config.getBool("app", "mark_as_read_notif_dismissal", false)
+        set(value) {
+            config.setBool("app", "mark_as_read_notif_dismissal", value)
+        }
+
+    var makePublicMediaFilesDownloaded: Boolean
+        // Keep old name for backward compatibility
+        get() = config.getBool("app", "make_downloaded_images_public_in_gallery", true)
+        set(value) {
+            config.setBool("app", "make_downloaded_images_public_in_gallery", value)
+        }
+
+    var useInAppFileViewerForNonEncryptedFiles: Boolean
+        get() = config.getBool("app", "use_in_app_file_viewer_for_non_encrypted_files", false)
+        set(value) {
+            config.setBool("app", "use_in_app_file_viewer_for_non_encrypted_files", value)
+        }
+
+    var hideChatMessageContentInNotification: Boolean
+        get() = config.getBool("app", "hide_chat_message_content_in_notification", false)
+        set(value) {
+            config.setBool("app", "hide_chat_message_content_in_notification", value)
+        }
+
+    var hideEmptyRooms: Boolean
+        get() = config.getBool("app", "hide_empty_chat_rooms", true)
+        set(value) {
+            config.setBool("app", "hide_empty_chat_rooms", value)
+        }
+
+    var hideRoomsFromRemovedProxies: Boolean
+        get() = config.getBool("app", "hide_chat_rooms_from_removed_proxies", true)
+        set(value) {
+            config.setBool("app", "hide_chat_rooms_from_removed_proxies", value)
+        }
+
+    var deviceName: String
+        get() = config.getString("app", "device_name", "Android")!!
+        set(value) = config.setString("app", "device_name", value)
+
+    var chatRoomShortcuts: Boolean
+        get() = config.getBool("app", "chat_room_shortcuts", true)
+        set(value) {
+            config.setBool("app", "chat_room_shortcuts", value)
+        }
+
+    /* Contacts */
+
+    var storePresenceInNativeContact: Boolean
+        get() = config.getBool("app", "store_presence_in_native_contact", false)
+        set(value) {
+            config.setBool("app", "store_presence_in_native_contact", value)
+        }
+
+    var showNewContactAccountDialog: Boolean
+        get() = config.getBool("app", "show_new_contact_account_dialog", true)
+        set(value) {
+            config.setBool("app", "show_new_contact_account_dialog", value)
+        }
+
+    var displayOrganization: Boolean
+        get() = config.getBool("app", "display_contact_organization", contactOrganizationVisible)
+        set(value) {
+            config.setBool("app", "display_contact_organization", value)
+        }
+
+    var contactsShortcuts: Boolean
+        get() = config.getBool("app", "contact_shortcuts", false)
+        set(value) {
+            config.setBool("app", "contact_shortcuts", value)
+        }
+
+    /* Call */
+
+    var acceptEarlyMedia: Boolean
+        get() = config.getBool("sip", "incoming_calls_early_media", false)
+        set(value) {
+            config.setBool("sip", "incoming_calls_early_media", value)
+        }
+
+    var autoAnswerEnabled: Boolean
+        get() = config.getBool("app", "auto_answer", false)
+        set(value) {
+            config.setBool("app", "auto_answer", value)
+        }
+
+    var autoAnswerDelay: Int
+        get() = config.getInt("app", "auto_answer_delay", 0)
+        set(value) {
+            config.setInt("app", "auto_answer_delay", value)
+        }
+
+    // Show overlay inside of application
+    var showCallOverlay: Boolean
+        get() = config.getBool("app", "call_overlay", true)
+        set(value) {
+            config.setBool("app", "call_overlay", value)
+        }
+
+    // Show overlay even when app is in background, requires permission
+    var systemWideCallOverlay: Boolean
+        get() = config.getBool("app", "system_wide_call_overlay", false)
+        set(value) {
+            config.setBool("app", "system_wide_call_overlay", value)
+        }
+
+    var callRightAway: Boolean
+        get() = config.getBool("app", "call_right_away", false)
+        set(value) {
+            config.setBool("app", "call_right_away", value)
+        }
+
+    var fullScreenCallUI: Boolean
+        get() = config.getBool("app", "full_screen_call", true)
+        set(value) {
+            config.setBool("app", "full_screen_call", value)
+        }
+
+    var routeAudioToBluetoothIfAvailable: Boolean
+        get() = config.getBool("app", "route_audio_to_bluetooth_if_available", false)
+        set(value) {
+            config.setBool("app", "route_audio_to_bluetooth_if_available", value)
+        }
+
+    // This won't be done if bluetooth or wired headset is used
+    var routeAudioToSpeakerWhenVideoIsEnabled: Boolean
+        get() = config.getBool("app", "route_audio_to_speaker_when_video_enabled", false)
+        set(value) {
+            config.setBool("app", "route_audio_to_speaker_when_video_enabled", value)
+        }
+
+    /* Assistant */
+
+    var firstStart: Boolean
+        get() = config.getBool("app", "first_start", true)
+        set(value) {
+            config.setBool("app", "first_start", value)
+        }
+
+    var xmlRpcServerUrl: String?
+        get() = config.getString("assistant", "xmlrpc_url", null)
+        set(value) {
+            config.setString("assistant", "xmlrpc_url", value)
+        }
+
+    /* Dialog related */
+
+    var limeSecurityPopupEnabled: Boolean
+        get() = config.getBool("app", "lime_security_popup_enabled", true)
+        set(value) {
+            config.setBool("app", "lime_security_popup_enabled", value)
+        }
+
+    /* Other */
+
+    var voiceMailUri: String?
+        get() = config.getString("app", "voice_mail", null)
+        set(value) {
+            config.setString("app", "voice_mail", value)
+        }
+
+    var redirectDeclinedCallToVoiceMail: Boolean
+        get() = config.getBool("app", "redirect_declined_call_to_voice_mail", true)
+        set(value) {
+            config.setBool("app", "redirect_declined_call_to_voice_mail", value)
+        }
+
+    var lastUpdateAvailableCheckTimestamp: Int
+        get() = config.getInt("app", "version_check_url_last_timestamp", 0)
+        set(value) {
+            config.setInt("app", "version_check_url_last_timestamp", value)
+        }
+
+    var defaultAccountAvatarPath: String?
+        get() = config.getString("app", "default_avatar_path", null)
+        set(value) {
+            config.setString("app", "default_avatar_path", value)
+        }
+
+    /* *** Read only application settings, some were previously in non_localizable_custom *** */
+
+    /* UI related */
+
+    val hideContactsWithoutPresence: Boolean
+        get() = config.getBool("app", "hide_contacts_without_presence", false)
+
+    val contactOrganizationVisible: Boolean
+        get() = config.getBool("app", "display_contact_organization", true)
+
+    val showBorderOnContactAvatar: Boolean
+        get() = config.getBool("app", "show_border_on_contact_avatar", false)
+
+    val showBorderOnBigContactAvatar: Boolean
+        get() = config.getBool("app", "show_border_on_big_contact_avatar", true)
+
+    private val darkModeAllowed: Boolean
+        get() = config.getBool("app", "dark_mode_allowed", true)
+
+    /* Feature related */
+
+    val showScreenshotButton: Boolean
+        get() = config.getBool("app", "show_take_screenshot_button_in_call", false)
+
+    val dtmfKeypadVibration: Boolean
+        get() = config.getBool("app", "dtmf_keypad_vibraton", false)
+
+    val allowMultipleFilesAndTextInSameMessage: Boolean
+        get() = config.getBool("app", "allow_multiple_files_and_text_in_same_message", true)
+
+    val fetchContactsFromDefaultDirectory: Boolean
+        get() = config.getBool("app", "fetch_contacts_from_default_directory", true)
+
+    val hideStaticImageCamera: Boolean
+        get() = config.getBool("app", "hide_static_image_camera", true)
+
+    // Will disable chat feature completely
+    val disableChat: Boolean
+        get() = config.getBool("app", "disable_chat_feature", false)
+
+    // If enabled, this will cause the video to "freeze" on your correspondent screen
+    // as you won't send video packets anymore
+    val hideCameraPreviewInPipMode: Boolean
+        get() = config.getBool("app", "hide_camera_preview_in_pip_mode", false)
+
+    // This will prevent UI from showing up, except for the launcher & the foreground service notification
+    val preventInterfaceFromShowingUp: Boolean
+        get() = config.getBool("app", "keep_app_invisible", false)
+
+    /* Default values related */
+
+    val echoCancellerCalibration: Int
+        get() = config.getInt("sound", "ec_delay", -1)
+
+    val defaultDomain: String
+        get() = config.getString("app", "default_domain", "sip.linphone.org")!!
+
+    val debugPopupCode: String
+        get() = config.getString("app", "debug_popup_magic", "#1234#")!!
+
+    val conferenceServerUri: String
+        get() = config.getString(
+            "app",
+            "default_conference_factory_uri",
+            "sip:conference-factory@sip.linphone.org"
+        )!!
+
+    val limeX3dhServerUrl: String
+        get() = config.getString(
+            "app",
+            "default_lime_x3dh_server_url",
+            "https://lime.linphone.org/lime-server/lime-server.php"
+        )!!
+
+    val checkIfUpdateAvailableUrl: String?
+        get() = config.getString(
+            "misc",
+            "version_check_url_root",
+            "https://linphone.org/releases/android/RELEASE"
+        )
+
+    val checkUpdateAvailableInterval: Int
+        get() = config.getInt("app", "version_check_interval", 86400000)
+
+    /* Assistant */
+
+    val showCreateAccount: Boolean
+        get() = config.getBool("app", "assistant_create_account", true)
+
+    val showLinphoneLogin: Boolean
+        get() = config.getBool("app", "assistant_linphone_login", true)
+
+    val showGenericLogin: Boolean
+        get() = config.getBool("app", "assistant_generic_login", true)
+
+    val showRemoteProvisioning: Boolean
+        get() = config.getBool("app", "assistant_remote_provisioning", true)
+
+    /* Side Menu */
+
+    val showAccountsInSideMenu: Boolean
+        get() = config.getBool("app", "side_menu_accounts", true)
+
+    val showAssistantInSideMenu: Boolean
+        get() = config.getBool("app", "side_menu_assistant", true)
+
+    val showSettingsInSideMenu: Boolean
+        get() = config.getBool("app", "side_menu_settings", true)
+
+    val showRecordingsInSideMenu: Boolean
+        get() = config.getBool("app", "side_menu_recordings", true)
+
+    val showAboutInSideMenu: Boolean
+        get() = config.getBool("app", "side_menu_about", true)
+
+    val showQuitInSideMenu: Boolean
+        get() = config.getBool("app", "side_menu_quit", true)
+
+    /* Settings */
+
+    val allowDtlsTransport: Boolean
+        get() = config.getBool("app", "allow_dtls_transport", false)
+
+    val showAccountSettings: Boolean
+        get() = config.getBool("app", "settings_accounts", true)
+
+    val showTunnelSettings: Boolean
+        get() = config.getBool("app", "settings_tunnel", true)
+
+    val showAudioSettings: Boolean
+        get() = config.getBool("app", "settings_audio", true)
+
+    val showVideoSettings: Boolean
+        get() = config.getBool("app", "settings_video", true)
+
+    val showCallSettings: Boolean
+        get() = config.getBool("app", "settings_call", true)
+
+    val showChatSettings: Boolean
+        get() = config.getBool("app", "settings_chat", true)
+
+    val showNetworkSettings: Boolean
+        get() = config.getBool("app", "settings_network", true)
+
+    val showContactsSettings: Boolean
+        get() = config.getBool("app", "settings_contacts", true)
+
+    val showAdvancedSettings: Boolean
+        get() = config.getBool("app", "settings_advanced", true)
+
+    /* Assets stuff */
+
+    val configPath: String
+        get() = context.filesDir.absolutePath + "/.linphonerc"
+
+    val factoryConfigPath: String
+        get() = context.filesDir.absolutePath + "/linphonerc"
+
+    val linphoneDefaultValuesPath: String
+        get() = context.filesDir.absolutePath + "/assistant_linphone_default_values"
+
+    val defaultValuesPath: String
+        get() = context.filesDir.absolutePath + "/assistant_default_values"
+
+    val ringtonePath: String
+        get() = context.filesDir.absolutePath + "/share/sounds/linphone/rings/notes_of_the_optimistic.mkv"
+
+    val userCertificatesPath: String
+        get() = context.filesDir.absolutePath + "/user-certs"
+
+    val staticPicturePath: String
+        get() = context.filesDir.absolutePath + "/share/images/nowebcamcif.jpg"
+
+    fun copyAssetsFromPackage() {
+        copy("linphonerc_default", configPath)
+        copy("linphonerc_factory", factoryConfigPath, true)
+        copy("assistant_linphone_default_values", linphoneDefaultValuesPath, true)
+        copy("assistant_default_values", defaultValuesPath, true)
+
+        move(context.filesDir.absolutePath + "/linphone-log-history.db", context.filesDir.absolutePath + "/call-history.db")
+        move(context.filesDir.absolutePath + "/zrtp_secrets", context.filesDir.absolutePath + "/zrtp-secrets.db")
+    }
+
+    fun getString(resource: Int): String {
+        return context.getString(resource)
+    }
+
+    private fun copy(from: String, to: String, overrideIfExists: Boolean = false) {
+        val outFile = File(to)
+        if (outFile.exists()) {
+            if (!overrideIfExists) {
+                Log.i(context.getString(R.string.app_name), "[Preferences] File $to already exists")
+                return
+            }
+        }
+        Log.i(context.getString(R.string.app_name), "[Preferences] Overriding $to by $from asset")
+
+        val outStream = FileOutputStream(outFile)
+        val inFile = context.assets.open(from)
+        val buffer = ByteArray(1024)
+        var length: Int = inFile.read(buffer)
+
+        while (length > 0) {
+            outStream.write(buffer, 0, length)
+            length = inFile.read(buffer)
+        }
+
+        inFile.close()
+        outStream.flush()
+        outStream.close()
+    }
+
+    private fun move(from: String, to: String, overrideIfExists: Boolean = false) {
+        val inFile = File(from)
+        val outFile = File(to)
+        if (inFile.exists()) {
+            if (outFile.exists() && !overrideIfExists) {
+                Log.w(context.getString(R.string.app_name), "[Preferences] Can't move [$from] to [$to], destination file already exists")
+            } else {
+                val inStream = FileInputStream(inFile)
+                val outStream = FileOutputStream(outFile)
+
+                val buffer = ByteArray(1024)
+                var read: Int
+                while (inStream.read(buffer).also { read = it } != -1) {
+                    outStream.write(buffer, 0, read)
+                }
+
+                inStream.close()
+                outStream.flush()
+                outStream.close()
+
+                inFile.delete()
+                Log.i(context.getString(R.string.app_name), "[Preferences] Successfully moved [$from] to [$to]")
+            }
+        } else {
+            Log.w(context.getString(R.string.app_name), "[Preferences] Can't move [$from] to [$to], source file doesn't exists")
+        }
+    }
+}

+ 248 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/core/LinCoreService.java

@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2010-2022 Belledonne Communications SARL.
+ *
+ * This file is part of Liblinphone
+ * (see https://gitlab.linphone.org/BC/public/liblinphone).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.wdkl.app.ncs.conversion_box.sip.core;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Vibrator;
+
+import org.linphone.core.Call;
+import org.linphone.core.Core;
+import org.linphone.core.CoreListenerStub;
+import org.linphone.core.Factory;
+import org.linphone.core.tools.Log;
+import org.linphone.core.tools.compatibility.DeviceUtils;
+
+/**
+ * This service is used to monitor activities lifecycle and detect when app is in background/foreground.
+ * It is also used as a foreground service while at least one call is running to prevent the app from getting killed.
+ * Finally when task is removed, it will stop itself and the Core.
+ */
+public class LinCoreService extends Service {
+    protected static final int SERVICE_NOTIF_ID = 1;
+    protected static final String SERVICE_NOTIFICATION_CHANNEL_ID = "sip_core_service_notification_channel";
+    protected static final String SERVICE_NOTIFICATION_CHANNEL_NAME = "Sip Core Service";
+    protected static final String SERVICE_NOTIFICATION_CHANNEL_DESC = "keep the call(s) alive";
+    protected static final String SERVICE_NOTIFICATION_TITLE = "Sip Core Service";
+    protected static final String SERVICE_NOTIFICATION_CONTENT = "keep the call(s) alive";
+
+    protected boolean mIsInForegroundMode = false;
+    protected Notification mServiceNotification = null;
+
+    private CoreListenerStub mListener;
+    private Vibrator mVibrator;
+    private boolean mIsVibrating;
+    private AudioManager mAudioManager;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        // No-op, just to ensure libraries have been loaded and thus prevent crash in log below
+        // if service has been started directly by Android (that can happen...)
+        Factory.instance();
+
+        createServiceNotificationChannel();
+
+        mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        mListener = new CoreListenerStub() {
+            public void onFirstCallStarted(Core core) {
+                Log.i("[Core Service] First call started");
+                if (!mIsInForegroundMode) {
+                    startForeground();
+                }
+
+                /*Call call = core.getCurrentCall();
+                if (call != null) {
+                    if (call.getDir() == Call.Dir.Incoming && core.isVibrationOnIncomingCallEnabled()) {
+                        vibrate();
+                    }
+                } else {
+                    Log.w("[Core Service] Couldn't find current call...");
+                }*/
+
+            }
+
+            public void onCallStateChanged(Core core, Call call, Call.State state, String message) {
+                /*if ((state == Call.State.End || state == Call.State.Error || state == Call.State.Connected) && mIsVibrating) {
+                    Log.i("[Core Service] Stopping vibrator");
+                    mVibrator.cancel();
+                    mIsVibrating = false;
+                }*/
+
+            }
+
+            public void onLastCallEnded(Core core) {
+                Log.i("[Core Service] Last call ended");
+                if (mIsInForegroundMode) {
+                    stopForeground();
+                }
+
+            }
+        };
+
+        /*if (CoreManager.isReady()) {
+            Core core = CoreManager.instance().getCore();
+            if (core != null) {
+                Log.i("[Core Service] Core Manager found, adding our listener");
+                core.addListener(mListener);
+                if (core.getCallsNb() > 0) {
+                    Log.w("[Core Service] Service started while at least one call active !");
+                    startForeground();
+                    Call call = core.getCurrentCall();
+                    if (call != null) {
+                        if (call.getDir() == Call.Dir.Incoming && call.getState() == Call.State.IncomingReceived && core.isVibrationOnIncomingCallEnabled()) {
+                            vibrate();
+                        }
+                    } else {
+                        Log.w("[Core Service] Couldn't find current call...");
+                    }
+                }
+            }
+        }*/
+
+        Log.i("[Core Service] Created");
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        super.onStartCommand(intent, flags, startId);
+        Log.i("[Core Service] Started");
+
+        return START_STICKY;
+    }
+
+    @Override
+    public void onTaskRemoved(Intent rootIntent) {
+        Log.i("[Core Service] Task removed");
+        super.onTaskRemoved(rootIntent);
+    }
+
+    @Override
+    public synchronized void onDestroy() {
+        Log.i("[Core Service] Stopping");
+        /*if (CoreManager.isReady()) {
+            Core core = CoreManager.instance().getCore();
+            if (core != null) {
+                Log.i("[Core Service] Core Manager found, removing our listener");
+                core.removeListener(mListener);
+            }
+        }*/
+
+        super.onDestroy();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    /**
+     * This method should create a notification channel for the foreground service notification.
+     * On Android < 8 it is not called.
+     */
+    public void createServiceNotificationChannel() {
+        Log.i("[Core Service] Android >= 8.0 detected, creating notification channel");
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+            NotificationChannel channel = new NotificationChannel(SERVICE_NOTIFICATION_CHANNEL_ID, SERVICE_NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
+            channel.setDescription(SERVICE_NOTIFICATION_CHANNEL_DESC);
+            channel.enableVibration(false);
+            channel.enableLights(false);
+            channel.setShowBadge(false);
+            channel.setBypassDnd(true);
+            channel.setSound(null, null);
+            notificationManager.createNotificationChannel(channel);
+        }
+    }
+
+    public void createServiceNotification() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            mServiceNotification = new Notification.Builder(this, SERVICE_NOTIFICATION_CHANNEL_ID)
+                    .setContentTitle(SERVICE_NOTIFICATION_TITLE)
+                    .setContentText(SERVICE_NOTIFICATION_CONTENT)
+                    .setSmallIcon(getApplicationInfo().icon)
+                    .setAutoCancel(false)
+                    .setCategory(Notification.CATEGORY_SERVICE)
+                    .setVisibility(Notification.VISIBILITY_SECRET)
+                    .setWhen(System.currentTimeMillis())
+                    .setShowWhen(true)
+                    .setOngoing(true)
+                    .build();
+        }
+    }
+
+    public void showForegroundServiceNotification() {
+        if (mServiceNotification == null) {
+            createServiceNotification();
+        }
+
+        startForeground(SERVICE_NOTIF_ID, mServiceNotification);
+    }
+
+    public void hideForegroundServiceNotification() {
+        stopForeground(true);
+    }
+
+    void startForeground() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            Log.i("[Core Service] Starting service as foreground");
+            showForegroundServiceNotification();
+            mIsInForegroundMode = true;
+        }
+    }
+
+    void stopForeground() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            if (!mIsInForegroundMode) {
+                Log.w("[Core Service] Service isn't in foreground mode, nothing to do");
+            } else {
+                Log.i("[Core Service] Stopping service as foreground");
+                hideForegroundServiceNotification();
+                mIsInForegroundMode = false;
+            }
+        }
+    }
+
+    private void vibrate() {
+        if (mVibrator != null && mVibrator.hasVibrator()) {
+            if (mAudioManager.getRingerMode() == 0) {
+                Log.i("[Core Service] Do not vibrate as ringer mode is set to silent");
+            } else {
+                Log.i("[Core Service] Starting vibrator");
+                DeviceUtils.vibrate(mVibrator);
+                mIsVibrating = true;
+            }
+        } else {
+            Log.e("[Core Service] Device doesn't have a vibrator");
+        }
+
+    }
+}

+ 517 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/core/LinphoneManager.kt

@@ -0,0 +1,517 @@
+package com.wdkl.ncs.host.sip.core
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Build
+import android.telephony.PhoneStateListener
+import android.telephony.TelephonyManager
+import android.util.Log
+import android.view.TextureView
+import com.wdkl.app.ncs.conversion_box.sip.callback.PhoneCallback
+import com.wdkl.app.ncs.conversion_box.sip.callback.RegistrationCallback
+import com.wdkl.app.ncs.conversion_box.sip.core.CorePreferences
+import com.wdkl.app.ncs.conversion_box.sip.utils.AudioRouteUtils
+import com.wdkl.ncs.host.sip.utils.LinphoneUtils
+import com.wdkl.ncs.host.sip.utils.VideoZoomHelper
+import org.linphone.core.*
+import java.io.File
+import java.util.*
+
+
+class LinphoneManager private constructor(private val context: Context) {
+    private val TAG = "sip_linphone"
+
+    private var core: Core
+    private var corePreferences: CorePreferences
+    private var coreIsStart = false
+    var registrationCallback: RegistrationCallback? = null
+    var phoneCallback: PhoneCallback? = null
+
+
+    init {
+        //日志收集
+        Factory.instance().setLogCollectionPath(context.filesDir.absolutePath)
+        Factory.instance().enableLogCollection(LogCollectionState.Disabled)
+
+        corePreferences = CorePreferences(context)
+        corePreferences.copyAssetsFromPackage()
+        val config = Factory.instance().createConfigWithFactory(
+            corePreferences.configPath,
+            corePreferences.factoryConfigPath
+        )
+        corePreferences.config = config
+
+        Factory.instance().setDebugMode(false, "SipManager")
+
+        core = Factory.instance().createCoreWithConfig(config, context)
+    }
+
+    private var previousCallState = Call.State.Idle
+
+    private val coreListener = object : CoreListenerStub() {
+        /*override fun onGlobalStateChanged(core: Core, state: GlobalState?, message: String) {
+            if (state === GlobalState.On) {
+            }
+        }*/
+
+        //登录状态回调
+        /*override fun onRegistrationStateChanged(
+            core: Core,
+            cfg: ProxyConfig,
+            state: RegistrationState,
+            message: String
+        ) {
+            when (state) {
+                RegistrationState.None -> registrationCallback?.registrationNone()
+                RegistrationState.Progress -> registrationCallback?.registrationProgress()
+                RegistrationState.Ok -> registrationCallback?.registrationOk()
+                RegistrationState.Cleared -> registrationCallback?.registrationCleared()
+                RegistrationState.Failed -> registrationCallback?.registrationFailed()
+            }
+        }*/
+
+        //电话状态回调
+        override fun onCallStateChanged(
+            core: Core,
+            call: Call,
+            state: Call.State,
+            message: String
+        ) {
+            Log.i(TAG, "[Context] Call state changed [$state]")
+
+            when (state) {
+                Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> {
+                    if (gsmCallActive) {
+                        Log.w(TAG, "[Context] Refusing the call with reason busy because a GSM call is active")
+                        call.decline(Reason.Busy)
+                        return
+                    }
+
+                    phoneCallback?.incomingCall(call)
+                    gsmCallActive = true
+
+                    //自动接听
+                    /*if (corePreferences.autoAnswerEnabled) {
+                        val autoAnswerDelay = corePreferences.autoAnswerDelay
+                        if (autoAnswerDelay == 0) {
+                            Log.w(TAG, "[Context] Auto answering call immediately")
+                            answerCall(call)
+                        } else {
+                            Log.i(
+                                TAG,
+                                "[Context] Scheduling auto answering in $autoAnswerDelay milliseconds"
+                            )
+                            val mainThreadHandler = Handler(Looper.getMainLooper())
+                            mainThreadHandler.postDelayed({
+                                Log.w(TAG, "[Context] Auto answering call")
+                                answerCall(call) },
+                                autoAnswerDelay.toLong())
+                        }
+                    }*/
+                }
+
+                Call.State.OutgoingInit -> {
+                    phoneCallback?.outgoingInit(call)
+                    gsmCallActive = true
+                }
+
+                /*Call.State.OutgoingProgress -> {
+                    if (core.callsNb == 1 && corePreferences.routeAudioToBluetoothIfAvailable) {
+                        AudioRouteUtils.routeAudioToBluetooth(core, call)
+                    }
+                }*/
+
+                Call.State.Connected -> {
+                    phoneCallback?.callConnected(call)
+                }
+
+                Call.State.StreamsRunning -> {
+                    Log.i(TAG, "StreamsRunning...")
+                    // Do not automatically route audio to bluetooth after first call
+                    /*if (core.callsNb == 1) {
+                        // Only try to route bluetooth / headphone / headset when the call is in StreamsRunning for the first time
+                        if (previousCallState == Call.State.Connected) {
+                            Log.i(TAG, "[Context] First call going into StreamsRunning state for the first time, trying to route audio to headset or bluetooth if available")
+                            if (AudioRouteUtils.isHeadsetAudioRouteAvailable(core)) {
+                                AudioRouteUtils.routeAudioToHeadset(core, call)
+                            } else if (corePreferences.routeAudioToBluetoothIfAvailable && AudioRouteUtils.isBluetoothAudioRouteAvailable(core)) {
+                                AudioRouteUtils.routeAudioToBluetooth(core, call)
+                            }
+                        }
+                    }
+
+                    if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled && call.currentParams.videoEnabled()) {
+                        // Do not turn speaker on when video is enabled if headset or bluetooth is used
+                        if (!AudioRouteUtils.isHeadsetAudioRouteAvailable(core) &&
+                            !AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed(core, call)
+                        ) {
+                            Log.i(TAG, "[Context] Video enabled and no wired headset not bluetooth in use, routing audio to speaker")
+                            AudioRouteUtils.routeAudioToSpeaker(core, call)
+                        }
+                    }*/
+                }
+
+                Call.State.End, Call.State.Released, Call.State.Error -> {
+                    //if (core.callsNb == 0) {
+                        when (state) {
+                            Call.State.End -> phoneCallback?.callEnd(call)
+
+                            Call.State.Released -> phoneCallback?.callReleased(call)
+
+                            Call.State.Error -> {
+                                phoneCallback?.error(call.errorInfo.reason.toString())
+                            }
+                        }
+                        gsmCallActive = false
+                    //}
+                }
+            }
+            previousCallState = state
+        }
+    }
+
+    /**
+     * 启动linphone
+     */
+    fun start() {
+        if (!coreIsStart) {
+            coreIsStart = true
+            Log.i(TAG, "[Context] Starting")
+            core.addListener(coreListener)
+            core.start()
+
+            initLinphone()
+
+            val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
+            Log.i(TAG, "[Context] Registering phone state listener")
+            telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
+        }
+    }
+
+    /**
+     * 停止linphone
+     */
+    fun stop() {
+        coreIsStart = false
+        val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
+
+        Log.i(TAG, "[Context] Unregistering phone state listener")
+        telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE)
+
+        core.removeListener(coreListener)
+        core.stop()
+    }
+
+
+    /**
+     * 注册到服务器
+     *
+     * @param username     账号名
+     * @param password      密码
+     * @param domain     IP地址:端口号
+     */
+    fun createProxyConfig(
+        username: String,
+        password: String,
+        domain: String,
+        type: TransportType? = TransportType.Udp
+    ) {
+        core.clearProxyConfig()
+
+        val accountCreator = core.createAccountCreator(corePreferences.xmlRpcServerUrl)
+        accountCreator.language = Locale.getDefault().language
+        accountCreator.reset()
+
+        accountCreator.username = username
+        accountCreator.password = password
+        accountCreator.domain = domain
+        accountCreator.displayName = username
+        accountCreator.transport = type
+
+        accountCreator.createProxyConfig()
+    }
+
+
+    /**
+     * 取消注册
+     */
+    fun removeInvalidProxyConfig() {
+        core.clearProxyConfig()
+
+    }
+
+    fun getCore(): Core {
+        return core
+    }
+
+    /**
+     * 拨打电话
+     * @param to String
+     * @param isVideoCall Boolean
+     */
+    fun startCall(to: String, isVideoCall: Boolean) {
+        try {
+            val addressToCall = core.interpretUrl(to)
+            addressToCall?.displayName = to
+            val params = core.createCallParams(null)
+            //启用通话录音
+            //params?.recordFile = LinphoneUtils.getRecordingFilePathForAddress(context, addressToCall!!)
+            //启动低宽带模式
+            if (LinphoneUtils.checkIfNetworkHasLowBandwidth(context)) {
+                Log.w(TAG, "[Context] Enabling low bandwidth mode!")
+                params?.enableLowBandwidth(true)
+            }
+            if (isVideoCall) {
+                params?.enableVideo(true)
+                core.enableVideoCapture(true)
+                core.enableVideoDisplay(true)
+            } else {
+                params?.enableVideo(false)
+            }
+            if (params != null) {
+                core.inviteAddressWithParams(addressToCall!!, params)
+            } else {
+                core.inviteAddress(addressToCall!!)
+            }
+            Log.d(TAG, ">>>>>>>>>>> invite address: " + addressToCall.asString())
+
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+
+    }
+
+
+    /**
+     * 接听来电
+     *
+     */
+    fun answerCall(call: Call, supportVideo: Boolean) {
+        Log.i(TAG, "[Context] Answering call $call")
+        val params = core.createCallParams(call)
+        //启用通话录音
+        //params?.recordFile = LinphoneUtils.getRecordingFilePathForAddress(context, call.remoteAddress)
+        if (LinphoneUtils.checkIfNetworkHasLowBandwidth(context)) {
+            Log.w(TAG, "[Context] Enabling low bandwidth mode!")
+            params?.enableLowBandwidth(true)
+        }
+        if (supportVideo) {
+            params?.enableVideo(isVideoCall(call))
+        }
+        call.acceptWithParams(params)
+    }
+
+    /**
+     * 谢绝电话
+     * @param call Call
+     */
+    fun declineCall(call: Call) {
+        val voiceMailUri = corePreferences.voiceMailUri
+        if (voiceMailUri != null && corePreferences.redirectDeclinedCallToVoiceMail) {
+            val voiceMailAddress = core.interpretUrl(voiceMailUri)
+            if (voiceMailAddress != null) {
+                Log.i(TAG, "[Context] Redirecting call $call to voice mail URI: $voiceMailUri")
+                call.redirectTo(voiceMailAddress)
+            }
+        } else {
+            Log.i(TAG, "[Context] Declining call $call")
+            call.decline(Reason.Declined)
+        }
+    }
+
+    /**
+     * 挂断电话
+     */
+    fun terminateCall(call: Call) {
+        Log.i(TAG, "[Context] Terminating call $call")
+        call.terminate()
+    }
+
+    fun terminateCall() {
+        Log.i(TAG, "[Context] Terminating call: " + core.callsNb)
+        if (core.callsNb > 0) {
+            var call = core.currentCall
+            if (call == null) {
+                call = core.calls[0]
+            }
+            call?.terminate()
+        }
+    }
+
+    fun micEnabled() = core.micEnabled()
+
+    fun speakerEnabled() = core.outputAudioDevice?.type == AudioDevice.Type.Speaker
+
+    /**
+     * 启动麦克风
+     * @param micEnabled Boolean
+     */
+    fun enableMic(micEnabled: Boolean) {
+        core.enableMic(micEnabled)
+    }
+
+    /**
+     * 扬声器或听筒
+     * @param speakerEnabled Boolean
+     */
+    fun enableSpeaker(speakerEnabled: Boolean) {
+        Log.e(TAG, "audio enable speaker: $speakerEnabled")
+        if (speakerEnabled) {
+            AudioRouteUtils.routeAudioToSpeaker(core)
+        } else {
+            AudioRouteUtils.routeAudioToEarpiece(core)
+        }
+
+        //Log.e(TAG, "input device: " + core.defaultInputAudioDevice.type + ", output device: " + core.defaultOutputAudioDevice.type)
+    }
+
+
+    /**
+     * 是否是视频电话
+     * @return Boolean
+     */
+    fun isVideoCall(call: Call): Boolean {
+        val remoteParams = call.remoteParams
+        return remoteParams != null && remoteParams.videoEnabled()
+    }
+
+
+    /**
+     * 设置视频界面
+     * @param videoRendering TextureView 对方界面
+     * @param videoPreview CaptureTextureView 自己界面
+     */
+    fun setVideoWindowId(videoRendering: TextureView?, videoPreview: TextureView?) {
+        if (videoRendering != null) {
+            core.nativeVideoWindowId = videoRendering
+        }
+        if (videoPreview != null) {
+            core.enableVideoPreview(true)
+            core.nativePreviewWindowId = videoPreview
+        }
+    }
+
+    /**
+     * 设置视频电话可缩放
+     * @param context Context
+     * @param videoRendering TextureView
+     */
+    fun setVideoZoom(context: Context, videoRendering: TextureView) {
+        VideoZoomHelper(context, videoRendering, core)
+    }
+
+    fun switchCamera() {
+        val currentDevice = core.videoDevice
+        Log.i(TAG, "[Context] Current camera device is $currentDevice")
+
+        for (camera in core.videoDevicesList) {
+            if (camera != currentDevice && camera != "StaticImage: Static picture") {
+                Log.i(TAG, "[Context] New camera device will be $camera")
+                core.videoDevice = camera
+                break
+            }
+        }
+
+        val conference = core.conference
+        if (conference == null || !conference.isIn) {
+            val call = core.currentCall
+            if (call == null) {
+                Log.w(TAG, "[Context] Switching camera while not in call")
+                return
+            }
+            call.update(null)
+        }
+    }
+
+
+    //初始化一些操作
+    private fun initLinphone() {
+
+        configureCore()
+
+        initUserCertificates()
+    }
+
+
+    private fun configureCore() {
+        // 来电铃声
+        //core.isNativeRingingEnabled = true
+        // 来电振动
+        //core.isVibrationOnIncomingCallEnabled = true
+        core.enableEchoCancellation(true) //回声消除
+        core.enableAdaptiveRateControl(true) //自适应码率控制
+
+        //音频部分,设置指定的音频格式.
+        val payloads: Array<PayloadType> = core.audioPayloadTypes
+        for (i in payloads.indices) {
+            val pt = payloads[i]
+            //Log.i("sipCall", ">>>>>>>>>>>>>>>>>1 " + pt.getMimeType() + " = " + pt.enabled());
+            if (pt.mimeType == "PCMU"
+                || pt.mimeType == "opus"
+                //|| pt.getMimeType().equals("PCMA")
+                //|| pt.getMimeType().equals("PUMA")
+                //|| pt.getMimeType().equals("GSM")
+            ) {
+                pt.enable(true)
+            } else {
+                pt.enable(false)
+            }
+            Log.i("sipCall", ">>>>>>>>>>>>>>>>>2 " + pt.mimeType + " = " + pt.enabled())
+        }
+        core.setAudioPayloadTypes(payloads)
+    }
+
+    private var gsmCallActive = false
+    private val phoneStateListener = object : PhoneStateListener() {
+        override fun onCallStateChanged(state: Int, phoneNumber: String?) {
+            gsmCallActive = when (state) {
+                TelephonyManager.CALL_STATE_OFFHOOK -> {
+                    Log.i(TAG, "[Context] Phone state is off hook")
+                    true
+                }
+                TelephonyManager.CALL_STATE_RINGING -> {
+                    Log.i(TAG, "[Context] Phone state is ringing")
+                    true
+                }
+                TelephonyManager.CALL_STATE_IDLE -> {
+                    Log.i(TAG, "[Context] Phone state is idle")
+                    false
+                }
+                else -> {
+                    Log.i(TAG, "[Context] Phone state is unexpected: $state")
+                    false
+                }
+            }
+        }
+    }
+
+
+    //设置存放用户x509证书的目录路径
+    private fun initUserCertificates() {
+        val userCertsPath = corePreferences!!.userCertificatesPath
+        val f = File(userCertsPath)
+        if (!f.exists()) {
+            if (!f.mkdir()) {
+                Log.e(TAG, "[Context] $userCertsPath can't be created.")
+            }
+        }
+        core.userCertificatesPath = userCertsPath
+    }
+
+
+    companion object {
+
+        // For Singleton instantiation
+        @SuppressLint("StaticFieldLeak")
+        @Volatile
+        private var instance: LinphoneManager? = null
+        var sipTesting = false
+
+        fun getInstance(context: Context) =
+            instance ?: synchronized(this) {
+                instance ?: LinphoneManager(context).also { instance = it }
+            }
+
+    }
+
+}

+ 23 - 22
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/helper/AudioRouteUtils.kt

@@ -1,13 +1,14 @@
-package com.wdkl.app.ncs.conversion_box.helper
+package com.wdkl.app.ncs.conversion_box.sip.utils
 
-import android.telecom.CallAudioState
+import android.util.Log
 import org.linphone.core.AudioDevice
 import org.linphone.core.Call
 import org.linphone.core.Core
-import org.linphone.core.tools.Log
 
 class AudioRouteUtils {
     companion object {
+        val TAG = "AudioRoute"
+
         private fun applyAudioRouteChange(
             core: Core,
             call: Call?,
@@ -17,7 +18,7 @@ class AudioRouteUtils {
             val currentCall = if (core.callsNb > 0) {
                 call ?: core.currentCall ?: core.calls[0]
             } else {
-                Log.w("[Audio Route Helper] No call found, setting audio route on Core")
+                Log.w(TAG, "[Audio Route Helper] No call found, setting audio route on Core")
                 null
             }
             val conference = core.conference
@@ -26,18 +27,18 @@ class AudioRouteUtils {
             else
                 AudioDevice.Capabilities.CapabilityRecord
             val preferredDriver = if (output) {
-                core.defaultOutputAudioDevice?.driverName
+                core.defaultOutputAudioDevice.driverName
             } else {
-                core.defaultInputAudioDevice?.driverName
+                core.defaultInputAudioDevice.driverName
             }
 
             val extendedAudioDevices = core.extendedAudioDevices
-            Log.i("[Audio Route Helper] Looking for an ${if (output) "output" else "input"} audio device with capability [$capability], driver name [$preferredDriver] and type [$types] in extended audio devices list (size ${extendedAudioDevices.size})")
+            Log.i(TAG,"[Audio Route Helper] Looking for an ${if (output) "output" else "input"} audio device with capability [$capability], driver name [$preferredDriver] and type [$types] in extended audio devices list (size ${extendedAudioDevices.size})")
             val foundAudioDevice = extendedAudioDevices.find {
                 it.driverName == preferredDriver && types.contains(it.type) && it.hasCapability(capability)
             }
             val audioDevice = if (foundAudioDevice == null) {
-                Log.w("[Audio Route Helper] Failed to find an audio device with capability [$capability], driver name [$preferredDriver] and type [$types]")
+                Log.w(TAG,"[Audio Route Helper] Failed to find an audio device with capability [$capability], driver name [$preferredDriver] and type [$types]")
                 extendedAudioDevices.find {
                     types.contains(it.type) && it.hasCapability(capability)
                 }
@@ -46,23 +47,23 @@ class AudioRouteUtils {
             }
 
             if (audioDevice == null) {
-                Log.e("[Audio Route Helper] Couldn't find audio device with capability [$capability] and type [$types]")
+                Log.e(TAG,"[Audio Route Helper] Couldn't find audio device with capability [$capability] and type [$types]")
                 for (device in extendedAudioDevices) {
                     // TODO: switch to debug?
-                    Log.i("[Audio Route Helper] Extended audio device: [${device.deviceName} (${device.driverName}) ${device.type} / ${device.capabilities}]")
+                    Log.i(TAG,"[Audio Route Helper] Extended audio device: [${device.deviceName} (${device.driverName}) ${device.type} / ${device.capabilities}]")
                 }
                 return
             }
             if (conference != null && conference.isIn) {
-                Log.i("[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], routing conference audio to it")
+                Log.i(TAG,"[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], routing conference audio to it")
                 if (output) conference.outputAudioDevice = audioDevice
                 else conference.inputAudioDevice = audioDevice
             } else if (currentCall != null) {
-                Log.i("[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], routing call audio to it")
+                Log.i(TAG,"[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], routing call audio to it")
                 if (output) currentCall.outputAudioDevice = audioDevice
                 else currentCall.inputAudioDevice = audioDevice
             } else {
-                Log.i("[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], changing core default audio device")
+                Log.i(TAG,"[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], changing core default audio device")
                 if (output) core.outputAudioDevice = audioDevice
                 else core.inputAudioDevice = audioDevice
             }
@@ -72,22 +73,22 @@ class AudioRouteUtils {
             when (types.first()) {
                 AudioDevice.Type.Bluetooth -> {
                     if (isBluetoothAudioRecorderAvailable(core)) {
-                        Log.i("[Audio Route Helper] Bluetooth device is able to record audio, also change input audio device")
+                        Log.i(TAG,"[Audio Route Helper] Bluetooth device is able to record audio, also change input audio device")
                         applyAudioRouteChange(core, call, arrayListOf(AudioDevice.Type.Bluetooth), false)
                     }
                 }
                 AudioDevice.Type.Headset, AudioDevice.Type.Headphones -> {
                     if (isHeadsetAudioRecorderAvailable(core)) {
-                        Log.i("[Audio Route Helper] Headphones/Headset device is able to record audio, also change input audio device")
+                        Log.i(TAG,"[Audio Route Helper] Headphones/Headset device is able to record audio, also change input audio device")
                         applyAudioRouteChange(core, call, (arrayListOf(AudioDevice.Type.Headphones, AudioDevice.Type.Headset)), false)
                     }
                 }
                 AudioDevice.Type.Earpiece, AudioDevice.Type.Speaker -> {
-                    Log.i("[Audio Route Helper] Audio route requested to Earpiece or Speaker, setting input to Microphone")
+                    Log.i(TAG,"[Audio Route Helper] Audio route requested to Earpiece or Speaker, setting input to Microphone")
                     applyAudioRouteChange(core, call, (arrayListOf(AudioDevice.Type.Microphone)), false)
                 }
                 else -> {
-                    Log.w("[Audio Route Helper] Unexpected audio device type: ${types.first()}")
+                    Log.w(TAG,"[Audio Route Helper] Unexpected audio device type: ${types.first()}")
                 }
             }
         }
@@ -101,11 +102,11 @@ class AudioRouteUtils {
             val currentCall = call ?: core.currentCall ?: core.calls.firstOrNull()
 
                 if (currentCall != null) {
-                    Log.i("[Audio Route Helper] Telecom Helper & matching connection found, dispatching audio route change through it")
+                    Log.i(TAG,"[Audio Route Helper] Telecom Helper & matching connection found, dispatching audio route change through it")
                     // We will be called here again by NativeCallWrapper.onCallAudioStateChanged()
                     // but this time with skipTelecom = true
                     //if (!Compatibility.changeAudioRouteForTelecomManager(connection, route)) {
-                        Log.w("[Audio Route Helper] Connection is already using this route internally, make the change!")
+                        Log.w(TAG,"[Audio Route Helper] Connection is already using this route internally, make the change!")
                         applyAudioRouteChange(core, currentCall, types)
                         changeCaptureDeviceToMatchAudioRoute(core, currentCall, types)
                     //}
@@ -136,7 +137,7 @@ class AudioRouteUtils {
                 if (audioDevice.type == AudioDevice.Type.Bluetooth &&
                     audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityPlay)
                 ) {
-                    Log.i("[Audio Route Helper] Found bluetooth audio device [${audioDevice.deviceName} (${audioDevice.driverName})]")
+                    Log.i(TAG,"[Audio Route Helper] Found bluetooth audio device [${audioDevice.deviceName} (${audioDevice.driverName})]")
                     return true
                 }
             }
@@ -148,7 +149,7 @@ class AudioRouteUtils {
                 if (audioDevice.type == AudioDevice.Type.Bluetooth &&
                     audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityRecord)
                 ) {
-                    Log.i("[Audio Route Helper] Found bluetooth audio recorder [${audioDevice.deviceName} (${audioDevice.driverName})]")
+                    Log.i(TAG,"[Audio Route Helper] Found bluetooth audio recorder [${audioDevice.deviceName} (${audioDevice.driverName})]")
                     return true
                 }
             }
@@ -160,7 +161,7 @@ class AudioRouteUtils {
                 if ((audioDevice.type == AudioDevice.Type.Headset || audioDevice.type == AudioDevice.Type.Headphones) &&
                     audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityRecord)
                 ) {
-                    Log.i("[Audio Route Helper] Found headset/headphones audio recorder [${audioDevice.deviceName} (${audioDevice.driverName})]")
+                    Log.i(TAG,"[Audio Route Helper] Found headset/headphones audio recorder [${audioDevice.deviceName} (${audioDevice.driverName})]")
                     return true
                 }
             }

+ 171 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/utils/FileUtils.kt

@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+package com.wdkl.ncs.host.sip.utils
+
+import android.content.Context
+import android.database.CursorIndexOutOfBoundsException
+import android.net.Uri
+import android.os.Environment
+import android.provider.OpenableColumns
+import android.webkit.MimeTypeMap
+import java.io.*
+import java.util.*
+
+import org.linphone.core.tools.Log
+
+class FileUtils {
+    companion object {
+        private val TAG = javaClass.simpleName
+
+        const val VFS_PLAIN_FILE_EXTENSION = ".bctbx_evfs_plain"
+
+        fun getNameFromFilePath(filePath: String): String {
+            var name = filePath
+            val i = filePath.lastIndexOf('/')
+            if (i > 0) {
+                name = filePath.substring(i + 1)
+            }
+            return name
+        }
+
+        fun getExtensionFromFileName(fileName: String): String {
+            val realFileName = if (fileName.endsWith(VFS_PLAIN_FILE_EXTENSION)) {
+                fileName.substring(0, fileName.length - VFS_PLAIN_FILE_EXTENSION.length)
+            } else fileName
+
+            var extension = MimeTypeMap.getFileExtensionFromUrl(realFileName)
+            if (extension.isNullOrEmpty()) {
+                val i = realFileName.lastIndexOf('.')
+                if (i > 0) {
+                    extension = realFileName.substring(i + 1)
+                }
+            }
+
+            return extension
+        }
+
+        fun isPlainTextFile(path: String): Boolean {
+            val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault())
+            val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
+            return type?.startsWith("text/plain") ?: false
+        }
+
+        fun isExtensionPdf(path: String): Boolean {
+            val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault())
+            val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
+            return type?.startsWith("application/pdf") ?: false
+        }
+
+        fun isExtensionImage(path: String): Boolean {
+            val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault())
+            val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
+            return type?.startsWith("image/") ?: false
+        }
+
+        fun isExtensionVideo(path: String): Boolean {
+            val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault())
+            val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
+            return type?.startsWith("video/") ?: false
+        }
+
+        fun isExtensionAudio(path: String): Boolean {
+            val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault())
+            val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
+            return type?.startsWith("audio/") ?: false
+        }
+
+        fun getFileStorageDir(context: Context, isPicture: Boolean = false): File {
+            var path: File? = null
+            if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
+                Log.w("[File Utils] External storage is mounted")
+                var directory = Environment.DIRECTORY_DOWNLOADS
+                if (isPicture) {
+                    Log.w("[File Utils] Using pictures directory instead of downloads")
+                    directory = Environment.DIRECTORY_PICTURES
+                }
+                path = context.getExternalFilesDir(directory)
+            }
+
+            val returnPath: File = path ?: context.filesDir
+            if (path == null) Log.w("[File Utils] Couldn't get external storage path, using internal")
+
+            return returnPath
+        }
+
+        fun getFileStoragePath(context: Context, fileName: String): File {
+            val path = getFileStorageDir(context, isExtensionImage(fileName))
+            var file = File(path, fileName)
+
+            var prefix = 1
+            while (file.exists()) {
+                file = File(path, prefix.toString() + "_" + fileName)
+                Log.w("[File Utils] File with that name already exists, renamed to ${file.name}")
+                prefix += 1
+            }
+            return file
+        }
+
+        fun deleteFile(filePath: String) {
+            val file = File(filePath)
+            if (file.exists()) {
+                try {
+                    if (file.delete()) {
+                        Log.i("[File Utils] Deleted $filePath")
+                    } else {
+                        Log.e("[File Utils] Can't delete $filePath")
+                    }
+                } catch (e: Exception) {
+                    Log.e("[File Utils] Can't delete $filePath, exception: $e")
+                }
+            } else {
+                Log.e("[File Utils] File $filePath doesn't exists")
+            }
+        }
+
+
+        private fun getNameFromUri(uri: Uri, context: Context): String {
+            var name = ""
+            if (uri.scheme == "content") {
+                val returnCursor =
+                    context.contentResolver.query(uri, null, null, null, null)
+                if (returnCursor != null) {
+                    returnCursor.moveToFirst()
+                    val nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+                    if (nameIndex != -1) {
+                        try {
+                            name = returnCursor.getString(nameIndex)
+                        } catch (e: CursorIndexOutOfBoundsException) {
+                            Log.e("[File Utils] Failed to get the display name for URI $uri, exception is $e")
+                        }
+                    } else {
+                        Log.e("[File Utils] Couldn't get DISPLAY_NAME column index for URI: $uri")
+                    }
+                    returnCursor.close()
+                }
+            } else if (uri.scheme == "file") {
+                name = uri.lastPathSegment ?: ""
+            }
+            return name
+        }
+
+    }
+
+
+}

+ 106 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/utils/LinphoneUtils.kt

@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+package com.wdkl.ncs.host.sip.utils
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkInfo
+import android.telephony.TelephonyManager.*
+import com.wdkl.app.ncs.conversion_box.sip.core.CorePreferences
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+import java.util.*
+import org.linphone.core.*
+
+/**
+ * Various utility methods for Linphone SDK
+ */
+class LinphoneUtils {
+    companion object {
+        private const val RECORDING_DATE_PATTERN = "dd-MM-yyyy-HH-mm-ss"
+
+        fun getDisplayName(address: Address): String {
+            return address.displayName ?: address.username ?: ""
+        }
+
+        fun getDisplayableAddress(corePreferences: CorePreferences, address: Address?): String {
+            if (address == null) return "[null]"
+            return if (corePreferences.replaceSipUriByUsername) {
+                address.username ?: address.asStringUriOnly()
+            } else {
+                address.asStringUriOnly()
+            }
+        }
+
+        fun isLimeAvailable(core: Core): Boolean {
+            return core.limeX3DhAvailable() && core.limeX3DhEnabled() &&
+                    core.limeX3DhServerUrl != null &&
+                    core.defaultAccount?.params?.conferenceFactoryUri != null
+        }
+
+        fun isGroupChatAvailable(core: Core): Boolean {
+            return core.defaultAccount?.params?.conferenceFactoryUri != null
+        }
+
+
+        fun getRecordingFilePathForAddress(context: Context, address: Address): String {
+            val displayName = getDisplayName(address)
+            val dateFormat: DateFormat = SimpleDateFormat(
+                RECORDING_DATE_PATTERN,
+                Locale.getDefault()
+            )
+            val fileName = "${displayName}_${dateFormat.format(Date())}.mkv"
+            return FileUtils.getFileStoragePath(context, fileName).absolutePath
+        }
+
+        fun getRecordingDateFromFileName(name: String): Date {
+            return SimpleDateFormat(RECORDING_DATE_PATTERN, Locale.getDefault()).parse(name)
+        }
+
+        @SuppressLint("MissingPermission")
+        fun checkIfNetworkHasLowBandwidth(context: Context): Boolean {
+            val connMgr =
+                context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+            val networkInfo: NetworkInfo? = connMgr.activeNetworkInfo
+            if (networkInfo != null && networkInfo.isConnected) {
+                if (networkInfo.type == ConnectivityManager.TYPE_MOBILE) {
+                    return when (networkInfo.subtype) {
+                        NETWORK_TYPE_EDGE, NETWORK_TYPE_GPRS, NETWORK_TYPE_IDEN -> true
+                        else -> false
+                    }
+                }
+            }
+            // In doubt return false
+            return false
+        }
+
+        fun isCallLogMissed(callLog: CallLog): Boolean {
+            return (callLog.dir == Call.Dir.Incoming &&
+                    (callLog.status == Call.Status.Missed ||
+                            callLog.status == Call.Status.Aborted ||
+                            callLog.status == Call.Status.EarlyAborted))
+        }
+
+        fun getChatRoomId(localAddress: String, remoteAddress: String): String {
+            return "$localAddress~$remoteAddress"
+        }
+    }
+}

+ 148 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/sip/utils/VideoZoomHelper.kt

@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+package com.wdkl.ncs.host.sip.utils
+
+import android.content.Context
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.ScaleGestureDetector
+import android.view.View
+import kotlin.math.max
+import kotlin.math.min
+import org.linphone.core.Call
+import org.linphone.core.Core
+
+class VideoZoomHelper(
+    context: Context,
+    private var videoDisplayView: View,
+    private val core: Core
+) : GestureDetector.SimpleOnGestureListener() {
+    private var scaleDetector: ScaleGestureDetector
+
+    private var zoomFactor = 1f
+    private var zoomCenterX = 0f
+    private var zoomCenterY = 0f
+
+    init {
+        val gestureDetector = GestureDetector(context, this)
+
+        scaleDetector = ScaleGestureDetector(context, object :
+            ScaleGestureDetector.SimpleOnScaleGestureListener() {
+            override fun onScale(detector: ScaleGestureDetector): Boolean {
+                zoomFactor *= detector.scaleFactor
+                // Don't let the object get too small or too large.
+                // Zoom to make the video fill the screen vertically
+                val portraitZoomFactor =
+                    videoDisplayView.height.toFloat() / (3 * videoDisplayView.width / 4)
+                // Zoom to make the video fill the screen horizontally
+                val landscapeZoomFactor =
+                    videoDisplayView.width.toFloat() / (3 * videoDisplayView.height / 4)
+                zoomFactor =
+                    max(0.1f, min(zoomFactor, max(portraitZoomFactor, landscapeZoomFactor)))
+
+                val currentCall: Call? = core.currentCall
+                if (currentCall != null) {
+                    currentCall.zoom(zoomFactor, zoomCenterX, zoomCenterY)
+                    return true
+                }
+
+                return false
+            }
+        })
+
+        videoDisplayView.setOnTouchListener { _, event ->
+            val currentZoomFactor = zoomFactor
+            scaleDetector.onTouchEvent(event)
+
+            if (currentZoomFactor != zoomFactor) {
+                // We did scale, prevent touch event from going further
+                return@setOnTouchListener true
+            }
+
+            // If true, gesture detected, prevent touch event from going further
+            // Otherwise it seems we didn't use event,
+            // allow it to be dispatched somewhere else
+            gestureDetector.onTouchEvent(event)
+        }
+    }
+
+    override fun onScroll(
+        e1: MotionEvent,
+        e2: MotionEvent,
+        distanceX: Float,
+        distanceY: Float
+    ): Boolean {
+        val currentCall: Call? = core.currentCall
+        if (currentCall != null) {
+            if (zoomFactor > 1) {
+                // Video is zoomed, slide is used to change center of zoom
+                if (distanceX > 0 && zoomCenterX < 1) {
+                    zoomCenterX += 0.01f
+                } else if (distanceX < 0 && zoomCenterX > 0) {
+                    zoomCenterX -= 0.01f
+                }
+
+                if (distanceY < 0 && zoomCenterY < 1) {
+                    zoomCenterY += 0.01f
+                } else if (distanceY > 0 && zoomCenterY > 0) {
+                    zoomCenterY -= 0.01f
+                }
+
+                if (zoomCenterX > 1) zoomCenterX = 1f
+                if (zoomCenterX < 0) zoomCenterX = 0f
+                if (zoomCenterY > 1) zoomCenterY = 1f
+                if (zoomCenterY < 0) zoomCenterY = 0f
+
+                currentCall.zoom(zoomFactor, zoomCenterX, zoomCenterY)
+                return true
+            }
+        }
+
+        return false
+    }
+
+    override fun onDoubleTap(e: MotionEvent?): Boolean {
+        val currentCall: Call? = core.currentCall
+        if (currentCall != null) {
+            if (zoomFactor == 1f) {
+                // Zoom to make the video fill the screen vertically
+                val portraitZoomFactor =
+                    videoDisplayView.height.toFloat() / (3 * videoDisplayView.width / 4)
+                // Zoom to make the video fill the screen horizontally
+                val landscapeZoomFactor =
+                    videoDisplayView.width.toFloat() / (3 * videoDisplayView.height / 4)
+                zoomFactor = max(portraitZoomFactor, landscapeZoomFactor)
+            } else {
+                resetZoom()
+            }
+
+            currentCall.zoom(zoomFactor, zoomCenterX, zoomCenterY)
+            return true
+        }
+
+        return false
+    }
+
+    private fun resetZoom() {
+        zoomFactor = 1f
+        zoomCenterY = 0.5f
+        zoomCenterX = zoomCenterY
+    }
+}

+ 0 - 20
conversion_box/src/main/res/raw/linphonerc_default

@@ -1,20 +0,0 @@
-[sip]
-contact="Linphone Android" <sip:allen@8.129.220.143>
-use_info=0
-use_ipv6=1
-keepalive_period=30000
-sip_port=-1
-sip_tcp_port=-1
-sip_tls_port=-1
-media_encryption=none
-
-[video]
-size=vga
-
-[app]
-tunnel=disabled
-push_notification=1
-
-[misc]
-max_calls=10
-history_max_size=100

+ 0 - 34
conversion_box/src/main/res/raw/linphonerc_factory

@@ -1,34 +0,0 @@
-
-#
-#This file shall not contain path referencing package name, in order to be portable when app is renamed.
-#Paths to resources must be set from LinphoneManager, after creating LinphoneCore.
-[net]
-mtu=1300
-#Because dynamic bitrate adaption can increase bitrate, we must allow "no limit"
-download_bw=0
-upload_bw=0
-force_ice_disablement=0
-
-[sip]
-guess_hostname=1
-register_only_when_network_is_up=1
-auto_net_state_mon=1
-auto_answer_replacing_calls=1
-ping_with_options=0
-use_cpim=1
-
-[video]
-displaytype=MSAndroidTextureDisplay
-
-[misc]
-enable_basic_to_client_group_chat_room_migration=0
-enable_simple_group_chat_message_state=0
-aggregate_imdn=1
-notify_each_friend_individually_when_presence_received=0
-
-[app]
-activation_code_length=4
-prefer_basic_chat_room=1
-
-[assistant]
-xmlrpc_url=https://subscribe.linphone.org:444/wizard.php

+ 1 - 1
middleware/build.gradle

@@ -55,7 +55,7 @@ dependencies {
         exclude group: 'com.android.support', module: 'support-annotations'
     })
 
-    compile files('libs/linphone-sdk-android-5.2.10.aar')
+    compile files('libs/linphone-sdk-android-5.0.71.aar')
 
     compile project(':common')
     compile project(':resource')

BIN
middleware/libs/linphone-sdk-android-5.2.10.aar