weizhengliang 3 rokov pred
rodič
commit
f1bd24297c
57 zmenil súbory, kde vykonal 6939 pridanie a 12 odobranie
  1. 30 1
      app/src/main/code/com/wdkl/app/ncs/application/Application.kt
  2. 2 0
      build.gradle
  3. 7 0
      conversion_box/build.gradle
  4. 12 0
      conversion_box/src/main/AndroidManifest.xml
  5. 27 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/activity/MainActivity.kt
  6. 842 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/activity/OfflineMainActivity.kt
  7. 69 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/adapter/OfflineBedItemAdapter.kt
  8. 5 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/di/ConversionBoxComponent.kt
  9. 1 1
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/CallFragment.kt
  10. 28 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/MainFragment.kt
  11. 88 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/OfflineMainFragment.kt
  12. 289 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/SipCallFragment.kt
  13. 6 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/helper/AppUpdateHelper.java
  14. 4 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/helper/FrameHelper.java
  15. 187 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/AsyncPlayer.java
  16. 44 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/IEvent.java
  17. 11 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/IUserState.java
  18. 504 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/RtcSignalClient.java
  19. 358 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/SocketManager.java
  20. 42 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/Utils.java
  21. 103 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/VoipEvent.java
  22. 59 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/VoipReceiver.java
  23. 120 0
      conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/service/AppService.kt
  24. 217 0
      conversion_box/src/main/res/layout/offline_main_activity_layout.xml
  25. 196 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/AVEngine.java
  26. 543 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/CallSession.java
  27. 41 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/EnumType.java
  28. 208 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/SkyEngineKit.java
  29. 43 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/engine/EngineCallback.java
  30. 121 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/engine/IEngine.java
  31. 693 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/engine/webrtc/MyWebRTCEngine.java
  32. 355 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/engine/webrtc/Peer.java
  33. 11 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/except/NotInitializedException.java
  34. 12 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/inter/ILogEvent.java
  35. 45 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/inter/ISkyEvent.java
  36. 8 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/model/RoomInfo.java
  37. 18 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/model/UserInfo.java
  38. 28 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/render/ProxyVideoSink.java
  39. 171 0
      janus/src/main/java/com/wdkl/ncs/janus/rtc2/render/VideoFileRenderer.java
  40. 13 0
      middleware/build.gradle
  41. 99 0
      middleware/src/main/code/com/wdkl/greendao/gen/DaoMaster.java
  42. 62 0
      middleware/src/main/code/com/wdkl/greendao/gen/DaoSession.java
  43. 196 0
      middleware/src/main/code/com/wdkl/greendao/gen/DeviceBeanDao.java
  44. 238 0
      middleware/src/main/code/com/wdkl/greendao/gen/DeviceInfoBeanDao.java
  45. 4 2
      middleware/src/main/code/com/wdkl/ncs/android/middleware/common/Constant.java
  46. 110 0
      middleware/src/main/code/com/wdkl/ncs/android/middleware/greendao/DaoManager.java
  47. 104 0
      middleware/src/main/code/com/wdkl/ncs/android/middleware/greendao/entity/DeviceBean.java
  48. 140 0
      middleware/src/main/code/com/wdkl/ncs/android/middleware/greendao/entity/DeviceInfoBean.java
  49. 12 0
      middleware/src/main/code/com/wdkl/ncs/android/middleware/model/vo/DeviceNurseInfoVO.java
  50. 2 2
      middleware/src/main/code/com/wdkl/ncs/android/middleware/udp/ServerInfoUtil.java
  51. 90 0
      middleware/src/main/code/com/wdkl/ncs/android/middleware/udp2/AnalysisUdpUtil.java
  52. 102 0
      middleware/src/main/code/com/wdkl/ncs/android/middleware/udp2/UdpHelper.java
  53. 25 0
      middleware/src/main/code/com/wdkl/ncs/android/middleware/udp2/UdpIndex.java
  54. 99 0
      middleware/src/main/code/com/wdkl/ncs/android/middleware/udp2/UdpItem.java
  55. 52 0
      middleware/src/main/code/com/wdkl/ncs/android/middleware/udp2/UdpSendUtil.java
  56. 30 2
      middleware/src/main/code/com/wdkl/ncs/android/middleware/utils/CommonUtils.java
  57. 13 4
      welcome/src/main/code/com/wdkl/ncs/android/component/welcome/activity/WelcomeActivity.kt

+ 30 - 1
app/src/main/code/com/wdkl/app/ncs/application/Application.kt

@@ -1,5 +1,7 @@
 package com.wdkl.app.ncs.application
 
+import android.content.Intent
+import android.net.wifi.WifiManager
 import android.os.Build
 import com.enation.javashop.android.jrouter.JRouter
 import com.wdkl.ncs.android.lib.base.BaseApplication
@@ -8,6 +10,9 @@ import com.enation.javashop.net.engine.plugin.exception.RestfulExceptionIntercep
 import com.enation.javashop.utils.base.config.BaseConfig
 import com.wdkl.app.ncs.conversion_box.helper.AnrFcExceptionUtil
 import com.wdkl.app.ncs.conversion_box.helper.NetHelper
+import com.wdkl.app.ncs.conversion_box.service.AppService
+import com.wdkl.ncs.android.middleware.greendao.DaoManager
+import com.wdkl.ncs.android.middleware.udp2.UdpHelper
 //import com.wdkl.app.ncs.conversion_box.helper.XCrashUtils
 import serialporttest.utils.SerialPortUtil
 
@@ -19,6 +24,9 @@ import serialporttest.utils.SerialPortUtil
  */
 class Application : BaseApplication() {
 
+    //udp
+    var helper: UdpHelper? = null
+    var wifiManager: WifiManager? = null
 
     /**
      * @author LDD
@@ -77,7 +85,7 @@ class Application : BaseApplication() {
      */
     private fun initFrame() {
         //禁止滑掉退出
-        BaseConfig.getInstance().addActivity("MainActivity", "AppUpdateActivity", "WelcomeActivity")
+        BaseConfig.getInstance().addActivity("MainActivity", "AppUpdateActivity", "WelcomeActivity", "OfflineMainActivity")
 
         NetEngineConfig.init(baseContext)
                 .openLogger()
@@ -96,5 +104,26 @@ class Application : BaseApplication() {
 
         //xCrash catcher
         //XCrashUtils().init(this)
+
+        DaoManager.getInstance().init(this)
+
+        //初始化udp
+        initUdp()
+
+
+        //启动服务
+        val serviceIntent = Intent(this, AppService::class.java)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            //android8.0以上通过startForegroundService启动service
+            startForegroundService(serviceIntent)
+        } else {
+            startService(serviceIntent)
+        }
+    }
+
+    private fun initUdp() {
+        wifiManager = applicationContext.getSystemService(WIFI_SERVICE) as WifiManager
+        helper = UdpHelper(wifiManager, applicationContext)
+        helper!!.run()
     }
 }

+ 2 - 0
build.gradle

@@ -94,6 +94,8 @@ buildscript {
          * Aop埋点相关
          */
         classpath "org.aspectj:aspectjtools:$aspectj_version"
+
+        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // greendao
     }
 
     repositories {

+ 7 - 0
conversion_box/build.gradle

@@ -73,6 +73,13 @@ dependencies {
     compile "com.android.support:cardview-v7:$support_library_version"
     compile "com.android.support:appcompat-v7:$support_library_version"
 
+    //websocket
+    implementation group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.4.0'
+
+    implementation "com.fasterxml.jackson.core:jackson-annotations:2.9.0"
+    implementation "com.google.code.gson:gson:2.8.5"
+    implementation 'com.alibaba:fastjson:1.2.23'
+
     /**
      * 公共库依赖
      */

+ 12 - 0
conversion_box/src/main/AndroidManifest.xml

@@ -17,8 +17,20 @@
             android:launchMode="singleInstance"
             android:screenOrientation="landscape"/>
 
+        <activity android:name="com.wdkl.app.ncs.conversion_box.activity.OfflineMainActivity"
+            android:launchMode="singleInstance"
+            android:screenOrientation="landscape"/>
+
         <activity android:name="com.wdkl.app.ncs.conversion_box.activity.AppUpdateActivity"
             android:screenOrientation="landscape"
             android:launchMode="singleTask"/>
+
+        <service android:name=".service.AppService"/>
+
+        <receiver android:name=".rtc.client.VoipReceiver">
+            <intent-filter>
+                <action android:name="${applicationId}.voip.Receiver" />
+            </intent-filter>
+        </receiver>
     </application>
 </manifest>

+ 27 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/activity/MainActivity.kt

@@ -31,6 +31,8 @@ import com.wdkl.ncs.android.middleware.api.UrlManager
 import com.wdkl.ncs.android.middleware.common.Constant
 import com.wdkl.ncs.android.middleware.common.MessageEvent
 import com.wdkl.ncs.android.middleware.common.SipStatus
+import com.wdkl.ncs.android.middleware.greendao.DaoManager
+import com.wdkl.ncs.android.middleware.greendao.entity.DeviceInfoBean
 import com.wdkl.ncs.android.middleware.logic.contract.conversion_box.MainActivityContract
 import com.wdkl.ncs.android.middleware.logic.presenter.conversion_box.MainActivityPresenter
 import com.wdkl.ncs.android.middleware.model.ServerInfo
@@ -134,6 +136,9 @@ class MainActivity :BaseActivity<MainActivityPresenter, MainActivityLayoutBindin
     }
 
     override fun init() {
+        //在线模式
+        CommonUtils.setStartMode(BaseApplication.appContext, 1)
+
         //开始ping网络,30秒ping一次
         //NetHelper.startNetCheck()
 
@@ -492,6 +497,8 @@ class MainActivity :BaseActivity<MainActivityPresenter, MainActivityLayoutBindin
 
         handler.removeCallbacksAndMessages(null)
 
+        DaoManager.getInstance().closeConnection()
+
         //rs485
         //SerialPort485Util.getInstance().closeSerialPort()
     }
@@ -622,6 +629,24 @@ class MainActivity :BaseActivity<MainActivityPresenter, MainActivityLayoutBindin
             tv_device_status.setText("设备状态: 未注册或未启用")
         }
 
+        //保存本机设备信息到数据库
+        DaoManager.getInstance().asyncSession.runInTx {
+            Log.d(TAG, "save device info to db")
+            DaoManager.getInstance().daoSession.deviceInfoBeanDao.deleteAll()
+            val deviceInfoBean = DeviceInfoBean()
+            deviceInfoBean.hospitalId = deviceInfo.hospitalId
+            deviceInfoBean.hospitalName = deviceInfo.hospitalName
+            deviceInfoBean.id = deviceInfo.id
+            deviceInfoBean.partId = deviceInfo.partId
+            deviceInfoBean.partName = deviceInfo.partName
+            deviceInfoBean.partDisplay = deviceInfo.partDisplay
+            deviceInfoBean.deviceType = deviceInfo.deviceType
+            deviceInfoBean.name = deviceInfo.name
+            deviceInfoBean.sipId = deviceInfo.sipId
+            deviceInfoBean.sipPassword = deviceInfo.sipPassword
+            DaoManager.getInstance().daoSession.deviceInfoBeanDao.insert(deviceInfoBean)
+        }
+
         view_title_layout_tv_hospital_name.text = deviceInfo.hospitalName + deviceInfo.partName
         view_title_layout_tv_no.text = "设备ID: " + deviceInfo.id
         initialized = true
@@ -697,8 +722,10 @@ class MainActivity :BaseActivity<MainActivityPresenter, MainActivityLayoutBindin
 
         if (partSetting.autoAccept != null && partSetting.autoAccept == 1) {
             Constant.autoAnswer = true
+            CommonUtils.setAutoAnswer(activity, true)
         } else {
             Constant.autoAnswer = false
+            CommonUtils.setAutoAnswer(activity, false)
         }
 
         if (partSetting.twoColorDoorLightValid != null && partSetting.twoColorDoorLightValid == 1) {

+ 842 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/activity/OfflineMainActivity.kt

@@ -0,0 +1,842 @@
+package com.wdkl.app.ncs.conversion_box.activity
+
+import android.app.AlarmManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.os.*
+import android.support.v4.app.Fragment
+import android.text.TextUtils
+import android.util.Log
+import android.view.View
+import com.enation.javashop.android.jrouter.external.annotation.Router
+import com.enation.javashop.net.engine.model.NetState
+import com.google.gson.Gson
+import com.wdkl.app.ncs.conversion_box.BuildConfig
+import com.wdkl.app.ncs.conversion_box.R
+import com.wdkl.app.ncs.conversion_box.bean.SosItem
+import com.wdkl.app.ncs.conversion_box.databinding.OfflineMainActivityLayoutBinding
+import com.wdkl.app.ncs.conversion_box.fragment.OfflineMainFragment
+import com.wdkl.app.ncs.conversion_box.fragment.SipCallFragment
+import com.wdkl.app.ncs.conversion_box.helper.*
+import com.wdkl.app.ncs.conversion_box.launch.MainLaunch
+import com.wdkl.app.ncs.conversion_box.rtc.client.IUserState
+import com.wdkl.app.ncs.conversion_box.rtc.client.SocketManager
+import com.wdkl.app.ncs.conversion_box.settings.SettingConfig
+import com.wdkl.ncs.android.lib.base.BaseActivity
+import com.wdkl.ncs.android.lib.base.BaseApplication
+import com.wdkl.ncs.android.lib.utils.AppTool
+import com.wdkl.ncs.android.lib.utils.TimeHandle
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.lib.vo.filter
+import com.wdkl.ncs.android.middleware.api.UrlManager
+import com.wdkl.ncs.android.middleware.common.Constant
+import com.wdkl.ncs.android.middleware.common.MessageEvent
+import com.wdkl.ncs.android.middleware.greendao.DaoManager
+import com.wdkl.ncs.android.middleware.logic.contract.conversion_box.MainActivityContract
+import com.wdkl.ncs.android.middleware.logic.presenter.conversion_box.MainActivityPresenter
+import com.wdkl.ncs.android.middleware.model.ServerInfo
+import com.wdkl.ncs.android.middleware.model.dos.AppVersionDO
+import com.wdkl.ncs.android.middleware.model.dos.PartSettingDO
+import com.wdkl.ncs.android.middleware.model.dto.TcpSeverDTO
+import com.wdkl.ncs.android.middleware.model.vo.DeviceNurseInfoVO
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
+import com.wdkl.ncs.android.middleware.tcp.channel.OtherUtil
+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.DeviceTypeEnum
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpAction
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpType
+import com.wdkl.ncs.android.middleware.udp2.UdpIndex
+import com.wdkl.ncs.android.middleware.udp2.UdpItem
+import com.wdkl.ncs.android.middleware.udp2.UdpSendUtil
+import com.wdkl.ncs.android.middleware.utils.CommonUtils
+import com.wdkl.ncs.janus.rtc2.SkyEngineKit
+import kotlinx.android.synthetic.main.offline_main_activity_layout.*
+import kotlinx.android.synthetic.main.offline_main_activity_layout.view_title_layout_tv_hospital_name
+import kotlinx.android.synthetic.main.offline_main_activity_layout.view_title_layout_tv_no
+import kotlinx.android.synthetic.main.offline_main_activity_layout.view_title_layout_tv_point
+import kotlinx.android.synthetic.main.view_title_layout.*
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import serialporttest.utils.SerialPort485Util
+import serialporttest.utils.SerialPortUtil
+import java.io.DataOutputStream
+import java.io.IOException
+import java.util.*
+import kotlin.collections.ArrayList
+
+@Router(path = "/conversion_box_offline/main")
+class OfflineMainActivity :BaseActivity<MainActivityPresenter, OfflineMainActivityLayoutBinding>(), MainActivityContract.View,
+    SerialPortUtil.ISerialPortBedOnclickEvent, SerialPort485Util.ISerialPortData, IUserState {
+    private val TAG = "OfflineMainActivity"
+
+    private lateinit var receiver: TimeReceiver
+    private lateinit var curFragment: String
+    //首页
+    private val mainFragment = "main_fragment"
+
+
+    private var clickTime : Long = 0
+    private var clickSosTime : Long = 0
+
+    //网络异常计数
+    private var netErrCount : Int = 0
+
+    private var isMacRegister: Boolean = false
+
+    private var showMcuVersion = false
+
+    //通话界面fragment
+    private var callFragment: Fragment? = null
+    private var targetSip = ""
+
+    //正在通话分机串口地址
+    private var curDeviceUart = ""
+    //当前呼叫或通话分机id
+    private var curDeviceId = -1
+    //当前通话InteractionVO
+    private var curInteractionVO: InteractionVO? = null
+    //对方设备id
+    var fromId: Int = -1
+
+    private var sosList = ArrayList<SosItem>()
+    private var outCallList = ArrayList<String>()
+    private var callingUart = ""
+
+    private var serverIp = ""
+    private var testing = false
+
+    private val handler by lazy { Handler(Looper.getMainLooper()) }
+
+    override fun getLayId(): Int {
+        return R.layout.offline_main_activity_layout
+    }
+
+    override fun bindDagger() {
+        MainLaunch.component.inject(this)
+    }
+
+    override fun init() {
+        //离线模式
+        CommonUtils.setStartMode(BaseApplication.appContext, 0)
+
+        //获取mac地址
+        Constant.LOCAL_MAC = NetHelper.getInstance().macAddress
+
+        //注册广播
+        regReceiver()
+
+        EventBus.getDefault().register(this)
+
+        //串口监听
+        setSerialListner()
+        SerialPortHelper.sipRegState("2")
+
+        //启动主fragment
+        switchFragment(R.id.conversion_main_frame, OfflineMainFragment(), mainFragment)
+
+        val buildUrl = UrlManager.build()
+        serverIp =  buildUrl.buyer.substringAfterLast("//").substringBefore(":")
+
+        tv_mac_addr.setText("MAC: " + Constant.LOCAL_MAC + "\nIP: " + NetHelper.getInstance().localIP + ", server: " + serverIp + ", tcp: " + Constant.TCP_SERVER_URL)
+        tv_version.setText("V" + BuildConfig.VERSION_NAME + "_" + BuildConfig.VERSION_CODE)
+
+        //设置默认时区
+        val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
+        alarmManager.setTimeZone("Asia/Shanghai")
+
+        //打开网络调试
+        AppTool.Time.delay(120000) {
+            openNetwrokDebug()
+        }
+
+        val devices = DaoManager.getInstance().daoSession.deviceInfoBeanDao.loadAll()
+        if (devices != null && devices.size > 0) {
+            val deviceInfo = devices[0]
+            Constant.SIP_ID = deviceInfo.sipId
+            Constant.DEVICE_ID = deviceInfo.id
+            Constant.PART_ID = deviceInfo.partId
+            Constant.DEVICE_TYPE = deviceInfo.deviceType
+
+            view_title_layout_tv_hospital_name.text = deviceInfo.hospitalName + deviceInfo.partName + "--离线"
+            view_title_layout_tv_no.text = "设备ID: " + deviceInfo.id
+        }
+
+        AppTool.Time.delay(2000) {
+            if (!isMacRegister) {
+                isMacRegister = true
+                SerialPortHelper.changeRegisterState()
+                SerialPortHelper.changeCallingMode("0") //双工模式
+            }
+
+            // 设置登录状态回调
+            SocketManager.getInstance().addUserStateCallback(this)
+            //连接rtc服务器
+            connectRtcServer()
+        }
+    }
+
+    private fun connectRtcServer() {
+        val hostIP = CommonUtils.getHostIp(activity)
+        if (!TextUtils.isEmpty(Constant.SIP_ID) && !TextUtils.isEmpty(hostIP)) {
+            val wsUrl = "ws://" + hostIP + ":" + SkyEngineKit.signalPort
+            SocketManager.getInstance().connect(wsUrl, Constant.SIP_ID)
+        }
+    }
+
+    override fun userLogin() {
+        runOnUiThread {
+            view_title_layout_tv_point.setBackgroundResource(R.color.green)
+        }
+    }
+
+    override fun userLogout() {
+        runOnUiThread {
+            view_title_layout_tv_point.setBackgroundResource(R.color.red_color)
+        }
+    }
+
+    //开启网络调试
+    private fun openNetwrokDebug() {
+        val commands = arrayListOf(
+            "/system/bin/sh",
+            "setprop service.adb.tcp.port 5555",
+            "stop adbd",
+            "start adbd"
+        )
+        try {
+            RunAsRoot(commands)
+        } catch (e: IOException) {
+            e.printStackTrace()
+        }
+    }
+
+
+    private fun RunAsRoot(cmds: ArrayList<String>) {
+        val p = Runtime.getRuntime().exec("su")
+        val os = DataOutputStream(p.outputStream)
+        for (tmpCmd in cmds) {
+            os.writeBytes(
+                """
+            $tmpCmd
+            
+            """.trimIndent()
+            )
+        }
+        os.writeBytes("exit\n")
+        os.flush()
+    }
+
+    private fun showMsgMain(msg: String) {
+        runOnUiThread {
+            showMessage(msg)
+        }
+    }
+
+    private fun switchFragment(id: Int, fragment: Fragment, tag: String) {
+        supportFragmentManager.beginTransaction()
+                .replace(id, fragment, tag)
+                .commit()
+        curFragment = tag
+    }
+
+    override fun bindEvent() {
+        //
+    }
+
+    //设置串口监听
+    private fun setSerialListner() {
+        SerialPortUtil.getInstance().setOnDataReceiveListener(this)
+        //开启串口心跳
+        SerialPortUtil.getInstance().startHeartBeat()
+    }
+
+
+    override fun destory() {
+        unRegReceiver()
+        EventBus.getDefault().unregister(this)
+        Constant.CALL_STATE = Constant.CALL_STANDBY
+        SerialPortUtil.getInstance().closeHeart()
+        SerialPortUtil.getInstance().closeSerialPort()
+
+        handler.removeCallbacksAndMessages(null)
+
+        DaoManager.getInstance().closeConnection()
+    }
+
+    //数据加载错误
+    override fun onError(message: String, type: Int) {
+        //
+    }
+
+    //没有网络
+    override fun onNoNet() {
+        //
+    }
+
+    //数据加载完成
+    override fun complete(message: String, type: Int) {
+    }
+
+    //开始获取数据
+    override fun start() {
+    }
+
+    //网络监听
+    override fun networkMonitor(state: NetState) {
+        state.filter(onMobile = {
+
+        },onWifi = {
+
+        },offline = {
+
+        })
+    }
+
+    override fun setTcpServerHost(tcpSeverDTO: TcpSeverDTO) {
+        //
+    }
+
+    override fun setServerInfo(data: ServerInfo) {
+        //
+    }
+
+    //显示设备信息
+    override fun showDeviceInfo(deviceInfo: DeviceNurseInfoVO) {
+        //
+    }
+
+
+    override fun setPartSettings(partSetting: PartSettingDO) {
+        //
+    }
+
+    override fun loadAppVersion(appInfo: AppVersionDO) {
+        //
+    }
+
+    private fun regReceiver() {
+        receiver = TimeReceiver()
+        var intentFilter = IntentFilter()
+        intentFilter.addAction(Intent.ACTION_TIME_TICK)
+        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
+        registerReceiver(receiver, intentFilter)
+    }
+
+    private fun unRegReceiver() {
+        unregisterReceiver(receiver)
+    }
+
+    override fun onSerialPortData(data: ByteArray) {
+        //val hexStr = SerialPort485Util.bytesToHexString(data)
+        //updateUart0Info(hexStr)
+        //Log.d("onSerialPortData", " rs485 data: $hexStr")
+    }
+
+    //串口处理
+    override fun serialPortBedOnclick(str: String) {
+        try {
+            val newStr: String = CutSerialPortUtil.delHeadAndEnd(str, "$", "#")
+            val content = newStr.split(",").toTypedArray()
+            Log.d("serialPortBedOnclick", "newStr==$newStr")
+            updateUartInfo(newStr)
+            if (isMacRegister) {
+                val addr = content[1].substring(0, content[1].length - 1)
+                Log.d("serialPortBedOnclick", "handle serialPort click: event=${content[0]}, addr=$addr")
+                when (content[0]) {
+                    "A" -> {
+                        if (Constant.CALL_STATE == Constant.CALL_INCOMING && addr.equals(callingUart, true)) {
+                            RingPlayHelper.stopRingTone()
+                            SerialPortHelper.openSoundChannel(addr)
+                            showCallFragment(1, "", "", addr)
+                        } else {
+                            //分机呼叫
+                            startCall(addr)
+                        }
+                    }
+
+                    "B" -> {
+                        //分机取消呼叫或拒接
+                        outCallList.remove(addr.toUpperCase(Locale.ROOT))
+                        if (Constant.CALL_STATE == Constant.CALL_INCOMING) {
+                            if (addr.equals(callingUart, true)) {
+                                updateCallText("待机中")
+                                Constant.CALL_STATE = Constant.CALL_STANDBY
+                                UdpSendUtil.getInstance().sendUdpData(UdpIndex.HOST_CALL_REJECT, "", "", addr, "", "", "", Constant.DEVICE_ID.toString(), Constant.PART_ID.toString())
+                                SerialPortHelper.closeSoundChannel(addr)
+                                RingPlayHelper.stopRingTone()
+                            } else {
+                                cancelOutCall(addr, false)
+                            }
+                        } else if (Constant.CALL_STATE == Constant.CALL_CALLING) {
+                            if (addr.equals(callingUart, true)) {
+                                SerialPortHelper.closeSoundChannel(addr)
+                                RingPlayHelper.stopRingTone()
+                                handoffCall(addr)
+                            } else {
+                                cancelOutCall(addr, false)
+                            }
+                        } else {
+                            cancelOutCall(addr, true)
+                        }
+                    }
+
+                    "C" -> {
+                        synchronized(Unit) {
+                            //紧急呼叫: 同一个按钮一定时间内重复多次按无效
+                            val curTime = System.currentTimeMillis()
+                            if (sosList.size > 0) {
+                                val sosItem = sosList.stream().filter { addr.equals(it.addr, true) }.findFirst().orElse(null)
+                                if (sosItem == null) {
+                                    //该按钮还没有呼叫过
+                                    //Log.e("serialport", "sosclick 111111111: " + addr)
+                                    val item = SosItem(addr, curTime)
+                                    sosList.add(item)
+                                    startSosCall(addr)
+                                } else if (curTime - sosItem.time > 6000) {
+                                    //Log.e("serialport", "sosclick 222222222: " + addr)
+                                    //该紧急按钮按下距上次已超10s,重新呼叫
+                                    sosList.remove(sosItem)
+                                    val newItem = SosItem(addr, curTime)
+                                    sosList.add(newItem)
+                                    startSosCall(addr)
+                                } /*else {
+                                    Log.e("serialport", "sosclick 重复呼叫,不处理: " + addr)
+                                }*/
+                            } else {
+                                //Log.e("serialport", "sosclick 33333333: " + addr)
+                                val item = SosItem(addr, curTime)
+                                sosList.add(item)
+                                startSosCall(addr)
+                            }
+                        }
+                    }
+
+                    "D" -> {
+                        //已废弃
+                    }
+
+                    "V" -> {
+                        Constant.MCU_VERSION_NUMBER = content[1].substring(0, 17)
+                        Log.d("serialPortBedOnclick","Constants.MCU_VERSION_NUMBER==" + Constant.MCU_VERSION_NUMBER)
+                        if (!showMcuVersion) {
+                            showMcuVersion = true
+                            runOnUiThread {
+                                tv_mcu.setText("MCU: " + Constant.MCU_VERSION_NUMBER)
+                            }
+                        }
+                    }
+                }
+            } else {
+                //这里实际上是执行了“E”指令
+                /*if (content[1].length > 5) {
+                    UdpSendUtil.sendOnlyId(content[1].substring(0, 4), content[1].substring(4, 19));//后来增加了字符
+                } else {
+                    UdpSendUtil.sendRegister(content[1].substring(0, 4));
+                }*/
+
+                Log.d("serialPortBedOnclick", "isMacRegister is false: newStr==$newStr")
+                //上传分机设备地址及转换盒设备地址
+                if (content[1].length >= 5) {
+                    val deviceMac = content[1].substring(0, 4)
+                    if (Constant.PART_ID != null) {
+                        presenter.setDeviceMac(deviceMac, Constant.LOCAL_MAC)
+                    }
+
+                    if (content[1].contains("MD")) {
+                        startTestLight(deviceMac)
+                    }
+                }
+            }
+        } catch (e: java.lang.Exception) {
+            Log.d("serialPortBedOnclick", "Exception==")
+            e.printStackTrace()
+        }
+    }
+
+    private fun startTestLight(mac: String) {
+        if (!testing) {
+            testing = true
+            Thread{
+                try {
+                    SerialPortHelper.closeDoorLight(mac)
+                    Thread.sleep(500)
+                    SerialPortHelper.testDoorLightAll(mac)
+                } catch (e: Exception) {
+                    e.printStackTrace()
+                }
+
+                testing = false
+            }.start()
+        }
+    }
+
+    private fun startCall(uart: String) {
+        Log.d("serialPortBedOnclick", "startCall: $uart")
+        if (outCallList.contains(uart.toUpperCase(Locale.ROOT))) {
+            Log.d("serialPortBedOnclick", "$uart out call exist! return")
+            return
+        }
+
+        for (device in FrameHelper.offlineDevices) {
+            if (uart.equals(device.uartAddr, true)) {
+                //发送udp呼叫
+                outCallList.add(uart.toUpperCase(Locale.ROOT))
+                updateSendTcpInfo("udp call $uart")
+                updateCallText("正在呼叫-->$uart, ${device.fullName}")
+                //String index, String frameName, String patientName, String uartAddr, String myAddr, String targetAddr, String deviceId, String partId
+                UdpSendUtil.getInstance().sendUdpData(UdpIndex.BED_CALL_OUT, device.fullName, "", uart, "", "", "", Constant.DEVICE_ID.toString(), Constant.PART_ID.toString())
+                break
+            }
+        }
+    }
+
+    private fun cancelOutCall(uart: String, resetCall: Boolean) {
+        SerialPortHelper.closeSoundChannel(uart)
+        for (device in FrameHelper.offlineDevices) {
+            if (uart.equals(device.uartAddr, true)) {
+                updateSendTcpInfo("udp cancel $uart")
+                updateCallText("取消呼叫-->$uart, ${device.fullName}")
+                UdpSendUtil.getInstance().sendUdpData(UdpIndex.BED_CALL_CANCEL, device.fullName, "", uart, "", "", "", Constant.DEVICE_ID.toString(), Constant.PART_ID.toString())
+                if (resetCall) {
+                    Constant.CALL_STATE = Constant.CALL_STANDBY
+                }
+                break
+            }
+        }
+
+        //关闭门灯
+        /*val doorAddr = getDoorLightAddr(uart)
+        if (!TextUtils.isEmpty(doorAddr)) {
+            SerialPortHelper.closeDoorLight(doorAddr)
+        }*/
+    }
+
+    private fun startSosCall(uart: String) {
+        for (device in FrameHelper.offlineDevices) {
+            if (uart.equals(device.uartAddr, true)) {
+                updateSendTcpInfo("udp sos call $uart")
+                updateCallText("紧急呼叫-->$uart, ${device.fullName}")
+                UdpSendUtil.getInstance().sendUdpData(UdpIndex.SOS_CALL, device.fullName, "", uart, "", "", "", Constant.DEVICE_ID.toString(), Constant.PART_ID.toString())
+                break
+            }
+        }
+
+        //开启门灯
+        /*val doorAddr = getDoorLightAddrBySos(uart)
+        if (!TextUtils.isEmpty(doorAddr)) {
+            SerialPortHelper.openDoorLightRed(doorAddr)
+        }*/
+    }
+
+    private fun cancelSosCall(uart: String) {
+        if (!TextUtils.isEmpty(uart)) {
+            //关闭紧急按钮
+            SerialPortHelper.closeEmergency(uart)
+            //关闭门灯
+            /*val doorAddr = getDoorLightAddrBySos(uart)
+            if (!TextUtils.isEmpty(doorAddr)) {
+                SerialPortHelper.closeDoorLight(doorAddr)
+            }*/
+        }
+    }
+
+
+    //通过床位分机查找所在房间的门灯
+    private fun getDoorLightAddr(bedAddr: String) : String? {
+        //首先找到分机所在床位层级,然后通过该bed frame的父级id(即房间层级)找到对应room frame,获得门灯设备信息
+        if (!TextUtils.isEmpty(bedAddr) && FrameHelper.frameDeviceList.size > 0) {
+            var roomId = -1
+            for (bedFrameDevice in FrameHelper.frameDeviceList) {
+                if (bedFrameDevice.device != null && bedAddr.equals(bedFrameDevice.device.ethMac, true)) {
+                    roomId = bedFrameDevice.frame.parentId
+                    break
+                }
+            }
+
+            if (roomId != -1) {
+                for (frameDevice in FrameHelper.frameDeviceList) {
+                    if (frameDevice.frame != null && frameDevice.frame.id == roomId) {
+                        if (frameDevice.device != null && frameDevice.device.deviceType == DeviceTypeEnum.SIMULATE_DOOR_LIGHT.value()) {
+                            //找到对应门灯设备
+                            return frameDevice.device.ethMac
+                        }
+                    }
+                }
+            }
+        }
+
+        //没有找到对应门灯设备,返回null
+        return null
+    }
+
+    //通过紧急按钮地址查找门灯
+    private fun getDoorLightAddrBySos(sosAddr: String) : String? {
+        //首先找到分机所在床位层级,然后通过该bed frame的父级id(即房间层级)找到对应room frame,获得门灯设备信息
+        if (!TextUtils.isEmpty(sosAddr) && FrameHelper.frameDeviceList.size > 0) {
+            var roomId = -1
+            for (roomFrameDevice in FrameHelper.frameDeviceList) {
+                if (roomFrameDevice.device != null && sosAddr.equals(roomFrameDevice.device.ethMac, true)) {
+                    roomId = roomFrameDevice.frame.id
+                    break
+                }
+            }
+
+            if (roomId != -1) {
+                for (frameDevice in FrameHelper.frameDeviceList) {
+                    if (frameDevice.frame != null && frameDevice.frame.id == roomId) {
+                        if (frameDevice.device != null && frameDevice.device.deviceType == DeviceTypeEnum.SIMULATE_DOOR_LIGHT.value()) {
+                            //找到对应门灯设备
+                            return frameDevice.device.ethMac
+                        }
+                    }
+                }
+            }
+        }
+
+        //没有找到对应门灯设备,返回null
+        return null
+    }
+
+    private fun updateCallText(str: String) {
+        runOnUiThread {
+            tv_call_state.setText("呼叫状态: " + str)
+        }
+    }
+
+    private fun updateUart0Info(str: String) {
+        runOnUiThread {
+            tv_uart0_info.setText("串口0: $str")
+        }
+    }
+
+    private fun updateUartInfo(str: String) {
+        runOnUiThread {
+            val timeStr = TimeHandle.getDateTime(System.currentTimeMillis(), "HH:mm:ss")
+            tv_uart_info.setText("串口1: $str, $timeStr")
+        }
+    }
+
+    private fun updateTcpInfo(str: String) {
+        runOnUiThread {
+            tv_tcp_info.setText("Received TCP: $str")
+        }
+    }
+
+    private fun updateSendTcpInfo(str: String) {
+        runOnUiThread {
+            tv_send_tcp_info.setText("Send TCP: $str")
+        }
+    }
+
+    private fun updateNetState() {
+        /*
+        * 检查网络情况,若tcp断开连接多次且IP也是空的则网络异常,重启设备
+        * 仅对3128设备有效
+         */
+        if (Build.MODEL.equals("rk3128")) {
+            var count = SettingConfig.getNetErrResetCount(this)
+            if (SocketManager.getInstance().userState == 0 || TextUtils.isEmpty(NetHelper.getInstance().localIP)) {
+                netErrCount++
+            } else {
+                netErrCount = 0
+                if (count > 0) {
+                    count = 0
+                    SettingConfig.setNetErrResetCount(this, count)
+                }
+                return
+            }
+
+            if (netErrCount >= 5) {
+                //如果重启次数超过8次还是无网则不再重启
+                if (count < 8) {
+                    count++
+                    SettingConfig.setNetErrResetCount(this, count)
+                    Handler().postDelayed({
+                        AppUpdateHelper.reboot(this)
+                    }, 5000)
+                } else {
+                    WarningDialogHelper.showDialog(activity)
+                }
+            }
+        }
+    }
+
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        //代码同步
+        synchronized(Unit) {
+            handleTcpModel(messageEvent)
+        }
+    }
+
+    private fun handleTcpModel(messageEvent: MessageEvent) {
+        when (messageEvent.getType()) {
+            //UDP消息处理
+            Constant.EVENT_UDP -> {
+                if (messageEvent.message is UdpItem) {
+                    val udpItem = messageEvent.message as UdpItem
+                    if (UdpIndex.SERVER_MODE_OFFLINE.equals(udpItem.index)) {
+                        if (SocketManager.getInstance().userState == 0) {
+                            connectRtcServer()
+                        }
+                    } else if (UdpIndex.BED_CALL_REJECT.equals(udpItem.index)) {
+                        //主机拒接,从呼叫列表移除
+                        outCallList.remove(udpItem.uartAddr.toUpperCase(Locale.ROOT))
+                        updateCallText("对方拒绝: -->${udpItem.frameName}")
+                        showMessage("对方拒绝")
+                        SerialPortHelper.closeSoundChannel(udpItem.uartAddr.toUpperCase(Locale.ROOT))
+                    } else if (UdpIndex.HOST_CALL_BED.equals(udpItem.index)) {
+                        //当前有来电或正在通话则提示忙
+                        if (Constant.CALL_STATE == Constant.CALL_INCOMING || Constant.CALL_STATE == Constant.CALL_CALLING) {
+                            UdpSendUtil.getInstance().sendUdpData(UdpIndex.CALL_CALLING, udpItem.frameName, "", udpItem.uartAddr, "", "", "", Constant.DEVICE_ID.toString(), Constant.PART_ID.toString())
+                            return
+                        }
+
+                        //主机呼叫分机
+                        updateCallText("收到来电: <--${udpItem.frameName}")
+                        Constant.CALL_STATE = Constant.CALL_INCOMING
+                        if (CommonUtils.getAutoAnswer(activity)) {
+                            //自动接听
+                            SerialPortHelper.openSoundChannel(udpItem.uartAddr.toUpperCase(Locale.ROOT))
+                            showCallFragment(1, "", "", udpItem.uartAddr)
+                        } else {
+                            callingUart = udpItem.uartAddr
+                            SerialPortHelper.callInChannel(udpItem.uartAddr.toUpperCase(Locale.ROOT))
+                            RingPlayHelper.playRingTone(activity, R.raw.ring_tone, true)
+                        }
+
+                        //关闭门灯
+                        //val doorAddr = getDoorLightAddr(curDeviceUart)
+                        //if (!TextUtils.isEmpty(doorAddr)) {
+                        //    SerialPortHelper.closeDoorLight(doorAddr)
+                        //}
+                    } else if (UdpIndex.BED_CALL_ACCEPT.equals(udpItem.index)) {
+                        //主机接听,从呼叫列表移除
+                        outCallList.remove(udpItem.uartAddr.toUpperCase(Locale.ROOT))
+                        callingUart = udpItem.uartAddr
+
+                        if (TextUtils.isEmpty(udpItem.data)) {
+                            showMsgMain("没有目标sip id")
+                            SerialPortHelper.closeSoundChannel(udpItem.uartAddr.toUpperCase(Locale.ROOT))
+                        } else {
+                            if (SocketManager.getInstance().userState == 1) {
+                                //主机接听,开始通话
+                                SerialPortHelper.openSoundChannel(udpItem.uartAddr.toUpperCase(Locale.ROOT))
+                                showCallFragment(0, udpItem.frameName, udpItem.data, udpItem.uartAddr)
+                            } else {
+                                //socket未连接,挂断
+                                SerialPortHelper.closeSoundChannel(udpItem.uartAddr.toUpperCase(Locale.ROOT))
+                                showMsgMain("sip断开连接")
+                            }
+                        }
+
+                        //关闭门灯
+                        /*val doorAddr = getDoorLightAddr(curDeviceUart)
+                        if (!TextUtils.isEmpty(doorAddr)) {
+                            SerialPortHelper.closeDoorLight(doorAddr)
+                        }*/
+                    } else if (UdpIndex.HOST_CALL_CANCEL.equals(udpItem.index)) {
+                        //主机取消呼叫分机
+                        if (Constant.CALL_STATE == Constant.CALL_INCOMING) {
+                            Constant.CALL_STATE = Constant.CALL_STANDBY
+                        }
+                        SerialPortHelper.closeSoundChannel(udpItem.uartAddr.toUpperCase(Locale.ROOT))
+                        RingPlayHelper.stopRingTone()
+                    } else if (UdpIndex.BED_CALL_END.equals(udpItem.index)) {
+                        RingPlayHelper.stopRingTone()
+                        EventBus.getDefault().post(MessageEvent("endcall", Constant.EVENT_END_CALL))
+                    } else if (UdpIndex.SOS_CANCEL.equals(udpItem.index)) {
+                        //紧急呼叫已处理
+                        val addr = udpItem.uartAddr.toUpperCase(Locale.ROOT)
+                        cancelSosCall(addr)
+                        //删除sosList中对应item
+                        var iterator = sosList.iterator()
+                        while (iterator.hasNext()){
+                            val it = iterator.next()
+                            if (it.addr.equals(addr)) {
+                                sosList.remove(it)
+                            }
+                        }
+                    }
+                }
+            }
+
+            Constant.EVENT_RESTART_APP -> {
+                AppUpdateHelper.restartApp(activity)
+            }
+
+            Constant.EVENT_REMOVE_CALL_FRAGMENT -> {
+                Constant.CALL_STATE = Constant.CALL_STANDBY
+                updateCallText("待机中")
+
+                removeCallFragment()
+            }
+        }
+    }
+
+    fun inCalling() {
+        Constant.CALL_STATE = Constant.CALL_CALLING
+        updateCallText("通话中")
+    }
+
+    private fun handoffCall(addr: String) {
+        RingPlayHelper.stopRingTone()
+        EventBus.getDefault().post(MessageEvent("handoff", Constant.EVENT_END_CALL))
+        UdpSendUtil.getInstance().sendUdpData(UdpIndex.BED_CALL_END, "", "", addr, "", "", "", Constant.DEVICE_ID.toString(), Constant.PART_ID.toString())
+    }
+
+    private fun showCallFragment(state: Int, frame: String, target: String, addr: String) {
+        if (callFragment == null) {
+            var fragment = SipCallFragment()
+            var bundle = Bundle()
+            bundle.putInt("call_state", state)
+            bundle.putBoolean("audio_only", true)
+            bundle.putString("targetSip", target)
+            bundle.putString("frame", frame)
+            bundle.putString("uart_addr", addr)
+            fragment.arguments = bundle
+            addCallFragment(fragment)
+        }
+    }
+
+    private fun addCallFragment(fragment: Fragment) {
+        if (callFragment != null) {
+            supportFragmentManager.beginTransaction()
+                .remove(callFragment)
+                .commit()
+            callFragment = null
+        }
+
+        callFragment = fragment
+        supportFragmentManager.beginTransaction()
+            .add(R.id.call_frame, fragment)
+            .commit()
+    }
+
+    private fun removeCallFragment() {
+        if (callFragment != null) {
+            supportFragmentManager.beginTransaction()
+                .remove(callFragment)
+                .commit()
+            callFragment = null
+        }
+    }
+
+
+    inner class TimeReceiver: BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (intent.action == Intent.ACTION_TIME_TICK) {
+                if (SocketManager.getInstance().userState == 0) {
+                    connectRtcServer()
+                }
+                updateNetState()
+            }
+        }
+    }
+}

+ 69 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/adapter/OfflineBedItemAdapter.kt

@@ -0,0 +1,69 @@
+package com.wdkl.app.ncs.conversion_box.adapter
+
+import android.content.Context
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import com.wdkl.app.ncs.conversion_box.R
+import com.wdkl.app.ncs.conversion_box.bean.FrameDeviceBean
+import com.wdkl.ncs.android.middleware.greendao.entity.DeviceBean
+import com.wdkl.ncs.android.middleware.tcp.enums.DeviceTypeEnum
+import java.lang.Exception
+
+class OfflineBedItemAdapter : RecyclerView.Adapter<OfflineBedItemAdapter.BedViewHolder> {
+
+    private var context: Context
+    private var mainData: ArrayList<DeviceBean>
+
+    constructor(context: Context, data: ArrayList<DeviceBean>) {
+        this.context = context
+        this.mainData = data
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): BedViewHolder {
+        val view = LayoutInflater.from(parent?.context).inflate(R.layout.bed_item_view, parent, false)
+        val viewHolder = BedViewHolder(view)
+
+        return viewHolder
+    }
+
+    override fun onBindViewHolder(holder: BedViewHolder?, position: Int) {
+        try {
+            holder?.bedName?.text = mainData.get(position).fullName
+            holder?.bedDevice?.text = mainData.get(position).name
+            val deviceTypeEnum = DeviceTypeEnum.parse(mainData.get(position).deviceType)
+            holder?.bedType?.text = deviceTypeEnum.typeName()
+            holder?.bedAddr?.text = mainData.get(position).uartAddr
+            holder?.bed485Addr?.text = "--"
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+    }
+
+    override fun getItemCount(): Int {
+        return mainData.size
+    }
+
+    fun updateData(data: ArrayList<DeviceBean>) {
+        mainData = data
+        notifyDataSetChanged()
+    }
+
+    class BedViewHolder : RecyclerView.ViewHolder {
+        var bedName : TextView
+        var bedDevice : TextView
+        var bedType : TextView
+        var bedAddr : TextView
+        var bed485Addr : TextView
+
+        constructor(itemView: View): super(itemView) {
+            bedName = itemView.findViewById(R.id.tv_bed_item_name)
+            bedDevice = itemView.findViewById(R.id.tv_bed_device_name)
+            bedType = itemView.findViewById(R.id.tv_bed_item_type)
+            bedAddr = itemView.findViewById(R.id.tv_bed_item_mac_addr)
+            bed485Addr = itemView.findViewById(R.id.tv_bed_item_485_addr)
+        }
+    }
+}

+ 5 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/di/ConversionBoxComponent.kt

@@ -2,6 +2,7 @@ package com.wdkl.app.ncs.conversion_box.di
 
 import com.wdkl.app.ncs.conversion_box.activity.AppUpdateActivity
 import com.wdkl.app.ncs.conversion_box.activity.MainActivity
+import com.wdkl.app.ncs.conversion_box.activity.OfflineMainActivity
 import com.wdkl.app.ncs.conversion_box.fragment.*
 import com.wdkl.ncs.android.middleware.di.ApplicationComponent
 import dagger.Component
@@ -10,7 +11,11 @@ import dagger.Component
 interface ConversionBoxComponent {
     fun inject(activity: MainActivity)
 
+    fun inject(activity: OfflineMainActivity)
+
     fun inject(activity: AppUpdateActivity)
 
     fun inject(activity: MainFragment)
+
+    fun inject(activity: OfflineMainFragment)
 }

+ 1 - 1
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/CallFragment.kt

@@ -140,9 +140,9 @@ class CallFragment: Fragment(), CallSessionCallback {
      *******************************************************/
     override fun didChangeState(state: EnumType.CallState?) {
         Log.e("dds", "didChangeState: " + state)
+        RingPlayHelper.stopRingTone()
         handler.post {
             if (state == EnumType.CallState.Connected && !callEnded) {
-                RingPlayHelper.stopRingTone()
                 //更新界面显示
                 callSuccess = true
                 Constant.CALL_STATE = Constant.CALL_CALLING

+ 28 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/MainFragment.kt

@@ -16,9 +16,12 @@ import com.wdkl.ncs.android.lib.utils.showMessage
 import com.wdkl.ncs.android.lib.vo.filter
 import com.wdkl.ncs.android.middleware.common.Constant
 import com.wdkl.ncs.android.middleware.common.MessageEvent
+import com.wdkl.ncs.android.middleware.greendao.DaoManager
+import com.wdkl.ncs.android.middleware.greendao.entity.DeviceBean
 import com.wdkl.ncs.android.middleware.logic.contract.conversion_box.MainFragmentContract
 import com.wdkl.ncs.android.middleware.logic.presenter.conversion_box.MainFragmentPresenter
 import com.wdkl.ncs.android.middleware.model.vo.*
+import com.wdkl.ncs.android.middleware.utils.CommonUtils
 import kotlinx.android.synthetic.main.conversion_box_main_lay.*
 import org.greenrobot.eventbus.EventBus
 import org.greenrobot.eventbus.Subscribe
@@ -89,6 +92,31 @@ class MainFragment: BaseFragment<MainFragmentPresenter, ConversionBoxMainLayBind
             }
 
             adapter?.updateData(FrameHelper.frameDeviceList)
+
+            updateDeviceDB()
+        }
+    }
+
+    private fun updateDeviceDB() {
+        if (FrameHelper.frameDeviceList.size > 0) {
+            DaoManager.getInstance().asyncSession.runInTx {
+                Log.e(TAG, "update bed device db start")
+                DaoManager.getInstance().daoSession.deviceBeanDao.deleteAll()
+                for (frameDevice in FrameHelper.frameDeviceList) {
+                    val deviceBean = DeviceBean()
+                    deviceBean.id = frameDevice.frame.id
+                    deviceBean.partId = frameDevice.frame.partId
+                    deviceBean.name = frameDevice.device.name
+                    deviceBean.fullName = frameDevice.frame.fullName
+                    deviceBean.deviceId = frameDevice.device.id
+                    deviceBean.uartAddr = frameDevice.device.ethMac
+                    deviceBean.deviceType = frameDevice.device.deviceType
+
+                    //Log.e(TAG, "insert device in db: ${frameBean.id}, ${frameBean.fullName}, ${frameBean.bedDeviceId}")
+                    DaoManager.getInstance().daoSession.deviceBeanDao.insert(deviceBean)
+                }
+                Log.e(TAG, "update bed device db end")
+            }
         }
     }
 

+ 88 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/OfflineMainFragment.kt

@@ -0,0 +1,88 @@
+package com.wdkl.app.ncs.conversion_box.fragment
+
+import com.alibaba.android.vlayout.VirtualLayoutManager
+import com.enation.javashop.net.engine.model.NetState
+import com.wdkl.app.ncs.conversion_box.R
+import com.wdkl.app.ncs.conversion_box.adapter.OfflineBedItemAdapter
+import com.wdkl.app.ncs.conversion_box.databinding.ConversionBoxMainLayBinding
+import com.wdkl.app.ncs.conversion_box.helper.FrameHelper
+import com.wdkl.app.ncs.conversion_box.launch.MainLaunch
+import com.wdkl.ncs.android.lib.base.BaseFragment
+import com.wdkl.ncs.android.lib.vo.filter
+import com.wdkl.ncs.android.middleware.greendao.DaoManager
+import com.wdkl.ncs.android.middleware.greendao.entity.DeviceBean
+import com.wdkl.ncs.android.middleware.logic.contract.conversion_box.MainFragmentContract
+import com.wdkl.ncs.android.middleware.logic.presenter.conversion_box.MainFragmentPresenter
+import com.wdkl.ncs.android.middleware.model.vo.*
+import kotlinx.android.synthetic.main.conversion_box_main_lay.*
+
+class OfflineMainFragment: BaseFragment<MainFragmentPresenter, ConversionBoxMainLayBinding>(), MainFragmentContract.View {
+    val TAG = "OfflineMainFragment"
+
+    private var adapter: OfflineBedItemAdapter? = null
+
+    override fun getLayId(): Int {
+        return R.layout.conversion_box_main_lay
+    }
+
+    override fun bindDagger() {
+        MainLaunch.component.inject(this)
+    }
+
+    override fun init() {
+        adapter = OfflineBedItemAdapter(this.activity, ArrayList())
+        bed_list_view.adapter = adapter
+        bed_list_view.layoutManager = VirtualLayoutManager(this.activity)
+
+        DaoManager.getInstance().asyncSession.runInTx {
+            val data = DaoManager.getInstance().daoSession.deviceBeanDao.loadAll()
+            FrameHelper.offlineDevices.clear()
+            FrameHelper.offlineDevices.addAll(data)
+            activity.runOnUiThread {
+                adapter!!.updateData(FrameHelper.offlineDevices)
+            }
+        }
+    }
+
+    override fun bindEvent() {
+    }
+
+    override fun destory() {
+    }
+
+    override fun showCustomInfo(customInfo: CustomerInfoVO) {
+        //
+    }
+
+    override fun showFrames(framePartVO: FramePartVO) {
+        //
+    }
+
+    override fun showFrameDevices(frameVO: FrameAudioVO) {
+        //
+    }
+
+    override fun onError(message: String, type: Int) {
+        //
+    }
+
+    override fun onNoNet() {
+        //
+    }
+
+    override fun complete(message: String, type: Int) {
+    }
+
+    override fun start() {
+    }
+
+    override fun networkMonitor(state: NetState) {
+        state.filter(onWifi = {
+
+        },onMobile = {
+
+        },offline = {
+
+        })
+    }
+}

+ 289 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/fragment/SipCallFragment.kt

@@ -0,0 +1,289 @@
+package com.wdkl.app.ncs.conversion_box.fragment
+
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.support.v4.app.Fragment
+import android.text.TextUtils
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+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.OfflineMainActivity
+import com.wdkl.app.ncs.conversion_box.helper.RingPlayHelper
+import com.wdkl.app.ncs.conversion_box.helper.SerialPortHelper
+import com.wdkl.app.ncs.conversion_box.rtc.client.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.udp2.UdpIndex
+import com.wdkl.ncs.android.middleware.udp2.UdpItem
+import com.wdkl.ncs.android.middleware.udp2.UdpSendUtil
+import com.wdkl.ncs.janus.rtc2.CallSession
+import com.wdkl.ncs.janus.rtc2.EnumType
+import com.wdkl.ncs.janus.rtc2.SkyEngineKit
+import com.wdkl.ncs.janus.rtc2.except.NotInitializedException
+import kotlinx.android.synthetic.main.call_fragment_layout.*
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import java.util.*
+
+class SipCallFragment: Fragment(), CallSession.CallSessionCallback {
+    private val TAG = "SipCallFragment"
+
+    private lateinit var baseActivity: BaseToolActivity
+    private val handler = Handler(Looper.getMainLooper())
+
+    private var outGoing = false
+    private var targetSip = ""
+    private var frameName = ""
+    private var uartAddr = ""
+
+    //通话状态:0-去电, 1-来电
+    protected var callState : Int = 0
+    protected var onlyAudio: Boolean = true
+
+    private var callEnded: Boolean = false
+    private var callSuccess: Boolean = false
+
+    private var gEngineKit: SkyEngineKit? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        retainInstance = true
+
+        callState = arguments.getInt("call_state")
+        onlyAudio = arguments.getBoolean("audio_only")
+        targetSip = arguments.getString("targetSip", "")
+        frameName = arguments.getString("frame", "")
+        uartAddr = arguments.getString("uart_addr", "")
+
+        Log.e("sip", "frame: $frameName, target: $targetSip, uartAddr: $uartAddr")
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        /**初始化宿主Activity*/
+        baseActivity = activity as BaseToolActivity
+
+        return inflater.inflate(R.layout.call_fragment_layout, null)
+    }
+
+    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        init()
+    }
+
+    private fun init() {
+        try {
+            gEngineKit = SkyEngineKit.Instance()
+        } catch (e: NotInitializedException) {
+            SkyEngineKit.init(VoipEvent()) //重新初始化
+            try {
+                gEngineKit = SkyEngineKit.Instance()
+            } catch (ex: NotInitializedException) {
+                //finish()
+                backToMain()
+                return
+            }
+        }
+
+        if (callState == 0) {
+            //去电
+            outGoing = true
+            startCall()
+        } else if (callState == 1) {
+            //来电接听
+            outGoing = false
+            UdpSendUtil.getInstance().sendUdpData(UdpIndex.HOST_CALL_ACCEPT, "", "", uartAddr, "", "", Constant.SIP_ID, Constant.DEVICE_ID.toString(), Constant.PART_ID.toString())
+        }
+
+        Constant.CALL_STATE = Constant.CALL_CALLING
+    }
+
+    private fun startCall() {
+        val room = UUID.randomUUID().toString() + System.currentTimeMillis()
+        val b = gEngineKit!!.startOutCall(activity, room, targetSip, frameName, onlyAudio)
+        if (b) {
+            val session = gEngineKit?.currentSession
+            if (session != null) {
+                session.setSessionCallback(this)
+                session.toggleSpeaker(true)
+            }
+        } else {
+            showMessage("通话失败")
+            callEnd()
+        }
+    }
+
+    //语音接通
+    private fun joinAudioCall() {
+        val session = gEngineKit?.getCurrentSession()
+        if (session != null) {
+            Log.e(TAG, "audio call session state: " + session.state)
+            if (session.state == EnumType.CallState.Incoming) {
+                session.joinHome(session.roomId)
+                session.toggleSpeaker(true)
+            } else if (session.state == EnumType.CallState.Idle) {
+                callEnd()
+            }
+        }
+    }
+
+    override fun onStart() {
+        EventBus.getDefault().register(this)
+        super.onStart()
+    }
+
+    override fun onStop() {
+        EventBus.getDefault().unregister(this)
+        super.onStop()
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        RingPlayHelper.stopRingTone()
+        Constant.CALL_STATE = Constant.CALL_STANDBY
+        handler.removeCallbacksAndMessages(null)
+    }
+
+    override fun didCallEndWithReason(var1: EnumType.CallEndReason?) {
+        handler.post {
+            when (var1) {
+                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) {
+        //
+    }
+
+    override fun didChangeState(var1: EnumType.CallState?) {
+        RingPlayHelper.stopRingTone()
+        handler.post {
+            if (var1 == EnumType.CallState.Connected && !callEnded) {
+                //更新界面显示
+                callSuccess = true
+                Constant.CALL_STATE = Constant.CALL_CALLING
+
+                if (tv_call_text != null) {
+                    tv_call_text.setText("通话中...")
+                }
+                if (activity != null) {
+                    (activity as OfflineMainActivity).inCalling()
+                }
+            } else if (var1 == EnumType.CallState.Connecting && !callEnded) {
+                if (tv_call_text != null) {
+                    tv_call_text.setText("连接中...")
+                }
+            }
+        }
+    }
+
+    override fun didCreateLocalVideoTrack() {
+        //
+    }
+
+    override fun didDisconnected(userId: String?) {
+        handler.post {
+            callEnd()
+        }
+    }
+
+    override fun didError(error: String?) {
+        handler.post {
+            callEnd()
+        }
+    }
+
+    override fun didReceiveRemoteVideoTrack(userId: String?) {
+        //
+    }
+
+    override fun didUserLeave(userId: String?) {
+        handler.post {
+            callEnd()
+        }
+    }
+
+    //通话结束
+    private fun callEnd() {
+        if (!TextUtils.isEmpty(uartAddr)) {
+            SerialPortHelper.closeSoundChannel(uartAddr)
+        }
+
+        if (callEnded) {
+            return
+        }
+        callEnded = true
+
+        Log.e("sip", "call end !!!!!!!!!!!!!!!!!!")
+
+        if (gEngineKit != null && gEngineKit!!.currentSession != null && gEngineKit!!.currentSession.state != EnumType.CallState.Idle) {
+            gEngineKit!!.endCall()
+        }
+
+        targetSip = ""
+        Constant.CALL_STATE = Constant.CALL_STANDBY
+
+        backToMain()
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        when (messageEvent.getType()) {
+            Constant.EVENT_END_CALL -> {
+                //SkyEngineKit.Instance().endCall()
+                callEnd()
+            }
+
+            Constant.EVENT_SIP_CALL_STATUS -> {
+                val session = gEngineKit?.currentSession
+                if (session != null) {
+                    session.setSessionCallback(this)
+                }
+
+                Handler().postDelayed({
+                    joinAudioCall()
+                }, 300)
+            }
+        }
+    }
+
+    //返回主界面
+    private fun backToMain() {
+        EventBus.getDefault().post(MessageEvent("BackCall", Constant.EVENT_REMOVE_CALL_FRAGMENT))
+    }
+}

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

@@ -12,7 +12,9 @@ import android.os.Build;
 import android.os.Environment;
 import android.util.Log;
 
+import com.wdkl.app.ncs.conversion_box.service.AppService;
 import com.wdkl.ncs.android.component.welcome.activity.WelcomeActivity;
+import com.wdkl.ncs.android.lib.base.BaseApplication;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -211,6 +213,10 @@ public class AppUpdateHelper {
     }
 
     public static void restartApp(Context context) {
+        //停止服务
+        Intent serviceIntent = new Intent(BaseApplication.appContext.getApplicationContext(), AppService.class);
+        BaseApplication.appContext.getApplicationContext().stopService(serviceIntent);
+
         //重新启动app
         Intent mStartActivity = new Intent(context.getApplicationContext(), WelcomeActivity.class);
         mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

+ 4 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/helper/FrameHelper.java

@@ -1,6 +1,7 @@
 package com.wdkl.app.ncs.conversion_box.helper;
 
 import com.wdkl.app.ncs.conversion_box.bean.FrameDeviceBean;
+import com.wdkl.ncs.android.middleware.greendao.entity.DeviceBean;
 import com.wdkl.ncs.android.middleware.model.vo.FrameBedVO;
 import com.wdkl.ncs.android.middleware.model.vo.FrameRoomVO;
 
@@ -13,6 +14,9 @@ public class FrameHelper {
     //转换盒下属空间结构及设备信息
     public static ArrayList<FrameDeviceBean> frameDeviceList = new ArrayList<>();
 
+    //离线模式设备数据
+    public static ArrayList<DeviceBean> offlineDevices = new ArrayList<>();
+
     public static void setRoomFrames(ArrayList<FrameRoomVO> roomFrames) {
         frameRoomVOS.clear();
         frameRoomVOS.addAll(roomFrames);

+ 187 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/AsyncPlayer.java

@@ -0,0 +1,187 @@
+package com.wdkl.app.ncs.conversion_box.rtc.client;
+
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.LinkedList;
+
+/**
+ * 响铃相关类
+ */
+public class AsyncPlayer {
+    private static final int PLAY = 1;
+    private static final int STOP = 2;
+    private AudioManager audioManager;
+
+    private static final class Command {
+        int code;
+        Context context;
+        Uri uri;
+        boolean looping;
+        int stream;
+        long requestTime;
+
+        public String toString() {
+            return "{ code=" + code + " looping=" + looping + " stream=" + stream + " uri=" + uri + " }";
+        }
+    }
+
+    private final LinkedList mCmdQueue = new LinkedList();
+
+    private void startSound(Command cmd) {
+
+        try {
+            MediaPlayer player = new MediaPlayer();
+            player.setAudioStreamType(cmd.stream);
+            player.setDataSource(cmd.context, cmd.uri);
+            player.setLooping(cmd.looping);
+            player.prepare();
+            player.start();
+            if (mPlayer != null) {
+                mPlayer.release();
+            }
+            mPlayer = player;
+        } catch (IOException e) {
+            Log.w(mTag, "error loading sound for " + cmd.uri, e);
+        } catch (IllegalStateException e) {
+            Log.w(mTag, "IllegalStateException (content provider died?) " + cmd.uri, e);
+        }
+    }
+
+    private final class Thread extends java.lang.Thread {
+        Thread() {
+            super("AsyncPlayer-" + mTag);
+        }
+
+        public void run() {
+            while (true) {
+                Command cmd = null;
+
+                synchronized (mCmdQueue) {
+
+                    cmd = (Command) mCmdQueue.removeFirst();
+                }
+
+                switch (cmd.code) {
+                    case PLAY:
+                        startSound(cmd);
+                        break;
+                    case STOP:
+
+                        if (mPlayer != null) {
+                            mPlayer.stop();
+                            mPlayer.release();
+                            mPlayer = null;
+                        } else {
+                            Log.w(mTag, "STOP command without a player");
+                        }
+                        break;
+                }
+
+                synchronized (mCmdQueue) {
+                    if (mCmdQueue.size() == 0) {
+
+                        mThread = null;
+                        releaseWakeLock();
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    private String mTag;
+    private Thread mThread;
+    private MediaPlayer mPlayer;
+    private PowerManager.WakeLock mWakeLock;
+
+    private int mState = STOP;
+
+    public AsyncPlayer(String tag) {
+        if (tag != null) {
+            mTag = tag;
+        } else {
+            mTag = "AsyncPlayer";
+        }
+    }
+
+    public void play(Context context, Uri uri, boolean looping, int stream) {
+        Command cmd = new Command();
+        cmd.requestTime = SystemClock.uptimeMillis();
+        cmd.code = PLAY;
+        cmd.context = context;
+        cmd.uri = uri;
+        cmd.looping = looping;
+        cmd.stream = stream;
+        synchronized (mCmdQueue) {
+            enqueueLocked(cmd);
+            mState = PLAY;
+        }
+    }
+
+    public void stop() {
+        synchronized (mCmdQueue) {
+            if (mState != STOP) {
+                Command cmd = new Command();
+                cmd.requestTime = SystemClock.uptimeMillis();
+                cmd.code = STOP;
+                enqueueLocked(cmd);
+                mState = STOP;
+            }
+        }
+    }
+
+    private void enqueueLocked(Command cmd) {
+        mCmdQueue.add(cmd);
+        if (mThread == null) {
+            acquireWakeLock();
+            mThread = new Thread();
+            mThread.start();
+        }
+    }
+
+    public void setUsesWakeLock(Context context) {
+        if (mWakeLock != null || mThread != null) {
+            throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock + " mThread=" + mThread);
+        }
+        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
+    }
+
+    private void acquireWakeLock() {
+        if (mWakeLock != null) {
+            mWakeLock.acquire();
+        }
+    }
+
+    private void releaseWakeLock() {
+        if (mWakeLock != null) {
+            mWakeLock.release();
+        }
+    }
+
+    private boolean isHeadphonesPlugged(Context context) {
+        if (audioManager == null) {
+            audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        }
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+            AudioDeviceInfo[] audioDevices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+            for (AudioDeviceInfo deviceInfo : audioDevices) {
+                if (deviceInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES
+                        || deviceInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
+                    return true;
+                }
+            }
+            return false;
+        } else {
+            return audioManager.isWiredHeadsetOn();
+        }
+    }
+}

+ 44 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/IEvent.java

@@ -0,0 +1,44 @@
+package com.wdkl.app.ncs.conversion_box.rtc.client;
+
+public interface IEvent {
+
+
+    void onOpen();
+
+    void loginSuccess(String userId);
+
+
+    void onInvite(String room, boolean audioOnly, String inviteId, String frameName, String userList);
+
+
+    void onCancel(String inviteId);
+
+    void onRing(String userId);
+
+
+    void onPeers(String myId, String userList, int roomSize, String frameName);
+
+    void onNewPeer(String myId);
+
+    void onReject(String userId, int type);
+
+    // onOffer
+    void onOffer(String userId, String sdp);
+
+    // onAnswer
+    void onAnswer(String userId, String sdp);
+
+    // ice-candidate
+    void onIceCandidate(String userId, String id, int label, String candidate);
+
+    void onLeave(String userId);
+
+    void logout(String str);
+
+    void onTransAudio(String userId);
+
+    void onDisConnect(String userId);
+
+    void reConnect();
+
+}

+ 11 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/IUserState.java

@@ -0,0 +1,11 @@
+package com.wdkl.app.ncs.conversion_box.rtc.client;
+
+public interface IUserState {
+
+
+    void userLogin();
+
+    void userLogout();
+
+
+}

+ 504 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/RtcSignalClient.java

@@ -0,0 +1,504 @@
+package com.wdkl.app.ncs.conversion_box.rtc.client;
+
+import android.annotation.SuppressLint;
+import android.util.Log;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+
+import java.net.URI;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.X509TrustManager;
+
+public class RtcSignalClient extends WebSocketClient {
+    private final static String TAG = "RtcSignalClient";
+    private final IEvent iEvent;
+    private boolean connectFlag = false;
+
+
+    public RtcSignalClient(URI serverUri, IEvent event) {
+        super(serverUri);
+        this.iEvent = event;
+    }
+
+    @Override
+    public void onClose(int code, String reason, boolean remote) {
+        Log.w(TAG, "onClose:" + reason + "remote:" + remote);
+        if (connectFlag) {
+            try {
+                Thread.sleep(3000);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            this.iEvent.reConnect();
+        } else {
+            this.iEvent.logout("onClose");
+        }
+
+    }
+
+    @Override
+    public void onError(Exception ex) {
+        Log.e(TAG, "onError:" + ex.toString());
+        this.iEvent.logout("onError");
+        connectFlag = false;
+    }
+
+    @Override
+    public void onOpen(ServerHandshake handshakedata) {
+        Log.e(TAG, "onOpen");
+        this.iEvent.onOpen();
+        connectFlag = true;
+    }
+
+    @Override
+    public void onMessage(String message) {
+        Log.d(TAG, message);
+        handleMessage(message);
+    }
+
+
+    public void setConnectFlag(boolean flag) {
+        connectFlag = flag;
+    }
+
+    // ---------------------------------------处理接收消息-------------------------------------
+
+    private void handleMessage(String message) {
+        Map map = JSON.parseObject(message, Map.class);
+        String eventType = (String) map.get("eventType");
+        if (eventType == null) return;
+        // 登录成功
+        if (eventType.equals("__login_success")) {
+            handleLogin(map);
+            return;
+        }
+        // 被邀请
+        if (eventType.equals("__invite")) {
+            handleInvite(map);
+            return;
+        }
+        // 取消拨出
+        if (eventType.equals("__cancel")) {
+            handleCancel(map);
+            return;
+        }
+        // 响铃
+        if (eventType.equals("__ring")) {
+            handleRing(map);
+            return;
+        }
+        // 进入房间
+        if (eventType.equals("__peers")) {
+            handlePeers(map);
+            return;
+        }
+        // 新人入房间
+        if (eventType.equals("__new_peer")) {
+            handleNewPeer(map);
+            return;
+        }
+        // 拒绝接听
+        if (eventType.equals("__reject")) {
+            handleReject(map);
+            return;
+        }
+        // offer
+        if (eventType.equals("__offer")) {
+            handleOffer(map);
+            return;
+        }
+        // answer
+        if (eventType.equals("__answer")) {
+            handleAnswer(map);
+            return;
+        }
+        // ice-candidate
+        if (eventType.equals("__ice_candidate")) {
+            handleIceCandidate(map);
+        }
+        // 离开房间
+        if (eventType.equals("__leave")) {
+            handleLeave(map);
+        }
+        // 切换到语音
+        if (eventType.equals("__audio")) {
+            handleTransAudio(map);
+        }
+        // 意外断开
+        if (eventType.equals("__disconnect")) {
+            handleDisConnect(map);
+        }
+
+
+    }
+
+    private void handleDisConnect(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String fromId = (String) data.get("fromID");
+            this.iEvent.onDisConnect(fromId);
+        }
+    }
+
+    private void handleTransAudio(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String fromId = (String) data.get("fromID");
+            this.iEvent.onTransAudio(fromId);
+        }
+    }
+
+    private void handleLogin(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String userID = (String) data.get("userID");
+            this.iEvent.loginSuccess(userID);
+        }
+
+
+    }
+
+    private void handleIceCandidate(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String userID = (String) data.get("fromID");
+            String id = (String) data.get("id");
+            int label = (int) data.get("label");
+            String candidate = (String) data.get("candidate");
+            this.iEvent.onIceCandidate(userID, id, label, candidate);
+        }
+    }
+
+    private void handleAnswer(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String sdp = (String) data.get("sdp");
+            String userID = (String) data.get("fromID");
+            this.iEvent.onAnswer(userID, sdp);
+        }
+    }
+
+    private void handleOffer(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String sdp = (String) data.get("sdp");
+            String userID = (String) data.get("fromID");
+            this.iEvent.onOffer(userID, sdp);
+        }
+    }
+
+    private void handleReject(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String fromID = (String) data.get("fromID");
+            int rejectType = Integer.parseInt(String.valueOf(data.get("refuseType")));
+            this.iEvent.onReject(fromID, rejectType);
+        }
+    }
+
+    private void handlePeers(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String you = (String) data.get("you");
+            String connections = (String) data.get("connections");
+            int roomSize = (int) data.get("roomSize");
+            String frameName = (String) data.get("frameName");
+            this.iEvent.onPeers(you, connections, roomSize, frameName);
+        }
+    }
+
+    private void handleNewPeer(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String userID = (String) data.get("userID");
+            this.iEvent.onNewPeer(userID);
+        }
+    }
+
+    private void handleRing(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String fromId = (String) data.get("fromID");
+            this.iEvent.onRing(fromId);
+        }
+    }
+
+    private void handleCancel(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String inviteID = (String) data.get("inviteID");
+            String userList = (String) data.get("userList");
+            this.iEvent.onCancel(inviteID);
+        }
+    }
+
+    private void handleInvite(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String room = (String) data.get("room");
+            boolean audioOnly = (boolean) data.get("audioOnly");
+            String inviteID = (String) data.get("inviteID");
+            String userList = (String) data.get("userList");
+            String frameName = (String) data.get("frameName");
+            this.iEvent.onInvite(room, audioOnly, inviteID, frameName, userList);
+        }
+    }
+
+    private void handleLeave(Map map) {
+        Map data = (Map) map.get("data");
+        if (data != null) {
+            String fromID = (String) data.get("fromID");
+            this.iEvent.onLeave(fromID);
+        }
+    }
+
+    //------------------------------发送消息----------------------------------------
+
+    /**
+     * 创建对话房间
+     * @param room  房间ID
+     * @param roomSize  房间人数限制
+     * @param myId  自身设备ID
+     * @param frameName 自身空间名称
+     */
+    public void createRoom(String room, int roomSize, String myId, String frameName) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("eventType", "__create");
+
+        Map<String, Object> childMap = new HashMap<>();
+        childMap.put("room", room);
+        childMap.put("roomSize", roomSize);
+        childMap.put("userID", myId);
+        childMap.put("frameName", frameName);
+
+        map.put("data", childMap);
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        send(jsonString);
+    }
+
+    // 发送邀请
+    public void sendInvite(String room, String myId, List<String> users, String frameName, boolean audioOnly) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("eventType", "__invite");
+
+        Map<String, Object> childMap = new HashMap<>();
+        childMap.put("room", room);
+        childMap.put("audioOnly", audioOnly);
+        childMap.put("inviteID", myId);
+        childMap.put("frameName", frameName);
+
+        String join = Utils.listToString(users);
+        childMap.put("userList", join);
+
+        map.put("data", childMap);
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        send(jsonString);
+    }
+
+    // 取消邀请
+    public void sendCancel(String mRoomId, String useId, List<String> users) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("eventType", "__cancel");
+
+        Map<String, Object> childMap = new HashMap<>();
+        childMap.put("inviteID", useId);
+        childMap.put("room", mRoomId);
+
+        String join = Utils.listToString(users);
+        childMap.put("userList", join);
+
+
+        map.put("data", childMap);
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        send(jsonString);
+    }
+
+    // 发送响铃通知
+    public void sendRing(String myId, String toId, String room) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("eventType", "__ring");
+
+        Map<String, Object> childMap = new HashMap<>();
+        childMap.put("fromID", myId);
+        childMap.put("toID", toId);
+        childMap.put("room", room);
+
+
+        map.put("data", childMap);
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        send(jsonString);
+    }
+
+    //加入房间
+    public void sendJoin(String room, String myId) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("eventType", "__join");
+
+        Map<String, String> childMap = new HashMap<>();
+        childMap.put("room", room);
+        childMap.put("userID", myId);
+
+
+        map.put("data", childMap);
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        send(jsonString);
+    }
+
+    // 拒接接听
+    public void sendRefuse(String room, String inviteID, String myId, int refuseType) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("eventType", "__reject");
+
+        Map<String, Object> childMap = new HashMap<>();
+        childMap.put("room", room);
+        childMap.put("toID", inviteID);
+        childMap.put("fromID", myId);
+        childMap.put("refuseType", String.valueOf(refuseType));
+
+        map.put("data", childMap);
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        send(jsonString);
+    }
+
+    // 离开房间
+    public void sendLeave(String myId, String room, String userId) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("eventType", "__leave");
+
+        Map<String, Object> childMap = new HashMap<>();
+        childMap.put("room", room);
+        childMap.put("fromID", myId);
+        childMap.put("userID", userId);
+
+        map.put("data", childMap);
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        if (isOpen()) {
+            send(jsonString);
+        }
+    }
+
+    // send offer
+    public void sendOffer(String myId, String userId, String sdp) {
+        Map<String, Object> map = new HashMap<>();
+        Map<String, Object> childMap = new HashMap<>();
+        childMap.put("sdp", sdp);
+        childMap.put("userID", userId);
+        childMap.put("fromID", myId);
+        map.put("data", childMap);
+        map.put("eventType", "__offer");
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        send(jsonString);
+    }
+
+    // send answer
+    public void sendAnswer(String myId, String userId, String sdp) {
+        Map<String, Object> map = new HashMap<>();
+        Map<String, Object> childMap = new HashMap<>();
+        childMap.put("sdp", sdp);
+        childMap.put("fromID", myId);
+        childMap.put("userID", userId);
+        map.put("data", childMap);
+        map.put("eventType", "__answer");
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        send(jsonString);
+    }
+
+    // send ice-candidate
+    public void sendIceCandidate(String myId, String userId, String id, int label, String candidate) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("eventType", "__ice_candidate");
+
+        Map<String, Object> childMap = new HashMap<>();
+        childMap.put("userID", userId);
+        childMap.put("fromID", myId);
+        childMap.put("id", id);
+        childMap.put("label", label);
+        childMap.put("candidate", candidate);
+
+        map.put("data", childMap);
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        if (isOpen()) {
+            send(jsonString);
+        }
+    }
+
+    // 切换到语音
+    public void sendTransAudio(String myId, String userId) {
+        Map<String, Object> map = new HashMap<>();
+        Map<String, Object> childMap = new HashMap<>();
+        childMap.put("fromID", myId);
+        childMap.put("userID", userId);
+        map.put("data", childMap);
+        map.put("eventType", "__audio");
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        send(jsonString);
+    }
+
+    // 断开重连
+    public void sendDisconnect(String room, String myId, String userId) {
+        Map<String, Object> map = new HashMap<>();
+        Map<String, Object> childMap = new HashMap<>();
+        childMap.put("fromID", myId);
+        childMap.put("userID", userId);
+        childMap.put("room", room);
+        map.put("data", childMap);
+        map.put("eventType", "__disconnect");
+        JSONObject object = new JSONObject(map);
+        final String jsonString = object.toString();
+        Log.d(TAG, "send-->" + jsonString);
+        send(jsonString);
+    }
+
+    // 忽略证书
+    public static class TrustManagerTest implements X509TrustManager {
+
+        @SuppressLint("TrustAllX509TrustManager")
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+
+        }
+
+        @SuppressLint("TrustAllX509TrustManager")
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+
+        }
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[0];
+        }
+    }
+
+}

+ 358 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/SocketManager.java

@@ -0,0 +1,358 @@
+package com.wdkl.app.ncs.conversion_box.rtc.client;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.wdkl.ncs.android.lib.base.BaseApplication;
+import com.wdkl.ncs.janus.rtc2.CallSession;
+import com.wdkl.ncs.janus.rtc2.EnumType;
+import com.wdkl.ncs.janus.rtc2.SkyEngineKit;
+
+import java.lang.ref.WeakReference;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.SecureRandom;
+import java.util.List;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+
+/**
+ * Created by dds on 2019/7/26.
+ * ddssignsong@163.com
+ */
+public class SocketManager implements IEvent {
+    private final static String TAG = "dds_SocketManager";
+    private RtcSignalClient webSocket;
+    private int userState;
+    private String myId;
+
+    private final Handler handler = new Handler(Looper.getMainLooper());
+
+    private SocketManager() {
+
+    }
+
+    private static class Holder {
+        private static final SocketManager socketManager = new SocketManager();
+    }
+
+    public static SocketManager getInstance() {
+        return Holder.socketManager;
+    }
+
+    public void connect(String url, String userId) {
+        System.out.println(">>>>>>>>>>>>>" + url);
+        if (webSocket == null || !webSocket.isOpen()) {
+            URI uri;
+            try {
+                String urls = url + "/" + userId;
+                uri = new URI(urls);
+            } catch (URISyntaxException e) {
+                e.printStackTrace();
+                return;
+            }
+            webSocket = new RtcSignalClient(uri, this);
+            // 设置wss
+            if (url.startsWith("wss")) {
+                try {
+                    SSLContext sslContext = SSLContext.getInstance("TLS");
+                    if (sslContext != null) {
+                        sslContext.init(null, new TrustManager[]{new RtcSignalClient.TrustManagerTest()}, new SecureRandom());
+                    }
+
+                    SSLSocketFactory factory = null;
+                    if (sslContext != null) {
+                        factory = sslContext.getSocketFactory();
+                    }
+
+                    if (factory != null) {
+                        webSocket.setSocket(factory.createSocket());
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            // 开始connect
+            System.out.println(">>>>>>>>>>>>> 开始连接");
+            webSocket.connect();
+        }
+
+
+    }
+
+    public void unConnect() {
+        if (webSocket != null) {
+            webSocket.setConnectFlag(false);
+            webSocket.close();
+            webSocket = null;
+        }
+
+    }
+
+    @Override
+    public void onOpen() {
+        Log.i(TAG, "socket is open!");
+
+    }
+
+    @Override
+    public void loginSuccess(String userId) {
+        Log.i(TAG, "loginSuccess:" + userId);
+        myId = userId;
+        userState = 1;
+        if (iUserState != null && iUserState.get() != null) {
+            iUserState.get().userLogin();
+        }
+    }
+
+
+    // ======================================================================================
+    public void createRoom(String room, int roomSize, String frameName) {
+        if (webSocket != null) {
+            webSocket.createRoom(room, roomSize, myId, frameName);
+        }
+
+    }
+
+    public void sendInvite(String room, List<String> users, String frameName, boolean audioOnly) {
+        if (webSocket != null) {
+            webSocket.sendInvite(room, myId, users, frameName, audioOnly);
+        }
+    }
+
+    public void sendLeave(String room, String userId) {
+        if (webSocket != null) {
+            webSocket.sendLeave(myId, room, userId);
+        }
+    }
+
+    public void sendRingBack(String targetId, String room) {
+        if (webSocket != null) {
+            webSocket.sendRing(myId, targetId, room);
+        }
+    }
+
+    public void sendRefuse(String room, String inviteId, int refuseType) {
+        if (webSocket != null) {
+            webSocket.sendRefuse(room, inviteId, myId, refuseType);
+        }
+    }
+
+    public void sendCancel(String mRoomId, List<String> userIds) {
+        if (webSocket != null) {
+            webSocket.sendCancel(mRoomId, myId, userIds);
+        }
+    }
+
+    public void sendJoin(String room) {
+        if (webSocket != null) {
+            webSocket.sendJoin(room, myId);
+        }
+    }
+
+    public void sendMeetingInvite(String userList) {
+
+    }
+
+    public void sendOffer(String userId, String sdp) {
+        if (webSocket != null) {
+            webSocket.sendOffer(myId, userId, sdp);
+        }
+    }
+
+    public void sendAnswer(String userId, String sdp) {
+        if (webSocket != null) {
+            webSocket.sendAnswer(myId, userId, sdp);
+        }
+    }
+
+    public void sendIceCandidate(String userId, String id, int label, String candidate) {
+        if (webSocket != null) {
+            webSocket.sendIceCandidate(myId, userId, id, label, candidate);
+        }
+    }
+
+    public void sendTransAudio(String userId) {
+        if (webSocket != null) {
+            webSocket.sendTransAudio(myId, userId);
+        }
+    }
+
+    public void sendDisconnect(String room, String userId) {
+        if (webSocket != null) {
+            webSocket.sendDisconnect(room, myId, userId);
+        }
+    }
+
+
+    // ========================================================================================
+    @Override
+    public void onInvite(String room, boolean audioOnly, String inviteId, String frameName, String userList) {
+        Intent intent = new Intent();
+        intent.putExtra("room", room);
+        intent.putExtra("audioOnly", audioOnly);
+        intent.putExtra("inviteId", inviteId);
+        intent.putExtra("userList", userList);
+        intent.putExtra("frameName", frameName);
+        intent.setAction(Utils.ACTION_CALLIN);
+        intent.setComponent(new ComponentName(BaseApplication.appContext.getPackageName(), VoipReceiver.class.getName()));
+        // 发送广播
+        BaseApplication.appContext.sendBroadcast(intent);
+
+    }
+
+    @Override
+    public void onCancel(String inviteId) {
+        handler.post(() -> {
+            CallSession currentSession = SkyEngineKit.Instance().getCurrentSession();
+            if (currentSession != null) {
+                currentSession.onCancel(inviteId);
+            }
+        });
+
+    }
+
+    @Override
+    public void onRing(String fromId) {
+        handler.post(() -> {
+            CallSession currentSession = SkyEngineKit.Instance().getCurrentSession();
+            if (currentSession != null) {
+                currentSession.onRingBack(fromId);
+            }
+        });
+
+
+    }
+
+    @Override  // 加入房间
+    public void onPeers(String myId, String connections, int roomSize, String frameName) {
+        handler.post(() -> {
+            //自己进入了房间,然后开始发送offer
+            CallSession currentSession = SkyEngineKit.Instance().getCurrentSession();
+            if (currentSession != null) {
+                currentSession.onJoinHome(myId, connections, roomSize, frameName);
+            }
+        });
+
+    }
+
+    @Override
+    public void onNewPeer(String userId) {
+        handler.post(() -> {
+            CallSession currentSession = SkyEngineKit.Instance().getCurrentSession();
+            if (currentSession != null) {
+                currentSession.newPeer(userId);
+            }
+        });
+
+    }
+
+    @Override
+    public void onReject(String userId, int type) {
+        handler.post(() -> {
+            CallSession currentSession = SkyEngineKit.Instance().getCurrentSession();
+            if (currentSession != null) {
+                currentSession.onRefuse(userId, type);
+            }
+        });
+
+    }
+
+    @Override
+    public void onOffer(String userId, String sdp) {
+        handler.post(() -> {
+            CallSession currentSession = SkyEngineKit.Instance().getCurrentSession();
+            if (currentSession != null) {
+                currentSession.onReceiveOffer(userId, sdp);
+            }
+        });
+
+
+    }
+
+    @Override
+    public void onAnswer(String userId, String sdp) {
+        handler.post(() -> {
+            CallSession currentSession = SkyEngineKit.Instance().getCurrentSession();
+            if (currentSession != null) {
+                currentSession.onReceiverAnswer(userId, sdp);
+            }
+        });
+
+    }
+
+    @Override
+    public void onIceCandidate(String userId, String id, int label, String candidate) {
+        handler.post(() -> {
+            CallSession currentSession = SkyEngineKit.Instance().getCurrentSession();
+            if (currentSession != null) {
+                currentSession.onRemoteIceCandidate(userId, id, label, candidate);
+            }
+        });
+
+    }
+
+    @Override
+    public void onLeave(String userId) {
+        handler.post(() -> {
+            CallSession currentSession = SkyEngineKit.Instance().getCurrentSession();
+            if (currentSession != null) {
+                currentSession.onLeave(userId);
+            }
+        });
+    }
+
+    @Override
+    public void logout(String str) {
+        Log.i(TAG, "logout:" + str);
+        userState = 0;
+        if (iUserState != null && iUserState.get() != null) {
+            iUserState.get().userLogout();
+        }
+    }
+
+    @Override
+    public void onTransAudio(String userId) {
+        handler.post(() -> {
+            CallSession currentSession = SkyEngineKit.Instance().getCurrentSession();
+            if (currentSession != null) {
+                currentSession.onTransAudio(userId);
+            }
+        });
+    }
+
+    @Override
+    public void onDisConnect(String userId) {
+        handler.post(() -> {
+            CallSession currentSession = SkyEngineKit.Instance().getCurrentSession();
+            if (currentSession != null) {
+                currentSession.onDisConnect(userId, EnumType.CallEndReason.RemoteSignalError);
+            }
+        });
+    }
+
+    @Override
+    public void reConnect() {
+        handler.post(() -> {
+            webSocket.reconnect();
+        });
+    }
+    //===========================================================================================
+
+
+    public int getUserState() {
+        return userState;
+    }
+
+    private WeakReference<IUserState> iUserState;
+
+    public void addUserStateCallback(IUserState userState) {
+        iUserState = new WeakReference<>(userState);
+    }
+
+}

+ 42 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/Utils.java

@@ -0,0 +1,42 @@
+package com.wdkl.app.ncs.conversion_box.rtc.client;
+
+import android.app.ActivityManager;
+import android.app.Application;
+import android.content.Context;
+
+import java.util.List;
+
+public class Utils {
+    public static String SIGNAL_SERVER_ON = "SIGNAL_SERVER_ON";
+    public static String SIGNAL_SERVER_OFF = "SIGNAL_SERVER_OFF";
+    public static String ACTION_CALLIN = "CALLIN";
+    public static String ACTION_RING_STOP = "RING_STOP";
+
+    public static String listToString(List<String> mList) {
+        final String SEPARATOR = ",";
+        StringBuilder sb = new StringBuilder();
+        String convertedListStr;
+        if (null != mList && mList.size() > 0) {
+            for (String item : mList) {
+                sb.append(item);
+                sb.append(SEPARATOR);
+            }
+            convertedListStr = sb.toString();
+            convertedListStr = convertedListStr.substring(0, convertedListStr.length() - SEPARATOR.length());
+            return convertedListStr;
+        } else return "";
+    }
+
+    public static boolean isAppRunningForeground(Context context) {
+        ActivityManager activityManager =
+                (ActivityManager) context.getSystemService(Application.ACTIVITY_SERVICE);
+        List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
+        for (ActivityManager.RunningAppProcessInfo appProcessInfo : runningAppProcesses) {
+            if (appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+                if (appProcessInfo.processName.equals(context.getApplicationInfo().processName))
+                    return true;
+            }
+        }
+        return false;
+    }
+}

+ 103 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/VoipEvent.java

@@ -0,0 +1,103 @@
+package com.wdkl.app.ncs.conversion_box.rtc.client;
+
+import android.util.Log;
+
+import com.wdkl.ncs.janus.rtc2.inter.ISkyEvent;
+
+import java.util.List;
+
+public class VoipEvent implements ISkyEvent {
+    private static final String TAG = "VoipEvent";
+    private AsyncPlayer ringPlayer;
+
+    public VoipEvent() {
+        ringPlayer = new AsyncPlayer(TAG);
+    }
+
+    @Override
+    public void createRoom(String room, int roomSize, String frameName) {
+        SocketManager.getInstance().createRoom(room, roomSize, frameName);
+    }
+
+    @Override
+    public void sendInvite(String room, List<String> userIds, String frameName, boolean audioOnly) {
+        SocketManager.getInstance().sendInvite(room, userIds, frameName, audioOnly);
+    }
+
+    @Override
+    public void sendRefuse(String room, String inviteId, int refuseType) {
+        SocketManager.getInstance().sendRefuse(room, inviteId, refuseType);
+    }
+
+    @Override
+    public void sendTransAudio(String toId) {
+        SocketManager.getInstance().sendTransAudio(toId);
+    }
+
+    @Override
+    public void sendDisConnect(String room, String toId, boolean isCrashed) {
+        SocketManager.getInstance().sendDisconnect(room, toId);
+    }
+
+    @Override
+    public void sendCancel(String mRoomId, List<String> toIds) {
+        SocketManager.getInstance().sendCancel(mRoomId, toIds);
+    }
+
+
+    @Override
+    public void sendJoin(String room) {
+        SocketManager.getInstance().sendJoin(room);
+    }
+
+    @Override
+    public void sendRingBack(String targetId, String room) {
+        SocketManager.getInstance().sendRingBack(targetId, room);
+    }
+
+    @Override
+    public void sendLeave(String room, String userId) {
+        SocketManager.getInstance().sendLeave(room, userId);
+    }
+
+
+    @Override
+    public void sendOffer(String userId, String sdp) {
+        SocketManager.getInstance().sendOffer(userId, sdp);
+    }
+
+    @Override
+    public void sendAnswer(String userId, String sdp) {
+        SocketManager.getInstance().sendAnswer(userId, sdp);
+
+    }
+
+    @Override
+    public void sendIceCandidate(String userId, String id, int label, String candidate) {
+        SocketManager.getInstance().sendIceCandidate(userId, id, label, candidate);
+    }
+
+    @Override
+    public void onRemoteRing() {
+
+    }
+
+
+    //==============================================================================
+    @Override
+    public void shouldStartRing(boolean isComing) {
+        /*if (isComing) {
+            Uri uri = Uri.parse("android.resource://" + App.getInstance().getPackageName() + "/" + R.raw.incoming_call_ring);
+            ringPlayer.play(App.getInstance(), uri, true, AudioManager.STREAM_RING);
+        } else {
+            Uri uri = Uri.parse("android.resource://" + App.getInstance().getPackageName() + "/" + R.raw.wr_ringback);
+            ringPlayer.play(App.getInstance(), uri, true, AudioManager.STREAM_RING);
+        }*/
+    }
+
+    @Override
+    public void shouldStopRing() {
+        Log.d(TAG, "shouldStopRing begin");
+        ringPlayer.stop();
+    }
+}

+ 59 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/rtc/client/VoipReceiver.java

@@ -0,0 +1,59 @@
+package com.wdkl.app.ncs.conversion_box.rtc.client;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.wdkl.ncs.android.middleware.common.Constant;
+import com.wdkl.ncs.android.middleware.common.MessageEvent;
+import com.wdkl.ncs.janus.rtc2.SkyEngineKit;
+import com.wdkl.ncs.janus.rtc2.except.NotInitializedException;
+
+import org.greenrobot.eventbus.EventBus;
+
+public class VoipReceiver extends BroadcastReceiver {
+    private static final String TAG = "VoipReceiver";
+
+    private SkyEngineKit gEngineKit = null;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+
+        String action = intent.getAction();
+        if (Utils.ACTION_CALLIN.equals(action)) {
+            try {
+                gEngineKit = SkyEngineKit.Instance();
+            } catch (NotInitializedException e) {
+                SkyEngineKit.init(new VoipEvent()); //重新初始化
+                try {
+                    gEngineKit = SkyEngineKit.Instance();
+                } catch (NotInitializedException ex) {
+                    throw ex;
+                }
+            }
+
+            String room = intent.getStringExtra("room");
+            boolean audioOnly = intent.getBooleanExtra("audioOnly", true);
+            String inviteId = intent.getStringExtra("inviteId");
+            String frameName = intent.getStringExtra("frameName");
+            //处理响铃 或 回复忙线
+            // 注意: 这里需要提高 context 级别,否则报错 BroadcastReceiver components are not allowed to register to receive intents
+            if (gEngineKit.startInCall(context.getApplicationContext(), room, inviteId, true)) {
+                EventBus.getDefault().post(new MessageEvent("audio_call", Constant.EVENT_SIP_CALL_STATUS));
+
+                /*Intent voip = new Intent(context, RtcCallActivity.class);
+                voip.putExtra(RtcCallActivity.EXTRA_IS_COMING, true);
+                voip.putExtra(RtcCallActivity.EXTRA_ROOM, room);
+                voip.putExtra(RtcCallActivity.EXTRA_USER_ID, inviteId);
+                voip.putExtra(RtcCallActivity.EXTRA_FRAME_NAME, frameName);
+                voip.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                App.getInstance().startActivity(voip);*/
+            }
+
+//            shouldStartRing(true);
+        } else if (Utils.ACTION_RING_STOP.equals(action)){
+//            shouldStopRing();
+        }
+
+    }
+}

+ 120 - 0
conversion_box/src/main/java/com/wdkl/app/ncs/conversion_box/service/AppService.kt

@@ -0,0 +1,120 @@
+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.os.Binder
+import android.os.Build
+import android.os.IBinder
+import com.wdkl.app.ncs.conversion_box.R
+import com.wdkl.app.ncs.conversion_box.helper.AppUpdateHelper
+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.android.middleware.udp2.UdpIndex
+import com.wdkl.ncs.android.middleware.udp2.UdpItem
+import com.wdkl.ncs.android.middleware.utils.CommonUtils
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+
+class AppService : Service() {
+    private val TAG = AppService::class.java.simpleName
+    internal var myBinder = ServiceBinder()
+
+    private var notificationManager: NotificationManager? = null
+    private var notificationId: String = "channelId-0"
+    private var notificationName: String = "wdkl-converter"
+
+    override fun onCreate() {
+        super.onCreate()
+        EventBus.getDefault().register(this)
+
+        notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            var channel = NotificationChannel(
+                    notificationId,
+                    notificationName,
+                    NotificationManager.IMPORTANCE_HIGH
+            );
+            notificationManager!!.createNotificationChannel(channel)
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            startForeground(110, getNotification())//开前台服务
+        }
+    }
+
+    private fun getNotification(): Notification {
+        var builder: Notification.Builder = Notification.Builder(this)
+                .setSmallIcon(R.mipmap.ic_launcher)
+                .setContentTitle("service")
+                .setContentText("service is running")
+                .setOnlyAlertOnce(true)
+        //设置Notification的ChannelID,否则不能正常显示
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            builder.setChannelId(notificationId);
+        }
+        var notification: Notification = builder.build();
+        return notification;
+    }
+
+    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+        return super.onStartCommand(intent, flags, startId)
+    }
+
+
+    override fun onBind(intent: Intent): IBinder {
+        return myBinder
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            stopForeground(true)// 停止前台服务--参数:表示是否移除之前的通知
+        }
+
+        EventBus.getDefault().unregister(this)
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        when(messageEvent.type) {
+            Constant.EVENT_UDP -> {
+                if (messageEvent.message is UdpItem) {
+                    val udpItem = messageEvent.message as UdpItem
+                    if (UdpIndex.SERVER_MODE_OFFLINE.equals(udpItem.index)) {
+                        //如果当前是在线模式则启动离线模式
+                        if (CommonUtils.getStartMode(BaseApplication.appContext) != 0) {
+                            CommonUtils.setStartMode(BaseApplication.appContext, 0)
+                            CommonUtils.setHostIp(BaseApplication.appContext, udpItem.targetIpAddr)
+                            AppUpdateHelper.restartApp(BaseApplication.appContext)
+                        } else {
+                            val ip = CommonUtils.getHostIp(BaseApplication.appContext)
+                            if (!ip.equals(udpItem.targetIpAddr)) {
+                                CommonUtils.setHostIp(BaseApplication.appContext, udpItem.targetIpAddr)
+                                AppUpdateHelper.restartApp(BaseApplication.appContext)
+                            }
+                        }
+                    } else if (UdpIndex.SERVER_MODE_ONLINE.equals(udpItem.index)) {
+                        //如果当前是离线模式则启动在线模式
+                        if (CommonUtils.getStartMode(BaseApplication.appContext) != 1) {
+                            CommonUtils.setStartMode(BaseApplication.appContext, 1)
+                            AppUpdateHelper.restartApp(BaseApplication.appContext)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+
+    inner class ServiceBinder : Binder() {
+        fun doThings() {}
+    }
+
+}

+ 217 - 0
conversion_box/src/main/res/layout/offline_main_activity_layout.xml

@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout>
+    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#EAF2F9">
+
+        <RelativeLayout
+            android:id="@+id/activity_calling_bed_layout_title"
+            android:layout_width="match_parent"
+            android:layout_height="42dp"
+            android:background="#FFFFFF"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/view_title_layout_tv_point"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                android:layout_centerVertical="true"
+                android:layout_marginLeft="10dp"
+                android:background="@color/red_color"/>
+
+            <!--医院名称-->
+            <TextView
+                android:id="@+id/view_title_layout_tv_hospital_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_marginLeft="10dp"
+                android:layout_toRightOf="@id/view_title_layout_tv_point"
+                android:text="----"
+                android:textColor="@color/main_color"
+                android:textSize="@dimen/font_size_20" />
+
+            <!--日期时间-->
+            <TextClock
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerHorizontal="true"
+                android:layout_centerVertical="true"
+                android:format12Hour="yyyy-MM-dd HH:mm EEEE"
+                android:format24Hour="yyyy-MM-dd HH:mm EEEE"
+                android:timeZone="GMT+8"
+                android:textColor="@color/main_color"
+                android:textSize="@dimen/font_size_20" />
+
+            <!--设备号-->
+            <TextView
+                android:id="@+id/view_title_layout_tv_no"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_marginRight="10dp"
+                android:layout_toLeftOf="@+id/view_title_layout_ll_right"
+                android:text="设备ID:"
+                android:textColor="@color/main_color"
+                android:textSize="@dimen/font_size_20" />
+
+            <!--状态图标-->
+            <LinearLayout
+                android:id="@+id/view_title_layout_ll_right"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:layout_centerVertical="true"
+                android:layout_marginRight="10dp"
+                android:orientation="horizontal">
+
+                <ImageView
+                    android:id="@+id/view_title_layout_iv_tcp"
+                    android:layout_width="20dp"
+                    android:layout_height="20dp"
+                    android:layout_gravity="center_vertical"
+                    android:layout_marginLeft="4dp"
+                    android:layout_marginRight="4dp"
+                    android:src="@mipmap/ic_tcp_fail"/>
+
+            </LinearLayout>
+        </RelativeLayout>
+
+        <!--底部按钮区域-->
+        <RelativeLayout
+            android:id="@+id/ll_bottom"
+            android:layout_width="match_parent"
+            android:layout_height="60dp"
+            android:layout_alignParentBottom="true"
+            android:background="@color/main_color"
+            android:gravity="center_vertical"
+            android:paddingLeft="20dp"
+            android:paddingRight="20dp">
+
+            <TextView
+                android:id="@+id/tv_btn_home"
+                android:layout_width="140dp"
+                android:layout_height="wrap_content"
+                android:paddingLeft="32dp"
+                android:paddingTop="6dp"
+                android:paddingBottom="6dp"
+                android:background="@mipmap/bg_bottom_btn"
+                android:drawableLeft="@mipmap/ic_home"
+                android:drawablePadding="10dp"
+                android:text="首页"
+                android:textColor="@drawable/selector_bottom_btn_text_color"
+                android:textSize="24sp"/>
+
+            <TextView
+                android:id="@+id/tv_version"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:text="V1.0"
+                android:textColor="@drawable/selector_bottom_btn_text_color"
+                android:textSize="24sp"/>
+
+        </RelativeLayout>
+
+        <!--设备列表fragment区域-->
+        <FrameLayout
+            android:id="@+id/conversion_main_frame"
+            android:layout_width="580dp"
+            android:layout_height="400dp"
+            android:layout_marginTop="10dp"
+            android:layout_marginBottom="10dp"
+            android:layout_marginRight="10dp"
+            android:layout_below="@id/activity_calling_bed_layout_title" />
+
+        <!--通话界面-->
+        <FrameLayout
+            android:id="@+id/call_frame"
+            android:layout_width="580dp"
+            android:layout_height="match_parent"
+            android:layout_marginRight="10dp"
+            android:layout_below="@id/conversion_main_frame"
+            android:layout_above="@id/ll_bottom" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_toRightOf="@id/conversion_main_frame"
+            android:layout_below="@id/activity_calling_bed_layout_title"
+            android:layout_above="@id/ll_bottom"
+            android:orientation="vertical">
+            <TextView
+                android:id="@+id/tv_mac_addr"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@color/color_red"
+                android:textSize="20sp"
+                android:text="MAC:"/>
+            <TextView
+                android:id="@+id/tv_mcu"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@color/color_red"
+                android:textSize="20sp"
+                android:text="MCU:"
+                android:visibility="gone"/>
+            <TextView
+                android:id="@+id/tv_device_status"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:textSize="16sp"
+                android:textColor="@color/main_color"
+                android:text="设备状态:" />
+            <TextView
+                android:id="@+id/tv_call_state"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:textSize="16sp"
+                android:textColor="@color/main_color"
+                android:text="呼叫状态: 待机中" />
+            <TextView
+                android:id="@+id/tv_uart0_info"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:textSize="16sp"
+                android:textColor="@color/main_color"
+                android:text="串口0:" />
+            <TextView
+                android:id="@+id/tv_uart_info"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:textSize="16sp"
+                android:textColor="@color/main_color"
+                android:text="串口1:" />
+            <TextView
+                android:id="@+id/tv_tcp_info"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:textSize="16sp"
+                android:textColor="@color/main_color"
+                android:text="Received TCP:" />
+            <TextView
+                android:id="@+id/tv_send_tcp_info"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:textSize="16sp"
+                android:textColor="@color/main_color"
+                android:text="Send TCP:" />
+            <TextView
+                android:id="@+id/tv_serial_device"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="2dp"
+                android:textSize="16sp"
+                android:textColor="@color/main_color"
+                android:text=""/>
+        </LinearLayout>
+
+    </RelativeLayout>
+</layout>

+ 196 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/AVEngine.java

@@ -0,0 +1,196 @@
+package com.wdkl.ncs.janus.rtc2;
+
+import android.util.Log;
+import android.view.View;
+
+import com.wdkl.ncs.janus.rtc2.engine.EngineCallback;
+import com.wdkl.ncs.janus.rtc2.engine.IEngine;
+
+import java.util.List;
+
+public class AVEngine implements IEngine {
+    private static final String TAG = "AVEngine";
+    private final IEngine iEngine;
+    private static volatile AVEngine instance;
+
+    private AVEngine(IEngine engine) {
+        iEngine = engine;
+    }
+
+    public static AVEngine createEngine(IEngine engine) {
+        if (null == instance) {
+            synchronized (AVEngine.class) {
+                if (null == instance) {
+                    instance = new AVEngine(engine);
+                }
+            }
+        }
+
+        return instance;
+    }
+
+    @Override
+    public void init(EngineCallback callback) {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.init(callback);
+    }
+
+    @Override
+    public void joinRoom(List<String> userIds) {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.joinRoom(userIds);
+    }
+
+    @Override
+    public void userIn(String userId) {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.userIn(userId);
+    }
+
+    @Override
+    public void userReject(String userId, int type) {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.userReject(userId, type);
+    }
+
+    @Override
+    public void disconnected(String userId, EnumType.CallEndReason reason) {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.disconnected(userId, reason);
+    }
+
+    @Override
+    public void receiveOffer(String userId, String description) {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.receiveOffer(userId, description);
+    }
+
+    @Override
+    public void receiveAnswer(String userId, String sdp) {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.receiveAnswer(userId, sdp);
+    }
+
+    @Override
+    public void receiveIceCandidate(String userId, String id, int label, String candidate) {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.receiveIceCandidate(userId, id, label, candidate);
+    }
+
+
+    @Override
+    public void leaveRoom(String userId) {
+        Log.d(TAG, "leaveRoom iEngine = " + iEngine);
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.leaveRoom(userId);
+    }
+
+    @Override
+    public View startPreview(boolean isO) {
+        if (iEngine == null) {
+            return null;
+        }
+        return iEngine.startPreview(isO);
+    }
+
+    @Override
+    public void stopPreview() {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.stopPreview();
+    }
+
+    @Override
+    public void startStream() {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.startStream();
+    }
+
+    @Override
+    public void stopStream() {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.stopStream();
+    }
+
+    @Override
+    public View setupRemoteVideo(String userId, boolean isO) {
+        if (iEngine == null) {
+            return null;
+        }
+        return iEngine.setupRemoteVideo(userId, isO);
+    }
+
+    @Override
+    public void stopRemoteVideo() {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.stopRemoteVideo();
+    }
+
+
+    @Override
+    public void switchCamera() {
+        if (iEngine == null) {
+            return;
+        }
+        iEngine.switchCamera();
+    }
+
+    @Override
+    public boolean muteAudio(boolean enable) {
+        if (iEngine == null) {
+            return false;
+        }
+        return iEngine.muteAudio(enable);
+    }
+
+    @Override
+    public boolean toggleSpeaker(boolean enable) {
+        if (iEngine == null) {
+            return false;
+        }
+        return iEngine.toggleSpeaker(enable);
+    }
+
+    @Override
+    public boolean toggleHeadset(boolean isHeadset) {
+        if (iEngine == null) {
+            return false;
+        }
+        return iEngine.toggleHeadset(isHeadset);
+    }
+
+    @Override
+    public void release() {
+        if (iEngine == null) {
+            return;
+        }
+         Log.d(TAG,"release");
+        iEngine.release();
+    }
+
+}

+ 543 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/CallSession.java

@@ -0,0 +1,543 @@
+package com.wdkl.ncs.janus.rtc2;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import com.wdkl.ncs.janus.rtc2.engine.EngineCallback;
+import com.wdkl.ncs.janus.rtc2.engine.webrtc.MyWebRTCEngine;
+import com.wdkl.ncs.janus.rtc2.inter.ISkyEvent;
+
+import org.webrtc.IceCandidate;
+import org.webrtc.SessionDescription;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * 会话层
+ * Created by dds on 2019/8/19.
+ *
+ */
+public class CallSession implements EngineCallback {
+    private static final String TAG = "CallSession";
+    private WeakReference<CallSessionCallback> sessionCallback;
+    private ExecutorService executor;
+    private Handler handler = new Handler(Looper.getMainLooper());
+    // session参数
+    private boolean mIsAudioOnly;
+    // 房间人列表
+    private List<String> mUserIDList;
+    // 单聊对方Id/群聊邀请人
+    public String mTargetId;
+    // 房间Id
+    private String mRoomId;
+    // myId
+    public String mMyId;
+    // 房间大小
+    private int mRoomSize;
+
+    private boolean mIsComing;
+    private EnumType.CallState _callState = EnumType.CallState.Idle;
+    private long startTime;
+
+    private AVEngine iEngine;
+    private ISkyEvent mEvent;
+
+    public CallSession(Context context, String roomId, boolean audioOnly, ISkyEvent event) {
+        executor = Executors.newSingleThreadExecutor();
+        this.mIsAudioOnly = audioOnly;
+        this.mRoomId = roomId;
+
+        this.mEvent = event;
+        iEngine = AVEngine.createEngine(new MyWebRTCEngine(audioOnly, context));
+        iEngine.init(this);
+    }
+
+
+    // ----------------------------------------各种控制--------------------------------------------
+
+    // 创建房间
+    public void createHome(String room, int roomSize, String frameName) {
+        executor.execute(() -> {
+            if (mEvent != null) {
+                mEvent.createRoom(room, roomSize, frameName);
+            }
+        });
+    }
+
+    // 加入房间
+    public void joinHome(String roomId) {
+        executor.execute(() -> {
+            _callState = EnumType.CallState.Connecting;
+             Log.d(TAG, "joinHome mEvent = " + mEvent);
+            setIsComing(true);
+            if (mEvent != null) {
+                mEvent.sendJoin(roomId);
+            }
+        });
+
+    }
+
+    //开始响铃
+    public void shouldStartRing() {
+        if (mEvent != null) {
+            mEvent.shouldStartRing(true);
+        }
+    }
+
+    // 关闭响铃
+    public void shouldStopRing() {
+        Log.d(TAG, "shouldStopRing mEvent != null is " + (mEvent != null));
+        if (mEvent != null) {
+            mEvent.shouldStopRing();
+        }
+    }
+
+    // 发送响铃回复
+    public void sendRingBack(String targetId, String room) {
+        executor.execute(() -> {
+            if (mEvent != null) {
+                mEvent.sendRingBack(targetId, room);
+            }
+        });
+    }
+
+    // 发送拒绝信令
+    public void sendRefuse() {
+        executor.execute(() -> {
+            if (mEvent != null) {
+                // 取消拨出
+                mEvent.sendRefuse(mRoomId, mTargetId, EnumType.RefuseType.Hangup.ordinal());
+            }
+        });
+		release(EnumType.CallEndReason.Hangup);
+    }
+
+    // 发送忙时拒绝
+    void sendBusyRefuse(String room, String targetId) {
+        executor.execute(() -> {
+            if (mEvent != null) {
+                // 取消拨出
+                mEvent.sendRefuse(room, targetId, EnumType.RefuseType.Busy.ordinal());
+            }
+        });
+//		release(EnumType.CallEndReason.Hangup);
+    }
+
+    // 发送取消信令
+    public void sendCancel() {
+        executor.execute(() -> {
+            if (mEvent != null) {
+                // 取消拨出
+                List<String> list = new ArrayList<>();
+                list.add(mTargetId);
+                mEvent.sendCancel(mRoomId, list);
+            }
+        });
+		release(EnumType.CallEndReason.Hangup);
+
+    }
+
+    // 离开房间
+    public void leave() {
+        executor.execute(() -> {
+            if (mEvent != null) {
+                mEvent.sendLeave(mRoomId, mMyId);
+            }
+        });
+        // 释放变量
+        release(EnumType.CallEndReason.Hangup);
+
+    }
+
+    // 切换到语音接听
+    public void sendTransAudio() {
+        executor.execute(() -> {
+            if (mEvent != null) {
+                // 发送到对面,切换到语音
+                mEvent.sendTransAudio(mTargetId);
+            }
+        });
+    }
+
+    // 设置静音
+    public boolean toggleMuteAudio(boolean enable) {
+        return iEngine.muteAudio(enable);
+    }
+
+    // 设置扬声器
+    public boolean toggleSpeaker(boolean enable) {
+
+        return iEngine.toggleSpeaker(enable);
+    }
+
+    // 设置扬声器
+    public boolean toggleHeadset(boolean isHeadset) {
+        return iEngine.toggleHeadset(isHeadset);
+    }
+
+    // 切换到语音通话
+    public void switchToAudio() {
+        mIsAudioOnly = true;
+        // 告诉远端
+        sendTransAudio();
+        // 本地切换
+        if (sessionCallback != null && sessionCallback.get() != null) {
+            sessionCallback.get().didChangeMode(true);
+        }
+
+    }
+
+    // 调整摄像头前置后置
+    public void switchCamera() {
+        iEngine.switchCamera();
+    }
+
+    // 释放资源
+    private void release(EnumType.CallEndReason reason) {
+        executor.execute(() -> {
+            // 释放内容
+            iEngine.release();
+            // 状态设置为Idle
+            _callState = EnumType.CallState.Idle;
+
+            //界面回调
+            if (sessionCallback != null && sessionCallback.get() != null) {
+                sessionCallback.get().didCallEndWithReason(reason);
+            } else {
+				//TODO 结束会话
+			}
+        });
+    }
+
+    //------------------------------------receive---------------------------------------------------
+
+    // 加入房间成功
+    public void onJoinHome(String myId, String users, int roomSize, String frameName) {
+        // 开始计时
+        mRoomSize = roomSize;
+        startTime = 0;
+        handler.post(() -> executor.execute(() -> {
+            mMyId = myId;
+            List<String> strings;
+            if (!TextUtils.isEmpty(users)) {
+                String[] split = users.split(",");
+                strings = Arrays.asList(split);
+                mUserIDList = strings;
+            }
+
+            // 发送邀请
+            if (!mIsComing) {
+                if (roomSize == 2) {
+                    List<String> inviteList = new ArrayList<>();
+                    inviteList.add(mTargetId);
+                    mEvent.sendInvite(mRoomId, inviteList, frameName, mIsAudioOnly);
+                }
+            } else {
+                iEngine.joinRoom(mUserIDList);
+            }
+
+            if (!isAudioOnly()) {
+                // 画面预览
+                if (sessionCallback != null && sessionCallback.get() != null) {
+                    sessionCallback.get().didCreateLocalVideoTrack();
+                }
+
+            }
+
+
+        }));
+
+
+    }
+
+    // 新成员进入
+    public void newPeer(String userId) {
+        handler.post(() -> executor.execute(() -> {
+            // 其他人加入房间
+            iEngine.userIn(userId);
+
+            // 关闭响铃
+            if (mEvent != null) {
+                mEvent.shouldStopRing();
+            }
+            // 更换界面
+            _callState = EnumType.CallState.Connected;
+
+            if (sessionCallback != null && sessionCallback.get() != null) {
+                startTime = System.currentTimeMillis();
+                sessionCallback.get().didChangeState(_callState);
+
+            }
+        }));
+
+    }
+
+    // 对方已拒绝
+    public void onRefuse(String userId, int type) {
+        iEngine.userReject(userId, type);
+    }
+
+    // 对方已响铃
+    public void onRingBack(String userId) {
+        if (mEvent != null) {
+            mEvent.onRemoteRing();
+            //mEvent.shouldStartRing(false);
+        }
+    }
+
+    // 切换到语音
+    public void onTransAudio(String userId) {
+        mIsAudioOnly = true;
+        // 本地切换
+        if (sessionCallback != null && sessionCallback.get() != null) {
+            sessionCallback.get().didChangeMode(true);
+        }
+    }
+
+    // 对方网络断开
+    public void onDisConnect(String userId, EnumType.CallEndReason reason) {
+        executor.execute(() -> {
+            iEngine.disconnected(userId, reason);
+        });
+    }
+
+    // 对方取消拨出
+    public void onCancel(String userId) {
+        Log.d(TAG, "onCancel userId = " + userId);
+        shouldStopRing();
+        release(EnumType.CallEndReason.RemoteHangup);
+    }
+
+    public void onReceiveOffer(String userId, String description) {
+        executor.execute(() -> {
+            iEngine.receiveOffer(userId, description);
+        });
+
+    }
+
+    public void onReceiverAnswer(String userId, String sdp) {
+        executor.execute(() -> {
+            iEngine.receiveAnswer(userId, sdp);
+        });
+
+    }
+
+    public void onRemoteIceCandidate(String userId, String id, int label, String candidate) {
+        executor.execute(() -> {
+            iEngine.receiveIceCandidate(userId, id, label, candidate);
+        });
+
+    }
+
+    // 对方离开房间
+    public void onLeave(String userId) {
+        if (mRoomSize > 2) {
+            // 返回到界面上
+            if (sessionCallback != null && sessionCallback.get() != null) {
+                sessionCallback.get().didUserLeave(userId);
+            }
+        }
+        executor.execute(() -> iEngine.leaveRoom(userId));
+
+
+    }
+
+
+    // --------------------------------界面显示相关--------------------------------------------/
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public View setupLocalVideo(boolean isOverlay) {
+        return iEngine.startPreview(isOverlay);
+    }
+
+
+    public View setupRemoteVideo(String userId, boolean isOverlay) {
+        return iEngine.setupRemoteVideo(userId, isOverlay);
+    }
+
+
+    //------------------------------------各种参数----------------------------------------------/
+
+    public void setIsAudioOnly(boolean _isAudioOnly) {
+        this.mIsAudioOnly = _isAudioOnly;
+    }
+
+    public boolean isAudioOnly() {
+        return mIsAudioOnly;
+    }
+
+    public void setTargetId(String targetIds) {
+        this.mTargetId = targetIds;
+    }
+
+    public void setIsComing(boolean isComing) {
+        this.mIsComing = isComing;
+    }
+
+    public boolean isComing() {
+        return mIsComing;
+    }
+
+    public void setRoom(String _room) {
+        this.mRoomId = _room;
+    }
+
+    public String getRoomId() {
+        return mRoomId;
+    }
+
+    public EnumType.CallState getState() {
+        return _callState;
+    }
+
+    public void setCallState(EnumType.CallState callState) {
+        this._callState = callState;
+    }
+
+    public void setSessionCallback(CallSessionCallback sessionCallback) {
+        this.sessionCallback = new WeakReference<>(sessionCallback);
+    }
+
+    //-----------------------------Engine回调-----------------------------------------
+
+    @Override
+    public void joinRoomSucc() {
+        // 关闭响铃
+        if (mEvent != null) {
+            mEvent.shouldStopRing();
+        }
+        // 更换界面
+        _callState = EnumType.CallState.Connected;
+        //Log.d(TAG, "joinRoomSucc, sessionCallback.get() = " + sessionCallback.get());
+        if (sessionCallback != null && sessionCallback.get() != null) {
+            startTime = System.currentTimeMillis();
+            sessionCallback.get().didChangeState(_callState);
+
+        }
+    }
+
+    @Override
+    public void exitRoom() {
+        // 状态设置为Idle
+        if (mRoomSize == 2) {
+            handler.post(() -> {
+                release(EnumType.CallEndReason.RemoteHangup);
+            });
+        }
+    }
+
+    @Override
+    public void reject(int type) {
+        shouldStopRing();
+        Log.d(TAG, "reject type = " + type);
+//        handler.post(() -> {
+        switch (type) {
+            case 0:
+                release(EnumType.CallEndReason.Busy);
+                break;
+            case 1:
+                release(EnumType.CallEndReason.RemoteHangup);
+                break;
+
+        }
+//        });
+    }
+
+    @Override
+    public void disconnected(EnumType.CallEndReason reason) {
+        handler.post(() -> {
+            shouldStopRing();
+            release(reason);
+        });
+    }
+
+    @Override
+    public void onSendIceCandidate(String userId, IceCandidate candidate) {
+        executor.execute(() -> {
+            if (mEvent != null) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                Log.d("dds_test", "onSendIceCandidate");
+                mEvent.sendIceCandidate(userId, candidate.sdpMid, candidate.sdpMLineIndex, candidate.sdp);
+            }
+        });
+
+    }
+
+    @Override
+    public void onSendOffer(String userId, SessionDescription description) {
+        executor.execute(() -> {
+            if (mEvent != null) {
+                Log.d("dds_test", "onSendOffer");
+                mEvent.sendOffer(userId, description.description);
+            }
+        });
+
+    }
+
+    @Override
+    public void onSendAnswer(String userId, SessionDescription description) {
+        executor.execute(() -> {
+            if (mEvent != null) {
+                Log.d("dds_test", "onSendAnswer");
+                mEvent.sendAnswer(userId, description.description);
+            }
+        });
+
+    }
+
+    @Override
+    public void onRemoteStream(String userId) {
+        // 画面预览
+        if (sessionCallback != null && sessionCallback.get() != null) {
+           Log.d(TAG, "onRemoteStream sessionCallback.get() != null ");
+            sessionCallback.get().didReceiveRemoteVideoTrack(userId);
+        } else {
+            Log.d(TAG, "onRemoteStream sessionCallback.get() == null ");
+        }
+    }
+
+    @Override
+    public void onDisconnected(String userId) {
+        //断线了,需要关闭通话界面
+        if (sessionCallback != null && sessionCallback.get() != null) {
+           Log.d(TAG, "onDisconnected sessionCallback.get() != null ");
+            sessionCallback.get().didDisconnected(userId);
+        } else {
+            Log.d(TAG, "onDisconnected sessionCallback.get() == null ");
+        }
+    }
+
+    public interface CallSessionCallback {
+        void didCallEndWithReason(EnumType.CallEndReason var1);
+
+        void didChangeState(EnumType.CallState var1);
+
+        void didChangeMode(boolean isAudioOnly);
+
+        void didCreateLocalVideoTrack();
+
+        void didReceiveRemoteVideoTrack(String userId);
+
+        void didUserLeave(String userId);
+
+        void didError(String error);
+
+        void didDisconnected(String userId);
+    }
+}

+ 41 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/EnumType.java

@@ -0,0 +1,41 @@
+package com.wdkl.ncs.janus.rtc2;
+
+/**
+ * Created by dds on 2019/8/22.
+ * android_shuai@163.com
+ */
+public class EnumType {
+
+    public enum CallState {
+        Idle,
+        Outgoing,
+        Incoming,
+        Connecting,
+        Connected;
+
+        CallState() {
+        }
+    }
+
+    public enum CallEndReason {
+        Busy,
+        SignalError,
+        RemoteSignalError,
+        Hangup,
+        MediaError,
+        RemoteHangup,
+        OpenCameraFailure,
+        Timeout,
+        AcceptByOtherClient;
+
+        CallEndReason() {
+        }
+    }
+
+    public enum RefuseType {
+        Busy,
+        Hangup,
+    }
+
+
+}

+ 208 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/SkyEngineKit.java

@@ -0,0 +1,208 @@
+package com.wdkl.ncs.janus.rtc2;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.wdkl.ncs.janus.rtc2.except.NotInitializedException;
+import com.wdkl.ncs.janus.rtc2.inter.ISkyEvent;
+
+/**
+ * 主控类
+ * Created by dds on 2019/8/19.
+ */
+public class SkyEngineKit {
+    private final static String TAG = "dds_AVEngineKit";
+    private static SkyEngineKit avEngineKit;
+    private CallSession mCurrentCallSession;
+    private ISkyEvent mEvent;
+    private boolean isAudioOnly = false;
+    private boolean isOutGoing = false;
+
+    public static final Integer signalPort = 5000;
+
+
+    public static SkyEngineKit Instance() {
+        SkyEngineKit var;
+        if ((var = avEngineKit) != null) {
+            return var;
+        } else {
+            throw new NotInitializedException();
+        }
+    }
+
+    // 初始化
+    public static void init(ISkyEvent iSocketEvent) {
+        if (avEngineKit == null) {
+            avEngineKit = new SkyEngineKit();
+            avEngineKit.mEvent = iSocketEvent;
+        }
+    }
+
+
+    public void sendRefuseOnPermissionDenied(String room, String inviteId) {
+        // 未初始化
+        if (avEngineKit == null) {
+            Log.e(TAG, "startOutCall error,please init first");
+            return;
+        }
+        if (mCurrentCallSession != null) {
+            endCall();
+        } else {
+            avEngineKit.mEvent.sendRefuse(room, inviteId, EnumType.RefuseType.Hangup.ordinal());
+        }
+    }
+
+    public void sendDisconnected(String room, String toId, boolean isCrashed) {
+        // 未初始化
+        if (avEngineKit == null) {
+            Log.e(TAG, "startOutCall error,please init first");
+            return;
+        }
+        avEngineKit.mEvent.sendDisConnect(room, toId, isCrashed);
+    }
+
+    // 拨打电话
+    public boolean startOutCall(Context context, final String room, final String targetId, final String frameName,
+                                final boolean audioOnly) {
+        // 未初始化
+        if (avEngineKit == null) {
+            Log.e(TAG, "startOutCall error,please init first");
+            return false;
+        }
+        // 忙线中
+        if (mCurrentCallSession != null && mCurrentCallSession.getState() != EnumType.CallState.Idle) {
+            Log.i(TAG, "startCall error,currentCallSession is exist");
+            return false;
+        }
+        isAudioOnly = audioOnly;
+        isOutGoing = true;
+        // 初始化会话
+        mCurrentCallSession = new CallSession(context, room, audioOnly, mEvent);
+        mCurrentCallSession.setTargetId(targetId);
+        mCurrentCallSession.setIsComing(false);
+        mCurrentCallSession.setCallState(EnumType.CallState.Outgoing);
+        // 创建房间
+        mCurrentCallSession.createHome(room, 2, frameName);
+
+
+        return true;
+    }
+
+    // 接听电话
+    public boolean startInCall(Context context, final String room, final String targetId,
+                               final boolean audioOnly) {
+        if (avEngineKit == null) {
+            Log.e(TAG, "startInCall error,init is not set");
+            return false;
+        }
+        // 忙线中
+        if (mCurrentCallSession != null && mCurrentCallSession.getState() != EnumType.CallState.Idle) {
+            // 发送->忙线中...
+            Log.i(TAG, "startInCall busy,currentCallSession is exist,start sendBusyRefuse!");
+            mCurrentCallSession.sendBusyRefuse(room, targetId);
+            return false;
+        }
+		isOutGoing = false;
+		this.isAudioOnly = audioOnly;
+        // 初始化会话
+        mCurrentCallSession = new CallSession(context, room, audioOnly, mEvent);
+        mCurrentCallSession.setTargetId(targetId);
+        mCurrentCallSession.setIsComing(true);
+        mCurrentCallSession.setCallState(EnumType.CallState.Incoming);
+
+        // 开始响铃并回复
+        mCurrentCallSession.shouldStartRing();
+        mCurrentCallSession.sendRingBack(targetId, room);
+
+
+        return true;
+    }
+
+    // 挂断会话
+    public void endCall() {
+        Log.d(TAG, "endCall mCurrentCallSession != null is " + (mCurrentCallSession != null));
+        if (mCurrentCallSession != null) {
+            // 停止响铃
+            mCurrentCallSession.shouldStopRing();
+
+            if (mCurrentCallSession.isComing()) {
+                if (mCurrentCallSession.getState() == EnumType.CallState.Incoming) {
+                    // 接收到邀请,还没同意,发送拒绝
+                    mCurrentCallSession.sendRefuse();
+                } else {
+                    // 已经接通,挂断电话
+                    mCurrentCallSession.leave();
+                }
+            } else {
+                if (mCurrentCallSession.getState() == EnumType.CallState.Outgoing) {
+                    mCurrentCallSession.sendCancel();
+                } else {
+                    // 已经接通,挂断电话
+                    mCurrentCallSession.leave();
+                }
+            }
+            mCurrentCallSession.setCallState(EnumType.CallState.Idle);
+        }
+
+    }
+
+    // 加入房间
+    public void joinRoom(Context context, String room) {
+        if (avEngineKit == null) {
+            Log.e(TAG, "joinRoom error,init is not set");
+            return;
+        }
+        // 忙线中
+        if (mCurrentCallSession != null && mCurrentCallSession.getState() != EnumType.CallState.Idle) {
+            Log.e(TAG, "joinRoom error,currentCallSession is exist");
+            return;
+        }
+        mCurrentCallSession = new CallSession(context, room, true, mEvent);
+        mCurrentCallSession.setIsComing(true);
+        mCurrentCallSession.joinHome(room);
+    }
+
+    public void createAndJoinRoom(Context context, String room, String frameName) {
+        if (avEngineKit == null) {
+            Log.e(TAG, "joinRoom error,init is not set");
+            return;
+        }
+        // 忙线中
+        if (mCurrentCallSession != null && mCurrentCallSession.getState() != EnumType.CallState.Idle) {
+            Log.e(TAG, "joinRoom error,currentCallSession is exist");
+            return;
+        }
+        mCurrentCallSession = new CallSession(context, room, false, mEvent);
+        mCurrentCallSession.setIsComing(false);
+        mCurrentCallSession.createHome(room, 9, frameName);
+    }
+
+    // 离开房间
+    public void leaveRoom() {
+        if (avEngineKit == null) {
+            Log.e(TAG, "leaveRoom error,init is not set");
+            return;
+        }
+        if (mCurrentCallSession != null) {
+            mCurrentCallSession.leave();
+            mCurrentCallSession.setCallState(EnumType.CallState.Idle);
+        }
+    }
+
+    public void transferToAudio() {
+        isAudioOnly = true;
+    }
+    public boolean isOutGoing() {
+        return isOutGoing;
+    }
+
+    public boolean isAudioOnly() {
+        return isAudioOnly;
+    }
+    // 获取对话实例
+    public CallSession getCurrentSession() {
+        return this.mCurrentCallSession;
+    }
+
+
+}

+ 43 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/engine/EngineCallback.java

@@ -0,0 +1,43 @@
+package com.wdkl.ncs.janus.rtc2.engine;
+
+import com.wdkl.ncs.janus.rtc2.EnumType;
+
+import org.webrtc.IceCandidate;
+import org.webrtc.SessionDescription;
+
+/**
+ * Created by dds on 2020/4/12.
+ * 框架回调
+ */
+public interface EngineCallback {
+
+
+    /**
+     * 加入房间成功
+     */
+    void joinRoomSucc();
+
+    /**
+     * 退出房间成功
+     */
+    void exitRoom();
+
+    /**
+     * 拒绝连接
+     * @param type type
+     */
+    void reject(int type);
+
+    void disconnected(EnumType.CallEndReason reason);
+
+    void onSendIceCandidate(String userId, IceCandidate candidate);
+
+    void onSendOffer(String userId, SessionDescription description);
+
+    void onSendAnswer(String userId, SessionDescription description);
+
+    void onRemoteStream(String userId);
+
+    void onDisconnected(String userId);
+
+}

+ 121 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/engine/IEngine.java

@@ -0,0 +1,121 @@
+package com.wdkl.ncs.janus.rtc2.engine;
+
+
+import android.view.View;
+
+import com.wdkl.ncs.janus.rtc2.EnumType;
+
+import java.util.List;
+
+/**
+ * rtc基类
+ */
+public interface IEngine {
+
+    /**
+     * 初始化
+     */
+    void init(EngineCallback callback);
+
+    /**
+     * 加入房間
+     */
+    void joinRoom(List<String> userIds);
+
+    /**
+     * 有人进入房间
+     */
+    void userIn(String userId);
+
+    /**
+     * 用户拒绝
+     * @param userId userId
+     * @param type type
+     */
+    void userReject(String userId, int type);
+
+    /**
+     * 用户网络断开
+     * @param userId userId
+     * @param reason
+     */
+    void disconnected(String userId, EnumType.CallEndReason reason);
+
+    /**
+     * receive Offer
+     */
+    void receiveOffer(String userId, String description);
+
+    /**
+     * receive Answer
+     */
+    void receiveAnswer(String userId, String sdp);
+
+    /**
+     * receive IceCandidate
+     */
+    void receiveIceCandidate(String userId, String id, int label, String candidate);
+
+    /**
+     * 离开房间
+     *
+     * @param userId userId
+     */
+    void leaveRoom(String userId);
+
+    /**
+     * 开启本地预览
+     */
+    View startPreview(boolean isOverlay);
+
+    /**
+     * 关闭本地预览
+     */
+    void stopPreview();
+
+    /**
+     * 开始远端推流
+     */
+    void startStream();
+
+    /**
+     * 停止远端推流
+     */
+    void stopStream();
+
+    /**
+     * 开始远端预览
+     */
+    View setupRemoteVideo(String userId, boolean isO);
+
+    /**
+     * 关闭远端预览
+     */
+    void stopRemoteVideo();
+
+    /**
+     * 切换摄像头
+     */
+    void switchCamera();
+
+    /**
+     * 设置静音
+     */
+    boolean muteAudio(boolean enable);
+
+    /**
+     * 开启扬声器
+     */
+    boolean toggleSpeaker(boolean enable);
+
+    /**
+     * 切换外放和耳机
+     */
+    boolean toggleHeadset(boolean isHeadset);
+
+    /**
+     * 释放所有内容
+     */
+    void release();
+
+}

+ 693 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/engine/webrtc/MyWebRTCEngine.java

@@ -0,0 +1,693 @@
+package com.wdkl.ncs.janus.rtc2.engine.webrtc;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.projection.MediaProjection;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import com.wdkl.ncs.janus.rtc2.EnumType;
+import com.wdkl.ncs.janus.rtc2.engine.EngineCallback;
+import com.wdkl.ncs.janus.rtc2.engine.IEngine;
+import com.wdkl.ncs.janus.rtc2.render.ProxyVideoSink;
+
+import org.webrtc.AudioSource;
+import org.webrtc.AudioTrack;
+import org.webrtc.Camera1Enumerator;
+import org.webrtc.Camera2Enumerator;
+import org.webrtc.CameraEnumerator;
+import org.webrtc.CameraVideoCapturer;
+import org.webrtc.DefaultVideoDecoderFactory;
+import org.webrtc.DefaultVideoEncoderFactory;
+import org.webrtc.EglBase;
+import org.webrtc.IceCandidate;
+import org.webrtc.MediaConstraints;
+import org.webrtc.MediaStream;
+import org.webrtc.PeerConnection;
+import org.webrtc.PeerConnectionFactory;
+import org.webrtc.RendererCommon;
+import org.webrtc.ScreenCapturerAndroid;
+import org.webrtc.SessionDescription;
+import org.webrtc.SurfaceTextureHelper;
+import org.webrtc.SurfaceViewRenderer;
+import org.webrtc.VideoCapturer;
+import org.webrtc.VideoDecoderFactory;
+import org.webrtc.VideoEncoderFactory;
+import org.webrtc.VideoSource;
+import org.webrtc.VideoTrack;
+import org.webrtc.audio.AudioDeviceModule;
+import org.webrtc.audio.JavaAudioDeviceModule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MyWebRTCEngine implements IEngine, Peer.IPeerEvent {
+    private static final String TAG = "WebRTCEngine";
+    private PeerConnectionFactory _factory;
+    private EglBase mRootEglBase;
+    private MediaStream _localStream;
+    private VideoSource videoSource;
+    private AudioSource audioSource;
+    private AudioTrack _localAudioTrack;
+    private VideoCapturer captureAndroid;
+    private SurfaceTextureHelper surfaceTextureHelper;
+
+    private ProxyVideoSink localSink;
+    private SurfaceViewRenderer localRenderer;
+
+    // 服务器实例列表
+    private String serverIP = "120.76.246.253";
+    private String turnUser = "wdklrtc";
+    private String turnUserPwd = "Wdkl2021Rtc";
+
+    private static final String VIDEO_TRACK_ID = "ARDAMSv0";
+    private static final String AUDIO_TRACK_ID = "ARDAMSa0";
+    public static final String VIDEO_CODEC_H264 = "H264";
+    private static final int VIDEO_RESOLUTION_WIDTH = 640;
+    private static final int VIDEO_RESOLUTION_HEIGHT = 480;
+    private static final int FPS = 20;
+
+    // 对话实例列表
+    private ConcurrentHashMap<String, Peer> peers = new ConcurrentHashMap<>();
+    // 服务器实例列表
+    private List<PeerConnection.IceServer> iceServers = new ArrayList<>();
+
+    private EngineCallback mCallback;
+
+    public boolean mIsAudioOnly;
+    private Context mContext;
+    private AudioManager audioManager;
+    private boolean isSpeakerOn = true;
+
+    public MyWebRTCEngine(boolean mIsAudioOnly, Context mContext) {
+        this.mIsAudioOnly = mIsAudioOnly;
+        this.mContext = mContext;
+        audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        // 初始化ice地址
+        initIceServer();
+    }
+
+
+    // -----------------------------------对外方法------------------------------------------
+    @Override
+    public void init(EngineCallback callback) {
+        mCallback = callback;
+
+        if (mRootEglBase == null) {
+            mRootEglBase = EglBase.create();
+        }
+        if (_factory == null) {
+            _factory = createConnectionFactory();
+        }
+        if (_localStream == null) {
+            createLocalStream();
+        }
+    }
+
+    @Override
+    public void joinRoom(List<String> userIds) {
+        for (String id : userIds) {
+            // create Peer
+            Peer peer = new Peer(_factory, iceServers, id, this);
+            peer.setOffer(false);
+            // add localStream
+            peer.addLocalStream(_localStream);
+            // 添加列表
+            peers.put(id, peer);
+        }
+        if (mCallback != null) {
+            mCallback.joinRoomSucc();
+        }
+
+        if (isHeadphonesPlugged()) {
+            toggleHeadset(true);
+        } else {
+            if (mIsAudioOnly) {
+                toggleSpeaker(true);
+            } else {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                    audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+                } else {
+                    audioManager.setMode(AudioManager.MODE_IN_CALL);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void userIn(String userId) {
+        // create Peer
+        Peer peer = new Peer(_factory, iceServers, userId, this);
+        peer.setOffer(true);
+        // add localStream
+        peer.addLocalStream(_localStream);
+        // 添加列表
+        peers.put(userId, peer);
+        // createOffer
+        peer.createOffer();
+    }
+
+    @Override
+    public void userReject(String userId, int type) {
+        //拒绝接听userId应该是没有添加进peers里去不需要remove
+//       Peer peer = peers.get(userId);
+//        if (peer != null) {
+//            peer.close();
+//            peers.remove(userId);
+//        }
+//        if (peers.size() == 0) {
+
+        if (mCallback != null) {
+            mCallback.reject(type);
+        }
+//        }
+    }
+
+    @Override
+    public void disconnected(String userId, EnumType.CallEndReason reason) {
+        if (mCallback != null) {
+            mCallback.disconnected(reason);
+        }
+    }
+
+    @Override
+    public void receiveOffer(String userId, String description) {
+        Peer peer = peers.get(userId);
+        if (peer != null) {
+            SessionDescription sdp = new SessionDescription(SessionDescription.Type.OFFER, description);
+            peer.setOffer(false);
+            peer.setRemoteDescription(sdp);
+            peer.createAnswer();
+        }
+
+
+    }
+
+    @Override
+    public void receiveAnswer(String userId, String sdp) {
+        Log.d("dds_test", "receiveAnswer--" + userId);
+        Peer peer = peers.get(userId);
+        if (peer != null) {
+            SessionDescription sessionDescription = new SessionDescription(SessionDescription.Type.ANSWER, sdp);
+            peer.setRemoteDescription(sessionDescription);
+        }
+
+
+    }
+
+    @Override
+    public void receiveIceCandidate(String userId, String id, int label, String candidate) {
+        Log.d("dds_test", "receiveIceCandidate--" + userId);
+        Peer peer = peers.get(userId);
+        if (peer != null) {
+            IceCandidate iceCandidate = new IceCandidate(id, label, candidate);
+            peer.addRemoteIceCandidate(iceCandidate);
+
+        }
+    }
+
+    @Override
+    public void leaveRoom(String userId) {
+        Peer peer = peers.get(userId);
+        if (peer != null) {
+            peer.close();
+            peers.remove(userId);
+        }
+       Log.d(TAG, "leaveRoom peers.size() = " + peers.size() + "; mCallback = " + mCallback);
+        if (peers.size() <= 1) {
+
+            if (mCallback != null) {
+                mCallback.exitRoom();
+            }
+            if (peers.size() == 1) {
+                for (Map.Entry<String, Peer> set : peers.entrySet()) {
+                    set.getValue().close();
+                }
+                peers.clear();
+            }
+        }
+
+
+    }
+
+    @Override
+    public View startPreview(boolean isOverlay) {
+        if (mRootEglBase == null) {
+            return null;
+        }
+        localRenderer = new SurfaceViewRenderer(mContext);
+        localRenderer.init(mRootEglBase.getEglBaseContext(), null);
+        localRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
+        localRenderer.setMirror(true);
+        localRenderer.setZOrderMediaOverlay(isOverlay);
+
+        localSink = new ProxyVideoSink();
+        localSink.setTarget(localRenderer);
+        if (_localStream.videoTracks.size() > 0) {
+            _localStream.videoTracks.get(0).addSink(localSink);
+        }
+        return localRenderer;
+    }
+
+    @Override
+    public void stopPreview() {
+        if (localSink != null) {
+            localSink.setTarget(null);
+            localSink = null;
+        }
+        if (audioSource != null) {
+            audioSource.dispose();
+            audioSource = null;
+        }
+        // 释放摄像头
+        if (captureAndroid != null) {
+            try {
+                captureAndroid.stopCapture();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            captureAndroid.dispose();
+            captureAndroid = null;
+        }
+        // 释放画布
+        if (surfaceTextureHelper != null) {
+            surfaceTextureHelper.dispose();
+            surfaceTextureHelper = null;
+        }
+
+        if (videoSource != null) {
+            videoSource.dispose();
+            videoSource = null;
+        }
+        if (_localStream != null) {
+            _localStream = null;
+        }
+        if (localRenderer != null) {
+            localRenderer.release();
+        }
+
+
+    }
+
+    @Override
+    public void startStream() {
+
+    }
+
+    @Override
+    public void stopStream() {
+
+    }
+
+
+    @Override
+    public View setupRemoteVideo(String userId, boolean isO) {
+        if (TextUtils.isEmpty(userId)) {
+            Log.e(TAG, "setupRemoteVideo userId is null ");
+            return null;
+        }
+        Peer peer = peers.get(userId);
+        if (peer == null) return null;
+
+        if (peer.renderer == null) {
+            peer.createRender(mRootEglBase, mContext, isO);
+        }
+
+        return peer.renderer;
+
+    }
+
+    @Override
+    public void stopRemoteVideo() {
+
+    }
+
+    private boolean isSwitch = false; // 是否正在切换摄像头
+
+    @Override
+    public void switchCamera() {
+        if (isSwitch) return;
+        isSwitch = true;
+        if (captureAndroid == null) return;
+        if (captureAndroid instanceof CameraVideoCapturer) {
+            CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) captureAndroid;
+            try {
+                cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
+                    @Override
+                    public void onCameraSwitchDone(boolean isFrontCamera) {
+                        isSwitch = false;
+                    }
+
+                    @Override
+                    public void onCameraSwitchError(String errorDescription) {
+                        isSwitch = false;
+                    }
+                });
+            } catch (Exception e) {
+                isSwitch = false;
+            }
+        } else {
+            Log.d(TAG, "Will not switch camera, video caputurer is not a camera");
+        }
+    }
+
+    @Override
+    public boolean muteAudio(boolean enable) {
+        if (_localAudioTrack != null) {
+            _localAudioTrack.setEnabled(!enable);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean toggleSpeaker(boolean enable) {
+        if (audioManager != null) {
+            isSpeakerOn = enable;
+            audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+            if (enable) {
+                audioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                        audioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL),
+                        AudioManager.FX_KEY_CLICK);
+                audioManager.setSpeakerphoneOn(true);
+            } else {
+                //5.0以上
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                    //设置mode
+                    audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+                } else {
+                    //设置mode
+                    audioManager.setMode(AudioManager.MODE_IN_CALL);
+                }
+                //设置音量,解决有些机型切换后没声音或者声音突然变大的问题
+                audioManager.setStreamVolume(
+                        AudioManager.STREAM_VOICE_CALL,
+                        audioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL),
+                        AudioManager.FX_KEY_CLICK
+                );
+                audioManager.setSpeakerphoneOn(false);
+            }
+            return true;
+        }
+        return false;
+
+    }
+
+    @Override
+    public boolean toggleHeadset(boolean isHeadset) {
+        if (audioManager != null) {
+            if (isHeadset) {
+                //5.0以上
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                    //设置mode
+                    audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+                } else {
+                    //设置mode
+                    audioManager.setMode(AudioManager.MODE_IN_CALL);
+                }
+                audioManager.setSpeakerphoneOn(false);
+            } else {
+                if (mIsAudioOnly) {
+                    toggleSpeaker(isSpeakerOn);
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean isHeadphonesPlugged() {
+        if (audioManager == null) {
+            return false;
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            AudioDeviceInfo[] audioDevices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+            for (AudioDeviceInfo deviceInfo : audioDevices) {
+                if (deviceInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES
+                        || deviceInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
+                    return true;
+                }
+            }
+            return false;
+        } else {
+            return audioManager.isWiredHeadsetOn();
+        }
+    }
+
+    @Override
+    public void release() {
+        if (audioManager != null) {
+            audioManager.setMode(AudioManager.MODE_NORMAL);
+        }
+        // 清空peer
+        if (peers != null) {
+            for (Peer peer : peers.values()) {
+                peer.close();
+            }
+            peers.clear();
+        }
+
+
+        // 停止预览
+        stopPreview();
+
+        if (_factory != null) {
+            _factory.dispose();
+            _factory = null;
+        }
+
+        if (mRootEglBase != null) {
+            mRootEglBase.release();
+            mRootEglBase = null;
+        }
+
+
+    }
+
+    // -----------------------------其他方法--------------------------------
+
+    private void initIceServer() {
+        // 初始化一些stun和turn的地址
+        PeerConnection.IceServer var11 = PeerConnection.IceServer
+                .builder("stun:"+serverIP+":3478?transport=udp")
+                .setUsername(turnUser)
+                .setPassword(turnUserPwd)
+                .setTlsCertPolicy(PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK)
+                .createIceServer();
+        PeerConnection.IceServer var12 = PeerConnection.IceServer
+                .builder("turn:"+serverIP+":3478?transport=udp")
+                .setUsername(turnUser)
+                .setPassword(turnUserPwd)
+                .setTlsCertPolicy(PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK)
+                .createIceServer();
+        iceServers.add(var11);
+        iceServers.add(var12);
+    }
+
+    /**
+     * 构造PeerConnectionFactory
+     *
+     * @return PeerConnectionFactory
+     */
+    public PeerConnectionFactory createConnectionFactory() {
+
+        // 1. 初始化的方法,必须在开始之前调用
+        PeerConnectionFactory.initialize(PeerConnectionFactory
+                .InitializationOptions
+                .builder(mContext)
+                .createInitializationOptions());
+
+        // 2. 设置编解码方式:默认方法
+        final VideoEncoderFactory encoderFactory;
+        final VideoDecoderFactory decoderFactory;
+
+        encoderFactory = new DefaultVideoEncoderFactory(
+                mRootEglBase.getEglBaseContext(),
+                true,
+                true);
+        decoderFactory = new DefaultVideoDecoderFactory(mRootEglBase.getEglBaseContext());
+
+        // 构造Factory
+        AudioDeviceModule audioDeviceModule = JavaAudioDeviceModule.builder(mContext).createAudioDeviceModule();
+        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
+        return PeerConnectionFactory.builder()
+                .setOptions(options)
+                .setAudioDeviceModule(audioDeviceModule)
+                .setVideoEncoderFactory(encoderFactory)
+                .setVideoDecoderFactory(decoderFactory)
+                .createPeerConnectionFactory();
+    }
+
+    /**
+     * 创建本地流
+     */
+    public void createLocalStream() {
+        _localStream = _factory.createLocalMediaStream("ARDAMS");
+        // 音频
+        audioSource = _factory.createAudioSource(createAudioConstraints());
+        _localAudioTrack = _factory.createAudioTrack(AUDIO_TRACK_ID, audioSource);
+        _localStream.addTrack(_localAudioTrack);
+
+        // 视频
+        if (!mIsAudioOnly) {
+            captureAndroid = createVideoCapture();
+            surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", mRootEglBase.getEglBaseContext());
+            videoSource = _factory.createVideoSource(captureAndroid.isScreencast());
+
+            captureAndroid.initialize(surfaceTextureHelper, mContext, videoSource.getCapturerObserver());
+            captureAndroid.startCapture(VIDEO_RESOLUTION_WIDTH, VIDEO_RESOLUTION_HEIGHT, FPS);
+
+
+            VideoTrack _localVideoTrack = _factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
+            _localStream.addTrack(_localVideoTrack);
+        }
+
+    }
+
+
+    // 是否使用录屏
+    private boolean screencaptureEnabled = false;
+
+    /**
+     * 创建媒体方式
+     *
+     * @return VideoCapturer
+     */
+    private VideoCapturer createVideoCapture() {
+        VideoCapturer videoCapturer;
+
+
+        if (screencaptureEnabled) {
+            return createScreenCapturer();
+        }
+
+        if (Camera2Enumerator.isSupported(mContext)) {
+            videoCapturer = createCameraCapture(new Camera2Enumerator(mContext));
+        } else {
+            videoCapturer = createCameraCapture(new Camera1Enumerator(true));
+        }
+        return videoCapturer;
+    }
+
+    /**
+     * 创建相机媒体流
+     */
+    private VideoCapturer createCameraCapture(CameraEnumerator enumerator) {
+        final String[] deviceNames = enumerator.getDeviceNames();
+
+        // First, try to find front facing camera
+        for (String deviceName : deviceNames) {
+            if (enumerator.isFrontFacing(deviceName)) {
+                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
+
+                if (videoCapturer != null) {
+                    return videoCapturer;
+                }
+            }
+        }
+
+        // Front facing camera not found, try something else
+        for (String deviceName : deviceNames) {
+            if (!enumerator.isFrontFacing(deviceName)) {
+                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
+
+                if (videoCapturer != null) {
+                    return videoCapturer;
+                }
+            }
+        }
+
+        return null;
+    }
+
+
+    private static Intent mediaProjectionPermissionResultData;
+    private static int mediaProjectionPermissionResultCode;
+
+    @TargetApi(21)
+    private VideoCapturer createScreenCapturer() {
+        if (mediaProjectionPermissionResultCode != Activity.RESULT_OK) {
+            return null;
+        }
+        return new ScreenCapturerAndroid(
+                mediaProjectionPermissionResultData, new MediaProjection.Callback() {
+            @Override
+            public void onStop() {
+                Log.e(TAG, "User revoked permission to capture the screen.");
+            }
+        });
+    }
+
+    //**************************************各种约束******************************************/
+    private static final String AUDIO_ECHO_CANCELLATION_CONSTRAINT = "googEchoCancellation";
+    private static final String AUDIO_AUTO_GAIN_CONTROL_CONSTRAINT = "googAutoGainControl";
+    private static final String AUDIO_HIGH_PASS_FILTER_CONSTRAINT = "googHighpassFilter";
+    private static final String AUDIO_NOISE_SUPPRESSION_CONSTRAINT = "googNoiseSuppression";
+
+    // 配置音频参数
+    private MediaConstraints createAudioConstraints() {
+        MediaConstraints audioConstraints = new MediaConstraints();
+        audioConstraints.mandatory.add(
+                new MediaConstraints.KeyValuePair(AUDIO_ECHO_CANCELLATION_CONSTRAINT, "true"));
+        audioConstraints.mandatory.add(
+                new MediaConstraints.KeyValuePair(AUDIO_AUTO_GAIN_CONTROL_CONSTRAINT, "false"));
+        audioConstraints.mandatory.add(
+                new MediaConstraints.KeyValuePair(AUDIO_HIGH_PASS_FILTER_CONSTRAINT, "false"));
+        audioConstraints.mandatory.add(
+                new MediaConstraints.KeyValuePair(AUDIO_NOISE_SUPPRESSION_CONSTRAINT, "true"));
+        return audioConstraints;
+    }
+
+    //------------------------------------回调---------------------------------------------
+    @Override
+    public void onSendIceCandidate(String userId, IceCandidate candidate) {
+        if (mCallback != null) {
+            mCallback.onSendIceCandidate(userId, candidate);
+        }
+
+    }
+
+    @Override
+    public void onSendOffer(String userId, SessionDescription description) {
+        if (mCallback != null) {
+            mCallback.onSendOffer(userId, description);
+        }
+    }
+
+    @Override
+    public void onSendAnswer(String userId, SessionDescription description) {
+        if (mCallback != null) {
+            mCallback.onSendAnswer(userId, description);
+        }
+    }
+
+    @Override
+    public void onRemoteStream(String userId, MediaStream stream) {
+        if (mCallback != null) {
+            mCallback.onRemoteStream(userId);
+        }
+    }
+
+    @Override
+    public void onRemoveStream(String userId, MediaStream stream) {
+        leaveRoom(userId);
+    }
+
+    @Override
+    public void onDisconnected(String userId) {
+        if (mCallback != null) {
+           Log.d(TAG, "onDisconnected mCallback != null");
+            mCallback.onDisconnected(userId);
+        } else {
+           Log.d(TAG, "onDisconnected mCallback == null");
+        }
+    }
+
+}

+ 355 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/engine/webrtc/Peer.java

@@ -0,0 +1,355 @@
+package com.wdkl.ncs.janus.rtc2.engine.webrtc;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.wdkl.ncs.janus.rtc2.render.ProxyVideoSink;
+
+import org.webrtc.DataChannel;
+import org.webrtc.EglBase;
+import org.webrtc.IceCandidate;
+import org.webrtc.MediaConstraints;
+import org.webrtc.MediaStream;
+import org.webrtc.PeerConnection;
+import org.webrtc.PeerConnectionFactory;
+import org.webrtc.RendererCommon;
+import org.webrtc.RtpReceiver;
+import org.webrtc.SdpObserver;
+import org.webrtc.SessionDescription;
+import org.webrtc.SurfaceViewRenderer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by dds on 2020/3/11.
+ * android_shuai@163.com
+ */
+public class Peer implements SdpObserver, PeerConnection.Observer {
+    private final static String TAG = "dds_Peer";
+    private final PeerConnection pc;
+    private final String mUserId;
+    private List<IceCandidate> queuedRemoteCandidates;
+    private SessionDescription localSdp;
+    private final PeerConnectionFactory mFactory;
+    private final List<PeerConnection.IceServer> mIceLis;
+    private final IPeerEvent mEvent;
+    private boolean isOffer;
+
+    public MediaStream _remoteStream;
+    public SurfaceViewRenderer renderer;
+    public ProxyVideoSink sink;
+
+
+    public Peer(PeerConnectionFactory factory, List<PeerConnection.IceServer> list, String userId, IPeerEvent event) {
+        mFactory = factory;
+        mIceLis = list;
+        mEvent = event;
+        mUserId = userId;
+        queuedRemoteCandidates = new ArrayList<>();
+        this.pc = createPeerConnection();
+        Log.d("dds_test", "create Peer:" + mUserId);
+
+    }
+
+    public PeerConnection createPeerConnection() {
+        PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(mIceLis);
+        if (mFactory != null) {
+            return mFactory.createPeerConnection(rtcConfig, this);
+        } else {
+            return null;
+        }
+    }
+
+    public void setOffer(boolean isOffer) {
+        this.isOffer = isOffer;
+    }
+
+    // 创建offer
+    public void createOffer() {
+        if (pc == null) return;
+        Log.d("dds_test", "createOffer");
+        pc.createOffer(this, offerOrAnswerConstraint());
+    }
+
+    // 创建answer
+    public void createAnswer() {
+        if (pc == null) return;
+        Log.d("dds_test", "createAnswer");
+        pc.createAnswer(this, offerOrAnswerConstraint());
+
+    }
+
+    // 设置LocalDescription
+    public void setLocalDescription(SessionDescription sdp) {
+        Log.d("dds_test", "setLocalDescription");
+        if (pc == null) return;
+        pc.setLocalDescription(this, sdp);
+    }
+
+    // 设置RemoteDescription
+    public void setRemoteDescription(SessionDescription sdp) {
+        if (pc == null) return;
+        Log.d("dds_test", "setRemoteDescription");
+        pc.setRemoteDescription(this, sdp);
+    }
+
+    //添加本地流
+    public void addLocalStream(MediaStream stream) {
+        if (pc == null) return;
+        Log.d("dds_test", "addLocalStream" + mUserId);
+        pc.addStream(stream);
+    }
+
+    // 添加RemoteIceCandidate
+    public synchronized void addRemoteIceCandidate(final IceCandidate candidate) {
+        Log.d("dds_test", "addRemoteIceCandidate");
+        if (pc != null) {
+            if (queuedRemoteCandidates != null) {
+               Log.d("dds_test", "addRemoteIceCandidate  2222");
+                synchronized (Peer.class) {
+                    if (queuedRemoteCandidates != null) {
+                        queuedRemoteCandidates.add(candidate);
+                    }
+                }
+
+            } else {
+               Log.d("dds_test", "addRemoteIceCandidate1111");
+                pc.addIceCandidate(candidate);
+            }
+        }
+    }
+
+    // 移除RemoteIceCandidates
+    public void removeRemoteIceCandidates(final IceCandidate[] candidates) {
+        if (pc == null) {
+            return;
+        }
+        drainCandidates();
+        pc.removeIceCandidates(candidates);
+    }
+
+    public void createRender(EglBase mRootEglBase, Context context, boolean isOverlay) {
+        renderer = new SurfaceViewRenderer(context);
+        renderer.init(mRootEglBase.getEglBaseContext(), new RendererCommon.RendererEvents() {
+            @Override
+            public void onFirstFrameRendered() {
+                Log.d(TAG, "createRender onFirstFrameRendered");
+
+            }
+
+            @Override
+            public void onFrameResolutionChanged(int videoWidth, int videoHeight, int rotation) {
+                Log.d(TAG, "createRender onFrameResolutionChanged");
+            }
+        });
+        renderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
+        renderer.setMirror(true);
+        renderer.setZOrderMediaOverlay(isOverlay);
+        sink = new ProxyVideoSink();
+        sink.setTarget(renderer);
+        if (_remoteStream != null && _remoteStream.videoTracks.size() > 0) {
+            _remoteStream.videoTracks.get(0).addSink(sink);
+        }
+
+    }
+
+    // 关闭Peer
+    public void close() {
+        if (renderer != null) {
+            renderer.release();
+            renderer = null;
+        }
+        if (sink != null) {
+            sink.setTarget(null);
+        }
+        if (pc != null) {
+            try {
+                pc.close();
+                pc.dispose();
+            } catch (Exception e) {
+
+            }
+
+
+        }
+
+
+    }
+
+    //------------------------------Observer-------------------------------------
+    @Override
+    public void onSignalingChange(PeerConnection.SignalingState signalingState) {
+        Log.i(TAG, "onSignalingChange: " + signalingState);
+    }
+
+    @Override
+    public void onIceConnectionChange(PeerConnection.IceConnectionState newState) {
+        Log.i(TAG, "onIceConnectionChange: " + newState);
+        if (newState == PeerConnection.IceConnectionState.DISCONNECTED || newState == PeerConnection.IceConnectionState.FAILED) {
+            mEvent.onDisconnected(mUserId);
+        }
+
+    }
+
+    @Override
+    public void onIceConnectionReceivingChange(boolean receiving) {
+        Log.i(TAG, "onIceConnectionReceivingChange:" + receiving);
+    }
+
+    @Override
+    public void onIceGatheringChange(PeerConnection.IceGatheringState newState) {
+        Log.i(TAG, "onIceGatheringChange:" + newState.toString());
+    }
+
+    @Override
+    public void onIceCandidate(IceCandidate candidate) {
+        // 发送IceCandidate
+        mEvent.onSendIceCandidate(mUserId, candidate);
+    }
+
+    @Override
+    public void onIceCandidatesRemoved(IceCandidate[] candidates) {
+        Log.i(TAG, "onIceCandidatesRemoved:");
+    }
+
+    @Override
+    public void onAddStream(MediaStream stream) {
+        Log.i(TAG, "onAddStream:");
+        stream.audioTracks.get(0).setEnabled(true);
+        _remoteStream = stream;
+        if (mEvent != null) {
+            mEvent.onRemoteStream(mUserId, stream);
+        }
+    }
+
+    @Override
+    public void onRemoveStream(MediaStream stream) {
+        Log.i(TAG, "onRemoveStream:");
+        if (mEvent != null) {
+            mEvent.onRemoveStream(mUserId, stream);
+        }
+    }
+
+    @Override
+    public void onDataChannel(DataChannel dataChannel) {
+        Log.i(TAG, "onDataChannel:");
+    }
+
+    @Override
+    public void onRenegotiationNeeded() {
+        Log.i(TAG, "onRenegotiationNeeded:");
+    }
+
+    @Override
+    public void onAddTrack(RtpReceiver receiver, MediaStream[] mediaStreams) {
+        Log.i(TAG, "onAddTrack:" + mediaStreams.length);
+    }
+
+
+    //-------------SdpObserver--------------------
+    @Override
+    public void onCreateSuccess(SessionDescription origSdp) {
+        Log.d(TAG, "sdp创建成功       " + origSdp.type);
+        String sdpString = origSdp.description;
+        final SessionDescription sdp = new SessionDescription(origSdp.type, sdpString);
+        localSdp = sdp;
+        setLocalDescription(sdp);
+
+    }
+
+    @Override
+    public void onSetSuccess() {
+        Log.d(TAG, "sdp连接成功   " + pc.signalingState().toString());
+        if (pc == null) return;
+        // 发送者
+        if (isOffer) {
+            if (pc.getRemoteDescription() == null) {
+                Log.d(TAG, "Local SDP set succesfully");
+                if (!isOffer) {
+                    //接收者,发送Answer
+                    mEvent.onSendAnswer(mUserId, localSdp);
+                } else {
+                    //发送者,发送自己的offer
+                    mEvent.onSendOffer(mUserId, localSdp);
+                }
+            } else {
+                Log.d(TAG, "Remote SDP set succesfully");
+
+                drainCandidates();
+            }
+
+        } else {
+            if (pc.getLocalDescription() != null) {
+                Log.d(TAG, "Local SDP set succesfully");
+                if (!isOffer) {
+                    //接收者,发送Answer
+                    mEvent.onSendAnswer(mUserId, localSdp);
+                } else {
+                    //发送者,发送自己的offer
+                    mEvent.onSendOffer(mUserId, localSdp);
+                }
+
+                drainCandidates();
+            } else {
+                Log.d(TAG, "Remote SDP set succesfully");
+            }
+        }
+
+
+    }
+
+    @Override
+    public void onCreateFailure(String error) {
+        Log.i(TAG, " SdpObserver onCreateFailure:" + error);
+    }
+
+    @Override
+    public void onSetFailure(String error) {
+        Log.i(TAG, "SdpObserver onSetFailure:" + error);
+    }
+
+
+    private void drainCandidates() {
+        Log.i("dds_test", "drainCandidates");
+        synchronized (Peer.class) {
+            if (queuedRemoteCandidates != null) {
+                Log.d(TAG, "Add " + queuedRemoteCandidates.size() + " remote candidates");
+                for (IceCandidate candidate : queuedRemoteCandidates) {
+                    pc.addIceCandidate(candidate);
+                }
+                queuedRemoteCandidates = null;
+            }
+
+        }
+    }
+
+    private MediaConstraints offerOrAnswerConstraint() {
+        MediaConstraints mediaConstraints = new MediaConstraints();
+        ArrayList<MediaConstraints.KeyValuePair> keyValuePairs = new ArrayList<>();
+        keyValuePairs.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
+        keyValuePairs.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
+        mediaConstraints.mandatory.addAll(keyValuePairs);
+        return mediaConstraints;
+    }
+
+    // ----------------------------回调-----------------------------------
+
+    public interface IPeerEvent {
+
+
+        void onSendIceCandidate(String userId, IceCandidate candidate);
+
+        void onSendOffer(String userId, SessionDescription description);
+
+        void onSendAnswer(String userId, SessionDescription description);
+
+        void onRemoteStream(String userId, MediaStream stream);
+
+        void onRemoveStream(String userId, MediaStream stream);
+
+
+        void onDisconnected(String userId);
+    }
+
+}

+ 11 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/except/NotInitializedException.java

@@ -0,0 +1,11 @@
+package com.wdkl.ncs.janus.rtc2.except;
+
+/**
+ * Created by dds on 17/02/2018.
+ */
+
+public class NotInitializedException extends RuntimeException {
+    public NotInitializedException() {
+        super("Not init!!!");
+    }
+}

+ 12 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/inter/ILogEvent.java

@@ -0,0 +1,12 @@
+package com.wdkl.ncs.janus.rtc2.inter;
+
+/**
+ * Created by dds on 2020/2/10.
+ * 通话记录
+ * <p>
+ * 去电  来电  通话时长  通话类型  是否接听
+ */
+public interface ILogEvent {
+
+
+}

+ 45 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/inter/ISkyEvent.java

@@ -0,0 +1,45 @@
+package com.wdkl.ncs.janus.rtc2.inter;
+
+import java.util.List;
+
+/**
+ * Created by dds on 2019/8/21.
+ * android_shuai@163.com
+ */
+public interface ISkyEvent {
+
+    // 创建房间
+    void createRoom(String room, int roomSize, String frameName);
+
+    // 发送单人邀请
+    void sendInvite(String room, List<String> userIds, String frameName, boolean audioOnly);
+
+    void sendRefuse(String room, String inviteId, int refuseType);
+
+    void sendTransAudio(String toId);
+
+    void sendDisConnect(String room, String toId, boolean isCrashed);
+
+    void sendCancel(String mRoomId, List<String> toId);
+
+    void sendJoin(String room);
+
+    void sendRingBack(String targetId, String room);
+
+    void sendLeave(String room, String userId);
+
+    // sendOffer
+    void sendOffer(String userId, String sdp);
+
+    // sendAnswer
+    void sendAnswer(String userId, String sdp);
+
+    // sendIceCandidate
+    void sendIceCandidate(String userId, String id, int label, String candidate);
+
+    void onRemoteRing();
+
+    void shouldStartRing(boolean isComing);
+
+    void shouldStopRing();
+}

+ 8 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/model/RoomInfo.java

@@ -0,0 +1,8 @@
+package com.wdkl.ncs.janus.rtc2.model;
+
+public class RoomInfo {
+
+    public String roomId;
+    public int roomSize;
+
+}

+ 18 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/model/UserInfo.java

@@ -0,0 +1,18 @@
+package com.wdkl.ncs.janus.rtc2.model;
+
+/**
+ * Created by dds on 2020/4/11.
+ * 用户信息
+ */
+public class UserInfo {
+
+    private String userId;
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+}

+ 28 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/render/ProxyVideoSink.java

@@ -0,0 +1,28 @@
+package com.wdkl.ncs.janus.rtc2.render;
+
+import org.webrtc.Logging;
+import org.webrtc.VideoFrame;
+import org.webrtc.VideoSink;
+
+/**
+ * Created by dds on 2019/4/4.
+ * android_shuai@163.com
+ */
+public class ProxyVideoSink implements VideoSink {
+    private static final String TAG = "dds_ProxyVideoSink";
+    private VideoSink target;
+
+    @Override
+    synchronized public void onFrame(VideoFrame frame) {
+        if (target == null) {
+            Logging.d(TAG, "Dropping frame in proxy because target is null.");
+            return;
+        }
+        target.onFrame(frame);
+    }
+
+    synchronized public void setTarget(VideoSink target) {
+        this.target = target;
+    }
+
+}

+ 171 - 0
janus/src/main/java/com/wdkl/ncs/janus/rtc2/render/VideoFileRenderer.java

@@ -0,0 +1,171 @@
+/*
+ *  Copyright 2016 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+package com.wdkl.ncs.janus.rtc2.render;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import org.webrtc.EglBase;
+import org.webrtc.Logging;
+import org.webrtc.ThreadUtils;
+import org.webrtc.VideoFrame;
+import org.webrtc.VideoSink;
+import org.webrtc.YuvConverter;
+import org.webrtc.YuvHelper;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Can be used to save the video frames to file.
+ */
+public class VideoFileRenderer implements VideoSink {
+  private static final String TAG = "VideoFileRenderer";
+
+  private final HandlerThread renderThread;
+  private final Handler renderThreadHandler;
+  private final HandlerThread fileThread;
+  private final Handler fileThreadHandler;
+  private final FileOutputStream videoOutFile;
+  private final String outputFileName;
+  private final int outputFileWidth;
+  private final int outputFileHeight;
+  private final int outputFrameSize;
+  private final ByteBuffer outputFrameBuffer;
+  private EglBase eglBase;
+  private YuvConverter yuvConverter;
+  private int frameCount;
+
+  public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight,
+                           final EglBase.Context sharedContext) throws IOException {
+    if ((outputFileWidth % 2) == 1 || (outputFileHeight % 2) == 1) {
+      throw new IllegalArgumentException("Does not support uneven width or height");
+    }
+
+    this.outputFileName = outputFile;
+    this.outputFileWidth = outputFileWidth;
+    this.outputFileHeight = outputFileHeight;
+
+    outputFrameSize = outputFileWidth * outputFileHeight * 3 / 2;
+    outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize);
+
+    videoOutFile = new FileOutputStream(outputFile);
+    videoOutFile.write(
+        ("YUV4MPEG2 C420 W" + outputFileWidth + " H" + outputFileHeight + " Ip F30:1 A1:1\n")
+            .getBytes(Charset.forName("US-ASCII")));
+
+    renderThread = new HandlerThread(TAG + "RenderThread");
+    renderThread.start();
+    renderThreadHandler = new Handler(renderThread.getLooper());
+
+    fileThread = new HandlerThread(TAG + "FileThread");
+    fileThread.start();
+    fileThreadHandler = new Handler(fileThread.getLooper());
+
+    ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, new Runnable() {
+      @Override
+      public void run() {
+        eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER);
+        eglBase.createDummyPbufferSurface();
+        eglBase.makeCurrent();
+        yuvConverter = new YuvConverter();
+      }
+    });
+  }
+
+  @Override
+  public void onFrame(VideoFrame frame) {
+    frame.retain();
+    renderThreadHandler.post(() -> renderFrameOnRenderThread(frame));
+  }
+
+  private void renderFrameOnRenderThread(VideoFrame frame) {
+    final VideoFrame.Buffer buffer = frame.getBuffer();
+
+    // If the frame is rotated, it will be applied after cropAndScale. Therefore, if the frame is
+    // rotated by 90 degrees, swap width and height.
+    final int targetWidth = frame.getRotation() % 180 == 0 ? outputFileWidth : outputFileHeight;
+    final int targetHeight = frame.getRotation() % 180 == 0 ? outputFileHeight : outputFileWidth;
+
+    final float frameAspectRatio = (float) buffer.getWidth() / (float) buffer.getHeight();
+    final float fileAspectRatio = (float) targetWidth / (float) targetHeight;
+
+    // Calculate cropping to equalize the aspect ratio.
+    int cropWidth = buffer.getWidth();
+    int cropHeight = buffer.getHeight();
+    if (fileAspectRatio > frameAspectRatio) {
+      cropHeight = (int) (cropHeight * (frameAspectRatio / fileAspectRatio));
+    } else {
+      cropWidth = (int) (cropWidth * (fileAspectRatio / frameAspectRatio));
+    }
+
+    final int cropX = (buffer.getWidth() - cropWidth) / 2;
+    final int cropY = (buffer.getHeight() - cropHeight) / 2;
+
+    final VideoFrame.Buffer scaledBuffer =
+        buffer.cropAndScale(cropX, cropY, cropWidth, cropHeight, targetWidth, targetHeight);
+    frame.release();
+
+    final VideoFrame.I420Buffer i420 = scaledBuffer.toI420();
+    scaledBuffer.release();
+
+    fileThreadHandler.post(() -> {
+      YuvHelper.I420Rotate(i420.getDataY(), i420.getStrideY(), i420.getDataU(), i420.getStrideU(),
+          i420.getDataV(), i420.getStrideV(), outputFrameBuffer, i420.getWidth(), i420.getHeight(),
+          frame.getRotation());
+      i420.release();
+
+      try {
+        videoOutFile.write("FRAME\n".getBytes(Charset.forName("US-ASCII")));
+        videoOutFile.write(
+            outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize);
+      } catch (IOException e) {
+        throw new RuntimeException("Error writing video to disk", e);
+      }
+      frameCount++;
+    });
+  }
+
+  /**
+   * Release all resources. All already posted frames will be rendered first.
+   */
+  public void release() {
+    final CountDownLatch cleanupBarrier = new CountDownLatch(1);
+    renderThreadHandler.post(() -> {
+      yuvConverter.release();
+      eglBase.release();
+      renderThread.quit();
+      cleanupBarrier.countDown();
+    });
+    ThreadUtils.awaitUninterruptibly(cleanupBarrier);
+    fileThreadHandler.post(() -> {
+      try {
+        videoOutFile.close();
+        Logging.d(TAG,
+            "Video written to disk as " + outputFileName + ". The number of frames is " + frameCount
+                + " and the dimensions of the frames are " + outputFileWidth + "x"
+                + outputFileHeight + ".");
+      } catch (IOException e) {
+        throw new RuntimeException("Error closing output file", e);
+      }
+      fileThread.quit();
+    });
+    try {
+      fileThread.join();
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      Logging.e(TAG, "Interrupted while waiting for the write to disk to complete.", e);
+    }
+  }
+}

+ 13 - 0
middleware/build.gradle

@@ -2,6 +2,7 @@ apply plugin: 'com.android.library'
 apply plugin: 'kotlin-android'
 apply plugin: 'kotlin-android-extensions'
 apply plugin: 'kotlin-kapt'
+apply plugin: 'org.greenrobot.greendao' // greendao
 
 kapt {
     arguments {
@@ -81,4 +82,16 @@ dependencies {
 
     compile 'com.fasterxml.jackson.core:jackson-databind:2.9.5'
     compile 'io.swagger:swagger-annotations:1.5.14'
+
+    //greendao
+    compile 'org.greenrobot:greendao:3.2.2'
+}
+
+greendao {
+    //这里是数据库版本,需要比原来的大
+    schemaVersion 1
+    // 生成数据库文件的目录
+    targetGenDir 'src/main/code'
+    // 生成的数据库相关文件的包名
+    daoPackage 'com.wdkl.greendao.gen'
 }

+ 99 - 0
middleware/src/main/code/com/wdkl/greendao/gen/DaoMaster.java

@@ -0,0 +1,99 @@
+package com.wdkl.greendao.gen;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.util.Log;
+
+import org.greenrobot.greendao.AbstractDaoMaster;
+import org.greenrobot.greendao.database.StandardDatabase;
+import org.greenrobot.greendao.database.Database;
+import org.greenrobot.greendao.database.DatabaseOpenHelper;
+import org.greenrobot.greendao.identityscope.IdentityScopeType;
+
+
+// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
+/**
+ * Master of DAO (schema version 1): knows all DAOs.
+ */
+public class DaoMaster extends AbstractDaoMaster {
+    public static final int SCHEMA_VERSION = 1;
+
+    /** Creates underlying database table using DAOs. */
+    public static void createAllTables(Database db, boolean ifNotExists) {
+        DeviceBeanDao.createTable(db, ifNotExists);
+        DeviceInfoBeanDao.createTable(db, ifNotExists);
+    }
+
+    /** Drops underlying database table using DAOs. */
+    public static void dropAllTables(Database db, boolean ifExists) {
+        DeviceBeanDao.dropTable(db, ifExists);
+        DeviceInfoBeanDao.dropTable(db, ifExists);
+    }
+
+    /**
+     * WARNING: Drops all table on Upgrade! Use only during development.
+     * Convenience method using a {@link DevOpenHelper}.
+     */
+    public static DaoSession newDevSession(Context context, String name) {
+        Database db = new DevOpenHelper(context, name).getWritableDb();
+        DaoMaster daoMaster = new DaoMaster(db);
+        return daoMaster.newSession();
+    }
+
+    public DaoMaster(SQLiteDatabase db) {
+        this(new StandardDatabase(db));
+    }
+
+    public DaoMaster(Database db) {
+        super(db, SCHEMA_VERSION);
+        registerDaoClass(DeviceBeanDao.class);
+        registerDaoClass(DeviceInfoBeanDao.class);
+    }
+
+    public DaoSession newSession() {
+        return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
+    }
+
+    public DaoSession newSession(IdentityScopeType type) {
+        return new DaoSession(db, type, daoConfigMap);
+    }
+
+    /**
+     * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
+     */
+    public static abstract class OpenHelper extends DatabaseOpenHelper {
+        public OpenHelper(Context context, String name) {
+            super(context, name, SCHEMA_VERSION);
+        }
+
+        public OpenHelper(Context context, String name, CursorFactory factory) {
+            super(context, name, factory, SCHEMA_VERSION);
+        }
+
+        @Override
+        public void onCreate(Database db) {
+            Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
+            createAllTables(db, false);
+        }
+    }
+
+    /** WARNING: Drops all table on Upgrade! Use only during development. */
+    public static class DevOpenHelper extends OpenHelper {
+        public DevOpenHelper(Context context, String name) {
+            super(context, name);
+        }
+
+        public DevOpenHelper(Context context, String name, CursorFactory factory) {
+            super(context, name, factory);
+        }
+
+        @Override
+        public void onUpgrade(Database db, int oldVersion, int newVersion) {
+            Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
+            dropAllTables(db, true);
+            onCreate(db);
+        }
+    }
+
+}

+ 62 - 0
middleware/src/main/code/com/wdkl/greendao/gen/DaoSession.java

@@ -0,0 +1,62 @@
+package com.wdkl.greendao.gen;
+
+import java.util.Map;
+
+import org.greenrobot.greendao.AbstractDao;
+import org.greenrobot.greendao.AbstractDaoSession;
+import org.greenrobot.greendao.database.Database;
+import org.greenrobot.greendao.identityscope.IdentityScopeType;
+import org.greenrobot.greendao.internal.DaoConfig;
+
+import com.wdkl.ncs.android.middleware.greendao.entity.DeviceBean;
+import com.wdkl.ncs.android.middleware.greendao.entity.DeviceInfoBean;
+
+import com.wdkl.greendao.gen.DeviceBeanDao;
+import com.wdkl.greendao.gen.DeviceInfoBeanDao;
+
+// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
+
+/**
+ * {@inheritDoc}
+ * 
+ * @see org.greenrobot.greendao.AbstractDaoSession
+ */
+public class DaoSession extends AbstractDaoSession {
+
+    private final DaoConfig deviceBeanDaoConfig;
+    private final DaoConfig deviceInfoBeanDaoConfig;
+
+    private final DeviceBeanDao deviceBeanDao;
+    private final DeviceInfoBeanDao deviceInfoBeanDao;
+
+    public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
+            daoConfigMap) {
+        super(db);
+
+        deviceBeanDaoConfig = daoConfigMap.get(DeviceBeanDao.class).clone();
+        deviceBeanDaoConfig.initIdentityScope(type);
+
+        deviceInfoBeanDaoConfig = daoConfigMap.get(DeviceInfoBeanDao.class).clone();
+        deviceInfoBeanDaoConfig.initIdentityScope(type);
+
+        deviceBeanDao = new DeviceBeanDao(deviceBeanDaoConfig, this);
+        deviceInfoBeanDao = new DeviceInfoBeanDao(deviceInfoBeanDaoConfig, this);
+
+        registerDao(DeviceBean.class, deviceBeanDao);
+        registerDao(DeviceInfoBean.class, deviceInfoBeanDao);
+    }
+    
+    public void clear() {
+        deviceBeanDaoConfig.clearIdentityScope();
+        deviceInfoBeanDaoConfig.clearIdentityScope();
+    }
+
+    public DeviceBeanDao getDeviceBeanDao() {
+        return deviceBeanDao;
+    }
+
+    public DeviceInfoBeanDao getDeviceInfoBeanDao() {
+        return deviceInfoBeanDao;
+    }
+
+}

+ 196 - 0
middleware/src/main/code/com/wdkl/greendao/gen/DeviceBeanDao.java

@@ -0,0 +1,196 @@
+package com.wdkl.greendao.gen;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteStatement;
+
+import org.greenrobot.greendao.AbstractDao;
+import org.greenrobot.greendao.Property;
+import org.greenrobot.greendao.internal.DaoConfig;
+import org.greenrobot.greendao.database.Database;
+import org.greenrobot.greendao.database.DatabaseStatement;
+
+import com.wdkl.ncs.android.middleware.greendao.entity.DeviceBean;
+
+// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
+/** 
+ * DAO for table "DEVICE_BEAN".
+*/
+public class DeviceBeanDao extends AbstractDao<DeviceBean, Void> {
+
+    public static final String TABLENAME = "DEVICE_BEAN";
+
+    /**
+     * Properties of entity DeviceBean.<br/>
+     * Can be used for QueryBuilder and for referencing column names.
+     */
+    public static class Properties {
+        public final static Property Id = new Property(0, Integer.class, "id", false, "ID");
+        public final static Property PartId = new Property(1, Integer.class, "partId", false, "PART_ID");
+        public final static Property Name = new Property(2, String.class, "name", false, "NAME");
+        public final static Property FullName = new Property(3, String.class, "fullName", false, "FULL_NAME");
+        public final static Property DeviceId = new Property(4, Integer.class, "deviceId", false, "DEVICE_ID");
+        public final static Property UartAddr = new Property(5, String.class, "uartAddr", false, "UART_ADDR");
+        public final static Property DeviceType = new Property(6, Integer.class, "deviceType", false, "DEVICE_TYPE");
+    }
+
+
+    public DeviceBeanDao(DaoConfig config) {
+        super(config);
+    }
+    
+    public DeviceBeanDao(DaoConfig config, DaoSession daoSession) {
+        super(config, daoSession);
+    }
+
+    /** Creates the underlying database table. */
+    public static void createTable(Database db, boolean ifNotExists) {
+        String constraint = ifNotExists? "IF NOT EXISTS ": "";
+        db.execSQL("CREATE TABLE " + constraint + "\"DEVICE_BEAN\" (" + //
+                "\"ID\" INTEGER," + // 0: id
+                "\"PART_ID\" INTEGER," + // 1: partId
+                "\"NAME\" TEXT," + // 2: name
+                "\"FULL_NAME\" TEXT," + // 3: fullName
+                "\"DEVICE_ID\" INTEGER," + // 4: deviceId
+                "\"UART_ADDR\" TEXT," + // 5: uartAddr
+                "\"DEVICE_TYPE\" INTEGER);"); // 6: deviceType
+    }
+
+    /** Drops the underlying database table. */
+    public static void dropTable(Database db, boolean ifExists) {
+        String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"DEVICE_BEAN\"";
+        db.execSQL(sql);
+    }
+
+    @Override
+    protected final void bindValues(DatabaseStatement stmt, DeviceBean entity) {
+        stmt.clearBindings();
+ 
+        Integer id = entity.getId();
+        if (id != null) {
+            stmt.bindLong(1, id);
+        }
+ 
+        Integer partId = entity.getPartId();
+        if (partId != null) {
+            stmt.bindLong(2, partId);
+        }
+ 
+        String name = entity.getName();
+        if (name != null) {
+            stmt.bindString(3, name);
+        }
+ 
+        String fullName = entity.getFullName();
+        if (fullName != null) {
+            stmt.bindString(4, fullName);
+        }
+ 
+        Integer deviceId = entity.getDeviceId();
+        if (deviceId != null) {
+            stmt.bindLong(5, deviceId);
+        }
+ 
+        String uartAddr = entity.getUartAddr();
+        if (uartAddr != null) {
+            stmt.bindString(6, uartAddr);
+        }
+ 
+        Integer deviceType = entity.getDeviceType();
+        if (deviceType != null) {
+            stmt.bindLong(7, deviceType);
+        }
+    }
+
+    @Override
+    protected final void bindValues(SQLiteStatement stmt, DeviceBean entity) {
+        stmt.clearBindings();
+ 
+        Integer id = entity.getId();
+        if (id != null) {
+            stmt.bindLong(1, id);
+        }
+ 
+        Integer partId = entity.getPartId();
+        if (partId != null) {
+            stmt.bindLong(2, partId);
+        }
+ 
+        String name = entity.getName();
+        if (name != null) {
+            stmt.bindString(3, name);
+        }
+ 
+        String fullName = entity.getFullName();
+        if (fullName != null) {
+            stmt.bindString(4, fullName);
+        }
+ 
+        Integer deviceId = entity.getDeviceId();
+        if (deviceId != null) {
+            stmt.bindLong(5, deviceId);
+        }
+ 
+        String uartAddr = entity.getUartAddr();
+        if (uartAddr != null) {
+            stmt.bindString(6, uartAddr);
+        }
+ 
+        Integer deviceType = entity.getDeviceType();
+        if (deviceType != null) {
+            stmt.bindLong(7, deviceType);
+        }
+    }
+
+    @Override
+    public Void readKey(Cursor cursor, int offset) {
+        return null;
+    }    
+
+    @Override
+    public DeviceBean readEntity(Cursor cursor, int offset) {
+        DeviceBean entity = new DeviceBean( //
+            cursor.isNull(offset + 0) ? null : cursor.getInt(offset + 0), // id
+            cursor.isNull(offset + 1) ? null : cursor.getInt(offset + 1), // partId
+            cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // name
+            cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // fullName
+            cursor.isNull(offset + 4) ? null : cursor.getInt(offset + 4), // deviceId
+            cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5), // uartAddr
+            cursor.isNull(offset + 6) ? null : cursor.getInt(offset + 6) // deviceType
+        );
+        return entity;
+    }
+     
+    @Override
+    public void readEntity(Cursor cursor, DeviceBean entity, int offset) {
+        entity.setId(cursor.isNull(offset + 0) ? null : cursor.getInt(offset + 0));
+        entity.setPartId(cursor.isNull(offset + 1) ? null : cursor.getInt(offset + 1));
+        entity.setName(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2));
+        entity.setFullName(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3));
+        entity.setDeviceId(cursor.isNull(offset + 4) ? null : cursor.getInt(offset + 4));
+        entity.setUartAddr(cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5));
+        entity.setDeviceType(cursor.isNull(offset + 6) ? null : cursor.getInt(offset + 6));
+     }
+    
+    @Override
+    protected final Void updateKeyAfterInsert(DeviceBean entity, long rowId) {
+        // Unsupported or missing PK type
+        return null;
+    }
+    
+    @Override
+    public Void getKey(DeviceBean entity) {
+        return null;
+    }
+
+    @Override
+    public boolean hasKey(DeviceBean entity) {
+        // TODO
+        return false;
+    }
+
+    @Override
+    protected final boolean isEntityUpdateable() {
+        return true;
+    }
+    
+}

+ 238 - 0
middleware/src/main/code/com/wdkl/greendao/gen/DeviceInfoBeanDao.java

@@ -0,0 +1,238 @@
+package com.wdkl.greendao.gen;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteStatement;
+
+import org.greenrobot.greendao.AbstractDao;
+import org.greenrobot.greendao.Property;
+import org.greenrobot.greendao.internal.DaoConfig;
+import org.greenrobot.greendao.database.Database;
+import org.greenrobot.greendao.database.DatabaseStatement;
+
+import com.wdkl.ncs.android.middleware.greendao.entity.DeviceInfoBean;
+
+// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
+/** 
+ * DAO for table "DEVICE_INFO_BEAN".
+*/
+public class DeviceInfoBeanDao extends AbstractDao<DeviceInfoBean, Void> {
+
+    public static final String TABLENAME = "DEVICE_INFO_BEAN";
+
+    /**
+     * Properties of entity DeviceInfoBean.<br/>
+     * Can be used for QueryBuilder and for referencing column names.
+     */
+    public static class Properties {
+        public final static Property Id = new Property(0, Integer.class, "id", false, "ID");
+        public final static Property PartId = new Property(1, Integer.class, "partId", false, "PART_ID");
+        public final static Property DeviceType = new Property(2, Integer.class, "deviceType", false, "DEVICE_TYPE");
+        public final static Property Name = new Property(3, String.class, "name", false, "NAME");
+        public final static Property SipId = new Property(4, String.class, "sipId", false, "SIP_ID");
+        public final static Property SipPassword = new Property(5, String.class, "sipPassword", false, "SIP_PASSWORD");
+        public final static Property HospitalId = new Property(6, Integer.class, "hospitalId", false, "HOSPITAL_ID");
+        public final static Property HospitalName = new Property(7, String.class, "hospitalName", false, "HOSPITAL_NAME");
+        public final static Property PartName = new Property(8, String.class, "partName", false, "PART_NAME");
+        public final static Property PartDisplay = new Property(9, String.class, "partDisplay", false, "PART_DISPLAY");
+    }
+
+
+    public DeviceInfoBeanDao(DaoConfig config) {
+        super(config);
+    }
+    
+    public DeviceInfoBeanDao(DaoConfig config, DaoSession daoSession) {
+        super(config, daoSession);
+    }
+
+    /** Creates the underlying database table. */
+    public static void createTable(Database db, boolean ifNotExists) {
+        String constraint = ifNotExists? "IF NOT EXISTS ": "";
+        db.execSQL("CREATE TABLE " + constraint + "\"DEVICE_INFO_BEAN\" (" + //
+                "\"ID\" INTEGER," + // 0: id
+                "\"PART_ID\" INTEGER," + // 1: partId
+                "\"DEVICE_TYPE\" INTEGER," + // 2: deviceType
+                "\"NAME\" TEXT," + // 3: name
+                "\"SIP_ID\" TEXT," + // 4: sipId
+                "\"SIP_PASSWORD\" TEXT," + // 5: sipPassword
+                "\"HOSPITAL_ID\" INTEGER," + // 6: hospitalId
+                "\"HOSPITAL_NAME\" TEXT," + // 7: hospitalName
+                "\"PART_NAME\" TEXT," + // 8: partName
+                "\"PART_DISPLAY\" TEXT);"); // 9: partDisplay
+    }
+
+    /** Drops the underlying database table. */
+    public static void dropTable(Database db, boolean ifExists) {
+        String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"DEVICE_INFO_BEAN\"";
+        db.execSQL(sql);
+    }
+
+    @Override
+    protected final void bindValues(DatabaseStatement stmt, DeviceInfoBean entity) {
+        stmt.clearBindings();
+ 
+        Integer id = entity.getId();
+        if (id != null) {
+            stmt.bindLong(1, id);
+        }
+ 
+        Integer partId = entity.getPartId();
+        if (partId != null) {
+            stmt.bindLong(2, partId);
+        }
+ 
+        Integer deviceType = entity.getDeviceType();
+        if (deviceType != null) {
+            stmt.bindLong(3, deviceType);
+        }
+ 
+        String name = entity.getName();
+        if (name != null) {
+            stmt.bindString(4, name);
+        }
+ 
+        String sipId = entity.getSipId();
+        if (sipId != null) {
+            stmt.bindString(5, sipId);
+        }
+ 
+        String sipPassword = entity.getSipPassword();
+        if (sipPassword != null) {
+            stmt.bindString(6, sipPassword);
+        }
+ 
+        Integer hospitalId = entity.getHospitalId();
+        if (hospitalId != null) {
+            stmt.bindLong(7, hospitalId);
+        }
+ 
+        String hospitalName = entity.getHospitalName();
+        if (hospitalName != null) {
+            stmt.bindString(8, hospitalName);
+        }
+ 
+        String partName = entity.getPartName();
+        if (partName != null) {
+            stmt.bindString(9, partName);
+        }
+ 
+        String partDisplay = entity.getPartDisplay();
+        if (partDisplay != null) {
+            stmt.bindString(10, partDisplay);
+        }
+    }
+
+    @Override
+    protected final void bindValues(SQLiteStatement stmt, DeviceInfoBean entity) {
+        stmt.clearBindings();
+ 
+        Integer id = entity.getId();
+        if (id != null) {
+            stmt.bindLong(1, id);
+        }
+ 
+        Integer partId = entity.getPartId();
+        if (partId != null) {
+            stmt.bindLong(2, partId);
+        }
+ 
+        Integer deviceType = entity.getDeviceType();
+        if (deviceType != null) {
+            stmt.bindLong(3, deviceType);
+        }
+ 
+        String name = entity.getName();
+        if (name != null) {
+            stmt.bindString(4, name);
+        }
+ 
+        String sipId = entity.getSipId();
+        if (sipId != null) {
+            stmt.bindString(5, sipId);
+        }
+ 
+        String sipPassword = entity.getSipPassword();
+        if (sipPassword != null) {
+            stmt.bindString(6, sipPassword);
+        }
+ 
+        Integer hospitalId = entity.getHospitalId();
+        if (hospitalId != null) {
+            stmt.bindLong(7, hospitalId);
+        }
+ 
+        String hospitalName = entity.getHospitalName();
+        if (hospitalName != null) {
+            stmt.bindString(8, hospitalName);
+        }
+ 
+        String partName = entity.getPartName();
+        if (partName != null) {
+            stmt.bindString(9, partName);
+        }
+ 
+        String partDisplay = entity.getPartDisplay();
+        if (partDisplay != null) {
+            stmt.bindString(10, partDisplay);
+        }
+    }
+
+    @Override
+    public Void readKey(Cursor cursor, int offset) {
+        return null;
+    }    
+
+    @Override
+    public DeviceInfoBean readEntity(Cursor cursor, int offset) {
+        DeviceInfoBean entity = new DeviceInfoBean( //
+            cursor.isNull(offset + 0) ? null : cursor.getInt(offset + 0), // id
+            cursor.isNull(offset + 1) ? null : cursor.getInt(offset + 1), // partId
+            cursor.isNull(offset + 2) ? null : cursor.getInt(offset + 2), // deviceType
+            cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // name
+            cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4), // sipId
+            cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5), // sipPassword
+            cursor.isNull(offset + 6) ? null : cursor.getInt(offset + 6), // hospitalId
+            cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7), // hospitalName
+            cursor.isNull(offset + 8) ? null : cursor.getString(offset + 8), // partName
+            cursor.isNull(offset + 9) ? null : cursor.getString(offset + 9) // partDisplay
+        );
+        return entity;
+    }
+     
+    @Override
+    public void readEntity(Cursor cursor, DeviceInfoBean entity, int offset) {
+        entity.setId(cursor.isNull(offset + 0) ? null : cursor.getInt(offset + 0));
+        entity.setPartId(cursor.isNull(offset + 1) ? null : cursor.getInt(offset + 1));
+        entity.setDeviceType(cursor.isNull(offset + 2) ? null : cursor.getInt(offset + 2));
+        entity.setName(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3));
+        entity.setSipId(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4));
+        entity.setSipPassword(cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5));
+        entity.setHospitalId(cursor.isNull(offset + 6) ? null : cursor.getInt(offset + 6));
+        entity.setHospitalName(cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7));
+        entity.setPartName(cursor.isNull(offset + 8) ? null : cursor.getString(offset + 8));
+        entity.setPartDisplay(cursor.isNull(offset + 9) ? null : cursor.getString(offset + 9));
+     }
+    
+    @Override
+    protected final Void updateKeyAfterInsert(DeviceInfoBean entity, long rowId) {
+        // Unsupported or missing PK type
+        return null;
+    }
+    
+    @Override
+    public Void getKey(DeviceInfoBean entity) {
+        return null;
+    }
+
+    @Override
+    public boolean hasKey(DeviceInfoBean entity) {
+        // TODO
+        return false;
+    }
+
+    @Override
+    protected final boolean isEntityUpdateable() {
+        return true;
+    }
+    
+}

+ 4 - 2
middleware/src/main/code/com/wdkl/ncs/android/middleware/common/Constant.java

@@ -55,7 +55,7 @@ public class Constant {
     public static String LOCAL_MAC = "";
 
     //设备ID
-    public static Integer DEVICE_ID;
+    public static Integer DEVICE_ID = -1;
 
     //客户ID
     public static Integer CUSTOM_ID = -1;
@@ -64,7 +64,7 @@ public class Constant {
     public static String SIP_ID = "";
 
     //科室id
-    public static Integer PART_ID;
+    public static Integer PART_ID = -1;
 
     //设备启用状态
     public static Integer DEVICE_STATUS = -1;
@@ -158,4 +158,6 @@ public class Constant {
     public static final int EVENT_RESTART_APP = 0x11;
 
     public static final int EVENT_END_CALL = 0x12;
+
+    public static final int EVENT_UDP = 0x13;
 }

+ 110 - 0
middleware/src/main/code/com/wdkl/ncs/android/middleware/greendao/DaoManager.java

@@ -0,0 +1,110 @@
+package com.wdkl.ncs.android.middleware.greendao;
+
+import android.app.Application;
+
+import com.wdkl.greendao.gen.DaoMaster;
+import com.wdkl.greendao.gen.DaoSession;
+import com.wdkl.ncs.android.lib.BuildConfig;
+
+import org.greenrobot.greendao.async.AsyncSession;
+import org.greenrobot.greendao.query.QueryBuilder;
+
+/**
+ * 创建数据库、创建数据库表、包含增删改查的操作
+ */
+public class DaoManager {
+    private static final String TAG = DaoManager.class.getSimpleName();
+    private static final String DB_NAME = "ncs_converter_calling.db";
+
+    private Application mApplication;
+
+    //多线程中要被共享的使用volatile关键字修饰
+    private volatile static DaoManager manager = new DaoManager();
+    private DaoMaster mDaoMaster;
+    private DaoMaster.DevOpenHelper mHelper;
+    private DaoSession mDaoSession;
+    private AsyncSession asyncSession;
+
+    /**
+     * 单例模式获得操作数据库对象
+     */
+    public static DaoManager getInstance() {
+        return manager;
+    }
+
+    private DaoManager() {
+        setDebug();
+    }
+
+    public void init(Application application) {
+        this.mApplication = application;
+    }
+
+    /**
+     * 判断是否有存在数据库,如果没有则创建
+     */
+    public DaoMaster getDaoMaster() {
+        if (mDaoMaster == null) {
+            DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(mApplication, DB_NAME, null);
+            mDaoMaster = new DaoMaster(helper.getWritableDatabase());
+        }
+        return mDaoMaster;
+    }
+
+    /**
+     * 完成对数据库的添加、删除、修改、查询操作,仅仅是一个接口
+     */
+    public DaoSession getDaoSession() {
+        if (mDaoSession == null) {
+            if (mDaoMaster == null) {
+                mDaoMaster = getDaoMaster();
+            }
+            mDaoSession = mDaoMaster.newSession();
+        }
+        return mDaoSession;
+    }
+
+    public AsyncSession getAsyncSession() {
+        if (asyncSession == null) {
+            if (mDaoSession != null) {
+                return mDaoSession.startAsyncSession();
+            } else {
+                return getDaoSession().startAsyncSession();
+            }
+        } else {
+            return asyncSession;
+        }
+    }
+
+    /**
+     * 打开输出日志,默认关闭
+     */
+    public void setDebug() {
+        if (BuildConfig.DEBUG) {
+            QueryBuilder.LOG_SQL = true;
+            QueryBuilder.LOG_VALUES = true;
+        }
+    }
+
+    /**
+     * 关闭所有的操作,数据库开启后,使用完毕要关闭
+     */
+    public void closeConnection() {
+        closeHelper();
+        closeDaoSession();
+    }
+
+    public void closeHelper() {
+        if (mHelper != null) {
+            mHelper.close();
+            mHelper = null;
+        }
+    }
+
+    public void closeDaoSession() {
+        if (mDaoSession != null) {
+            mDaoSession.clear();
+            mDaoSession = null;
+        }
+    }
+}

+ 104 - 0
middleware/src/main/code/com/wdkl/ncs/android/middleware/greendao/entity/DeviceBean.java

@@ -0,0 +1,104 @@
+package com.wdkl.ncs.android.middleware.greendao.entity;
+
+import com.wdkl.ncs.android.middleware.model.annotation.Column;
+import com.wdkl.ncs.android.middleware.model.annotation.Id;
+
+import org.greenrobot.greendao.annotation.Entity;
+import org.greenrobot.greendao.annotation.Generated;
+
+@Entity
+public class DeviceBean {
+    @Column(name = "id")
+    @Id(name = "id")
+    private Integer id;
+
+    @Column(name = "part_id")
+    private Integer partId;
+
+    @Column(name = "name")
+    private String name;
+
+    @Column(name = "full_name")
+    private String fullName;
+
+    @Column(name = "device_id")
+    private Integer deviceId;
+
+    @Column(name = "uart_addr")
+    private String uartAddr;
+
+    @Column(name = "device_type")
+    private Integer deviceType;
+
+    @Generated(hash = 1118325881)
+    public DeviceBean(Integer id, Integer partId, String name, String fullName,
+            Integer deviceId, String uartAddr, Integer deviceType) {
+        this.id = id;
+        this.partId = partId;
+        this.name = name;
+        this.fullName = fullName;
+        this.deviceId = deviceId;
+        this.uartAddr = uartAddr;
+        this.deviceType = deviceType;
+    }
+
+    @Generated(hash = 74682814)
+    public DeviceBean() {
+    }
+
+    public Integer getId() {
+        return this.id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getPartId() {
+        return this.partId;
+    }
+
+    public void setPartId(Integer partId) {
+        this.partId = partId;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getFullName() {
+        return this.fullName;
+    }
+
+    public void setFullName(String fullName) {
+        this.fullName = fullName;
+    }
+
+    public Integer getDeviceId() {
+        return this.deviceId;
+    }
+
+    public void setDeviceId(Integer deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public String getUartAddr() {
+        return this.uartAddr;
+    }
+
+    public void setUartAddr(String uartAddr) {
+        this.uartAddr = uartAddr;
+    }
+
+    public Integer getDeviceType() {
+        return this.deviceType;
+    }
+
+    public void setDeviceType(Integer deviceType) {
+        this.deviceType = deviceType;
+    }
+}

+ 140 - 0
middleware/src/main/code/com/wdkl/ncs/android/middleware/greendao/entity/DeviceInfoBean.java

@@ -0,0 +1,140 @@
+package com.wdkl.ncs.android.middleware.greendao.entity;
+
+import com.wdkl.ncs.android.middleware.model.annotation.Column;
+import com.wdkl.ncs.android.middleware.model.annotation.Id;
+
+import org.greenrobot.greendao.annotation.Entity;
+import org.greenrobot.greendao.annotation.Generated;
+
+@Entity
+public class DeviceInfoBean {
+    @Id(name = "id")
+    private Integer id;
+
+    @Column(name = "part_id")
+    private Integer partId;
+
+    @Column(name = "device_type")
+    private Integer deviceType;
+
+    @Column(name = "name")
+    private String name;
+
+    @Column(name = "sip_id")
+    private String sipId;
+
+    @Column(name = "sip_password")
+    private String sipPassword;
+
+    @Column(name = "hospital_id")
+    private Integer hospitalId;
+
+    @Column(name = "hospital_name")
+    private String hospitalName;
+
+    @Column(name = "part_name")
+    private String partName;
+
+    @Column(name = "part_display")
+    private String partDisplay;
+
+    @Generated(hash = 616532455)
+    public DeviceInfoBean(Integer id, Integer partId, Integer deviceType,
+            String name, String sipId, String sipPassword, Integer hospitalId,
+            String hospitalName, String partName, String partDisplay) {
+        this.id = id;
+        this.partId = partId;
+        this.deviceType = deviceType;
+        this.name = name;
+        this.sipId = sipId;
+        this.sipPassword = sipPassword;
+        this.hospitalId = hospitalId;
+        this.hospitalName = hospitalName;
+        this.partName = partName;
+        this.partDisplay = partDisplay;
+    }
+
+    @Generated(hash = 784809703)
+    public DeviceInfoBean() {
+    }
+
+    public Integer getId() {
+        return this.id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getPartId() {
+        return this.partId;
+    }
+
+    public void setPartId(Integer partId) {
+        this.partId = partId;
+    }
+
+    public Integer getDeviceType() {
+        return this.deviceType;
+    }
+
+    public void setDeviceType(Integer deviceType) {
+        this.deviceType = deviceType;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getSipId() {
+        return this.sipId;
+    }
+
+    public void setSipId(String sipId) {
+        this.sipId = sipId;
+    }
+
+    public String getSipPassword() {
+        return this.sipPassword;
+    }
+
+    public void setSipPassword(String sipPassword) {
+        this.sipPassword = sipPassword;
+    }
+
+    public Integer getHospitalId() {
+        return this.hospitalId;
+    }
+
+    public void setHospitalId(Integer hospitalId) {
+        this.hospitalId = hospitalId;
+    }
+
+    public String getHospitalName() {
+        return this.hospitalName;
+    }
+
+    public void setHospitalName(String hospitalName) {
+        this.hospitalName = hospitalName;
+    }
+
+    public String getPartName() {
+        return this.partName;
+    }
+
+    public void setPartName(String partName) {
+        this.partName = partName;
+    }
+
+    public String getPartDisplay() {
+        return this.partDisplay;
+    }
+
+    public void setPartDisplay(String partDisplay) {
+        this.partDisplay = partDisplay;
+    }
+}

+ 12 - 0
middleware/src/main/code/com/wdkl/ncs/android/middleware/model/vo/DeviceNurseInfoVO.java

@@ -38,6 +38,10 @@ public class DeviceNurseInfoVO extends DeviceDO {
     @ApiModelProperty(value = "角色名称", required = false)
     private String roleName;
 
+    @Column(name = "part_display")
+    @ApiModelProperty(value = "科室显示名称(科室全称字段)", required = false)
+    private String partDisplay;
+
 
     public Integer getHospitalId() {
         return hospitalId;
@@ -86,4 +90,12 @@ public class DeviceNurseInfoVO extends DeviceDO {
     public void setRoleName(String roleName) {
         this.roleName = roleName;
     }
+
+    public String getPartDisplay() {
+        return partDisplay;
+    }
+
+    public void setPartDisplay(String partDisplay) {
+        this.partDisplay = partDisplay;
+    }
 }

+ 2 - 2
middleware/src/main/code/com/wdkl/ncs/android/middleware/udp/ServerInfoUtil.java

@@ -72,13 +72,13 @@ public class ServerInfoUtil {
         }
 
         //--------------------HTTP未请求成功,发送UDP获取
-        String udpResponse = UdpClient.getInstance().broadMsg(UdpClient.SEARCH_SERVER,UdpClient.UDP_TARGET_PORT);
+        /*String udpResponse = UdpClient.getInstance().broadMsg(UdpClient.SEARCH_SERVER,UdpClient.UDP_TARGET_PORT);
         jsonResponse = JSON.parseObject(udpResponse, JsonResponse.class);
         if (jsonResponse != null && jsonResponse.success){
             JSONObject jsonObject = JSON.parseObject(jsonResponse.data.toString());
             ThirdServerInfo thirdServerInfo = new ThirdServerInfo(jsonObject.getString("third_server"), jsonObject.getInteger("third_server_port"));
             return thirdServerInfo;
-        }
+        }*/
         return null;
     }
 

+ 90 - 0
middleware/src/main/code/com/wdkl/ncs/android/middleware/udp2/AnalysisUdpUtil.java

@@ -0,0 +1,90 @@
+package com.wdkl.ncs.android.middleware.udp2;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.wdkl.ncs.android.middleware.common.Constant;
+import com.wdkl.ncs.android.middleware.common.MessageEvent;
+
+import org.greenrobot.eventbus.EventBus;
+
+
+/**
+ * Created by 胡博文 on 2017/10/18.
+ */
+
+public class AnalysisUdpUtil {
+
+    public static String TAG = AnalysisUdpUtil.class.getSimpleName();
+
+    private AnalysisUdpUtil() {
+    }
+
+    public static void AnalysisUdp(String udpMsg, Context context) { //接收UDP
+        Log.d("AnalysisUdpUtil", "1===udpMsg==" + udpMsg);
+        //判断是否为美元符号开头,如果是则是android 端的命令
+        if ("$".equals(udpMsg.substring(0, 1))) {
+            udpMsg = delHeadAndEnd(udpMsg, "$", "#");
+            final String[] data = udpMsg.split(Character.toString((char) 3));
+
+            //判断udp消息长度是否符合条件,并且不是本机发出的
+            if (data.length < 9) {
+                return;
+            } else if (data[7].equals(Constant.DEVICE_ID.toString())) {
+                return;
+            } else if (!data[8].equals(Constant.PART_ID.toString())) {
+                return;
+            }
+
+            switch (data[0]) {
+                case UdpIndex.SERVER_MODE_OFFLINE: //进入离线模式
+                case UdpIndex.SERVER_MODE_ONLINE: //进入在线模式
+                    UdpItem udpItem = new UdpItem();
+                    udpItem.setIndex(data[0]);
+                    udpItem.setTargetIpAddr(data[4]);
+                    udpItem.setPartId(Integer.parseInt(data[8]));
+                    EventBus.getDefault().post(new MessageEvent(udpItem, Constant.EVENT_UDP));
+                    break;
+
+                case UdpIndex.BED_CALL_REJECT:
+                case UdpIndex.BED_CALL_ACCEPT:
+                case UdpIndex.BED_CALL_END:
+                case UdpIndex.HOST_CALL_BED:
+                case UdpIndex.HOST_CALL_CANCEL:
+                case UdpIndex.SOS_CANCEL:
+                    UdpItem rejectUdpItem = new UdpItem();
+                    rejectUdpItem.setIndex(data[0]);
+                    rejectUdpItem.setFrameName(data[1]);
+                    rejectUdpItem.setUartAddr(data[3]);
+                    rejectUdpItem.setData(data[6]);
+                    rejectUdpItem.setDeviceId(Integer.parseInt(data[7]));
+                    rejectUdpItem.setPartId(Integer.parseInt(data[8]));
+                    EventBus.getDefault().post(new MessageEvent(rejectUdpItem, Constant.EVENT_UDP));
+                    break;
+            }
+        }
+    }
+
+
+    public static String delHeadAndEnd(String source, String beginTrim, String endTrim) {
+        if (source == null) {
+            return "";
+        }
+        source = source.trim(); // 循环去掉字符串首的beTrim字符
+        if (source.isEmpty()) {
+            return "";
+        }
+        String beginChar = source.substring(0, 1);
+        if (beginChar.equalsIgnoreCase(beginTrim)) {
+            source = source.substring(1, source.length());
+            beginChar = source.substring(0, 1);
+        }
+        // 循环去掉字符串尾的beTrim字符
+        String endChar = source.substring(source.length() - 1, source.length());
+        if (endChar.equalsIgnoreCase(endTrim)) {
+            source = source.substring(0, source.length() - 1);
+            endChar = source.substring(source.length() - 1, source.length());
+        }
+        return source;
+    }
+}

+ 102 - 0
middleware/src/main/code/com/wdkl/ncs/android/middleware/udp2/UdpHelper.java

@@ -0,0 +1,102 @@
+package com.wdkl.ncs.android.middleware.udp2;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+/**
+ * UdpHelper帮助类
+ *
+ * @author 陈喆榕
+ */
+public class UdpHelper implements Runnable {
+    public Boolean IsThreadDisable = false;//指示监听线程是否终止
+    private static WifiManager.MulticastLock lock;
+    private Context context;
+    public static int udpPort = 5001;
+
+    public UdpHelper(WifiManager manager, Context context) {
+        this.lock = manager.createMulticastLock("UDPwifi");
+        this.context = context;
+    }
+
+    public void StartListen() {
+        // 接收的字节大小,客户端发送的数据不能超过这个大小
+        byte[] message = new byte[1400];
+        try {
+            // 建立Socket连接
+            DatagramSocket datagramSocket = new DatagramSocket(udpPort);
+            datagramSocket.setBroadcast(true);
+            DatagramPacket datagramPacket = new DatagramPacket(message,
+                    message.length);
+            try {
+
+                while (!IsThreadDisable) {
+                    // 准备接收数据
+                    this.lock.acquire();
+                    datagramSocket.receive(datagramPacket);
+                    String strMsg = new String(datagramPacket.getData()).trim();
+                    //Log.d("UDP_Helper", "UDP接收:" + strMsg);
+
+                    AnalysisUdpUtil.AnalysisUdp(strMsg, context);
+
+                    for (int i = 0; i < message.length; i++) {
+                        message[i] = 0;
+                    }
+                    this.lock.release();
+                }
+            } catch (IOException e) {//IOException
+                e.printStackTrace();
+            }
+        } catch (SocketException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    public static void send(String message) {
+        message = (message == null ? "Hello IdeasAndroid!" : message);
+        //如果list中包含 message 就不停发送
+        Log.d("UDP_Helper", "UDP发送:" + message);
+        DatagramSocket s = null;
+        try {
+            s = new DatagramSocket();
+        } catch (SocketException e) {
+            e.printStackTrace();
+        }
+        InetAddress local = null;
+        try {
+            local = InetAddress.getByName("255.255.255.255");
+        } catch (UnknownHostException e) {
+            e.printStackTrace();
+        }
+        int msg_length = message.getBytes().length;
+        byte[] messageByte = message.getBytes();
+        DatagramPacket p = new DatagramPacket(messageByte, msg_length, local, udpPort);
+        try {
+            s.send(p);
+            s.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    @Override
+    public void run() {
+        new Thread() {
+            @Override
+            public void run() {
+                //把网络访问的代码放在这里
+                StartListen();
+            }
+        }.start();
+    }
+}

+ 25 - 0
middleware/src/main/code/com/wdkl/ncs/android/middleware/udp2/UdpIndex.java

@@ -0,0 +1,25 @@
+package com.wdkl.ncs.android.middleware.udp2;
+
+public class UdpIndex {
+    public static final String SERVER_MODE_OFFLINE = "server_mode_offline";
+    public static final String SERVER_MODE_ONLINE = "server_mode_online";
+
+    //分机呼叫主机
+    public static final String BED_CALL_OUT = "BED_CALL_OUT";
+    public static final String BED_CALL_ACCEPT = "BED_CALL_ACCEPT";
+    public static final String BED_CALL_CANCEL = "BED_CALL_CANCEL";
+    public static final String BED_CALL_REJECT = "BED_CALL_REJECT";
+    public static final String BED_CALL_END = "BED_CALL_END";
+
+    public static final String CALL_CALLING = "CALL_CALLING";
+
+    //主机呼叫分机
+    public static final String HOST_CALL_BED = "HOST_CALL_BED";
+    public static final String HOST_CALL_CANCEL = "HOST_CALL_CANCEL";
+    public static final String HOST_CALL_ACCEPT = "HOST_CALL_ACCEPT";
+    public static final String HOST_CALL_REJECT = "HOST_CALL_REJECT";
+
+    //紧急呼叫
+    public static final String SOS_CALL = "SOS_CALL";
+    public static final String SOS_CANCEL = "SOS_CANCEL";
+}

+ 99 - 0
middleware/src/main/code/com/wdkl/ncs/android/middleware/udp2/UdpItem.java

@@ -0,0 +1,99 @@
+package com.wdkl.ncs.android.middleware.udp2;
+
+public class UdpItem {
+    private String index;
+    private String frameName;
+    private String patientName;
+    private String uartAddr;
+    private String myIpAddr;
+    private String targetIpAddr;
+    private String data;
+    private int deviceId;
+    private int partId;
+
+    public String getIndex() {
+        return null == index ? "null" : index;
+    }
+
+    public void setIndex(String index) {
+        this.index = index;
+    }
+
+    public String getFrameName() {
+        return frameName;
+    }
+
+    public void setFrameName(String frameName) {
+        this.frameName = frameName;
+    }
+
+    public String getMyIpAddr() {
+        return myIpAddr;
+    }
+
+    public void setMyIpAddr(String myAddr) {
+        this.myIpAddr = myAddr;
+    }
+
+    public String getTargetIpAddr() {
+        return targetIpAddr;
+    }
+
+    public void setTargetIpAddr(String targetAddr) {
+        this.targetIpAddr = targetAddr;
+    }
+
+    public String getPatientName() {
+        return patientName;
+    }
+
+    public void setPatientName(String patientName) {
+        this.patientName = patientName;
+    }
+
+    public String getUartAddr() {
+        return uartAddr;
+    }
+
+    public void setUartAddr(String uartAddr) {
+        this.uartAddr = uartAddr;
+    }
+
+    public String getData() {
+        return data;
+    }
+
+    public void setData(String data) {
+        this.data = data;
+    }
+
+    public int getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(int deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public int getPartId() {
+        return partId;
+    }
+
+    public void setPartId(int partId) {
+        this.partId = partId;
+    }
+
+    @Override
+    public String toString() {
+        return "UdpItem{" +
+                "frameName='" + frameName + '\'' +
+                ", patientName='" + patientName + '\'' +
+                ", uartAddr='" + uartAddr + '\'' +
+                ", myAddr='" + myIpAddr + '\'' +
+                ", targetAddr='" + targetIpAddr + '\'' +
+                ", data='" + data + '\'' +
+                ", deviceId='" + deviceId + '\'' +
+                ", partId='" + partId + '\'' +
+                '}';
+    }
+}

+ 52 - 0
middleware/src/main/code/com/wdkl/ncs/android/middleware/udp2/UdpSendUtil.java

@@ -0,0 +1,52 @@
+package com.wdkl.ncs.android.middleware.udp2;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * #############UDP发送工具类#############
+ */
+
+public class UdpSendUtil {
+
+    private final ExecutorService threadPool = Executors.newSingleThreadExecutor();
+    private SendUdpThread sendUdpThread;
+    private static String strUdp;
+
+    private static UdpSendUtil instance = null;
+
+    public static UdpSendUtil getInstance(){
+        if (instance == null){
+            synchronized (UdpSendUtil.class){
+                if (instance == null){
+                    instance = new UdpSendUtil();
+                }
+            }
+        }
+        return instance;
+    }
+
+    public static class SendUdpThread implements Runnable {
+        @Override
+        public void run() {
+            UdpHelper.send("$" + strUdp + "#");
+        }
+    }
+
+    public synchronized void sendUdpData(String index, String frameName, String patientName, String uartAddr, String myAddr, String targetAddr, String data, String deviceId, String partId) {
+        strUdp = index +
+                Character.toString((char) 3) + frameName +
+                Character.toString((char) 3) + patientName +
+                Character.toString((char) 3) + uartAddr +
+                Character.toString((char) 3) + myAddr +
+                Character.toString((char) 3) + targetAddr +
+                Character.toString((char) 3) + data +
+                Character.toString((char) 3) + deviceId +
+                Character.toString((char) 3) + partId;
+        if (sendUdpThread == null) {
+            sendUdpThread = new SendUdpThread();
+        }
+        threadPool.execute(sendUdpThread);
+    }
+
+}

+ 30 - 2
middleware/src/main/code/com/wdkl/ncs/android/middleware/utils/CommonUtils.java

@@ -13,9 +13,13 @@ public class CommonUtils {
     private static final String KEY_SP_SIP_URL = "KEY_SP_SIP_URL";
     private static final String KEY_SP_SIP_PORT = "KEY_SP_SIP_PORT";
 
+    private static final String KEY_SP_START_MODE = "KEY_SP_START_MODE";
+    private static final String KEY_SP_HOST_IP = "KEY_SP_HOST_IP";
+    private static final String KEY_SP_AUTO_ANSWER = "KEY_SP_AUTO_ANSWER";
+
     //默认服务器ip端口
-    private static final String DEFAULT_URL = "172.28.100.100";
-    //private static final String DEFAULT_URL = "8.129.220.143";
+    //private static final String DEFAULT_URL = "172.28.100.100";
+    private static final String DEFAULT_URL = "8.129.220.143";
     //private static final String DEFAULT_URL = "119.23.151.229";
     private static final String DEFAULT_URL_PORT = "8006";
     private static final String DEFAULT_SIP_PORT = "8188";
@@ -74,6 +78,30 @@ public class CommonUtils {
         getEditor(context).putString(KEY_SP_SIP_PORT, port).apply();
     }*/
 
+    public static int getStartMode(Context context) {
+        return getSP(context).getInt(KEY_SP_START_MODE, 1);
+    }
+
+    public static void setStartMode(Context context, int mode) {
+        getEditor(context).putInt(KEY_SP_START_MODE, mode).apply();
+    }
+
+    public static String getHostIp(Context context) {
+        return getSP(context).getString(KEY_SP_HOST_IP, "");
+    }
+
+    public static void setHostIp(Context context, String ip) {
+        getEditor(context).putString(KEY_SP_HOST_IP, ip).apply();
+    }
+
+    public static boolean getAutoAnswer(Context context) {
+        return getSP(context).getBoolean(KEY_SP_AUTO_ANSWER, false);
+    }
+
+    public static void setAutoAnswer(Context context, boolean value) {
+        getEditor(context).putBoolean(KEY_SP_AUTO_ANSWER, value).apply();
+    }
+
 
     private static SharedPreferences getSP(Context context) {
         return context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);

+ 13 - 4
welcome/src/main/code/com/wdkl/ncs/android/component/welcome/activity/WelcomeActivity.kt

@@ -15,6 +15,8 @@ import com.enation.javashop.android.welcome.databinding.ActivityWelcomeBinding
 import com.enation.javashop.net.engine.model.NetState
 import com.enation.javashop.net.engine.plugin.permission.RxPermissions
 import com.enation.javashop.utils.base.tool.CommonTool
+import com.wdkl.ncs.android.lib.base.BaseApplication
+import com.wdkl.ncs.android.middleware.utils.CommonUtils
 import io.reactivex.Observable
 import java.util.*
 
@@ -91,9 +93,16 @@ class WelcomeActivity :BaseActivity<WelcomePresenter, ActivityWelcomeBinding>(),
      * @Note   跳转首页
      */
     override fun toHome() {
-        AppTool.Time.delay(200) {
-            push("/conversion_box/main")
-            finish()
+        if (CommonUtils.getStartMode(BaseApplication.appContext) == 0) {
+            AppTool.Time.delay(200) {
+                push("/conversion_box_offline/main")
+                finish()
+            }
+        } else {
+            AppTool.Time.delay(200) {
+                push("/conversion_box/main")
+                finish()
+            }
         }
     }
 
@@ -232,6 +241,6 @@ class WelcomeActivity :BaseActivity<WelcomePresenter, ActivityWelcomeBinding>(),
      * 重写返回监听事件 直接销毁页面
      */
     override fun onBackPressed() {
-        finish()
+        //
     }
 }