Browse Source

新增腕表版本

weizhengliang 1 year ago
parent
commit
ed6f4279de
100 changed files with 10999 additions and 0 deletions
  1. 5 0
      android_mobile/build.gradle
  2. 91 0
      android_mobile/src/main/yd_watch_2/AndroidManifest.xml
  3. 7 0
      android_mobile/src/main/yd_watch_2/aidl/com/android/internal/telephony/ITelephony.java
  4. 192 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/AppUpdateActivity.kt
  5. 330 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/BaseActivity.kt
  6. 298 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/ChannelImActivity.kt
  7. 271 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/NewCallListActivity.kt
  8. 222 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/NewEventListActivity.kt
  9. 115 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/TakeoverActivity.kt
  10. 244 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/VoiceMsgActivity.kt
  11. 327 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/WatchCallRecordsActivity.kt
  12. 158 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/WatchContactsActivity.kt
  13. 778 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/WatchHome2Activity.kt
  14. 156 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/WatchUserSettingActivity.java
  15. 126 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/ChannelImItemAdapter.kt
  16. 73 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/NewCallItemAdapter.kt
  17. 310 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/NewEventItemAdapter.kt
  18. 179 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/TakeoverItemAdapter.kt
  19. 222 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/WatchCallRecordsItemAdapter.kt
  20. 140 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/WatchContactsItemAdapter.kt
  21. 35 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/binding/HomeFragmentBindHelper.kt
  22. 43 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/broadcast/BatteryBroadcastReceiver.java
  23. 34 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/broadcast/MyMediaButtonReceiver.kt
  24. 22 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/broadcast/NetworkBroadcastReceiver.kt
  25. 34 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/di/HomeComponent.kt
  26. 242 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/helper/AppUpdateHelper.java
  27. 168 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/helper/HttpHelper.java
  28. 27 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/launch/HomeLaunch.kt
  29. 258 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/service/FloatingService.java
  30. 158 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/service/PhoneStateReceiver.java
  31. 987 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/service/WdKeepAliveService.kt
  32. 95 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/settingconfig/SettingConfig.java
  33. 481 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/ui/CallSingleActivity.java
  34. 429 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/ui/FragmentAudio.java
  35. 286 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/ui/FragmentVideo.java
  36. 607 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/ui/SingleCallFragment.java
  37. 15 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/ActivityStackUtil.java
  38. 233 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/AnrFcExceptionUtil.java
  39. 33 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/AppUtils.java
  40. 187 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/AsyncPlayer.java
  41. 14 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/EthernetWifiCallBack.java
  42. 89 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/HomeWatcher.java
  43. 58 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/IMDialogHelper.java
  44. 6 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/ISpeechCallback.java
  45. 77 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/ImPlayDialogHelper.java
  46. 83 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/LocaleMangerUtils.java
  47. 52 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/LogUpload.java
  48. 291 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/MediaPlayHelper.java
  49. 115 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/MediaPlayer.kt
  50. 600 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/NetHelper.java
  51. 81 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/NetWorkChangeReceiver.kt
  52. 112 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/PhoneCallUtil.java
  53. 246 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/RecordHelper.java
  54. 22 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/RingPlayHelper.java
  55. 72 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/SoundPoolManager.java
  56. 239 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/SpeechUtil.java
  57. 30 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/TimeTransition.kt
  58. 168 0
      android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/Util.kt
  59. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-hdpi/hu_jiao.png
  60. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-hdpi/hu_jiao_bg.png
  61. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-hdpi/wei_chu_li_bg.png
  62. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-hdpi/yi_chu_li_bg.png
  63. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-hdpi/yu_yin_wei_chu_li.png
  64. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/bao_ma_tou_xiang.png
  65. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/dian_liang.png
  66. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/geng_duo.png
  67. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/gu_ke_lie_biao.png
  68. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_chu_wei_jie.png
  69. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_chu_yi_jie.png
  70. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_jiao.png
  71. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_jiao_bg.png
  72. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_ru_wei_jie.png
  73. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_ru_yi_jie.png
  74. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_shi_to_xiang.png
  75. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/ju_jie.png
  76. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/sip_lian_jie.png
  77. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/tong_hu_ji_lu.png
  78. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/voice_recorder.png
  79. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/wei_bo_fang.png
  80. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/wei_chu_li.png
  81. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/wei_chu_li_bg.png
  82. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/wifi_lian_jie.png
  83. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yi_bo_fang.png
  84. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yi_chu_li.png
  85. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yi_chu_li_bg.png
  86. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yu_yin_gua_duan.png
  87. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yu_yin_jie_ting.png
  88. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yu_yin_wei_chu_li.png
  89. BIN
      android_mobile/src/main/yd_watch_2/res/drawable-mdpi/zhe_die.png
  90. BIN
      android_mobile/src/main/yd_watch_2/res/drawable/av_audio_answer.png
  91. BIN
      android_mobile/src/main/yd_watch_2/res/drawable/av_audio_answer_hover.png
  92. 10 0
      android_mobile/src/main/yd_watch_2/res/drawable/av_audio_answer_selector.xml
  93. BIN
      android_mobile/src/main/yd_watch_2/res/drawable/av_camera.png
  94. BIN
      android_mobile/src/main/yd_watch_2/res/drawable/av_camera_hover.png
  95. BIN
      android_mobile/src/main/yd_watch_2/res/drawable/av_default_header.png
  96. BIN
      android_mobile/src/main/yd_watch_2/res/drawable/av_float_audio.png
  97. 16 0
      android_mobile/src/main/yd_watch_2/res/drawable/av_float_bg.xml
  98. BIN
      android_mobile/src/main/yd_watch_2/res/drawable/av_handfree.png
  99. BIN
      android_mobile/src/main/yd_watch_2/res/drawable/av_handfree_hover.png
  100. 0 0
      android_mobile/src/main/yd_watch_2/res/drawable/av_hang_up.png

+ 5 - 0
android_mobile/build.gradle

@@ -64,6 +64,11 @@ android {
             main.java.srcDirs += 'src/main/yd_w_xiaomi_2_chile/aidl'
             main.res.srcDirs += 'src/main/yd_w_xiaomi_2_chile/res'
             main.manifest.srcFile 'src/main/yd_w_xiaomi_2_chile/AndroidManifest.xml'
+        } else if ("yd_watch_2" == app_device_type) {
+            main.java.srcDirs += 'src/main/yd_watch_2/code'
+            main.java.srcDirs += 'src/main/yd_watch_2/aidl'
+            main.res.srcDirs += 'src/main/yd_watch_2/res'
+            main.manifest.srcFile 'src/main/yd_watch_2/AndroidManifest.xml'
         }
     }
     compileOptions {

+ 91 - 0
android_mobile/src/main/yd_watch_2/AndroidManifest.xml

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.wdkl.ncs.android.component.home">
+
+    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
+        tools:ignore="ProtectedPermissions" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
+    <!-- 读写通讯录权限-->
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
+
+    <application
+        android:allowBackup="true"
+        android:supportsRtl="true">
+
+        <activity android:screenOrientation="portrait" android:name=".activity.WatchContactsActivity" />
+        <activity android:screenOrientation="portrait" android:name=".activity.WatchCallRecordsActivity" />
+        <activity android:screenOrientation="portrait" android:name=".activity.WatchUserSettingActivity" />
+        <activity android:screenOrientation="portrait" android:name=".activity.TakeoverActivity" />
+        <activity android:screenOrientation="portrait" android:name=".activity.AppUpdateActivity" />
+        <activity android:screenOrientation="portrait" android:name=".activity.NewEventListActivity"
+            android:launchMode="singleTask"/>
+        <activity android:screenOrientation="portrait" android:name=".activity.NewCallListActivity"
+            android:launchMode="singleTask"/>
+        <activity android:name=".activity.VoiceMsgActivity" android:screenOrientation="portrait"/>
+        <activity android:name=".activity.ChannelImActivity" android:screenOrientation="portrait"/>
+
+        <activity
+            android:name=".ui.CallSingleActivity"
+            android:showOnLockScreen="true"
+            android:showWhenLocked="true"
+            android:launchMode="singleInstance"
+            android:excludeFromRecents="true">
+            <intent-filter>
+                <action android:name="${applicationId}.kit.voip.single" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <service android:name=".service.FloatingService"/>
+
+        <service android:name="com.wdkl.ncs.android.component.home.service.WdKeepAliveService">
+            <intent-filter>
+                <action android:name="com.wdkl.app.ncs.service.WdKeepAliveService"/>
+            </intent-filter>
+        </service>
+<!--        <receiver android:name=".broadcast.MyMediaButtonReceiver">-->
+<!--            <intent-filter>-->
+<!--                <action android:name="android.intent.action.MEDIA_BUTTON" />-->
+<!--                <action android:name="android.media.AUDIO.BECOMING_NOISY" />-->
+<!--            </intent-filter>-->
+<!--        </receiver>-->
+        <service android:name="com.wdkl.ncs.keepbackground.watch.WatchDogService">
+            <intent-filter>
+                <action android:name="com.wdkl.ncs.keepbackground.watch.WatchDogService"/>
+            </intent-filter>
+        </service>
+
+        <receiver android:name=".util.NetWorkChangeReceiver" >
+            <intent-filter>
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+            </intent-filter>
+        </receiver>
+
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="${applicationId}.provider"
+            tools:replace="android:authorities"
+            android:grantUriPermissions="true"
+            android:exported="false">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
+        </provider>
+
+    </application>
+
+</manifest>

+ 7 - 0
android_mobile/src/main/yd_watch_2/aidl/com/android/internal/telephony/ITelephony.java

@@ -0,0 +1,7 @@
+package com.android.internal.telephony;
+
+public interface ITelephony {
+    boolean endCall();
+    void answerRingingCall();
+    void silenceRinger();
+}

+ 192 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/AppUpdateActivity.kt

@@ -0,0 +1,192 @@
+package com.wdkl.ncs.android.component.home.activity
+
+import android.text.TextUtils
+import android.util.Log
+import android.view.View
+import android.view.WindowManager
+import com.enation.javashop.android.jrouter.external.annotation.Router
+import com.enation.javashop.net.engine.model.NetState
+import com.wdkl.ncs.android.component.home.BuildConfig
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.databinding.ActivityAppUpdateBinding
+import com.wdkl.ncs.android.component.home.helper.AppUpdateHelper
+import com.wdkl.ncs.android.component.home.helper.HttpHelper
+import com.wdkl.ncs.android.component.home.launch.HomeLaunch
+import com.wdkl.ncs.android.component.home.service.WdKeepAliveService
+import com.wdkl.ncs.android.lib.utils.AppTool
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import com.wdkl.ncs.android.lib.vo.filter
+import com.wdkl.ncs.android.middleware.api.UrlManager
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.middleware.logic.contract.home.AppUpdateContract
+import com.wdkl.ncs.android.middleware.logic.presenter.home.AppUpdatePresenter
+import com.wdkl.ncs.android.middleware.model.dos.AppVersionDO
+import com.wdkl.ncs.android.middleware.utils.CommonUtils
+import kotlinx.android.synthetic.main.activity_app_update.*
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+
+@Router(path = "/app/update")
+class AppUpdateActivity : BaseActivity<AppUpdatePresenter, ActivityAppUpdateBinding>(), AppUpdateContract.View {
+    private val TAG = "AppUpdateActivity"
+
+    private val urlManager = UrlManager.build()
+
+    companion object instance {
+        var opened = false;
+    }
+
+    override fun getLayId(): Int {
+        return R.layout.activity_app_update
+    }
+
+    override fun bindDagger() {
+        HomeLaunch.component.inject(this)
+    }
+
+    override fun init() {
+        val window = activity.window
+        //全屏+锁屏+常亮显示
+        window.addFlags(
+            WindowManager.LayoutParams.FLAG_FULLSCREEN or
+                    WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
+                    WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
+                    WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+        )
+
+        Constants.allowVoiceMsg = false
+        opened = true
+        checkAppVersion()
+    }
+
+    override fun enableHeadsetVoiceMsg(): Boolean {
+        return false
+    }
+
+    override fun handleAppVersion(appInfo: AppVersionDO) {
+        val versionCode = CommonUtils.getAppVersionCode(activity)
+        if (versionCode < appInfo.versionNo){
+            if (!TextUtils.isEmpty(appInfo.appPath)) {
+                downLoadAPK(urlManager.base + ":8006/" + appInfo.appPath)
+            } else {
+                showMessage("下载路径异常")
+                finish()
+            }
+        } else {
+            showMessage("本机已经是最新版本")
+            activity_calling_bed_text_download.text = "本机已经是最新版本"
+            activity_appupdate_dialog_progressview.visibility = View.GONE
+            WdKeepAliveService.updateLastTime = System.currentTimeMillis() / 1000
+            AppTool.Time.delay(1000){
+                finish()
+            }
+        }
+    }
+
+    fun checkAppVersion() {
+        if (Constants.partId != -1) {
+            presenter.getAppVersion(Constants.partId, 7)
+        }
+    }
+
+    /**
+     * 下载APK包
+     */
+    fun downLoadAPK(url: String) {
+        Log.d(TAG, "downLoadAPK  url==$url")
+        activity_appupdate_dialog_progressview.setCurProgress(0)
+        HttpHelper.download(url, object : HttpHelper.DownloadListener {
+            override fun onDownloadSuccess() {
+                Log.d("download", "onDownloadSuccess==" + "成功")
+                runOnUiThread {
+                    activity_calling_bed_text_download.text = "正在安装,请稍后..."
+                }
+                startInstallApk()
+            }
+
+            override fun onDownloading(progress: Int) {
+                runOnUiThread {
+                    activity_appupdate_dialog_progressview.setCurProgress(progress)
+                }
+            }
+
+            override fun onDownloadFailed() {
+                Log.d("download", "onDownloadFailed==" + "失败")
+                finish()
+            }
+        })
+    }
+
+    fun startInstallApk() {
+        Thread{
+            AppUpdateHelper.updateApp(this, object : AppUpdateHelper.UpdateCallBack {
+                override fun onFailed() {
+                    runOnUiThread {
+                        showMessage("升级失败")
+                        finish()
+                    }
+                }
+
+                override fun onSuccess() {
+                    runOnUiThread {
+                        showMessage("升级成功")
+                        finish()
+                        //有root权限用这个
+//                        android.os.Process.killProcess(android.os.Process.myPid())
+//                        System.exit(0)
+                    }
+                }
+            })
+        }.start()
+    }
+
+    override fun bindEvent() {
+        //
+    }
+
+    override fun onBackPressed() {
+        //禁用退出
+    }
+
+    override fun destory() {
+        Constants.allowVoiceMsg = true
+        opened = false
+        //退出时重置呼叫状态
+        //DeviceChannel.calling = false
+    }
+
+    //数据加载错误
+    override fun onError(message: String, type: Int) {
+        showMessage("获取升级数据失败")
+        finish()
+    }
+
+    //数据加载完成
+    override fun complete(message: String, type: Int) {
+        //
+    }
+
+    //开始获取数据
+    override fun start() {
+        //
+    }
+
+    //网络监听
+    override fun networkMonitor(state: NetState) {
+        state.filter(onMobile = {
+
+        },onWifi = {
+
+        },offline = {
+
+        })
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+
+    }
+
+
+}

+ 330 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/BaseActivity.kt

@@ -0,0 +1,330 @@
+package com.wdkl.ncs.android.component.home.activity
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import com.enation.javashop.android.jrouter.JRouter
+import com.enation.javashop.utils.base.tool.BaseToolActivity
+import com.wdkl.ncs.android.lib.base.*
+import com.wdkl.ncs.android.lib.utils.AppTool
+import com.wdkl.ncs.android.lib.utils.debugLog
+import com.wdkl.ncs.android.lib.utils.errorLog
+import com.wdkl.ncs.android.lib.utils.showMessage
+import org.greenrobot.eventbus.EventBus
+import java.lang.reflect.Field
+import javax.inject.Inject
+
+/**
+ * @author  LDD
+ * @Data   2017/12/26 上午9:26
+ * @From   com.wdkl.ncs.android.lib.base
+ * @Note   Activity基类
+ */
+abstract class BaseActivity<PresenterType : BaseContract.BasePresenter, DataBindingType : ViewDataBinding> : BaseToolActivity(), BaseControl {
+
+    protected var pressDownTime : Long = 0
+    protected var pressUpTime : Long = 0
+    protected var voiceMsgFile : String? = null
+    protected var pressDown : Boolean = false
+
+    private var clickTime : Long = 0
+    private var recording : Boolean = false
+
+
+    /**
+     * @Name  presenter
+     * @Type  T : BaseContract.BasePresenter
+     * @Note  Activity中Presenter Dagger自动注入
+     */
+    @Inject
+    protected lateinit var presenter: PresenterType
+
+    /**
+     * @Name  presenter
+     * @Type  T2 : ViewDataBinding
+     * @Note  DataBinding对象
+     */
+    protected lateinit var mViewBinding: DataBindingType
+
+    /**
+     * @Name  lifecycleCalls
+     * @Type  ArrayList<((state :Int) ->Unit)>
+     * @Note  回调集合
+     */
+    private val lifecycleCalls by lazy { ArrayList<((state :Int) ->Unit)>() }
+
+    /**
+     * @Name  disposableManager
+     * @Type  DisposableManager
+     * @Note  Rx索引管理
+     */
+    protected val disposableManager by lazy { DisposableManager() }
+
+    /**
+     * @author  LDD
+     * @From   com.wdkl.ncs.android.component.home.activity.BaseActivity
+     * @Data   2017/12/26 上午9:30
+     * @Note   Activity创建时进行相关的配置
+     */
+    override fun onCreate(savedInstanceState: Bundle?) {
+        try {
+            JRouter.prepare().inject(this)
+        }catch (e :Exception){
+            debugLog("Init","首页初始化完毕")
+        }
+        /**父类初始化*/
+        super.onCreate(savedInstanceState)
+        /**执行生命周期监听*/
+        lifeCycleDo(LIFE_CYCLE_CREATE)
+        /**创建根视图*/
+        val rootView = layoutInflater.inflate(getLayId(), null, false)
+        /**初始化Databinding对象*/
+        mViewBinding = DataBindingUtil.bind(rootView)!!
+        /**设置根视图到Activity*/
+        setContentView(rootView)
+        /**执行抽象方法初始化Dagger相应操作*/
+        bindDagger()
+        /**Presenter绑定View*/
+        attachView()
+        /**执行初始化操作*/
+        init()
+        /**执行绑定event操作*/
+        bindEvent()
+    }
+
+    /**
+     * @author LDD
+     * @From   BaseActivity
+     * @Date   2018/3/29 上午11:46
+     * @Note   执行生命周期监听 恢复
+     */
+    override fun onResume() {
+        super.onResume()
+        /**执行生命周期监听*/
+        lifeCycleDo(LIFE_CYCLE_RESUME)
+    }
+
+    /**
+     * @author LDD
+     * @From   BaseActivity
+     * @Date   2018/3/29 上午11:46
+     * @Note   执行生命周期监听 暂停
+     */
+    override fun onPause() {
+        super.onPause()
+        /**执行生命周期监听*/
+        lifeCycleDo(LIFE_CYCLE_PAUSE)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        if(!EventBus.getDefault().isRegistered(this)){
+            EventBus.getDefault().register(this)
+        }
+    }
+
+
+    override fun onStop() {
+        super.onStop()
+        if (EventBus.getDefault().isRegistered(this)) {//加上判断
+            EventBus.getDefault().unregister(this)
+        }
+//        EventBus.getDefault().unregister(this)
+    }
+    /**
+     * @author  LDD
+     * @From   com.wdkl.ncs.android.component.home.activity.BaseActivity
+     * @Data   2017/12/26 上午9:34
+     * @Note   Activity销毁回调
+     */
+    override fun onDestroy() {
+        super.onDestroy()
+
+        /**Presenter解除绑定*/
+        detachView()
+        /**DataBinding解除绑定*/
+        mViewBinding.unbind()
+        /**执行抽象方法destory()*/
+        destory()
+        /**解除RX引用*/
+        disposableManager.unDisposable()
+        /**执行生命周期监听*/
+        lifeCycleDo(LIFE_CYCLE_DESTORY)
+        /**清除声明周期监听引用*/
+        removeAllCallBack()
+        /**处理android4.4.2 底层内存泄漏*/
+        fixInputMethodManagerLeak(activity)
+        errorLog("PageDestory","页面销毁======>$localClassName")
+    }
+
+    /**
+     * @author  LDD
+     * @From   com.wdkl.ncs.android.component.home.activity.BaseActivity
+     * @Data   2017/12/26 上午9:39
+     * @Note   获取Activity_LayoutId
+     */
+    protected abstract fun getLayId(): Int
+
+    /**
+     * @author  LDD
+     * @From   com.wdkl.ncs.android.component.home.activity.BaseActivity
+     * @Data   2017/12/26 上午9:44
+     * @Note   执行绑定Dagger操作
+     */
+    protected abstract fun bindDagger()
+
+    /**
+     * @author  LDD
+     * @From   com.wdkl.ncs.android.component.home.activity.BaseActivity
+     * @Data   2017/12/26 上午9:45
+     * @Note   执行初始化操作
+     */
+    protected abstract fun init()
+
+    /**
+     * @author  LDD
+     * @From   com.wdkl.ncs.android.component.home.activity.BaseActivity
+     * @Data   2017/12/26 上午9:45
+     * @Note   执行绑定事件操作
+     */
+    protected abstract fun bindEvent()
+
+    /**
+     * @author  LDD
+     * @From   com.wdkl.ncs.android.component.home.activity.BaseActivity
+     * @Data   2017/12/26 上午9:46
+     * @Note   执行销毁相关操作
+     */
+    protected abstract fun destory()
+
+    /**
+     * @author LDD
+     * @From   BaseActivity
+     * @Date   2018/1/19 下午6:03
+     * @Note   绑定View接口
+     */
+    protected open fun attachView() {
+       presenter.attachView(this )
+    }
+
+    /**
+     * @author LDD
+     * @From   BaseActivity
+     * @Date   2018/5/10 下午3:17
+     * @Note   解绑View接口
+     */
+    protected open fun detachView(){
+        presenter.detachView()
+    }
+
+
+    //是否启用耳机线控留言功能
+    protected abstract fun enableHeadsetVoiceMsg(): Boolean
+
+
+    /**
+     * @author LDD
+     * @From   BaseActivity
+     * @Date   2018/3/29 上午11:37
+     * @Note   添加callBack
+     * @param  listener 监听回调
+     */
+    override fun addLifeCycleListener(listener: (state: Int) -> Unit) {
+        lifecycleCalls.add(listener)
+    }
+
+    /**
+     * @author LDD
+     * @From   BaseActivity
+     * @Date   2018/3/29 上午11:43
+     * @Note   清除生命周期监听
+     */
+    private fun removeAllCallBack(){
+        lifecycleCalls.clear()
+    }
+
+    /**
+     * @author LDD
+     * @From   BaseActivity
+     * @Date   2018/3/29 上午11:44
+     * @Note   在各个生命周期监听执行
+     * @param  state 生命周期状态
+     */
+    private fun lifeCycleDo(state :Int){
+        lifecycleCalls.forEach {
+            item ->
+            item.invoke(state)
+        }
+    }
+
+    /**
+     * @author LDD
+     * @From   BaseActivity
+     * @Date   2018/5/27 下午1:22
+     * @Note   页面返回数据
+     * @param  resultCode 返回码
+     * @param  data       数据
+     */
+    open fun resultHandle(resultCode: Int,data: Intent?){
+
+    }
+
+    /**
+     * @author LDD
+     * @From   BaseActivity
+     * @Date   2018/5/27 下午1:22
+     * @Note   页面返回数据回调
+     */
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        resultHandle(resultCode,data)
+    }
+
+//    /**
+//     * 修改默认加载Dialog
+//     */
+//    override fun loadingDialogProvider(): LoadingDialog {
+//        return CommonTool.createLoadingDialog(this, R.layout.custom_loading,R.id.loadding_image)
+//    }
+
+    /**
+     * @author  LDD
+     * @From   com.wdkl.ncs.android.component.home.activity.BaseActivity
+     * @Data   2017/12/26 上午9:50
+     * @Note   处理4.4.2 Android底层内存泄漏
+     * @param  destContext 上下文
+     */
+    private fun fixInputMethodManagerLeak(destContext: Context?) {
+        if (destContext == null) {
+            return
+        }
+        val imm = destContext!!.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager ?: return
+        val arr = arrayOf("mCurRootView", "mServedView", "mNextServedView")
+        var f: Field?
+        var obj_get: Any?
+        arr.indices
+                .asSequence()
+                .map { arr[it] }
+                .forEach {
+                    try {
+                        f = imm.javaClass.getDeclaredField(it)
+                        if (f!!.isAccessible === false) {
+                            f!!.isAccessible = true
+                        }
+                        obj_get = f!!.get(imm)
+                        if (obj_get != null && obj_get is View) {
+                            val v_get = obj_get as View?
+                            if (v_get!!.getContext() === destContext) { // 被InputMethodManager持有引用的context是想要目标销毁的
+                                f!!.set(imm, null) // 置空,破坏掉path to gc节点
+                            }
+                        }
+                    } catch (t: Throwable) {
+                        t.printStackTrace()
+                    }
+                }
+    }
+}

+ 298 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/ChannelImActivity.kt

@@ -0,0 +1,298 @@
+package com.wdkl.ncs.android.component.home.activity
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.PowerManager
+import android.view.MotionEvent
+import android.view.View
+import com.alibaba.android.vlayout.DelegateAdapter
+import com.alibaba.android.vlayout.VirtualLayoutManager
+import com.alibaba.fastjson.JSONObject
+import com.enation.javashop.net.engine.model.NetState
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.adapter.ChannelImItemAdapter
+import com.wdkl.ncs.android.component.home.databinding.ActivityChannelImListBinding
+import com.wdkl.ncs.android.component.home.launch.HomeLaunch
+import com.wdkl.ncs.android.component.home.util.*
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.middleware.common.Constants.Companion.channelId
+import com.wdkl.ncs.android.middleware.common.Constants.Companion.deviceId
+import com.wdkl.ncs.android.middleware.common.Constants.Companion.memberId
+import com.wdkl.ncs.android.middleware.logic.contract.home.ChannelImContract
+import com.wdkl.ncs.android.middleware.logic.presenter.home.ChannelImPresenter
+import com.wdkl.ncs.android.middleware.model.dos.ChannelImDO
+import com.wdkl.ncs.android.middleware.model.vo.ChannelIMVO
+import com.wdkl.ncs.android.middleware.tcp.TcpClient
+import com.wdkl.ncs.android.middleware.tcp.TcpClientHandler
+import com.wdkl.ncs.android.middleware.tcp.channel.ChannelImUtil
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback
+import kotlinx.android.synthetic.main.activity_channel_im_list.*
+import okhttp3.MediaType
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import java.io.File
+import java.util.*
+import kotlin.collections.ArrayList
+
+class ChannelImActivity : BaseActivity<ChannelImPresenter, ActivityChannelImListBinding>(), ChannelImContract.View, ChannelImItemAdapter.CallBack {
+
+    private lateinit var adapter: ChannelImItemAdapter
+    private lateinit var virtualLayoutManager: VirtualLayoutManager
+    private lateinit var delegateAdapter: DelegateAdapter
+    private var page: Int = 1
+    private val page_size: Int = 10
+
+    private var language = "zh"
+
+    var downTime : Long = 0
+    var upTime : Long = 0
+    var downY : Float = 0f
+    var cancel : Boolean = false
+    var voiceFile : String? = null
+
+    lateinit var wakeLock : PowerManager.WakeLock
+
+    override fun getLayId(): Int {
+        return R.layout.activity_channel_im_list
+    }
+
+    override fun bindDagger() {
+        HomeLaunch.component.inject(this)
+    }
+
+    @SuppressLint("InvalidWakeLockTag")
+    override fun init() {
+        // 获取WakeLock对象
+        val powerManager= getSystemService(Context.POWER_SERVICE) as PowerManager
+        wakeLock = powerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_DIM_WAKE_LOCK, "voice_msg_on")
+
+        /**初始化LayoutMannager*/
+        virtualLayoutManager = VirtualLayoutManager(this.activity)
+
+        /**初始化适配器*/
+        adapter = ChannelImItemAdapter(ArrayList(), activity)
+        delegateAdapter = DelegateAdapter(virtualLayoutManager)
+        delegateAdapter.addAdapter(adapter)
+        /**配置到RecycleView*/
+        rv_channel_im_list.layoutManager = virtualLayoutManager
+        rv_channel_im_list.adapter = delegateAdapter
+
+        adapter.setCallBack(this)
+
+        Constants.allowVoiceMsg = false
+
+        presenter.getChannelIm(page, page_size, memberId!!, channelId)
+
+        if (RecordHelper.getInstance().isRecording()) {
+            IMDialogHelper.showIMDialog(activity)
+        }
+
+        language = LocaleMangerUtils.getApplicationLocale().language
+    }
+
+    override fun bindEvent() {
+        srl_channel_im_list.setOnLoadMoreListener {
+            page += 1
+            presenter.getChannelIm(page, page_size, memberId!!, channelId)
+        }
+        srl_channel_im_list.setOnRefreshListener {
+            page = 1
+            presenter.getChannelIm(page, page_size, memberId!!, channelId)
+        }
+
+
+        //长按群留言
+        btn_channel_msg.setOnTouchListener { v, event ->
+            when(event.action) {
+                MotionEvent.ACTION_DOWN -> {
+                    cancel = false
+                    downTime = System.currentTimeMillis()
+                    downY = event.getY()
+                    RecordHelper.getInstance().startRecord()
+                    tv_channel_text.text = "松开留言,上滑取消"
+                    voiceFile = RecordHelper.getInstance().audiofilePath
+
+                    //按下时保持常亮
+                    wakeLock.acquire()
+                }
+
+                MotionEvent.ACTION_MOVE -> {
+                    val moveY = event.getY()
+                    if (Math.abs(downY - moveY) > 100) {
+                        cancel = true
+                    }
+                }
+
+                MotionEvent.ACTION_UP -> {
+                    upTime = System.currentTimeMillis()
+                    RecordHelper.getInstance().stopRecord()
+                    tv_channel_text.text = "按住说话"
+
+                    if (Math.abs(upTime - downTime) <= 1000) {
+                        RecordHelper.getInstance().deleteAudioFile(voiceFile)
+                        showMessage("说话时间太短")
+                    } else if(cancel) {
+                        RecordHelper.getInstance().deleteAudioFile(voiceFile)
+                        showMessage("语音留言已取消")
+                    } else {
+                        //上传语音留言
+                        if (voiceFile != null) {
+                            val part = MultipartBody.Part.createFormData("file", voiceFile, RequestBody.create(
+                                MediaType.parse("multipart/form-data"), File(voiceFile)
+                            ))
+                            presenter.uploadVoiceMsg(part)
+                        }
+                    }
+
+                    //松开时释放
+                    wakeLock.release()
+                }
+            }
+
+            return@setOnTouchListener false
+        }
+    }
+
+    @SuppressLint("CheckResult")
+    override fun uploadResponse(result: String) {
+        //上传完成后删除本地文件
+        RecordHelper.getInstance().deleteAudioFile(voiceFile)
+        val channelImDO = ChannelImDO()
+        channelImDO.channelId = channelId
+        channelImDO.senderMemberId = memberId
+        channelImDO.audioPath = result
+        /*val tcpModel = ChannelImUtil.channelImMsg(deviceId, channelImDO)
+        instance.sendMsg(tcpModel.toJson()).subscribe { it: Boolean ->
+            if (it) {
+                SoundPoolManager.getInstance().playSound(3)
+                showMessage("留言发送成功!")
+                //刷新列表
+                page = 1
+                presenter.getChannelIm(page, page_size, memberId!!, channelId)
+            } else {
+                showMessage("留言发送失败!")
+            }
+        }*/
+
+        if (TcpClientHandler.getConnected()) {
+            val tcpModel = ChannelImUtil.channelImMsg(deviceId, channelImDO)
+            val transaction: TcpCallback = object : TcpCallback(tcpModel.tid) {
+                override fun onAck() {
+                    //发送成功
+                    //if (Locale.CHINESE.getLanguage().equals(language)) {
+                        SoundPoolManager.getInstance().playSound(3)
+                    //}
+                    activity.runOnUiThread {
+                        showMessage("send success!")
+                        //刷新列表
+                        page = 1
+                        presenter.getChannelIm(page, page_size, memberId!!, channelId)
+                    }
+                }
+                override fun onSuccess(jsonObject: JSONObject) {
+                    //
+                }
+
+                override fun onFailed(jsonObject: JSONObject) {
+                    // 这里写发送失败的方法
+                    SoundPoolManager.getInstance().playSound(6)
+                    val callbackString = jsonObject.getString(CALLBACK)
+                    activity.runOnUiThread {
+                        showMessage("Failed: $callbackString")
+                    }
+                }
+            }
+            TcpClient.getInstance().sendTcp(tcpModel, false, transaction)
+        } else {
+            SoundPoolManager.getInstance().playSound(6)
+            showMessage("send failed!")
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        if (!MediaPlayHelper.getInstance().isMediaPlaying) {
+            ImPlayDialogHelper.dismissIMDialog()
+        }
+    }
+
+    override fun destory() {
+        Constants.allowVoiceMsg = true
+        IMDialogHelper.dismissIMDialog()
+    }
+
+    override fun enableHeadsetVoiceMsg(): Boolean {
+        return false
+    }
+
+    override fun showChannelIm(data: ArrayList<ChannelIMVO>) {
+        srl_channel_im_list.finishRefresh()
+
+        if (page == 1) {
+            srl_channel_im_list.resetNoMoreData()
+            if (data.size > 0) {
+                adapter.data.clear()
+                adapter.data.addAll(data)
+                adapter.notifyDataSetChanged()
+            } else {
+                tv_empty_channel_im.visibility = View.VISIBLE
+            }
+            srl_channel_im_list.finishLoadMore()
+        } else {
+            if (data.size > 0) {
+                adapter.data.addAll(data)
+                adapter.notifyDataSetChanged()
+                srl_channel_im_list.finishLoadMore()
+            } else {
+                srl_channel_im_list.finishLoadMoreWithNoMoreData()
+            }
+        }
+    }
+
+    override fun onChannelImRead(item: ChannelIMVO, pos: Int) {
+        //该条留言已读,刷新状态
+        var update = false
+        for (e in adapter.data) {
+            if (e.id == item.id) {
+                e.readed = true
+                update = true
+                break
+            }
+        }
+
+        if (update && pos < adapter.data.size) {
+            runOnUiThread {
+                adapter.notifyItemChanged(pos)
+            }
+        }
+    }
+
+    override fun onError(message: String, type: Int) {
+        srl_channel_im_list.finishRefresh()
+    }
+
+    override fun complete(message: String, type: Int) {
+        //
+    }
+
+    override fun start() {
+        //
+    }
+
+    override fun networkMonitor(state: NetState) {
+        //
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        if (messageEvent.tag == 3 || messageEvent.tag == Constants.EVENT_CHANNEL_IM_UPDATE) {
+            //刷新列表
+            page = 1
+            presenter.getChannelIm(page, page_size, memberId!!, channelId)
+        }
+    }
+}

+ 271 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/NewCallListActivity.kt

@@ -0,0 +1,271 @@
+package com.wdkl.ncs.android.component.home.activity
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.alibaba.android.vlayout.DelegateAdapter
+import com.alibaba.android.vlayout.VirtualLayoutManager
+import com.alibaba.fastjson.JSON
+import com.enation.javashop.utils.base.tool.BaseToolActivity
+import com.google.gson.Gson
+import com.scwang.smartrefresh.layout.SmartRefreshLayout
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.adapter.NewCallItemAdapter
+import com.wdkl.ncs.android.component.home.service.WdKeepAliveService
+import com.wdkl.ncs.android.component.home.settingconfig.SettingConfig
+import com.wdkl.ncs.android.component.home.ui.CallSingleActivity
+import com.wdkl.ncs.android.component.home.util.RingPlayHelper
+import com.wdkl.ncs.android.component.home.util.SpeechUtil
+import com.wdkl.ncs.android.lib.base.BaseApplication
+import com.wdkl.ncs.android.lib.utils.AppTool
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
+import com.wdkl.ncs.android.middleware.tcp.TcpClient
+import com.wdkl.ncs.android.middleware.tcp.channel.DeviceChannel
+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 org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+
+class NewCallListActivity : BaseToolActivity(), NewCallItemAdapter.CallClickListener {
+    var TAG = NewCallListActivity::class.java.getSimpleName()
+
+    private val adapter = NewCallItemAdapter(ArrayList(),this)
+    private lateinit var virtualLayoutManager: VirtualLayoutManager
+    private lateinit var delegateAdapter: DelegateAdapter
+
+    private lateinit var recyclerView: RecyclerView
+    private lateinit var refreshView: SmartRefreshLayout
+    private lateinit var emptyView: TextView
+
+    private var transSeconds = 15
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContentView(R.layout.activity_call_list)
+
+        if(!EventBus.getDefault().isRegistered(this)){
+            EventBus.getDefault().register(this)
+        }
+
+        transSeconds = SettingConfig.getCountdownTime(activity)
+
+        recyclerView = findViewById(R.id.rv_call_list)
+        refreshView = findViewById(R.id.srl_call_list)
+        emptyView = findViewById(R.id.tv_empty_call)
+
+        /**初始化LayoutMannager*/
+        virtualLayoutManager = VirtualLayoutManager(this.activity)
+
+        /**初始化适配器*/
+        delegateAdapter = DelegateAdapter(virtualLayoutManager)
+        delegateAdapter.addAdapter(adapter)
+        recyclerView.layoutManager = virtualLayoutManager
+        recyclerView.adapter = delegateAdapter
+
+        adapter.setCallClick(this)
+
+        if (Constants.supportCall) {
+            adapter.setOnItemClickListener { data, position ->
+                SpeechUtil.getInstance().stopSpeak()
+                val interactionVO = Gson().fromJson(data.data.toString(), InteractionVO::class.java)
+                var roomId: String
+                if (interactionVO.fromDeviceType == DeviceTypeEnum.SIMULATE_BED_DEVICE.value()) {
+                    //模拟分机呼叫,通话由手机端创建
+                    DeviceChannel.callId = interactionVO.fromDeviceId
+                    roomId = Constants.sipId!!
+                } else {
+                    if (interactionVO.fromDeviceId.equals(Constants.deviceId)) {
+                        DeviceChannel.callId = interactionVO.toDeviceId
+                        roomId = interactionVO.toSipId
+                    } else {
+                        DeviceChannel.callId = interactionVO.fromDeviceId
+                        roomId = interactionVO.fromSipId
+                    }
+                }
+                Log.i(TAG, "来电:" + JSON.toJSONString(data))
+
+                //启动 activity
+                val intent = Intent(this, CallSingleActivity::class.java)
+                intent.putExtra(CallSingleActivity.EXTRA_ROOM_ID, roomId)
+                intent.putExtra(CallSingleActivity.EXTRA_MO, false)
+                intent.putExtra(CallSingleActivity.EXTRA_AUDIO_ONLY, true)
+                intent.putExtra(CallSingleActivity.EXTRA_TCPMODEL, data)
+                intent.putExtra(CallSingleActivity.EXTRA_RING, false)
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                startActivity(intent)
+
+                //移除该条呼叫
+                removeCall(interactionVO)
+            }
+        } else {
+            adapter.setOnItemClickListener { data, position ->
+                SpeechUtil.getInstance().stopSpeak()
+                val interactionVO = Gson().fromJson(data.data.toString(), InteractionVO::class.java)
+                val callTcp = VoiceUtil.voiceReject(data.tid, Constants.deviceId, interactionVO.fromDeviceId, interactionVO.id)
+                TcpClient.getInstance().sendMsg(callTcp.toJson())
+
+                /*NettyClient.instance.sendMsg(callTcp.toJson())
+                    .subscribe { success: Boolean ->
+                        if (success) {
+                            Log.d("CallItem", "TCP.发送消息完成")
+                        } else {
+                            Log.e("CallItem", "TCP.发送消息失败")
+                            HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+                        }
+                    }*/
+
+                //移除该条呼叫
+                removeCall(interactionVO)
+            }
+        }
+
+        refreshView.setOnRefreshListener {
+            AppTool.Time.delay(800) {
+                updateCallList()
+                refreshView.finishRefresh()
+            }
+        }
+
+        WdKeepAliveService.mNewCallListActive = true
+
+        //启动线程处理呼叫转移
+        if (SettingConfig.getTransCall(BaseApplication.appContext) == 1) {
+            startLoopTransfer()
+        }
+    }
+
+    @SuppressLint("CheckResult")
+    private fun startLoopTransfer() {
+        Thread {
+            while (Constants.newCallList.size > 0) {
+                val curTime = System.currentTimeMillis()
+                var interactionVO: InteractionVO? = null
+                var removeCall = false
+                for (item in Constants.newCallList) {
+                    interactionVO = Gson().fromJson(item.data.toString(), InteractionVO::class.java)
+                    if (curTime - interactionVO.createDate*1000L >= transSeconds*1000L) {
+                        val voiceTransferTcpModel = VoiceUtil.voiceTransfer(item.tid, Constants.deviceId, interactionVO.fromDeviceId, interactionVO.id);
+                        TcpClient.getInstance().sendMsg(voiceTransferTcpModel.toJson())
+
+                        /*val voiceTransferTcpModel = VoiceUtil.voiceTransferToHost(Constants.deviceId, interactionVO.fromDeviceId, interactionVO)
+                        NettyClient.instance.sendMsg(voiceTransferTcpModel.toJson())
+                            .subscribe {
+                                if (it) {
+                                    //Log.d(TAG, "TCP.发送消息完成")
+                                } else {
+                                    //Log.e(TAG, "TCP.发送消息失败")
+                                    HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+                                }
+                            }*/
+
+                        removeCall = true
+                        break
+                    }
+                }
+
+                if (removeCall) {
+                    runOnUiThread {
+                        removeCall(interactionVO!!)
+                    }
+                }
+
+                try {
+                    Thread.sleep(5000)
+                } catch (ex: Exception) {
+                    //ex.printStackTrace()
+                }
+            }
+        }.start()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        updateCallList()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        RingPlayHelper.stopRingTone()
+        SpeechUtil.getInstance().stopSpeak()
+        if (EventBus.getDefault().isRegistered(this)) {
+            EventBus.getDefault().unregister(this)
+        }
+        WdKeepAliveService.mNewCallListActive = false
+        Constants.newCallList.clear()
+    }
+
+    override fun onBackPressed() {
+        showMessage("您还有呼叫未处理")
+    }
+
+    private fun updateCallList() {
+        synchronized(this) {
+            if (Constants.newCallList.size > 0) {
+                adapter.data.clear()
+                adapter.data.addAll(Constants.newCallList)
+                adapter.notifyDataSetChanged()
+            } else {
+                emptyView.visibility = View.VISIBLE
+                AppTool.Time.delay(500) {
+                    finish()
+                }
+            }
+        }
+    }
+
+    private fun removeCall(item: InteractionVO) {
+        synchronized(this) {
+            val iterator = Constants.newCallList.iterator()
+            while (iterator.hasNext()) {
+                val it = iterator.next()
+                val interaction = Gson().fromJson(it.data.toString(), InteractionVO::class.java)
+                if (interaction.id.equals(item.id)) {
+                    iterator.remove()
+                }
+            }
+
+            if (Constants.newCallList.size > 0) {
+                adapter.data.clear()
+                adapter.data.addAll(Constants.newCallList)
+                adapter.notifyDataSetChanged()
+            } else {
+                emptyView.visibility = View.VISIBLE
+                AppTool.Time.delay(500) {
+                    finish()
+                }
+            }
+        }
+    }
+
+    override fun callAccept(model: TcpModel) {
+        //接听电话,进入通话界面,并刷新呼叫列表
+    }
+
+    override fun callReject(model: TcpModel) {
+        //拒绝电话,刷新列表
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        if (messageEvent.tag == Constants.EVENT_UPDATE_CALL) { //新呼叫
+            updateCallList()
+        } /*else if (messageEvent.tag == Constants.EVENT_NEW_TCP) {
+            val tcpModel = messageEvent.getMessage() as TcpModel
+            if (tcpModel.getAction() === TcpAction.VoiceAction.CANCEL || tcpModel.action == TcpAction.VoiceAction.VOICE_OFF) {
+                //对方取消呼叫或者呼叫已被其他主机处理(接听或拒绝)
+                val interactionVO = Gson().fromJson(tcpModel.data.toString(), InteractionVO::class.java)
+                removeCall(interactionVO)
+            }
+        }*/
+    }
+}

+ 222 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/NewEventListActivity.kt

@@ -0,0 +1,222 @@
+package com.wdkl.ncs.android.component.home.activity
+
+import android.content.Context
+import android.os.Vibrator
+import android.util.Log
+import android.view.View
+import com.alibaba.android.vlayout.DelegateAdapter
+import com.alibaba.android.vlayout.VirtualLayoutManager
+import com.enation.javashop.net.engine.model.NetState
+import com.google.gson.Gson
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.adapter.NewEventItemAdapter
+import com.wdkl.ncs.android.component.home.databinding.ActivityEventListBinding
+import com.wdkl.ncs.android.component.home.launch.HomeLaunch
+import com.wdkl.ncs.android.component.home.service.WdKeepAliveService
+import com.wdkl.ncs.android.component.home.util.ImPlayDialogHelper
+import com.wdkl.ncs.android.component.home.util.MediaPlayHelper
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.lib.utils.AppTool
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import com.wdkl.ncs.android.middleware.logic.contract.home.NewEventListContract
+import com.wdkl.ncs.android.middleware.logic.presenter.home.NewEventListPresenter
+import com.wdkl.ncs.android.middleware.model.dos.ClerkDO
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpAction
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpType
+import kotlinx.android.synthetic.main.activity_event_list.*
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+
+class NewEventListActivity : BaseActivity<NewEventListPresenter, ActivityEventListBinding>(),NewEventListContract.View {
+    var TAG = NewEventListActivity::class.java.getSimpleName()
+
+    lateinit var mVibrator: Vibrator
+
+    private val adapter = NewEventItemAdapter(ArrayList(),this)
+    private lateinit var virtualLayoutManager: VirtualLayoutManager
+    private lateinit var delegateAdapter: DelegateAdapter
+
+    //参数自动注入
+//    @Autowired(name = "tcpModelStr", required = true)
+//    @JvmField
+//    var tcpModelStr:String = ""
+//    @Autowired(name = "newEvent", required = false)
+//    @JvmField
+//    var newEvent = false
+
+    override fun getLayId(): Int {
+        return R.layout.activity_event_list
+    }
+
+    override fun bindDagger() {
+        HomeLaunch.component.inject(this)
+    }
+
+    override fun enableHeadsetVoiceMsg(): Boolean {
+        return false
+    }
+
+    override fun init() {
+        //震动
+        mVibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+
+        /**初始化LayoutMannager*/
+        virtualLayoutManager = VirtualLayoutManager(this.activity)
+
+        /**初始化适配器*/
+        delegateAdapter = DelegateAdapter(virtualLayoutManager)
+        delegateAdapter.addAdapter(adapter)
+        /**配置到RecycleView*/
+        rv_event_list.layoutManager = virtualLayoutManager
+        rv_event_list.adapter = delegateAdapter
+    }
+
+    override fun bindEvent() {
+
+    }
+
+    override fun onResume() {
+        super.onResume()
+        WdKeepAliveService.mNewEventListActive = true
+        renderData(Constants.eventList)
+        if (!MediaPlayHelper.getInstance().isMediaPlaying) {
+            ImPlayDialogHelper.dismissIMDialog()
+        }
+    }
+
+    override fun onPause() {
+        super.onPause()
+        WdKeepAliveService.mNewEventListActive = false
+    }
+
+    override fun destory() {
+        MediaPlayHelper.getInstance().stopMusic(true)
+        if (mVibrator!=null) {
+            mVibrator.cancel()
+        }
+        Constants.eventList.clear()
+    }
+
+    fun renderData(data: ArrayList<InteractionVO>) {
+        Log.e(TAG,"返回的数据 "+data.size)
+        //data.sortByDescending { it.id }
+        srl_event_list.finishRefresh()
+        if (data.size > 0) {
+            adapter.data.clear()
+            adapter.data.addAll(data)
+            adapter.notifyDataSetChanged()
+        } else {
+            tv_empty_event.visibility = View.VISIBLE
+            AppTool.Time.delay(800){
+                finish()
+            }
+        }
+    }
+
+    override fun showClerks(clerks: ArrayList<ClerkDO>) {
+        //
+    }
+
+    override fun updateInteractionRes(interaction: InteractionVO) {
+        //
+    }
+
+    override fun onError(message: String, type: Int) {
+        finish()
+    }
+
+    override fun complete(message: String, type: Int) {
+
+    }
+
+    override fun start() {
+
+    }
+
+    override fun networkMonitor(state: NetState) {
+
+    }
+
+    override fun onBackPressed() {
+        if (!Constants.oldEvent) {
+            showMessage("您还有事件未处理")
+        } else {
+            super.onBackPressed()
+        }
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        if (messageEvent.tag == 0) {
+            if (mVibrator!=null) mVibrator.cancel()
+            val resTcpModel = messageEvent.getMessage() as TcpModel
+            if (resTcpModel.type === TcpType.CALLBACK && resTcpModel.action === TcpAction.VoiceAction.FAILED) {
+                showMessage("呼叫失败,可能对方不在线")
+                dismissDialog()
+            }
+        } else if (messageEvent.tag == 2) {
+            if (mVibrator!=null) mVibrator.cancel()
+            val resTcpModel = messageEvent.getMessage() as TcpModel
+            if (resTcpModel.action == TcpAction.VoiceAction.CALLING) {
+                showMessage("对方通话中")
+            }
+        } else if (messageEvent.tag == 4) {
+            val resTcpModel = messageEvent.getMessage() as TcpModel
+            if (resTcpModel.type == TcpType.DATA && resTcpModel.action == TcpAction.DataAction.INTERACTION) {
+                if (resTcpModel.data != null) {
+                    dismissDialog()
+                    if (mVibrator!=null) mVibrator.cancel()
+                    val responseInteractionVO = Gson().fromJson<InteractionVO>(resTcpModel.data.toString(), InteractionVO::class.java)
+                    showMessage("已响应"+responseInteractionVO.fromFrameFullName + " " + responseInteractionVO.data)
+
+                    var doFinish = false
+                    var iterator = Constants.eventList.iterator()
+                    while (iterator.hasNext()){
+                        val it = iterator.next()
+                        if (it.id == responseInteractionVO.id) {
+                            if (responseInteractionVO.actionType.equals(TcpType.IM.name)){
+                                AppTool.Time.delay(1000){
+                                    Thread {
+                                        while (MediaPlayHelper.getInstance().isMediaPlaying){
+                                            Thread.sleep(1000)
+                                        }
+
+                                        runOnUiThread {
+                                            Constants.eventList.remove(it)
+                                            adapter.data.remove(it)
+                                            adapter.notifyDataSetChanged()
+                                            if (Constants.eventList.isEmpty() || Constants.eventList.size==0){
+                                                AppTool.Time.delay(800) {
+                                                    finish()
+                                                }
+                                            } else {
+                                                this.renderData(Constants.eventList)
+                                            }
+                                        }
+                                    }.start()
+                                }
+                            } else {
+                                Constants.eventList.remove(it)
+                                this.adapter.data.remove(it)
+                                this.adapter.notifyDataSetChanged()
+                                doFinish = true
+                            }
+                        }
+                    }
+                    if ((Constants.eventList.isEmpty() || Constants.eventList.size==0) && doFinish){
+                        AppTool.Time.delay(800) {
+                            finish()
+                        }
+                    } else {
+                        this.renderData(Constants.eventList)
+                    }
+                }
+            }
+        } else if (messageEvent.tag == Constants.EVENT_UPDATE_EVENT) { //新事件到来
+            renderData(Constants.eventList)
+        }
+    }
+}

+ 115 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/TakeoverActivity.kt

@@ -0,0 +1,115 @@
+package com.wdkl.ncs.android.component.home.activity
+
+import android.util.Log
+import android.view.View
+import com.alibaba.android.vlayout.DelegateAdapter
+import com.alibaba.android.vlayout.VirtualLayoutManager
+import com.enation.javashop.net.engine.model.NetState
+import com.google.gson.JsonArray
+import com.google.gson.JsonObject
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.adapter.TakeoverItemAdapter
+import com.wdkl.ncs.android.component.home.databinding.ActivityTakeoverBinding
+import com.wdkl.ncs.android.component.home.launch.HomeLaunch
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.middleware.logic.contract.home.TakeoverContract
+import com.wdkl.ncs.android.middleware.logic.presenter.home.TakeoverPresenter
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpAction
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import kotlinx.android.synthetic.main.activity_takeover.*
+import kotlinx.android.synthetic.main.watch_contacts_lay.refresh
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+
+class TakeoverActivity: BaseActivity<TakeoverPresenter, ActivityTakeoverBinding>(), TakeoverContract.View {
+    var TAG = TakeoverActivity::class.java.getSimpleName()
+
+    private val adapter = TakeoverItemAdapter(ArrayList(),this)
+    private lateinit var virtualLayoutManager: VirtualLayoutManager
+    private lateinit var delegateAdapter: DelegateAdapter
+    var partId: Int = -1
+
+    override fun getLayId(): Int {
+        return R.layout.activity_takeover
+    }
+
+    override fun bindDagger() {
+        HomeLaunch.component.inject(this)
+    }
+
+    override fun enableHeadsetVoiceMsg(): Boolean {
+        return false
+    }
+
+    override fun onError(message: String, type: Int) {
+        dismissDialog()
+        showMessage(message)
+    }
+
+    override fun complete(message: String, type: Int) {
+        dismissDialog()
+        showMessage(message)
+    }
+
+    override fun start() {
+    }
+
+    override fun networkMonitor(state: NetState) {
+    }
+
+    override fun init(){
+        /**初始化LayoutMannager*/
+        virtualLayoutManager = VirtualLayoutManager(this.activity)
+
+        /**初始化适配器*/
+        delegateAdapter = DelegateAdapter(virtualLayoutManager)
+        delegateAdapter.addAdapter(adapter)
+        /**配置到RecycleView*/
+        takeover_list.layoutManager = virtualLayoutManager
+        takeover_list.adapter = delegateAdapter
+        partId = Constants.partId
+        presenter.loadList(partId)
+    }
+
+    override fun renderData(data: JsonArray) {
+        Log.i(TAG,"返回的数据 "+data)
+        refresh.finishRefresh()
+        if (data.size() > 0) {
+            adapter.data.clear()
+            var listData = ArrayList<JsonObject>()
+            for (item in data){
+                val _item = item as JsonObject
+                if (_item.get("id").asInt != Constants.deviceId){
+                    listData.add(_item)
+                }
+            }
+            adapter.data.addAll(listData)
+            adapter.notifyDataSetChanged()
+        } else {
+            tv_empty_takeover.visibility = View.VISIBLE
+        }
+    }
+
+    override fun bindEvent() {
+        refresh.setOnRefreshListener {
+            presenter.loadList(partId)
+        }
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        if (messageEvent.tag == 2) {
+            val resTcpModel = messageEvent.getMessage() as TcpModel
+            if (resTcpModel.action  == TcpAction.VoiceAction.FAILED){
+                showMessage("呼叫失败,可能对方不在线")
+            } else if (resTcpModel.action == TcpAction.VoiceAction.CALLING){
+                showMessage("对方通话中")
+            }
+        }
+    }
+
+    override fun destory() {
+    }
+}

+ 244 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/VoiceMsgActivity.kt

@@ -0,0 +1,244 @@
+package com.wdkl.ncs.android.component.home.activity
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.PowerManager
+import android.os.SystemClock
+import android.view.MotionEvent
+import com.alibaba.fastjson.JSONObject
+import com.enation.javashop.android.jrouter.external.annotation.Autowired
+import com.enation.javashop.net.engine.model.NetState
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.databinding.ActivityVoiceMsgBinding
+import com.wdkl.ncs.android.component.home.launch.HomeLaunch
+import com.wdkl.ncs.android.component.home.util.IMDialogHelper
+import com.wdkl.ncs.android.component.home.util.RecordHelper
+import com.wdkl.ncs.android.component.home.util.SoundPoolManager
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import com.wdkl.ncs.android.lib.vo.filter
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.middleware.logic.contract.home.VoiceMsgContract
+import com.wdkl.ncs.android.middleware.logic.presenter.home.VoiceMsgPresenter
+import com.wdkl.ncs.android.middleware.tcp.TcpClient
+import com.wdkl.ncs.android.middleware.tcp.channel.ImUtil
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpAction
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpType
+import kotlinx.android.synthetic.main.activity_voice_msg.*
+import okhttp3.MediaType
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import java.io.File
+import java.util.*
+
+class VoiceMsgActivity:BaseActivity<VoiceMsgPresenter,ActivityVoiceMsgBinding>(), VoiceMsgContract.View {
+    var TAG = VoiceMsgActivity::class.java.simpleName
+
+    companion object instance{
+        const val TO_DEVICE_ID = "toDeviceId"
+    }
+
+    var downTime : Long = 0
+    var upTime : Long = 0
+    var downY : Float = 0f
+    var cancel : Boolean = false
+    var voiceFile : String? = null
+    lateinit var wakeLock : PowerManager.WakeLock
+
+    @Autowired(name = TO_DEVICE_ID, required = true)
+    @JvmField
+    var toDeviceId = 0
+
+    override fun getLayId(): Int {
+        return R.layout.activity_voice_msg
+    }
+
+    override fun bindDagger() {
+        HomeLaunch.component.inject(this)
+    }
+
+    @SuppressLint("InvalidWakeLockTag")
+    override fun init() {
+        //RecordHelper.getInstance().init()
+        Constants.allowVoiceMsg = false
+        if (RecordHelper.getInstance().isRecording()) {
+            IMDialogHelper.showIMDialog(activity)
+        }
+
+        // 获取WakeLock对象
+        val powerManager= getSystemService(Context.POWER_SERVICE) as PowerManager
+        wakeLock = powerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_DIM_WAKE_LOCK, "voice_msg_on")
+}
+
+    override fun enableHeadsetVoiceMsg(): Boolean {
+        return false
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    override fun bindEvent() {
+        tv_voice_button.setOnTouchListener { v, event ->
+            when(event.action) {
+                MotionEvent.ACTION_DOWN -> {
+                    cancel = false
+                    downTime = System.currentTimeMillis()
+                    downY = event.getY()
+                    RecordHelper.getInstance().startRecord()
+                    tv_voice_text.text = "松开留言,上滑取消"
+                    voice_call_timer.base = SystemClock.elapsedRealtime()
+                    voice_call_timer.start()
+                    voiceFile = RecordHelper.getInstance().audiofilePath
+
+                    //按下时保持常亮
+                    wakeLock.acquire()
+                }
+
+                MotionEvent.ACTION_MOVE -> {
+                    val moveY = event.getY()
+                    if (Math.abs(downY - moveY) > 100) {
+                        cancel = true
+                    }
+                }
+
+                MotionEvent.ACTION_UP -> {
+                    upTime = System.currentTimeMillis()
+                    RecordHelper.getInstance().stopRecord()
+                    tv_voice_text.text = "按住说话"
+                    voice_call_timer.base = SystemClock.elapsedRealtime()
+                    voice_call_timer.stop()
+
+                    if (Math.abs(upTime - downTime) <= 1000) {
+                        RecordHelper.getInstance().deleteAudioFile(voiceFile)
+                        showMessage("说话时间太短")
+                    } else if(cancel) {
+                        RecordHelper.getInstance().deleteAudioFile(voiceFile)
+                        showMessage("语音留言已取消")
+                    } else {
+                        //上传语音留言
+                        if (voiceFile != null) {
+                            val part = MultipartBody.Part.createFormData("file", voiceFile, RequestBody.create(MediaType.parse("multipart/form-data"), File(voiceFile)))
+                            presenter.uploadVoiceMsg(part)
+                        }
+                    }
+
+                    //松开时释放
+                    wakeLock.release()
+                }
+            }
+
+            return@setOnTouchListener false
+        }
+    }
+
+    override fun destory() {
+        Constants.allowVoiceMsg = true
+        IMDialogHelper.dismissIMDialog()
+    }
+
+    @SuppressLint("CheckResult")
+    override fun uploadResponse(result: String) {
+        //上传完成后删除本地文件
+        RecordHelper.getInstance().deleteAudioFile(voiceFile)
+        /*val tcpModel = ImUtil.imMsg(Constants.deviceId, toDeviceId, result)
+        NettyClient.instance.sendMsg(tcpModel.toJson()).subscribe{
+            if (it){
+                SoundPoolManager.getInstance().playSound(3)
+                showMessage("留言发送成功!")
+            } else {
+                showMessage("留言发送失败!")
+            }
+            finish()
+        }*/
+
+        val tcpModel = ImUtil.imMsg(Constants.deviceId, toDeviceId, result)
+        val transaction: TcpCallback = object : TcpCallback(tcpModel.tid) {
+            override fun onAck() {
+                //发送成功
+                //if (Locale.CHINESE.getLanguage().equals(language)) {
+                    SoundPoolManager.getInstance().playSound(3)
+                //}
+                runOnUiThread {
+                    showMessage("send success!")
+                }
+                finish()
+            }
+
+            override fun onSuccess(jsonObject: JSONObject) {
+                //对方接收成功
+            }
+
+            override fun onFailed(jsonObject: JSONObject) {
+                // 这里写发送失败的方法
+                SoundPoolManager.getInstance().playSound(6)
+                val callbackString = jsonObject.getString(CALLBACK)
+                runOnUiThread {
+                    showMessage("Failed: $callbackString")
+                }
+            }
+        }
+        TcpClient.getInstance().sendTcp(tcpModel, false, transaction)
+    }
+
+    override fun onError(message: String, type: Int) {
+    }
+
+    override fun complete(message: String, type: Int) {
+    }
+
+    override fun start() {
+    }
+
+    override fun networkMonitor(state: NetState) {
+        state.filter(onWifi = {
+
+        },onMobile = {
+
+        },offline = {
+
+        })
+    }
+
+    private fun cancelRecord() {
+        if (voice_call_timer != null) {
+            voice_call_timer.base = SystemClock.elapsedRealtime()
+            voice_call_timer.stop()
+        }
+        RecordHelper.getInstance().stopRecord()
+        RecordHelper.getInstance().deleteAudioFile(voiceFile)
+        showMessage("语音留言已取消")
+        finish()
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent){
+        if (messageEvent.tag == 1) {
+            val tcpModel = messageEvent.getMessage() as TcpModel
+            if (tcpModel.type == TcpType.VOICE) {
+                if (tcpModel.action == TcpAction.VoiceAction.CALL) {
+                    //新来电
+                    cancelRecord()
+                }
+            }
+        } else if (messageEvent.tag == 3) {
+            //收到新事件
+            val tcpModel = messageEvent.getMessage() as TcpModel
+            if (tcpModel.type == TcpType.EVENT) {
+                if (tcpModel.action == TcpAction.EventAction.KEY_CLICK) {
+                    //取消语音留言录音并退出
+                    cancelRecord()
+                }
+            } else if (tcpModel.type == TcpType.IM) {
+                /*if (tcpModel.action == TcpAction.IMAction.MSG) {
+                    //取消语音留言录音并退出
+                    cancelRecord()
+                }*/
+            }
+        } else if (messageEvent.tag == 999) {
+            //紧急呼叫
+            cancelRecord()
+        }
+    }
+}

+ 327 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/WatchCallRecordsActivity.kt

@@ -0,0 +1,327 @@
+package com.wdkl.ncs.android.component.home.activity
+
+import android.content.Intent
+import android.net.Uri
+import android.os.CountDownTimer
+import android.util.Log
+import android.view.View
+import com.alibaba.android.vlayout.DelegateAdapter
+import com.alibaba.android.vlayout.VirtualLayoutManager
+import com.alibaba.fastjson.JSONObject
+import com.enation.javashop.android.jrouter.external.annotation.Router
+import com.enation.javashop.net.engine.model.NetState
+import com.enation.javashop.utils.base.widget.LoadingDialog
+import com.scwang.smartrefresh.layout.footer.ClassicsFooter
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.adapter.WatchCallRecordsItemAdapter
+import com.wdkl.ncs.android.component.home.databinding.WatchActivityCallRecordsBinding
+import com.wdkl.ncs.android.component.home.launch.HomeLaunch
+import com.wdkl.ncs.android.component.home.ui.CallSingleActivity
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.middleware.logic.contract.home.WatchCallRecordsFragmentContract
+import com.wdkl.ncs.android.middleware.logic.presenter.home.WatchCallRecordsFragmentPresenter
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
+import com.wdkl.ncs.android.middleware.model.vo.WatchContactsVO
+import com.wdkl.ncs.android.middleware.tcp.channel.DeviceChannel
+import com.wdkl.ncs.android.middleware.tcp.channel.VoiceUtil
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpAction
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpType
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import com.wdkl.ncs.android.middleware.tcp.TcpClient
+import com.wdkl.ncs.android.middleware.tcp.channel.PhoneUtil
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback
+import com.wdkl.ncs.android.middleware.tcp.enums.CommunicationEnum
+import kotlinx.android.synthetic.main.watch_activity_call_records.*
+import kotlinx.android.synthetic.main.watch_activity_call_records.refresh
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+
+
+@Router(path = "/watch/callrecords")
+class WatchCallRecordsActivity : BaseActivity<WatchCallRecordsFragmentPresenter, WatchActivityCallRecordsBinding>(), WatchCallRecordsFragmentContract.View {
+    var TAG = WatchCallRecordsActivity::class.java.getSimpleName()
+
+    /**
+     * @Name  virtualLayoutManager
+     * @Type  VirtualLayoutManager
+     * @Note  VLayoutManager
+     */
+    private lateinit var virtualLayoutManager: VirtualLayoutManager
+
+    private var page: Int = 1
+    /**
+     * @Name  delegateAdapter
+     * @Type  DelegateAdapter
+     * @Note  七巧板适配器
+     */
+    private lateinit var delegateAdapter: DelegateAdapter
+
+    private val watchCallRecordsItemAdapter = WatchCallRecordsItemAdapter(ArrayList())
+
+    private lateinit var loadingDialog: LoadingDialog
+
+    private var customerId = ""
+
+    var all = "ALL"
+    var customerRecords = "CUSTOMER_RECORDS"
+    var unread = "UNREAD"
+
+    var action: String? = null
+    var receivedData: WatchContactsVO? = null
+
+    private var CALL_TIMEOUT = 5 //多久可以再次点击
+    //呼叫倒计时
+    lateinit var countDownTimer: CountDownTimer
+
+    override fun getLayId(): Int {
+        return R.layout.watch_activity_call_records
+    }
+
+    override fun bindDagger() {
+        HomeLaunch.component.inject(this)
+    }
+
+    override fun enableHeadsetVoiceMsg(): Boolean {
+        return false
+    }
+
+    override fun init() {
+        /**初始化LayoutMannager*/
+        virtualLayoutManager = VirtualLayoutManager(this.activity)
+
+        /**初始化适配器*/
+        delegateAdapter = DelegateAdapter(virtualLayoutManager)
+        delegateAdapter.addAdapter(watchCallRecordsItemAdapter)
+        mViewBinding.refresh.setRefreshFooter(ClassicsFooter(activity))
+
+        /**配置到RecycleView*/
+        call_records_recyv.layoutManager = virtualLayoutManager
+        call_records_recyv.adapter = delegateAdapter
+
+        //loadingDialog = CommonTool.createLoadingDialog(this, R.layout.custom_loading,R.id.loadding_image)
+
+        receivedData = intent.getSerializableExtra("data") as WatchContactsVO?
+        action = intent.getStringExtra("action")
+        customerId = intent.getStringExtra("customer_id").toString()
+
+        when(action){
+            all->{
+                call_relyout.visibility = View.GONE
+                presenter.loadPage(all, 1, 30, Constants.deviceId, 0, 0)
+
+                refresh.setOnLoadMoreListener {
+                    page += 1
+                    presenter.loadPage(all, page, 30, Constants.deviceId, 0, 0)
+                }
+                refresh.setOnRefreshListener {
+                    page = 1
+                    presenter.loadPage(all, 1, 30, Constants.deviceId, 0, 0)
+                }
+            }
+            unread->{
+                call_relyout.visibility = View.GONE
+                presenter.loadPage(all, 1, 30, Constants.deviceId, 0, 1)
+
+                refresh.setOnLoadMoreListener {
+                    page += 1
+                    presenter.loadPage(all, page, 30, Constants.deviceId, 0, 1)
+                }
+                refresh.setOnRefreshListener {
+                    page = 1
+                    presenter.loadPage(all, 1, 30, Constants.deviceId, 0, 1)
+                }
+            }
+            customerRecords->{
+                //通话处理
+                if (Constants.supportCall) {
+                    call_relyout.visibility = View.VISIBLE
+                } else {
+                    call_relyout.visibility = View.GONE
+                }
+
+                call_relyout.setOnClickListener {
+                    DeviceChannel.calling = true
+                    call_relyout.isEnabled = false
+
+                    if (Constants.bedPhoneType == CommunicationEnum.MOBILE_PHONE.value()) {
+                        try {
+                            if (receivedData!!.phoneNumber != null) {
+                                val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:" + receivedData!!.phoneNumber))
+                                startActivity(intent)
+
+                                //发送tcp
+                                val tcpModel = PhoneUtil.phoneCall(Constants.deviceId, receivedData!!.deviceId)
+                                val transaction: TcpCallback = object : TcpCallback(tcpModel.tid) {
+                                    override fun onSuccess(jsonObject: JSONObject) {
+                                        //
+                                    }
+
+                                    override fun onFailed(jsonObject: JSONObject) {
+                                        // 这里写发送失败的方法
+                                    }
+                                }
+                                TcpClient.getInstance().sendTcp(tcpModel, true, transaction)
+
+                                /*val tcpModel = PhoneUtil.phoneCall(Constants.deviceId, receivedData!!.deviceId)
+                                NettyClient.instance.sendMsg(tcpModel.toJson())
+                                    .subscribe { res: Boolean ->
+                                        if (res) {
+                                            Log.d(TAG, "TCP.发送消息完成")
+                                        } else {
+                                            Log.e(TAG, "TCP.发送消息失败")
+                                            HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+                                        }
+                                    }*/
+                            } else {
+                                showMessage("呼叫失败,可能手机号异常")
+                            }
+                        } catch (e: Exception) {
+                            showMessage("呼叫失败,请检查手机号")
+                        }
+                    } else {
+                        val tcpModel = VoiceUtil.voiceCall(Constants.deviceId, receivedData!!.deviceId)
+                        //启动通话界面
+                        val intent = Intent(this@WatchCallRecordsActivity, CallSingleActivity::class.java)
+                        intent.putExtra(CallSingleActivity.EXTRA_ROOM_ID, Constants.sipId)
+                        intent.putExtra(CallSingleActivity.EXTRA_MO, true)
+                        intent.putExtra(CallSingleActivity.EXTRA_AUDIO_ONLY, true)
+                        intent.putExtra(CallSingleActivity.EXTRA_TCPMODEL, tcpModel);
+                        intent.putExtra(CallSingleActivity.EXTRA_SHOWNAME, receivedData!!.frameFullName + " " + receivedData!!.customerNamed)
+                        //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                        startActivity(intent)
+
+                        countDownTimer.start()
+                    }
+                }
+
+                presenter.loadPage(customerRecords, 1, 30, Constants.deviceId, customerId.toInt(), -1)
+
+                refresh.setOnLoadMoreListener {
+                    page += 1
+                    presenter.loadPage(customerRecords, page, 30, Constants.deviceId, customerId.toInt(), -1)
+                }
+                refresh.setOnRefreshListener {
+                    page = 1
+                    presenter.loadPage(customerRecords, 1, 30, Constants.deviceId, customerId.toInt(), -1)
+                }
+            }
+        }
+
+        initCountDownTimer()
+    }
+
+    override fun bindEvent() {
+        watchCallRecordsItemAdapter.setOnItemClickListener { data, position ->
+
+            //先存储数据然后启动NewEventListActivity显示数据
+            if (Constants.oldEvent) {
+                Constants.eventList.clear()
+            }
+            Constants.oldEvent = true
+            Constants.eventList.add(data)
+
+            var intent = Intent()
+            intent.setClass(this, NewEventListActivity::class.java)
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            startActivity(intent)
+        }
+    }
+
+    override fun destory() {
+        if(countDownTimer != null){
+            countDownTimer.cancel()
+        }
+    }
+
+    override fun renderList(data: ArrayList<InteractionVO>) {
+        if (action.equals(unread)) {
+            EventBus.getDefault().post(MessageEvent(data, Constants.EVENT_UNTREATED_QUANTITY))
+        }
+
+        tv_empty_records.visibility = View.GONE
+        refresh.finishRefresh()
+        Log.i("abc1", " " + data.size);
+        if (page == 1) {
+            refresh.resetNoMoreData()
+            watchCallRecordsItemAdapter.data.clear()
+            if (data.size > 0) {
+                watchCallRecordsItemAdapter.data.addAll(data)
+            } else {
+                tv_empty_records.visibility = View.VISIBLE
+            }
+            watchCallRecordsItemAdapter.notifyDataSetChanged()
+            refresh.finishLoadMore()
+        } else {
+            if (data.size > 0) {
+                watchCallRecordsItemAdapter.data.addAll(data)
+                watchCallRecordsItemAdapter.notifyDataSetChanged()
+                refresh.finishLoadMore()
+            } else {
+                refresh.finishLoadMoreWithNoMoreData()
+            }
+        }
+    }
+
+    override fun onError(message: String, type: Int) {
+    }
+
+    override fun complete(message: String, type: Int) {
+    }
+
+    override fun start() {
+    }
+
+    override fun networkMonitor(state: NetState) {
+    }
+
+    override fun onResume() {
+        super.onResume()
+        page = 1
+        when(action){
+            all->{
+                presenter.loadPage(all, 1, 30, Constants.deviceId, 0, 0)
+            }
+            unread->{
+                presenter.loadPage(all, 1, 30, Constants.deviceId, 0, 1)
+            }
+            customerRecords->{
+                presenter.loadPage(customerRecords, 1, 30, Constants.deviceId, customerId.toInt(), -1)
+            }
+        }
+    }
+
+    fun initCountDownTimer() {
+        countDownTimer = object : CountDownTimer(CALL_TIMEOUT * 3000L, 1000) {
+            override fun onTick(millisUntilFinished: Long) {
+            }
+            override fun onFinish() {
+                call_relyout.isEnabled = true
+            }
+        }
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        if (messageEvent.tag == 1 || messageEvent.tag == 2) {
+            val tcpModel = messageEvent.getMessage() as TcpModel
+            Log.i(TAG, tcpModel.toJson())
+            if (tcpModel.type == TcpType.VOICE) {
+                if (tcpModel.action == TcpAction.VoiceAction.SUCCESS) {
+                    //loadingDialog.dismiss()
+                    //界面呈现,逻辑在service中
+                } else if (tcpModel.action == TcpAction.VoiceAction.FAILED) {
+                    //loadingDialog.dismiss()
+                    call_relyout.isEnabled = true
+                    DeviceChannel.calling = false;
+                    DeviceChannel.callId = 0
+                    showMessage("呼叫失败,可能对方不在线")
+                }else if (tcpModel.action == TcpAction.VoiceAction.CALLING){
+                    showMessage("对方通话中")
+                }
+            }
+        }
+    }
+}

+ 158 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/WatchContactsActivity.kt

@@ -0,0 +1,158 @@
+package com.wdkl.ncs.android.component.home.activity
+
+import android.content.Intent
+import android.util.Log
+import android.view.View
+import com.alibaba.android.vlayout.DelegateAdapter
+import com.alibaba.android.vlayout.VirtualLayoutManager
+import com.enation.javashop.android.jrouter.external.annotation.Router
+import com.enation.javashop.net.engine.model.NetState
+import com.scwang.smartrefresh.layout.footer.ClassicsFooter
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.adapter.WatchContactsItemAdapter
+import com.wdkl.ncs.android.component.home.databinding.WatchContactsLayBinding
+import com.wdkl.ncs.android.component.home.launch.HomeLaunch
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.middleware.logic.contract.home.WatchActivityContract
+import com.wdkl.ncs.android.middleware.logic.presenter.home.WatchActivityPresenter
+import com.wdkl.ncs.android.middleware.model.vo.WatchContactsVO
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import com.wdkl.ncs.android.middleware.model.vo.DeviceVO
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpAction
+import kotlinx.android.synthetic.main.watch_contacts_lay.*
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+
+@Router(path = "/watch/contacts")
+class WatchContactsActivity : BaseActivity<WatchActivityPresenter, WatchContactsLayBinding>(), WatchActivityContract.View {
+    var TAG = WatchContactsActivity::class.java.getSimpleName()
+
+    private var page: Int = 1
+    private val adapter = WatchContactsItemAdapter(ArrayList(),this)
+
+    private lateinit var virtualLayoutManager: VirtualLayoutManager
+
+    private lateinit var delegateAdapter: DelegateAdapter
+
+
+    override fun getLayId(): Int {
+        return R.layout.watch_contacts_lay
+    }
+
+    override fun bindDagger() {
+        HomeLaunch.component.inject(this)
+    }
+
+    override fun enableHeadsetVoiceMsg(): Boolean {
+        return false
+    }
+
+    override fun onError(message: String, type: Int) {
+        dismissDialog()
+        showMessage(message)
+    }
+
+    override fun complete(message: String, type: Int) {
+        dismissDialog()
+        showMessage(message)
+    }
+
+    override fun start() {
+        showDialog()
+    }
+
+    override fun networkMonitor(state: NetState) {
+
+    }
+
+    override fun destory() {
+
+    }
+
+    override fun init() {
+//        AppTool.SystemUI.showNavigationBar(this,false)
+//        AppTool.SystemUI.ImmersiveWithBottomBarColor(this, Color.BLACK)
+
+        /**初始化LayoutMannager*/
+        virtualLayoutManager = VirtualLayoutManager(this.activity)
+
+        /**初始化适配器*/
+        delegateAdapter = DelegateAdapter(virtualLayoutManager)
+        delegateAdapter.addAdapter(adapter)
+        mViewBinding.refresh.setRefreshFooter(ClassicsFooter(activity))
+        /**配置到RecycleView*/
+        watch_contacts_list.layoutManager = virtualLayoutManager
+        watch_contacts_list.adapter = delegateAdapter
+        presenter.loadData(page, Constants.deviceId)
+    }
+
+    override fun bindEvent() {
+        refresh.setOnLoadMoreListener {
+            page += 1
+            presenter.loadData(page, Constants.deviceId)
+        }
+        refresh.setOnRefreshListener {
+            page = 1
+            presenter.loadData(1, Constants.deviceId)
+        }
+
+        if (Constants.deviceType != 1) {
+            adapter.setOnItemClickListener { data, position ->
+                var customerId = data.customerId
+                if (data.customerId == null) {
+                    customerId = 0
+                }
+                val watchContactsVO: WatchContactsVO = data
+                var intent = Intent(this, WatchCallRecordsActivity::class.java)
+                intent.putExtra("data", watchContactsVO)
+                intent.putExtra("action", "CUSTOMER_RECORDS")
+                intent.putExtra("customer_id", "" + customerId)
+                startActivity(intent)
+            }
+        }
+    }
+
+    override fun render(data: ArrayList<WatchContactsVO>) {
+
+        Log.i(TAG,"返回的数据 "+data)
+        refresh.finishRefresh()
+        if (page == 1) {
+            refresh.resetNoMoreData()
+            if (data.size > 0) {
+                adapter.data.clear()
+                adapter.data.addAll(data)
+                Log.i("abc1", " " + data.size);
+                adapter.notifyDataSetChanged()
+            } else {
+                tv_empty_contacts.visibility = View.VISIBLE
+            }
+            refresh.finishLoadMore()
+        } else {
+            if (data.size > 0) {
+                adapter.data.addAll(data)
+                adapter.notifyDataSetChanged()
+                refresh.finishLoadMore()
+            } else {
+                refresh.finishLoadMoreWithNoMoreData()
+            }
+        }
+    }
+
+    override fun showDeviceVos(devices: ArrayList<DeviceVO>) {
+        //
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        if (messageEvent.tag == 2) {
+            val resTcpModel = messageEvent.getMessage() as TcpModel
+            if (resTcpModel.action  == TcpAction.VoiceAction.FAILED){
+                showMessage("呼叫失败,可能对方不在线")
+            } else if (resTcpModel.action == TcpAction.VoiceAction.CALLING){
+                showMessage("对方通话中")
+            }
+        }
+    }
+}

+ 778 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/WatchHome2Activity.kt

@@ -0,0 +1,778 @@
+package com.wdkl.ncs.android.component.home.activity
+
+import android.Manifest
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.Color
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.CountDownTimer
+import android.provider.Settings
+import android.telephony.PhoneStateListener
+import android.telephony.SignalStrength
+import android.telephony.TelephonyManager
+import android.text.TextUtils
+import android.util.Log
+import android.view.View
+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.enation.javashop.utils.base.widget.LoadingDialog
+import com.google.common.base.Strings
+import com.wdkl.ncs.android.component.home.BuildConfig
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.broadcast.BatteryBroadcastReceiver
+import com.wdkl.ncs.android.component.home.broadcast.MyMediaButtonReceiver
+import com.wdkl.ncs.android.component.home.databinding.WatchActivityHome2Binding
+import com.wdkl.ncs.android.component.home.launch.HomeLaunch
+import com.wdkl.ncs.android.component.home.service.WdKeepAliveService
+import com.wdkl.ncs.android.component.home.settingconfig.SettingConfig
+import com.wdkl.ncs.android.component.home.util.*
+import com.wdkl.ncs.android.lib.base.BaseApplication
+import com.wdkl.ncs.android.lib.utils.AppTool
+import com.wdkl.ncs.android.lib.utils.joinManager
+import com.wdkl.ncs.android.lib.utils.push
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.middleware.logic.contract.home.WatchHomeActivityContract
+import com.wdkl.ncs.android.middleware.logic.presenter.home.WatchHomeActivityPresenter
+import com.wdkl.ncs.android.middleware.model.ServerIpInfo
+import com.wdkl.ncs.android.middleware.model.dos.AppVersionDO
+import com.wdkl.ncs.android.middleware.model.dos.ChannelDO
+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.DeviceVO
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
+import com.wdkl.ncs.android.middleware.model.vo.WatchContactVO
+import com.wdkl.ncs.android.middleware.tcp.TcpClientHandler
+import com.wdkl.ncs.android.middleware.tcp.enums.RoleTypeEnum
+import com.wdkl.ncs.android.middleware.utils.CommonUtils
+import com.wdkl.ncs.android.middleware.utils.ContactHelper
+import com.wdkl.ncs.keepbackground.utils.SpManager
+import com.wdkl.ncs.keepbackground.work.DaemonEnv
+import com.wdkl.rtc.util.JanusConstant
+import io.reactivex.Observable
+import kotlinx.android.synthetic.main.watch_activity_home2.*
+import kotlinx.android.synthetic.main.watch_activity_register.*
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import java.util.*
+import kotlin.collections.ArrayList
+
+//@Router(path = "/watch/home")
+class WatchHome2Activity : BaseActivity<WatchHomeActivityPresenter, WatchActivityHome2Binding>(), WatchHomeActivityContract.View, View.OnClickListener {
+
+    var TAG = WatchHome2Activity::class.java.getSimpleName()
+
+    //监听网络变化
+    lateinit var teleManager: TelephonyManager
+    private var netType: Int = -1
+
+    lateinit var batteryBroadcastReceiver: BatteryBroadcastReceiver
+    lateinit var myMediaButtonReceiver: MyMediaButtonReceiver
+    private var isUnRegister = true
+    lateinit var loadingDialog: LoadingDialog
+    lateinit var netOffLoadingDialog: LoadingDialog
+    lateinit var countDownTimer: CountDownTimer
+
+    private var clickTime: Long = 0
+
+    private lateinit var netWorkChangeReceiver: NetWorkChangeReceiver
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        if (!isTaskRoot) {
+            finish()
+        }
+        loadingDialog = CommonTool.createLoadingDialog(this, R.layout.custom_loading, R.id.loadding_image)
+        netOffLoadingDialog = CommonTool.createLoadingDialog(this, R.layout.netoff_loading, R.id.netoff_loading_image)
+
+        super.onCreate(savedInstanceState)
+    }
+
+    override fun getLayId(): Int {
+        return R.layout.watch_activity_home2
+    }
+
+    override fun bindDagger() {
+        HomeLaunch.component.inject(this)
+    }
+
+    //初始化
+    override fun init() {
+        AnrFcExceptionUtil.getInstance(application).initFCException()
+        //注册广播
+        regReceiver()
+
+        tv_register_version.text = "v" + CommonUtils.getAppVersionName(activity)
+
+        initCountDownTimer()
+
+        //网络强度监听
+        teleManager = (applicationContext.getSystemService(Context.TELEPHONY_SERVICE)) as TelephonyManager
+        netType = NetHelper.getInstance().getNetworkState(applicationContext)
+        Log.i(TAG, "网络类型:" + netType)
+        /*
+-50 to -79 dBm, then it's generally considered great signal (4 to 5 bars).
+-80 to -89 dBm, then it's generally considered good signal (3 to 4 bars).
+-90 to -99 dBm, then it's generally considered average signal (2 to 3 bars).
+-100 to -109 dBm, then it's generally considered poor signal (1 to 2 bars).
+-110 to -120 dBm, then it's generally considered very poor signal (0 to 1 bar)
+         */
+        /*if (netType == NetHelper.NETWORK_4G) {
+            if (teleManager != null) {
+                teleManager.listen(object : PhoneStateListener() {
+                    override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+                        if (NetHelper.getInstance().getNetworkState(applicationContext) == NetHelper.NETWORK_NONE) {
+                            tv_signal_strength.text = "网络断开"
+                        } else {
+                            val signalinfo = signalStrength.toString()
+                            val parts = signalinfo.split(" ".toRegex()).toTypedArray()
+                            val ltedbm = parts[9].toInt()
+                            if (ltedbm >= -79) {
+                                tv_signal_strength.text = "网络极好"
+                            } else if (ltedbm >= -89 && ltedbm <= -80) {
+                                tv_signal_strength.text = "网络好"
+                            } else if (ltedbm >= -99 && ltedbm <= -90) {
+                                tv_signal_strength.text = "网络正常"
+                            } else if (ltedbm >= -109 && ltedbm <= -100) {
+                                tv_signal_strength.text = "网络较差"
+                            } else if (ltedbm >= -120 && ltedbm <= -110) {
+                                tv_signal_strength.text = "网络极差"
+                            }
+                        }
+                        //Log.i(TAG, "网络:LTE 信号强度:$ltedbm======Detail:$signalinfo")
+
+                        super.onSignalStrengthsChanged(signalStrength)
+                    }
+                }, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+            }
+        }*/
+
+        requestPermissions()
+
+        tv_system_settings.setOnClickListener {
+            val intent = Intent(Settings.ACTION_SETTINGS)
+            startActivity(intent)
+        }
+    }
+
+    private fun permissionGranted() {
+        Constants.imei = Util.getIMEI(this)
+        Log.i(TAG, "IMEI " + Util.getIMEI(this))
+        Log.i(TAG, "mac " + Constants.mac)
+        tv_feedback_device_info.text = Constants.imei
+
+        //申请悬浮窗权限
+        if (Build.VERSION.SDK_INT >= 23) {
+            if (!Settings.canDrawOverlays(this)) {
+                val intent = Intent(
+                        Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
+                        Uri.parse("package:$packageName"))
+                startActivityForResult(intent, 10)
+            }
+        }
+
+        initTcp()
+
+        //默认使用网络电话
+        if (SettingConfig.getVoiceCallType(BaseApplication.appContext) == -1) {
+            SettingConfig.setVoiceCallType(BaseApplication.appContext, SettingConfig.SIP_CALL)
+        }
+        if (SettingConfig.getTransCall(BaseApplication.appContext) == -1) {
+            SettingConfig.setTransCall(BaseApplication.appContext, 0)
+        }
+    }
+
+    private fun regReceiver() {
+
+//        myMediaButtonReceiver = MyMediaButtonReceiver()
+//        val ittFilterButton = IntentFilter(Intent.ACTION_MEDIA_BUTTON) //控制键
+//        registerReceiver(myMediaButtonReceiver, ittFilterButton);
+
+//        val ittFilterBluetooth = IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)  //蓝牙断开
+//        registerReceiver(myMediaButtonReceiver, ittFilterBluetooth);
+
+        // 动态注册广播接收器
+        // 过滤器
+        val intentFilter = IntentFilter()
+        // 系统的网络被更改的过滤器
+        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE")
+        netWorkChangeReceiver = NetWorkChangeReceiver()
+        registerReceiver(netWorkChangeReceiver, intentFilter)
+
+        //电池
+        batteryBroadcastReceiver = BatteryBroadcastReceiver(electric_quantity_tv)
+        val intentFilter2 = IntentFilter()
+        intentFilter2.addAction(Intent.ACTION_BATTERY_CHANGED)
+        registerReceiver(batteryBroadcastReceiver, intentFilter2)
+    }
+
+    private fun releaseReceiver() {
+        if (batteryBroadcastReceiver != null) {
+            unregisterReceiver(batteryBroadcastReceiver)
+        }
+
+        if (netWorkChangeReceiver != null) {
+            unregisterReceiver(netWorkChangeReceiver)
+        }
+//        if (myMediaButtonReceiver != null ){
+//            unregisterReceiver(myMediaButtonReceiver)
+//        }
+    }
+
+    /**
+     * 返回的tcp信息
+     */
+    override fun setTcpServerHost(tcpSeverDTO: TcpSeverDTO) {
+        /*Log.d(TAG, "获取服务器IP完成")
+        if (tcpSeverDTO.publicIp != null && tcpSeverDTO.tcpPort != null && tcpSeverDTO.readerIdleTime != null) {
+            Constants.tcpServer = tcpSeverDTO.publicIp
+            Constants.tcpPort = tcpSeverDTO.tcpPort
+            Constants.heartBeat = tcpSeverDTO.readerIdleTime
+            tv_server_ip.text = tcpSeverDTO.publicIp
+
+            //成功获取到数据后再启动keepService并连接tcp服务器
+            if (DaemonEnv.app == null && !Strings.isNullOrEmpty(Constants.tcpServer) && !WdKeepAliveService.instanceCreated) {
+                Log.d(TAG, "开始 WdKeepAliveService")
+                //保活守护进程
+                DaemonEnv.init(this)
+                DaemonEnv.startServiceSafely(this, WdKeepAliveService::class.java)
+            }
+
+            presenter.getDeviceVO(Constants.imei)
+        }*/
+    }
+
+    override fun setServerInfo(serverIpInfo: ServerIpInfo) {
+        if (serverIpInfo.tcpPublicIp != null && serverIpInfo.tcpPort != null && serverIpInfo.tcpIdleSeconds != null) {
+            Constants.tcpServer = serverIpInfo.tcpPublicIp
+            Constants.tcpPort = serverIpInfo.tcpPort
+            Constants.heartBeat = serverIpInfo.tcpIdleSeconds
+            tv_server_ip.text = serverIpInfo.tcpPublicIp
+
+            JanusConstant.JANUS_URL = "ws://" + serverIpInfo.rtcPublicIp + ":" + serverIpInfo.rtcPort
+            JanusConstant.STUN_SERVER = arrayOf<String>(serverIpInfo.stunServer)
+            //JanusConstant.TURN_SERVER = data.turnServer
+
+            //成功获取到数据后再启动keepService并连接tcp服务器
+            if (DaemonEnv.app == null && !Strings.isNullOrEmpty(Constants.tcpServer) && !WdKeepAliveService.instanceCreated) {
+                Log.d(TAG, "开始 WdKeepAliveService")
+                //保活守护进程
+                DaemonEnv.init(this)
+                DaemonEnv.startServiceSafely(this, WdKeepAliveService::class.java)
+            }
+
+            presenter.getDeviceVO(Constants.imei)
+        }
+
+
+        //通过服务端设置语言
+        if (SettingConfig.getLanguageMode(activity) == 0) {
+            var needReboot = false
+            if ("cn".equals(serverIpInfo.lang)) {
+                //中文
+                if (SettingConfig.getLanguageId(activity) != 2) {
+                    needReboot = true
+                }
+                SettingConfig.setLanguageId(activity, 2)
+            } else if ("en".equals(serverIpInfo.lang)) {
+                //英文
+                if (SettingConfig.getLanguageId(activity) != 1) {
+                    needReboot = true
+                }
+                SettingConfig.setLanguageId(activity, 1)
+            } else if ("es".equals(serverIpInfo.lang)) {
+                //西班牙语
+                if (SettingConfig.getLanguageId(activity) != 3) {
+                    needReboot = true
+                }
+                SettingConfig.setLanguageId(activity, 3)
+            } else if ("ru".equals(serverIpInfo.lang)) {
+                //俄语
+                if (SettingConfig.getLanguageId(activity) != 4) {
+                    needReboot = true
+                }
+                SettingConfig.setLanguageId(activity, 4)
+            }
+
+            if (needReboot) {
+                AppTool.Time.delay(5000) {
+                    AppUtils.restartApp()
+                }
+            }
+        }
+    }
+
+    private fun requestPermissions() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            Observable.just("")
+                    .compose(
+                            RxPermissions(this)
+                                    .ensure(
+                                            Manifest.permission.CAMERA,
+                                            Manifest.permission.READ_EXTERNAL_STORAGE,
+                                            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                                            Manifest.permission.ACCESS_WIFI_STATE,
+                                            Manifest.permission.BLUETOOTH,
+                                            Manifest.permission.RECORD_AUDIO,
+                                            Manifest.permission.CALL_PHONE,
+                                            Manifest.permission.READ_PHONE_STATE,
+                                            Manifest.permission.READ_CALL_LOG,
+                                            Manifest.permission.ANSWER_PHONE_CALLS,
+                                            Manifest.permission.WRITE_CALL_LOG,
+                                            Manifest.permission.READ_CONTACTS,
+                                            Manifest.permission.WRITE_CONTACTS
+                                    )
+                    )
+                    .subscribe {
+                        if (!it) {
+                            showMessage("请重新授权,进入App")
+                            requestPermissions()
+                        } else {
+                            permissionGranted()
+                        }
+                    }.joinManager(disposableManager)
+        } else {
+            Observable.just("")
+                    .compose(
+                            RxPermissions(this)
+                                    .ensure(
+                                            Manifest.permission.CAMERA,
+                                            Manifest.permission.READ_EXTERNAL_STORAGE,
+                                            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                                            Manifest.permission.ACCESS_WIFI_STATE,
+                                            Manifest.permission.BLUETOOTH,
+                                            Manifest.permission.RECORD_AUDIO,
+                                            Manifest.permission.CALL_PHONE,
+                                            Manifest.permission.READ_PHONE_STATE,
+                                            Manifest.permission.READ_CALL_LOG,
+                                            Manifest.permission.WRITE_CALL_LOG,
+                                            Manifest.permission.READ_CONTACTS,
+                                            Manifest.permission.WRITE_CONTACTS
+                                    )
+                    )
+                    .subscribe {
+                        if (!it) {
+                            showMessage("请重新授权,进入App")
+                            requestPermissions()
+                        } else {
+                            permissionGranted()
+                        }
+                    }.joinManager(disposableManager)
+        }
+    }
+
+    override fun handleAppVersion(appInfo: AppVersionDO) {
+        /*if (CommonUtils.getAppVersionCode(activity) < appInfo.versionNo && !AppUpdateActivity.opened) {
+            showMessage(R.string.new_version_tips)
+            val intent = Intent()
+            intent.setClass(this, AppUpdateActivity::class.java)
+            startActivity(intent)
+        }*/
+    }
+
+    /**
+     * 返回的设备信息
+     */
+    override fun setDeviceDo(data: DeviceVO) {
+        if (data.id == null || data.id <= 0) {
+            loadingDialog.dismiss()
+            showMessage("本机未注册,请将识别码发给管理员")
+            tv_register_status.text = "本机未注册,请将识别码发给管理员。管理员注册本机后请点击注册完成进入"
+            if (DaemonEnv.app != null) {
+                SpManager.getInstance().putBoolean(Constants.SYSTEM_REGISTERED, false)
+            }
+            tv_register_ok.visibility = View.VISIBLE
+            watch_activity_register_layout.visibility = View.VISIBLE
+            watch_activity_home_linyout.visibility = View.GONE
+            return
+        }
+
+        Constants.partId = data.partId
+        Constants.deviceId = data.id
+        Constants.sipId = data.sipId
+        Constants.deviceType = data.deviceType
+
+        loadingDialog.dismiss()
+        watch_activity_register_layout.visibility = View.GONE
+        watch_activity_home_linyout.visibility = View.VISIBLE
+        tv_register_ok.visibility = View.GONE
+
+        tv_register_status.text = "本机已注册,进入系统中"
+        tv_register_status.setTextColor(Color.GREEN)
+
+        isUnRegister = false
+
+        Log.i(TAG, "收到返回的设备信息 ")
+        Constants.ethIp = data.ethIp
+        Constants.sipPassword = data.sipPassword
+        Constants.memberId = data.memberId
+        Constants.userName = data.memberName
+        Constants.roleName = data.roleName
+        Constants.roleId = data.roleId
+        if (!TextUtils.isEmpty(data.fullName)) {
+            Constants.partName = data.fullName
+        }
+
+        presenter.getAppVersion(Constants.partId, 7)
+        presenter.getWatchContacts(Constants.deviceId)
+
+        if (Constants.deviceId <= 0 || TextUtils.isEmpty(Constants.sipId)) {
+            showMessage("初始化数据时服务器返回数据不全,请联系管理员")
+            tv_feedback_device_info.text = "初始化数据时服务器返回数据不全,请联系管理员"
+            return
+        } else if (TextUtils.isEmpty(Constants.tcpServer)) {
+            showMessage("初始化tcp连接数据为null")
+            tv_feedback_device_info.text = "初始化tcp连接数据为null"
+            return
+        }
+
+        if (DaemonEnv.app != null) {
+            SpManager.getInstance().putBoolean(Constants.SYSTEM_REGISTERED, true)
+        }
+
+        presenter.getDeviceSettingData(Constants.partId)
+
+        //onTcpConnectSuccess()
+
+        if (Strings.isNullOrEmpty(Constants.userName)) {
+            watch_name_tv.setText(R.string.str_reload)
+            watch_name_tv.setTextColor(Color.BLACK)
+            watch_name_tv.textSize = 13f
+            watch_name_tv.setBackgroundResource(R.drawable.javashop_btn_balck_line_bg)
+            watch_name_tv.setOnClickListener {
+                loadingDialog.show()
+                presenter.getDeviceVO(Constants.imei)
+                return@setOnClickListener
+            }
+
+            watch_role_name_tv.setText(R.string.device_user_empty)
+            watch_role_name_tv.textSize = 12f
+        } else {
+            watch_name_tv.text = Constants.userName
+            if (Constants.userName != null) {
+                watch_role_name_tv.text = Constants.roleName
+                watch_user_phone_tv.text = data.phoneNumber
+            }
+        }
+
+        //获取频道列表
+        /*if (data.memberId != null) {
+            presenter.getDeviceVoiceChannel(data.memberId)
+        }*/
+
+        ll_clerk_list.setOnClickListener {
+            var intent = Intent(this, TakeoverActivity().javaClass)
+            startActivity(intent)
+        }
+
+        //初始化语音留言文件保存路径
+        RecordHelper.getInstance().init()
+
+        //加载电话号码白名单
+        presenter.getPhoneNumberWhiteList(Constants.deviceId)
+
+        showMessage("载入完成")
+    }
+
+    override fun setDeviceVoiceChannel(channelList: ArrayList<ChannelDO>) {
+        //取第一个频道
+        if (channelList != null && channelList.size > 0) {
+            Constants.channelId = channelList.get(0).id
+        }
+    }
+
+    override fun setPhoneNumberWhiteList(phoneList: ArrayList<String>) {
+        //保存手机号码白名单
+        Constants.phoneWhiteList.clear()
+        Constants.phoneWhiteList.addAll(phoneList)
+    }
+
+    //写手机通讯录
+    override fun setContact(contactVOs: List<WatchContactVO>) {
+        if (contactVOs != null && contactVOs.size > 0) {
+            for (contactVO in contactVOs) {
+                if (!Strings.isNullOrEmpty(contactVO.name) && !Strings.isNullOrEmpty(contactVO.phoneNumber)) {
+                    ContactHelper.setContact(this, contactVO.name, contactVO.phoneNumber)
+                }
+            }
+        }
+    }
+
+    /**
+     * 设置设备数据
+     */
+    override fun setDeviceSettingData(partSettingDO: PartSettingDO) {
+        Log.i(TAG, "收到设置设备数据 transferDuration" + partSettingDO.transferDuration)
+        Log.i(TAG, "收到设置设备数据 transferDurationLeader" + partSettingDO.transferDurationLeader)
+
+        if (partSettingDO != null && partSettingDO.transferDuration != null && partSettingDO.transferDurationLeader != null) {
+            if (Constants.roleId != null) {
+                if (Constants.roleId == RoleTypeEnum.NURSE_HEAD.value()) {
+                    //护士组长
+                    SettingConfig.setCountdownTime(application, partSettingDO.transferDurationLeader)
+                } else if (Constants.roleId == RoleTypeEnum.NURSE.value()) {
+                    //护士
+                    SettingConfig.setCountdownTime(application, partSettingDO.transferDuration)
+                }
+            }
+        }
+
+        //设置通话方式
+        if (partSettingDO.communicationModeMobile != null) {
+            Constants.phoneType = partSettingDO.communicationModeMobile
+        }
+        if (partSettingDO.communicationModeBed != null) {
+            Constants.bedPhoneType = partSettingDO.communicationModeBed
+        }
+        if (partSettingDO.communicationModeNurse != null) {
+            Constants.nursePhoneType = partSettingDO.communicationModeNurse
+        }
+    }
+
+    fun onTcpConnectSuccess() {
+        Log.i(TAG, "连接成功")
+
+        runOnUiThread(Runnable {
+            Log.i(TAG, "连接成功设置UI")
+            tv_net_reconnect_text.visibility = View.GONE
+            tv_signal_strength.text = "网络正常"
+            netOffLoadingDialog.dismiss()
+            sip_state_tv.setBackgroundColor(Color.parseColor("#00FFFF"))
+        })
+    }
+
+    fun onTcpConnectFailed() {
+        Log.w(TAG, "网络断开")
+
+        runOnUiThread(Runnable {
+            tv_signal_strength.text = "网络断开"
+            tv_net_reconnect_text.visibility = View.VISIBLE
+            netOffLoadingDialog.show()
+            sip_state_tv.setBackgroundColor(Color.parseColor("#FF0000"))
+        })
+
+        //等30秒
+        var waitSeconds = 0;
+        Thread {
+            while (!TcpClientHandler.getConnected()) {
+                Log.w(TAG, "无网络" + waitSeconds)
+                waitSeconds++
+                if (waitSeconds > 30) {
+                    break
+                }
+                Thread.sleep(5000)
+            }
+        }.start()
+    }
+
+    override fun bindEvent() {
+        customer_list_linlyout.setOnClickListener(this)
+        call_records_linlyout.setOnClickListener(this)
+        user_name_linlyout.setOnClickListener(this)
+        state_linlyout.setOnClickListener(this)
+        other_linyout.setOnClickListener(this)
+        channel_im_layout.setOnClickListener(this)
+        tv_register_ok.setOnClickListener {
+            Log.d(TAG, "注册完成")
+            initTcp()
+
+            //presenter.getDeviceVO(Constants.imei)
+            countDownTimer.start()
+        }
+    }
+
+    override fun enableHeadsetVoiceMsg(): Boolean {
+        return false
+    }
+
+    fun initTcp() {
+        if (!Strings.isNullOrEmpty(Constants.tcpServer)) {    //有网且得到了服务器IP
+            presenter.getTcpServerHost()
+            return
+        }
+
+        var count = 30
+        Thread(Runnable {
+            while (isUnRegister && count > 0 && Strings.isNullOrEmpty(Constants.tcpServer)) {
+                //DaemonEnv.sendStopWorkBroadcast(this)
+                Log.i(TAG, "获取TCP服务器IP和端口")
+                presenter.getTcpServerHost()
+
+                try {
+                    Thread.sleep(5000)
+                } catch (e: Exception) {
+                }
+                count--
+            }
+
+            if (NetHelper.getInstance().getNetworkState(this) == NetHelper.NETWORK_NONE) {
+                runOnUiThread(Runnable {
+                    showMessage("未能正确连接,请重试")
+                })
+            }
+        }).start()
+    }
+
+    fun initCountDownTimer() {
+        countDownTimer = object : CountDownTimer(1 * 1000L, 1000) {
+            override fun onTick(millisUntilFinished: Long) {
+            }
+
+            override fun onFinish() {
+                tv_register_ok.isEnabled = true
+                //loadingDialog.dismiss()
+            }
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        //主界面恢复时检查tcp连接状态
+        Log.d(TAG, "Watch home activity resumed...")
+        if (TcpClientHandler.getConnected()) {
+            onTcpConnectSuccess()
+        } else {
+            onTcpConnectFailed()
+        }
+
+        Constants.allowVoiceMsg = true
+
+        //返回主界面时重置呼叫状态
+        /*if (!Constants.showFloatWindow) {
+            DeviceChannel.calling = false
+        }*/
+    }
+
+    override fun onError(message: String, type: Int) {
+    }
+
+    override fun complete(message: String, type: Int) {
+    }
+
+    override fun start() {
+    }
+
+    override fun networkMonitor(state: NetState) {
+    }
+
+    override fun destory() {
+        releaseReceiver()
+        //停止服务
+        //DaemonEnv.sendStopWorkBroadcast(this)
+
+        if (countDownTimer != null) {
+            countDownTimer.cancel()
+        }
+    }
+
+    /**
+     * 监听
+     */
+    override fun onClick(p0: View) {
+        when (p0.id) {
+            R.id.customer_list_linlyout -> {
+                push("/watch/contacts")
+            }
+            R.id.call_records_linlyout -> {
+                var intent = Intent(this, WatchCallRecordsActivity::class.java)
+                intent.putExtra("action", "ALL")
+                intent.putExtra("customer_id", "")
+                startActivity(intent)
+            }
+            R.id.user_name_linlyout -> {
+                if (Constants.deviceType != 1) {
+                    if (Constants.userName == null) {
+                        var intent = Intent(this, TakeoverActivity().javaClass)
+                        startActivity(intent)
+                    } else {
+                        var intent = Intent(this, WatchCallRecordsActivity::class.java)
+                        intent.putExtra("action", "UNREAD")
+                        intent.putExtra("customer_id", "")
+                        startActivity(intent)
+                    }
+                }
+            }
+            R.id.channel_im_layout -> {
+                //群留言
+                if (Constants.channelId == -1) {
+                    showMessage("请先建立频道")
+                } else if (TextUtils.isEmpty(Constants.userName)) {
+                    showMessage("设备未绑定")
+                } else {
+                    var intent = Intent(this, ChannelImActivity::class.java)
+                    startActivity(intent)
+                }
+            }
+            R.id.state_linlyout -> {
+//                Log.d(TAG,"点击了状态,测试崩溃")
+//                netOffLoadingDialog.show()
+//                Thread.sleep(10000)
+//                netOffLoadingDialog.dismiss()
+            }
+            R.id.other_linyout -> {
+                if (System.currentTimeMillis() - clickTime > 2000) {
+                    showMessage("再点一次进入设置")
+                } else {
+                    val intent = Intent(this, WatchUserSettingActivity::class.java)
+                    startActivity(intent)
+                }
+                clickTime = System.currentTimeMillis()
+            }
+        }
+    }
+
+
+    override fun onBackPressed() {
+        //禁用返回
+        //super.onBackPressed()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        if (loadingDialog != null) {
+            loadingDialog.dismiss()
+        }
+        if (netOffLoadingDialog != null) {
+            netOffLoadingDialog.dismiss()
+        }
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        if (messageEvent.tag == Constants.EVENT_UNTREATED_QUANTITY) {   //未读
+            var message = messageEvent.getMessage() as ArrayList<InteractionVO>
+            list_item_img_icon_num.text = "" + message.size
+        } else if (messageEvent.tag == Constants.EVENT_BATTERY_PERCENT) {
+            var message = messageEvent.getMessage() as Int
+            if (10 < message && message < 20) {
+                electric_quantity_tv.text = "" + message
+                battery_warning_tv.setTextColor(Color.parseColor("#FF3030"))
+                battery_warning_tv.visibility = View.VISIBLE
+                battery_warning_tv.text = "低电量,请注意充电"
+            } else if (10 > message) {
+                electric_quantity_tv.text = "" + message
+                battery_warning_tv.setTextColor(Color.parseColor("#8B2323"))
+                battery_warning_tv.visibility = View.VISIBLE
+                battery_warning_tv.text = "电量过低,请充电"
+            } else {
+                battery_warning_tv.visibility = View.GONE
+                electric_quantity_tv.text = "" + message
+            }
+        } else if (messageEvent.tag == Constants.EVENT_RTC_STATE) {
+            runOnUiThread(Runnable {
+                sip_state_tv.setBackgroundColor(Color.parseColor("#00FFFF"))
+                netOffLoadingDialog.dismiss()
+                //tv_signal_strength.text = "网络恢复"
+                tv_net_reconnect_text.visibility = View.GONE
+            })
+        } else if (messageEvent.tag == Constants.EVENT_TCP_BREAK) {
+            onTcpConnectFailed()
+        } else if (messageEvent.tag == Constants.EVENT_TCP_CONNECTED) {
+            onTcpConnectSuccess()
+        }
+    }
+}

+ 156 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/activity/WatchUserSettingActivity.java

@@ -0,0 +1,156 @@
+package com.wdkl.ncs.android.component.home.activity;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import com.alibaba.fastjson.JSONObject;
+import com.wdkl.ncs.android.component.home.BuildConfig;
+import com.wdkl.ncs.android.component.home.R;
+import com.wdkl.ncs.android.component.home.service.WdKeepAliveService;
+import com.wdkl.ncs.android.component.home.settingconfig.SettingConfig;
+import com.wdkl.ncs.android.component.home.util.AppUtils;
+import com.wdkl.ncs.android.component.home.util.NetHelper;
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.lib.base.BaseApplication;
+import com.wdkl.ncs.android.lib.utils.ExtendMethodsKt;
+import com.wdkl.ncs.android.middleware.tcp.TcpClient;
+import com.wdkl.ncs.android.middleware.tcp.channel.DeviceUtil;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel;
+import com.wdkl.ncs.android.middleware.utils.CommonUtils;
+
+public class WatchUserSettingActivity extends Activity {
+    private static final String TAG = WatchUserSettingActivity.class.getSimpleName();
+
+    private TextView tvAppVersion,tvDeviceId,tvDeviceImei,tvDeviceIp,tvServerIp,tvTtsStatus,tvPhoneType;
+
+    private Button btnChange,btnAppRestart,btnCheckUpdate,btnSystemSetting;
+
+    private RadioGroup callGroup;
+    private RadioButton callYes, callNo;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.user_setting_layout);
+
+        tvAppVersion = findViewById(R.id.tv_app_version);
+        tvDeviceId = findViewById(R.id.tv_device_id);
+        tvDeviceImei = findViewById(R.id.tv_device_imei);
+        tvDeviceIp = findViewById(R.id.tv_device_ip);
+        tvServerIp = findViewById(R.id.tv_server_ip);
+        tvTtsStatus = findViewById(R.id.tv_tts_status);
+        tvPhoneType = findViewById(R.id.tv_phone_type);
+
+        btnChange = findViewById(R.id.btn_user_change);
+        btnAppRestart = findViewById(R.id.btn_app_restart);
+        btnCheckUpdate = findViewById(R.id.btn_check_update);
+        btnSystemSetting = findViewById(R.id.btn_system_setting);
+
+        callGroup = findViewById(R.id.group_call);
+        callYes = findViewById(R.id.rb_call_yes);
+        callNo = findViewById(R.id.rb_call_no);
+
+        tvAppVersion.setText(CommonUtils.getAppVersionName(BaseApplication.appContext) + "_" + BuildConfig.BUILD_TIME);
+        tvDeviceId.setText("" + Constants.Companion.getDeviceId());
+        tvDeviceImei.setText(Constants.Companion.getImei());
+        tvDeviceIp.setText(NetHelper.getInstance().getLocalIP());
+        tvServerIp.setText(Constants.Companion.getTcpServer());
+        tvPhoneType.setText("手机-" + Constants.Companion.getPhoneType() + ", 分机-" + Constants.Companion.getBedPhoneType() + ", 主机-" + Constants.Companion.getNursePhoneType());
+
+        if (SettingConfig.getTransCall(BaseApplication.appContext) == 1) {
+            callYes.setChecked(true);
+        } else {
+            callNo.setChecked(true);
+        }
+
+
+        btnChange.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                TcpModel userChangeModel = DeviceUtil.userChange(Constants.Companion.getDeviceId());
+                TcpCallback transaction = new TcpCallback(userChangeModel.getTid()) {
+                    @Override
+                    public void onSuccess(JSONObject jsonObject) {
+                        runOnUiThread(() -> ExtendMethodsKt.showMessage(R.string.user_change_success));
+                        WatchUserSettingActivity.this.finish();
+                    }
+
+                    @Override
+                    public void onFailed(JSONObject jsonObject) {
+                        runOnUiThread(() -> ExtendMethodsKt.showMessage(R.string.user_change_failed));
+                    }
+                };
+                TcpClient.getInstance().sendTcp(userChangeModel, false, transaction);
+
+                /*NettyClient.Companion.getInstance().sendMsg(userChangeModel.toJson()).subscribe(it->{
+                    if (it) {
+                        Log.d(TAG, "TCP.发送消息完成");
+                        ExtendMethodsKt.showMessage("换班通知发送成功,请等待处理...");
+                        WatchUserSettingActivity.this.finish();
+                    } else {
+                        Log.e(TAG, "TCP.发送消息失败");
+                        ExtendMethodsKt.showMessage("换班通知发送失败,请重试");
+                    }
+                });*/
+            }
+        });
+
+        final Context _this = this;
+        btnAppRestart.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                AppUtils.restartApp();
+            }
+        });
+        btnCheckUpdate.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if ((System.currentTimeMillis() / 1000) - WdKeepAliveService.instance.getUpdateLastTime() > 10) {
+                    WdKeepAliveService.instance.setUpdateLastTime(System.currentTimeMillis() / 1000);
+                    Intent intent = new Intent();
+                    intent.setClass(_this, AppUpdateActivity.class);
+                    startActivity(intent);
+                } else {
+                    Toast.makeText(_this, "请10秒后再尝试", Toast.LENGTH_SHORT).show();
+                }
+            }
+        });
+        btnSystemSetting.setOnClickListener(view -> {
+            Intent intent = new Intent(Settings.ACTION_SETTINGS);
+            startActivity(intent);
+        });
+
+        callGroup.setOnCheckedChangeListener((group, checkedId) -> {
+            if (checkedId == R.id.rb_call_yes) {
+                SettingConfig.setTransCall(BaseApplication.appContext, 1);
+            } else {
+                SettingConfig.setTransCall(BaseApplication.appContext, 0);
+            }
+        });
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        Log.d(TAG, "tts state: " + Constants.Companion.getTtsState());
+        if (Constants.Companion.getTtsState() == 2) {
+            tvTtsStatus.setText("正常");
+        } else {
+            tvTtsStatus.setText("异常");
+        }
+    }
+}

+ 126 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/ChannelImItemAdapter.kt

@@ -0,0 +1,126 @@
+package com.wdkl.ncs.android.component.home.adapter
+
+import android.app.Activity
+import android.view.View
+import android.view.ViewGroup
+import com.alibaba.android.vlayout.LayoutHelper
+import com.alibaba.android.vlayout.layout.LinearLayoutHelper
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.databinding.AdapterChannelImRecordsItemBinding
+import com.wdkl.ncs.android.component.home.util.ImPlayDialogHelper
+import com.wdkl.ncs.android.component.home.util.MediaPlayHelper
+import com.wdkl.ncs.android.component.home.util.TimeTransition
+import com.wdkl.ncs.android.lib.adapter.BaseDelegateAdapter
+import com.wdkl.ncs.android.lib.utils.BaseRecyclerViewHolder
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import com.wdkl.ncs.android.middleware.api.ApiManager
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.middleware.model.dos.ChannelImDO
+import com.wdkl.ncs.android.middleware.model.vo.ChannelIMVO
+import com.wdkl.ncs.android.middleware.tcp.TcpClient
+import com.wdkl.ncs.android.middleware.tcp.channel.ChannelImUtil
+import org.greenrobot.eventbus.EventBus
+
+
+/**
+ * 呼叫记录适配器
+ */
+class ChannelImItemAdapter(val data: ArrayList<ChannelIMVO>, val imActivity: Activity) : BaseDelegateAdapter<BaseRecyclerViewHolder<AdapterChannelImRecordsItemBinding>, ChannelIMVO>() {
+    var TAG = ChannelImItemAdapter::class.java.getSimpleName()
+    var imCallBack: CallBack? = null
+
+    /**
+     * 数据提供者
+     */
+    override fun dataProvider(): Any {
+        return data
+    }
+
+    /**
+     * Item坐标
+     */
+    override fun itemFilter(position: Int): Boolean {
+        return true
+    }
+
+    /**
+     * 获取Item总数
+     */
+    override fun getItemCount(): Int {
+        return data.size
+    }
+
+    /**
+     * 创建LayoutHelper
+     */
+    override fun onCreateLayoutHelper(): LayoutHelper {
+        return LinearLayoutHelper(0, data.size)
+    }
+
+    /**
+     * 创建ViewHolder
+     */
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseRecyclerViewHolder<AdapterChannelImRecordsItemBinding> {
+        return BaseRecyclerViewHolder.build(parent, R.layout.adapter_channel_im_records_item)
+    }
+
+    /**
+     * 绑定数据
+     */
+    override fun onBindViewHolder(holder: BaseRecyclerViewHolder<AdapterChannelImRecordsItemBinding>, position: Int) {
+        holder.bind { binding ->
+            val itemData = getItem(position)
+
+            binding.imCallTimeTv.text = TimeTransition().stampToTime(itemData.sendTime*1000)
+            binding.imSickbedTv.text = itemData.senderMemberName
+            if (itemData.is_self) {
+                binding.imTreatmentStateImagev.visibility = View.GONE
+            } else {
+                binding.imTreatmentStateImagev.visibility = View.VISIBLE
+            }
+
+            if (itemData.readed) {
+                binding.imTreatmentStateImagev.setImageResource(R.drawable.checked_100)
+            } else {
+                binding.imTreatmentStateImagev.setImageResource(R.drawable.unchecked_100)
+            }
+
+
+            binding.imPlayTv.setOnClickListener {
+                showMessage("播放:" + itemData.audioPath)
+                EventBus.getDefault().post(MessageEvent(false, Constants.EVENT_CLEAR_IM))
+                MediaPlayHelper.getInstance().stopMusic(false)
+                MediaPlayHelper.getInstance().playUrlMusic(ApiManager.urlManager.device_url + itemData.audioPath, 1f, false)
+                ImPlayDialogHelper.showImPlayDialog(imActivity, itemData.senderMemberName, TimeTransition().stampToTime(itemData.sendTime*1000))
+
+                //如果是未读留言则回复已读tcp
+                if (!itemData.readed) {
+                    val imTcpModel = ChannelImUtil.channelImRead(Constants.deviceId, itemData as ChannelImDO)
+                    TcpClient.getInstance().sendTcp(imTcpModel, false, null)
+
+                    /*NettyClient.instance.sendMsg(imTcpModel.toJson()).subscribe {
+                        if (it) {
+                            Log.d(TAG, "TCP.发送消息完成")
+                            //EventBus.getDefault().post(MessageEvent("im_update", Constants.EVENT_CHANNEL_IM_UPDATE))
+                            if (imCallBack != null) {
+                                imCallBack!!.onChannelImRead(itemData, position)
+                            }
+                        } else {
+                            Log.e(TAG, "TCP.发送消息失败")
+                            HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+                        }
+                    }*/
+                }
+            }
+        }
+    }
+
+    fun setCallBack(callback: CallBack) {
+        imCallBack = callback
+    }
+
+    interface CallBack {
+        fun onChannelImRead(item: ChannelIMVO, pos: Int)
+    }
+}

+ 73 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/NewCallItemAdapter.kt

@@ -0,0 +1,73 @@
+package com.wdkl.ncs.android.component.home.adapter
+
+import android.app.Activity
+import android.view.ViewGroup
+import com.alibaba.android.vlayout.LayoutHelper
+import com.alibaba.android.vlayout.layout.LinearLayoutHelper
+import com.google.gson.Gson
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.databinding.CallListItemBinding
+import com.wdkl.ncs.android.lib.adapter.BaseDelegateAdapter
+import com.wdkl.ncs.android.lib.utils.BaseRecyclerViewHolder
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel
+
+class NewCallItemAdapter(var data:ArrayList<TcpModel>, val activity: Activity) : BaseDelegateAdapter<BaseRecyclerViewHolder<CallListItemBinding>, TcpModel>() {
+
+    private var callListener: CallClickListener? = null
+
+    override fun dataProvider(): Any {
+        return data
+    }
+
+    override fun itemFilter(position: Int): Boolean {
+        return true
+    }
+
+    override fun getItemCount(): Int {
+        return data.size
+    }
+
+    override fun onCreateViewHolder(p0: ViewGroup, p1: Int): BaseRecyclerViewHolder<CallListItemBinding> {
+        return BaseRecyclerViewHolder.build(p0, R.layout.call_list_item)
+    }
+
+    override fun onCreateLayoutHelper(): LayoutHelper {
+        return LinearLayoutHelper(10, data.size)
+    }
+
+    override fun onBindViewHolder(p0: BaseRecyclerViewHolder<CallListItemBinding>, p1: Int) {
+        p0.bind { binding ->
+            val itemData = getItem(p1)
+            if (itemData.data != null) {
+                val interactionVO = Gson().fromJson(itemData.data.toString(), InteractionVO::class.java)
+
+                binding.tvCallName.text = interactionVO.fromFrameFullName + " 呼叫"
+                binding.btnCallAccept.setOnClickListener {
+                    //来电接听
+                    if (callListener != null) {
+                        callListener!!.callAccept(itemData)
+                    }
+                }
+
+                binding.btnCallReject.setOnClickListener {
+                    //来电拒绝
+                    if (callListener != null) {
+                        callListener!!.callReject(itemData)
+                    }
+                }
+            }
+        }
+
+    }
+
+    fun setCallClick(listener: CallClickListener) {
+        callListener = listener
+    }
+
+
+    interface CallClickListener{
+        fun callAccept(model: TcpModel)
+        fun callReject(model: TcpModel)
+    }
+}

+ 310 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/NewEventItemAdapter.kt

@@ -0,0 +1,310 @@
+package com.wdkl.ncs.android.component.home.adapter
+
+import android.app.Activity
+import android.content.Intent
+import android.graphics.Color
+import android.view.View
+import android.view.ViewGroup
+import com.alibaba.android.vlayout.LayoutHelper
+import com.alibaba.android.vlayout.layout.LinearLayoutHelper
+import com.alibaba.fastjson.JSONObject
+import com.enation.javashop.utils.base.tool.CommonTool
+import com.enation.javashop.utils.base.widget.LoadingDialog
+import com.google.gson.Gson
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.databinding.EventListItemBinding
+import com.wdkl.ncs.android.component.home.ui.CallSingleActivity
+import com.wdkl.ncs.android.component.home.util.ImPlayDialogHelper
+import com.wdkl.ncs.android.component.home.util.MediaPlayHelper
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.component.home.util.TimeTransition
+import com.wdkl.ncs.android.lib.adapter.BaseDelegateAdapter
+import com.wdkl.ncs.android.lib.utils.BaseRecyclerViewHolder
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.middleware.api.ApiManager
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpType
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import com.wdkl.ncs.android.middleware.tcp.TcpClient
+import com.wdkl.ncs.android.middleware.tcp.channel.*
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback
+import org.greenrobot.eventbus.EventBus
+
+
+class NewEventItemAdapter(var data:ArrayList<InteractionVO>, val activity: Activity) : BaseDelegateAdapter<BaseRecyclerViewHolder<EventListItemBinding>, InteractionVO>() {
+    val TAG = NewEventItemAdapter::class.simpleName
+
+    private lateinit var loadingDialog: LoadingDialog
+
+    override fun dataProvider(): Any {
+        return data
+    }
+
+    override fun itemFilter(position: Int): Boolean {
+        return true
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseRecyclerViewHolder<EventListItemBinding> {
+        return BaseRecyclerViewHolder.build(parent, R.layout.event_list_item)
+    }
+
+    override fun getItemCount(): Int {
+        return data.size
+    }
+
+    override fun onCreateLayoutHelper(): LayoutHelper {
+        loadingDialog = CommonTool.createLoadingDialog(activity, R.layout.custom_loading, R.id.loadding_image)
+        return LinearLayoutHelper(0,data.size)
+    }
+
+    override fun onBindViewHolder(holder: BaseRecyclerViewHolder<EventListItemBinding>, position: Int) {
+        holder.bind { binding ->
+            val itemData = getItem(position)
+
+            var toDeviceId:Int?
+            binding.eliEventTime.text = TimeTransition().stampToDate(itemData.createDate*1000)
+            binding.eliRoomName.text = itemData.fromFrameFullName
+            var showName: String? = ""
+            if (itemData.fromDeviceMemberId == Constants.memberId){
+                showName = itemData.toMemberName
+                toDeviceId = itemData.toDeviceId
+            } else {
+                showName = itemData.fromMemberName
+                toDeviceId = itemData.fromDeviceId
+            }
+            binding.eliMemberName.text = showName
+
+            /*binding.eliVoiceRecorder.visibility = View.VISIBLE
+            binding.eliVoiceRecorder.setOnClickListener{
+                var intent = Intent(activity,VoiceMsgActivity::class.java)
+                intent.putExtra(VoiceMsgActivity.TO_DEVICE_ID,toDeviceId)
+                activity.startActivity(intent)
+            }*/
+
+            when (itemData.actionType){
+                TcpType.EVENT.name->{
+                    binding.eliImRead.visibility = View.GONE
+                    binding.eliEventResponse.visibility = View.VISIBLE
+                    binding.eliEventName.text = itemData.data
+                    if (itemData.actionEnd!=null){
+                        binding.eliEventResponse.visibility = View.GONE
+                        binding.eliEventName.setTextColor(Color.GREEN)
+                    } else {
+                        binding.eliEventName.setTextColor(activity.resources.getColor(R.color.warn_orange))
+                        binding.eliEventResponse.setOnClickListener {
+                            loadingDialog.show()
+                            val tcpModel = EventUtil.eventResponse(Constants.deviceId, itemData.fromDeviceId, itemData.id)
+                            val transaction: TcpCallback = object : TcpCallback(tcpModel.tid) {
+                                override fun onSuccess(jsonObject: JSONObject) {
+                                    //操作成功,更新事件列表界面
+                                    updateEventItem(itemData)
+
+                                    if (activity != null) {
+                                        activity.runOnUiThread {
+                                            showMessage("Success")
+                                            loadingDialog.dismiss()
+                                        }
+                                    }
+                                }
+
+                                override fun onFailed(jsonObject: JSONObject) {
+                                    // 这里写发送失败的方法
+                                    val callbackString = jsonObject.getString(CALLBACK)
+                                    if (activity != null) {
+                                        activity.runOnUiThread {
+                                            showMessage("Failed: $callbackString")
+                                            loadingDialog.dismiss()
+                                        }
+                                    }
+                                }
+                            }
+                            TcpClient.getInstance().sendTcp(tcpModel, false, transaction)
+
+                            /*NettyClient.instance.sendMsg(tcpModel.toJson()).subscribe {
+                                if (it) {
+                                    Log.d(TAG, "TCP.发送消息完成")
+                                } else {
+                                    Log.e(TAG, "TCP.发送消息失败")
+                                    HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+                                }
+                                loadingDialog.dismiss()
+                            }*/
+                        }
+                    }
+                }
+                TcpType.IM.name->{
+                    binding.eliEventName.text = "语音留言"
+                    binding.eliEventResponse.visibility = View.GONE
+                    if (itemData.actionEnd!=null){
+                        binding.eliEventResponse.visibility = View.GONE
+                        binding.eliImRead.visibility = View.VISIBLE
+                        binding.eliEventName.setTextColor(Color.GREEN)
+
+                        binding.eliImRead.setOnClickListener {
+                            showMessage("播放:" + itemData.data)
+                            MediaPlayHelper.getInstance().stopMusic(false)
+                            MediaPlayHelper.getInstance().playUrlMusic(ApiManager.urlManager.device_url + itemData.data, 1f, false)
+                            ImPlayDialogHelper.showImPlayDialog(activity, itemData.fromFrameFullName, TimeTransition().stampToTime(itemData.createDate*1000))
+                        }
+                    } else {
+                        binding.eliEventName.setTextColor(activity.resources.getColor(R.color.warn_orange))
+                        binding.eliImRead.visibility = View.VISIBLE
+
+                        binding.eliImRead.setOnClickListener {
+                            showMessage("播放:" + itemData.data)
+                            MediaPlayHelper.getInstance().stopMusic(false)
+                            MediaPlayHelper.getInstance().playUrlMusic(ApiManager.urlManager.device_url + itemData.data, 1f, false)
+                            ImPlayDialogHelper.showImPlayDialog(activity, itemData.fromFrameFullName, TimeTransition().stampToTime(itemData.createDate*1000))
+                            //如果是自己发的留言则不发送已读tcp
+                            if (Constants.deviceId != itemData.fromDeviceId) {
+                                val tcpModel = ImUtil.imRead(Constants.deviceId, itemData.fromDeviceId, itemData.id)
+                                val transaction: TcpCallback = object : TcpCallback(tcpModel.tid) {
+                                    override fun onSuccess(jsonObject: JSONObject) {
+                                        //操作成功,更新事件列表界面
+                                        updateEventItem(itemData)
+
+                                        if (activity != null) {
+                                            activity.runOnUiThread {
+                                                showMessage("Success")
+                                            }
+                                        }
+                                    }
+
+                                    override fun onFailed(jsonObject: JSONObject) {
+                                        // 这里写发送失败的方法
+                                        val callbackString = jsonObject.getString(CALLBACK)
+                                        if (activity != null) {
+                                            activity.runOnUiThread {
+                                                showMessage("Failed: $callbackString")
+                                            }
+                                        }
+                                    }
+                                }
+                                TcpClient.getInstance().sendTcp(tcpModel, false, transaction)
+
+                                /*NettyClient.instance.sendMsg(tcpModel.toJson()).subscribe {
+                                    if (it) {
+                                        Log.d(TAG, "TCP.发送消息完成")
+                                    } else {
+                                        Log.e(TAG, "TCP.发送消息失败")
+                                        HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+                                    }
+                                }*/
+                            }
+                        }
+                    }
+                }
+
+                TcpType.SOS.name->{
+                    binding.eliRoomName.text = itemData.fromFrameFullName.substringBefore("-")
+                    binding.eliMemberName.text = ""
+                    binding.eliEventName.setTextColor(activity.resources.getColor(R.color.warn_orange))
+                    binding.eliImRead.visibility = View.GONE
+                    binding.eliEventResponse.visibility = View.VISIBLE
+                    binding.eliEventName.text = "SOS"
+//                    binding.eliEventName.setBackgroundResource(R.drawable.sos_96)
+                    if (itemData.actionEnd!=null){
+                        binding.eliEventResponse.visibility = View.GONE
+                        binding.eliEventName.setTextColor(Color.GREEN)
+                    } else {
+                        binding.eliEventResponse.setOnClickListener {
+                            loadingDialog.show()
+                            val tcpModel = OtherUtil.SOSCancel(Constants.deviceId, itemData.fromDeviceId, itemData.id)
+                            val transaction: TcpCallback = object : TcpCallback(tcpModel.tid) {
+                                override fun onSuccess(jsonObject: JSONObject) {
+                                    //操作成功,更新事件列表界面
+                                    updateEventItem(itemData)
+
+                                    if (activity != null) {
+                                        activity.runOnUiThread {
+                                            tcpModel.data = Gson().toJson(itemData)
+                                            EventBus.getDefault().post(MessageEvent(tcpModel, 999))
+
+                                            showMessage("Success")
+                                            loadingDialog.dismiss()
+                                        }
+                                    }
+                                }
+
+                                override fun onFailed(jsonObject: JSONObject) {
+                                    // 这里写发送失败的方法
+                                    val callbackString = jsonObject.getString(CALLBACK)
+                                    if (activity != null) {
+                                        activity.runOnUiThread {
+                                            showMessage("Failed: $callbackString")
+                                            loadingDialog.dismiss()
+                                        }
+                                    }
+                                }
+                            }
+                            TcpClient.getInstance().sendTcp(tcpModel, false, transaction)
+
+                            /*NettyClient.instance.sendMsg(tcpModel.toJson()).subscribe {
+                                if (it) {
+                                    Log.d(TAG, "TCP.发送消息完成")
+                                    tcpModel.data = Gson().toJson(itemData)
+                                    EventBus.getDefault().post(MessageEvent(tcpModel, 999))
+                                } else {
+                                    Log.e(TAG, "TCP.发送消息失败")
+
+                                    HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+                                }
+                                loadingDialog.dismiss()
+                            }*/
+                        }
+                    }
+                }
+                TcpType.VOICE.name->{
+                    binding.eliEventName.text = "通话"
+                    binding.eliEventResponse.visibility = View.GONE
+                }
+                TcpType.PHONE.name->{
+                    binding.eliEventName.text = "通话"
+                    binding.eliEventResponse.visibility = View.GONE
+                }
+            }
+
+            binding.eliCallout.setOnClickListener {
+                if (itemData.fromDeviceMemberId == Constants.memberId){
+                    DeviceChannel.calling = true
+
+                    val tcpModel = VoiceUtil.voiceCall(Constants.deviceId, itemData.toDeviceId)
+                    val intent = Intent(activity, CallSingleActivity::class.java)
+                    intent.putExtra(CallSingleActivity.EXTRA_ROOM_ID, Constants.sipId)
+                    intent.putExtra(CallSingleActivity.EXTRA_MO, true)
+                    intent.putExtra(CallSingleActivity.EXTRA_AUDIO_ONLY, true)
+                    intent.putExtra(CallSingleActivity.EXTRA_TCPMODEL, tcpModel);
+                    intent.putExtra(CallSingleActivity.EXTRA_SHOWNAME, itemData.fromFrameFullName + " "+showName)
+                    //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    activity.startActivity(intent)
+
+                } else {
+                    DeviceChannel.calling = true
+                    val tcpModel = VoiceUtil.voiceCall(Constants.deviceId, itemData.fromDeviceId)
+
+                    val intent = Intent(activity, CallSingleActivity::class.java)
+                    intent.putExtra(CallSingleActivity.EXTRA_ROOM_ID, Constants.sipId)
+                    intent.putExtra(CallSingleActivity.EXTRA_MO, true)
+                    intent.putExtra(CallSingleActivity.EXTRA_AUDIO_ONLY, true)
+                    intent.putExtra(CallSingleActivity.EXTRA_TCPMODEL, tcpModel);
+                    intent.putExtra(CallSingleActivity.EXTRA_SHOWNAME, itemData.fromFrameFullName + " "+showName)
+                    //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    activity.startActivity(intent)
+
+                }
+            }
+        }
+    }
+
+    private fun updateEventItem(responseInteractionVO: InteractionVO) {
+        val iterator = Constants.eventList.iterator()
+        while (iterator.hasNext()) {
+            val it = iterator.next()
+            if (it.id == responseInteractionVO.id) {
+                iterator.remove()
+            }
+        }
+
+        EventBus.getDefault().post(MessageEvent("update_event", Constants.EVENT_UPDATE_EVENT))
+    }
+}

+ 179 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/TakeoverItemAdapter.kt

@@ -0,0 +1,179 @@
+package com.wdkl.ncs.android.component.home.adapter
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import com.alibaba.android.vlayout.LayoutHelper
+import com.alibaba.android.vlayout.layout.LinearLayoutHelper
+import com.alibaba.fastjson.JSONObject
+import com.enation.javashop.utils.base.tool.CommonTool
+import com.enation.javashop.utils.base.widget.LoadingDialog
+import com.google.common.base.Strings
+import com.google.gson.JsonObject
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.databinding.TakeoverItemBinding
+import com.wdkl.ncs.android.component.home.ui.CallSingleActivity
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.lib.adapter.BaseDelegateAdapter
+import com.wdkl.ncs.android.lib.utils.BaseRecyclerViewHolder
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.middleware.tcp.TcpClient
+import com.wdkl.ncs.android.middleware.tcp.channel.DeviceUtil
+import com.wdkl.ncs.android.middleware.tcp.channel.PhoneUtil
+import com.wdkl.ncs.android.middleware.tcp.channel.VoiceUtil
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback
+import com.wdkl.ncs.android.middleware.tcp.enums.CommunicationEnum
+
+class TakeoverItemAdapter(var data:ArrayList<JsonObject>, val activity: Activity) : BaseDelegateAdapter<BaseRecyclerViewHolder<TakeoverItemBinding>, JsonObject>(){
+    val TAG = "TakeoverItemAdapter"
+
+    private lateinit var loadingDialog: LoadingDialog
+
+    override fun dataProvider(): Any {
+        return data
+    }
+
+    override fun itemFilter(position: Int): Boolean {
+        return true
+    }
+
+    override fun getItemCount(): Int {
+        return data.size
+    }
+
+    override fun onCreateLayoutHelper(): LayoutHelper {
+        Log.i(TAG,data.toString())
+        loadingDialog = CommonTool.createLoadingDialog(activity, R.layout.custom_loading,R.id.loadding_image)
+        return LinearLayoutHelper(0,data.size)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseRecyclerViewHolder<TakeoverItemBinding> {
+        return BaseRecyclerViewHolder.build(parent, R.layout.takeover_item)
+    }
+
+    override fun onBindViewHolder(holder: BaseRecyclerViewHolder<TakeoverItemBinding>, position: Int) {
+        holder.bind {
+            binding ->
+            val itemData = getItem(position)
+            binding.clerkName.text = "未知"
+            binding.tvPhoneNumber.text = "暂无"
+            try {
+                binding.clerkName.text = itemData.get("clerk_name").asString
+                binding.tvPhoneNumber.text = itemData.get("phone_number").asString
+            } catch (e: Exception) {
+                //
+            }
+
+            if (Strings.isNullOrEmpty(Constants.userName)){
+                binding.btnCallOut.visibility = View.GONE
+                binding.btnUserChange.setOnClickListener {
+                    CommonTool.createVerifyDialog("确认切换吗","取消","确定", activity, object : CommonTool.DialogInterface{
+                        override fun yes() {
+                            loadingDialog.show()
+                            showMessage("切换中,请稍候")
+                            val tcpModel = DeviceUtil.deviceChange(Constants.deviceId, itemData.get("id").asInt)
+                            val transaction: TcpCallback = object : TcpCallback(tcpModel.tid) {
+                                override fun onSuccess(jsonObject: JSONObject) {
+                                    if (activity != null) {
+                                        activity.runOnUiThread {
+                                            showMessage("Success")
+                                            loadingDialog.dismiss()
+                                        }
+                                    }
+                                }
+
+                                override fun onFailed(jsonObject: JSONObject) {
+                                    // 这里写发送失败的方法
+                                    val callbackString = jsonObject.getString(CALLBACK)
+                                    if (activity != null) {
+                                        activity.runOnUiThread {
+                                            showMessage("Failed: $callbackString")
+                                            loadingDialog.dismiss()
+                                        }
+                                    }
+                                }
+                            }
+                            TcpClient.getInstance().sendTcp(tcpModel, false, transaction)
+
+                            /*NettyClient.instance.sendMsg(tcpModel.toJson()).subscribe {
+                                if (it) {
+                                    Log.d(TAG, "TCP.发送消息完成")
+                                } else {
+                                    Log.e(TAG, "TCP.发送消息失败")
+                                    HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+                                }
+                                loadingDialog.dismiss()
+                            }*/
+                        }
+                        override fun no() {
+                        }
+                    }).show()
+                }
+            } else {
+                binding.btnUserChange.visibility = View.GONE
+                binding.btnCallOut.setOnClickListener {
+                    //DeviceChannel.calling = true
+
+                    if (Constants.phoneType == CommunicationEnum.MOBILE_PHONE.value()) {
+                        try {
+                            val number = itemData.get("phone_number")
+                            val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:" + number.asString))
+                            activity.startActivity(intent)
+
+                            //发送tcp
+                            val tcpModel = PhoneUtil.phoneCall(Constants.deviceId, itemData.get("id").asInt)
+                            val transaction: TcpCallback = object : TcpCallback(tcpModel.tid) {
+                                override fun onSuccess(jsonObject: JSONObject) {
+                                    //
+                                }
+
+                                override fun onFailed(jsonObject: JSONObject) {
+                                    // 这里写发送失败的方法
+                                }
+                            }
+                            TcpClient.getInstance().sendTcp(tcpModel, true, transaction)
+
+                            /*NettyClient.instance.sendMsg(tcpModel.toJson()).subscribe { res: Boolean ->
+                                if (res) {
+                                    Log.d(TAG, "TCP.发送消息完成")
+                                } else {
+                                    Log.e(TAG, "TCP.发送消息失败")
+                                    HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+                                }
+                            }*/
+                        } catch (e: Exception) {
+                            showMessage("呼叫失败,请检查手机号")
+                        }
+
+                        //直接呼叫
+                        //val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:10010"))
+                        //先启动dialer然后手动呼叫
+                        //val intent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:10010"))
+                        //context.startActivity(intent)
+                    } else {
+                        //网络电话
+                        val tcpModel = VoiceUtil.voiceCall(Constants.deviceId, itemData.get("id").asInt)
+                        val intent = Intent(activity, CallSingleActivity::class.java)
+                        intent.putExtra(CallSingleActivity.EXTRA_ROOM_ID, Constants.sipId)
+                        intent.putExtra(CallSingleActivity.EXTRA_MO, true)
+                        intent.putExtra(CallSingleActivity.EXTRA_AUDIO_ONLY, true)
+                        intent.putExtra(CallSingleActivity.EXTRA_TCPMODEL, tcpModel);
+                        intent.putExtra(CallSingleActivity.EXTRA_SHOWNAME, itemData.get("clerk_name").asString)
+                        //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                        activity.startActivity(intent)
+                    }
+                }
+
+                /*binding.tiVoiceRecorder.visibility = View.VISIBLE
+                binding.tiVoiceRecorder.setOnClickListener{
+                    var intent = Intent(context, VoiceMsgActivity::class.java)
+                    intent.putExtra(VoiceMsgActivity.TO_DEVICE_ID,itemData.get("id").asInt)
+                    context.startActivity(intent)
+                }*/
+            }
+        }
+    }
+}

+ 222 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/WatchCallRecordsItemAdapter.kt

@@ -0,0 +1,222 @@
+package com.wdkl.ncs.android.component.home.adapter
+
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import com.alibaba.android.vlayout.LayoutHelper
+import com.alibaba.android.vlayout.layout.LinearLayoutHelper
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.databinding.AdapterWatchCallRecordsItemBinding
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.component.home.util.TimeTransition
+import com.wdkl.ncs.android.lib.adapter.BaseDelegateAdapter
+import com.wdkl.ncs.android.lib.utils.BaseRecyclerViewHolder
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpType
+
+/**
+ * 呼叫记录适配器
+ */
+class WatchCallRecordsItemAdapter(val data: ArrayList<InteractionVO>) : BaseDelegateAdapter<BaseRecyclerViewHolder<AdapterWatchCallRecordsItemBinding>, InteractionVO>() {
+    var TAG = WatchCallRecordsItemAdapter::class.java.getSimpleName()
+
+    /**
+     * 数据提供者
+     */
+    override fun dataProvider(): Any {
+        return data
+    }
+
+    /**
+     * Item坐标
+     */
+    override fun itemFilter(position: Int): Boolean {
+        return true
+    }
+
+    /**
+     * 获取Item总数
+     */
+    override fun getItemCount(): Int {
+        return data.size
+    }
+
+    /**
+     * 创建LayoutHelper
+     */
+    override fun onCreateLayoutHelper(): LayoutHelper {
+        Log.i("abc", data.toString());
+        return LinearLayoutHelper(0, data.size)
+    }
+
+    /**
+     * 创建ViewHolder
+     */
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseRecyclerViewHolder<AdapterWatchCallRecordsItemBinding> {
+        return BaseRecyclerViewHolder.build(parent, R.layout.adapter_watch_call_records_item)
+    }
+
+    /**
+     * 绑定数据
+     */
+    override fun onBindViewHolder(holder: BaseRecyclerViewHolder<AdapterWatchCallRecordsItemBinding>, position: Int) {
+        holder.bind { binding ->
+            val itemData = getItem(position)
+
+            if (itemData.createDate != null) {
+                binding.callTimeTv.text = TimeTransition().stampToDate(itemData.createDate*1000)
+            }
+
+            //是否已播放 已响应
+            if (itemData.actionEnd != null) {
+                binding.treatmentStateImagev.setImageResource(R.drawable.checked_100)
+                binding.treatmentStateImagev.visibility = View.VISIBLE
+                binding.processingTimeTv.visibility = View.VISIBLE
+
+                //判断是呼入还是呼出 1 分机到主机 2主机到分机
+                if (itemData.fromDeviceMemberId == Constants.memberId){
+                    binding.sickbedTv.text = itemData.toFrameFullName
+                    binding.nameTv.text = itemData.toMemberName
+                    binding.callStatusImagev.setImageResource(R.drawable.hu_chu_yi_jie)
+                } else {
+                    binding.sickbedTv.text = itemData.fromFrameFullName
+                    binding.nameTv.text = itemData.fromMemberName
+                    binding.callStatusImagev.setImageResource(R.drawable.hu_ru_yi_jie)
+                }
+
+                if(itemData.actionType == TcpType.SOS.name){ //sos紧急呼叫 已处理
+                    binding.sickbedTv.text = itemData.fromFrameFullName.substringBefore("-")
+                    binding.nameTv.text = ""
+                    binding.playTv.visibility = View.GONE
+                    binding.projectTv.visibility = View.VISIBLE
+                    binding.conductorNameTv.visibility = View.VISIBLE
+                    binding.projectTv.text = "SOS"
+                    binding.conductorNameTv.text = itemData.toMemberName
+                    binding.processingTimeTv.text = TimeTransition().stampToDate(itemData.actionEnd*1000)
+                    binding.projectTv.setBackgroundResource(R.drawable.sp_event_do)
+                    binding.callStatusImagev.setImageResource(R.drawable.sos_96)
+                }else if(itemData.actionType == TcpType.EVENT.name){//事件已经响应 相应的数据
+                    binding.playTv.visibility = View.GONE
+                    binding.projectTv.visibility = View.VISIBLE
+                    binding.conductorNameTv.visibility = View.VISIBLE
+                    binding.projectTv.text = itemData.data
+                    binding.conductorNameTv.text = itemData.toMemberName
+                    binding.processingTimeTv.text = TimeTransition().stampToDate(itemData.actionEnd*1000)
+                    binding.projectTv.setBackgroundResource(R.drawable.sp_event_do)
+                    binding.callStatusImagev.setImageResource(R.drawable.event_do)
+                }else if (itemData.actionType == TcpType.IM.name){//语音已播放
+
+                    binding.projectTv.visibility = View.GONE
+                    binding.playTv.visibility = View.VISIBLE
+                    binding.conductorNameTv.visibility = View.GONE
+                    binding.playTv.setBackgroundResource(R.drawable.yi_bo_fang)
+                    binding.processingTimeTv.text = "已播放"
+
+                }else if(itemData.actionType == TcpType.VOICE.name || itemData.actionType == TcpType.PHONE.name){ //语音呼叫已接听
+                    binding.projectTv.visibility = View.GONE
+                    binding.playTv.visibility = View.GONE
+                    binding.treatmentStateImagev.visibility = View.GONE
+                    binding.conductorNameTv.visibility = View.GONE
+                    binding.processingTimeTv.visibility = View.GONE
+
+                }else if(itemData.actionType == TcpType.VIDEO.name){ //视频呼叫已接听
+
+                    binding.projectTv.visibility = View.GONE
+                    binding.playTv.visibility = View.GONE
+                    binding.treatmentStateImagev.visibility = View.GONE
+                    binding.conductorNameTv.visibility = View.GONE
+                    binding.processingTimeTv.visibility = View.GONE
+                }
+
+            } else {
+                binding.treatmentStateImagev.setImageResource(R.drawable.unchecked_100)
+                binding.treatmentStateImagev.visibility = View.VISIBLE
+                binding.conductorNameTv.visibility = View.GONE
+                binding.processingTimeTv.visibility = View.VISIBLE
+
+                //判断是呼入还是呼出 1 分机到主机 2主机到分机
+                if(Constants.memberId!=itemData.fromDeviceMemberId){
+                    binding.sickbedTv.text = itemData.fromFrameFullName
+                    binding.nameTv.text = itemData.fromMemberName
+                    binding.callStatusImagev.setImageResource(R.drawable.hu_ru_wei_jie)
+                }else{
+                    binding.sickbedTv.text = itemData.toFrameFullName
+                    binding.nameTv.text = itemData.toMemberName
+                    binding.callStatusImagev.setImageResource(R.drawable.hu_chu_wei_jie)
+                }
+
+                if(itemData.actionType == TcpType.SOS.name){ //sos紧急呼叫 未处理
+                    binding.sickbedTv.text = itemData.fromFrameFullName.substringBefore("-")
+                    binding.nameTv.text = ""
+                    binding.playTv.visibility = View.GONE
+                    binding.projectTv.visibility = View.VISIBLE
+                    binding.projectTv.text = "SOS"
+                    binding.projectTv.setBackgroundResource(R.drawable.sp_event_undo)
+                    binding.processingTimeTv.text = "未处理"
+                    binding.callStatusImagev.setImageResource(R.drawable.sos_96)
+                }else if(itemData.actionType == TcpType.EVENT.name){//事件未处理
+                    binding.playTv.visibility = View.GONE
+                    binding.projectTv.visibility = View.VISIBLE
+                    binding.projectTv.text = itemData.data
+                    binding.projectTv.setBackgroundResource(R.drawable.sp_event_undo)
+                    binding.callStatusImagev.setImageResource(R.drawable.event_undo)
+                    binding.processingTimeTv.text = "未处理"
+                }else if (itemData.actionType == TcpType.IM.name){//语音未播放
+                    binding.projectTv.visibility = View.GONE
+                    binding.playTv.visibility = View.VISIBLE
+                    binding.playTv.setBackgroundResource(R.drawable.wei_bo_fang)
+                    binding.processingTimeTv.text = "未播放"
+                }else if(itemData.actionType == TcpType.VOICE.name || itemData.actionType == TcpType.PHONE.name){ //语音呼叫未接听
+                    binding.projectTv.visibility = View.GONE
+                    binding.playTv.visibility = View.GONE
+                    binding.treatmentStateImagev.visibility = View.GONE
+                    binding.processingTimeTv.visibility = View.GONE
+                }else if(itemData.actionType == TcpType.VIDEO.name){ //视频呼叫未接听
+                    binding.projectTv.visibility = View.GONE
+                    binding.playTv.visibility = View.GONE
+                    binding.treatmentStateImagev.visibility = View.GONE
+                    binding.processingTimeTv.visibility = View.GONE
+
+                }
+            }
+
+//            binding.projectTv.setOnClickListener {
+//                if (itemData.actionEnd != null)return@setOnClickListener
+//                Log.i(TAG, "点击了事件按钮")
+//
+//                Thread(Runnable {
+//                    run {
+//                        if (!Constants.deviceId.equals("")) {
+//                            var eventTcpModel = EventUtil.eventResponse(Constants.deviceId.toInt(), itemData.fromDeviceId, itemData.id)
+//                            NettyClient.Companion.getInstance().sendMsg(eventTcpModel.toJson())
+//                        } else {
+//                            showMessage("获取不到当给前设备id")
+//                        }
+//                    }
+//                }).start()
+//
+//                //处理完之后要刷新数据
+//            }
+//            binding.playTv.setOnClickListener {
+//                if (itemData.actionEnd != null)return@setOnClickListener
+//                //点击之后播放语音留言
+//                showMessage("点击了播放留言")
+//                Thread(Runnable {
+//                    run {
+//                        if (!Constants.deviceId.equals("")) {
+//                            var imUtilTcpModel = ImUtil.imRead(Constants.deviceId.toInt(), itemData.fromDeviceId, itemData.id)
+//                            NettyClient.Companion.getInstance().sendMsg(imUtilTcpModel.toJson())
+//                            MediaPlayHelper.getInstance().playUrlMusic(UrlManager.build().device_url+itemData.data,0.8f,false)
+//                        } else {
+//                            showMessage("获取不到当给前设备id")
+//                        }
+//                    }
+//                }).start()
+//
+//                //处理完之后要刷新数据
+//            }
+
+        }
+    }
+
+}

+ 140 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/adapter/WatchContactsItemAdapter.kt

@@ -0,0 +1,140 @@
+package com.wdkl.ncs.android.component.home.adapter
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.alibaba.android.vlayout.LayoutHelper
+import com.alibaba.android.vlayout.layout.LinearLayoutHelper
+import com.alibaba.fastjson.JSONObject
+import com.enation.javashop.utils.base.tool.CommonTool
+import com.enation.javashop.utils.base.widget.LoadingDialog
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.databinding.AdapterWatchContactsItemBinding
+import com.wdkl.ncs.android.component.home.ui.CallSingleActivity
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.lib.adapter.BaseDelegateAdapter
+import com.wdkl.ncs.android.lib.utils.BaseRecyclerViewHolder
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.middleware.model.vo.WatchContactsVO
+import com.wdkl.ncs.android.middleware.tcp.TcpClient
+import com.wdkl.ncs.android.middleware.tcp.channel.DeviceChannel
+import com.wdkl.ncs.android.middleware.tcp.channel.PhoneUtil
+import com.wdkl.ncs.android.middleware.tcp.channel.VoiceUtil
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback
+import com.wdkl.ncs.android.middleware.tcp.enums.CommunicationEnum
+
+class WatchContactsItemAdapter(val data:ArrayList<WatchContactsVO>, val context: Context) : BaseDelegateAdapter<BaseRecyclerViewHolder<AdapterWatchContactsItemBinding>, WatchContactsVO>(){
+
+    val TAG = WatchContactsItemAdapter::class.java.simpleName
+
+    private var thisPosition: Int = -1
+    private lateinit var loadingDialog: LoadingDialog
+
+    override fun dataProvider(): Any {
+        return data
+    }
+
+    override fun itemFilter(position: Int): Boolean {
+        return true
+    }
+    fun setThisPosition(thisPosition:Int) {
+        this.thisPosition = thisPosition
+    }
+    override fun getItemCount(): Int {
+        return data.size
+    }
+
+    override fun onCreateLayoutHelper(): LayoutHelper {
+        Log.i("abc",data.toString())
+        loadingDialog = CommonTool.createLoadingDialog(context, R.layout.custom_loading,R.id.loadding_image)
+        return LinearLayoutHelper(0,data.size)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseRecyclerViewHolder<AdapterWatchContactsItemBinding> {
+        return BaseRecyclerViewHolder.build(parent, R.layout.adapter_watch_contacts_item)
+    }
+
+    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
+        super.onDetachedFromRecyclerView(recyclerView)
+        loadingDialog.dismiss()
+    }
+
+    override fun onBindViewHolder(holder: BaseRecyclerViewHolder<AdapterWatchContactsItemBinding>, position: Int) {
+        holder.bind {
+            binding ->
+            val itemData = getItem(position)
+//            if(position == thisPosition){
+//                binding.itemRelayout.setBackgroundColor(Color.parseColor("#DCDCDC"))
+//            }else{
+//                binding.itemRelayout.setBackgroundColor(Color.parseColor("#ffffff"))
+//            }
+            binding.roomNumberTv.text = itemData.frameFullName
+            binding.nameTv.text = itemData.customerNamed
+
+            /*binding.awciVoiceRecorder.setOnClickListener{
+                var intent = Intent(context, VoiceMsgActivity::class.java)
+                intent.putExtra(VoiceMsgActivity.TO_DEVICE_ID,itemData.deviceId)
+                context.startActivity(intent)
+            }*/
+
+            if (!Constants.supportCall) {
+                binding.awciBtnCallOut.visibility = View.GONE
+            }
+
+            binding.awciBtnCallOut.setOnClickListener{
+                DeviceChannel.calling = true
+                if (Constants.bedPhoneType == CommunicationEnum.MOBILE_PHONE.value()) {
+                    try {
+                        if (itemData.phoneNumber != null) {
+                            val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:" + itemData.phoneNumber))
+                            context.startActivity(intent)
+
+                            //发送tcp
+                            val tcpModel = PhoneUtil.phoneCall(Constants.deviceId, itemData.deviceId)
+                            val transaction: TcpCallback = object : TcpCallback(tcpModel.tid) {
+                                override fun onSuccess(jsonObject: JSONObject) {
+                                    //
+                                }
+
+                                override fun onFailed(jsonObject: JSONObject) {
+                                    // 这里写发送失败的方法
+                                }
+                            }
+                            TcpClient.getInstance().sendTcp(tcpModel, true, transaction)
+
+                            /*NettyClient.instance.sendMsg(tcpModel.toJson())
+                                .subscribe { res: Boolean ->
+                                    if (res) {
+                                        Log.d(TAG, "TCP.发送消息完成")
+                                    } else {
+                                        Log.e(TAG, "TCP.发送消息失败")
+                                        HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+                                    }
+                                }*/
+                        } else {
+                            showMessage("呼叫失败,可能手机号异常")
+                        }
+                    } catch (e: Exception) {
+                        showMessage("呼叫失败,请检查手机号")
+                    }
+                } else {
+                    //通话
+                    val tcpModel = VoiceUtil.voiceCall(Constants.deviceId, itemData.deviceId)
+                    val intent = Intent(context, CallSingleActivity::class.java)
+                    intent.putExtra(CallSingleActivity.EXTRA_ROOM_ID, Constants.sipId)
+                    intent.putExtra(CallSingleActivity.EXTRA_MO, true)
+                    intent.putExtra(CallSingleActivity.EXTRA_AUDIO_ONLY, true)
+                    intent.putExtra(CallSingleActivity.EXTRA_TCPMODEL, tcpModel)
+                    intent.putExtra(CallSingleActivity.EXTRA_SHOWNAME, itemData.frameFullName + " " + itemData.customerNamed)
+                    //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    context.startActivity(intent)
+                }
+
+            }
+        }
+    }
+}

+ 35 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/binding/HomeFragmentBindHelper.kt

@@ -0,0 +1,35 @@
+package com.wdkl.ncs.android.component.home.binding
+
+import androidx.databinding.ObservableField
+
+/**
+ * @author LDD
+ * @From   com.wdkl.ncs.android.component.home.binding
+ * @Date   2018/1/30 下午3:03
+ * @Note   首页UIVM
+ */
+class HomeFragmentBindHelper {
+
+    /**
+     * @Name  scrollY
+     * @Type  ObservableField<Int>
+     * @Note  滑动距离
+     */
+    val scrollY = ObservableField(0)
+
+    /**
+     * @Name  image
+     * @Type  String
+     * @Note  测试图片
+     */
+    var image = "http://onghqryqs.bkt.clouddn.com/ChMkJlauzbOIb6JqABF4o12gc_AAAH9HgF1sh0AEXi7441.jpeg"
+
+    /**
+     * @Name  isHide
+     * @Type  ObservableField<Boolean>
+     * @Note  是否隐藏
+     */
+    val isHide  = ObservableField(true)
+
+
+}

+ 43 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/broadcast/BatteryBroadcastReceiver.java

@@ -0,0 +1,43 @@
+package com.wdkl.ncs.android.component.home.broadcast;
+
+import android.annotation.SuppressLint;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.widget.TextView;
+
+import com.wdkl.ncs.android.component.home.util.SpeechUtil;
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.lib.vo.MessageEvent;
+
+import org.greenrobot.eventbus.EventBus;
+
+
+public class BatteryBroadcastReceiver extends BroadcastReceiver {
+    private String TAG = BatteryBroadcastReceiver.class.getSimpleName();
+
+    private TextView mBatteryView;
+
+    public BatteryBroadcastReceiver(TextView mBatteryView) {
+        this.mBatteryView = mBatteryView;
+    }
+
+    @SuppressLint("LongLogTag")
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
+            int level = intent.getIntExtra("level", 0);
+            int scale = intent.getIntExtra("scale", 100);
+            int power = level * 100 / scale;
+            if (power<15){
+                if (Constants.Companion.getEVENT_BATTERY_ALARM()) {
+                    SpeechUtil.getInstance().speak("电池电量低,请注意!电池电量低,请注意!电池电量低,请注意!");
+                    Constants.Companion.setEVENT_BATTERY_ALARM(false);
+                }
+            } else {
+                Constants.Companion.setEVENT_BATTERY_ALARM(true);
+            }
+            EventBus.getDefault().post(new MessageEvent(power, Constants.EVENT_BATTERY_PERCENT));
+        }
+    }
+}

+ 34 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/broadcast/MyMediaButtonReceiver.kt

@@ -0,0 +1,34 @@
+package com.wdkl.ncs.android.component.home.broadcast
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import android.view.KeyEvent
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import org.greenrobot.eventbus.EventBus
+
+class MyMediaButtonReceiver: BroadcastReceiver() {
+    var TAG = "MyMediaButtonReceiver"
+    override fun onReceive(context: Context?, intent: Intent?) {
+        val action = intent!!.action
+        if (action!=null&&action.equals(Intent.ACTION_MEDIA_BUTTON)){
+            var keyEvent = intent!!.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
+            if (keyEvent!=null && keyEvent.action == KeyEvent.ACTION_DOWN){
+                val keyCode = keyEvent.keyCode
+                Log.i(TAG, "keyCode " + keyCode)
+                when (keyCode){
+                    KeyEvent.KEYCODE_MEDIA_PLAY -> {
+                        EventBus.getDefault().post(MessageEvent(1, Constants.EVENT_BLUETOOTH_ACCEPT_CALL))
+                        return
+                    }
+                    KeyEvent.KEYCODE_MEDIA_PAUSE -> {
+                        EventBus.getDefault().post(MessageEvent(1, Constants.EVENT_BLUETOOTH_ACCEPT_CALL))
+                        return
+                    }
+                }
+            }
+        }
+    }
+}

+ 22 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/broadcast/NetworkBroadcastReceiver.kt

@@ -0,0 +1,22 @@
+package com.wdkl.ncs.android.component.home.broadcast
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.net.ConnectivityManager
+import com.wdkl.ncs.android.component.home.util.NetHelper
+import com.wdkl.ncs.android.component.home.util.Util.getCpuWakeLock
+import com.wdkl.ncs.android.component.home.util.Util.wakeUpAndUnlock
+import com.wdkl.ncs.android.lib.utils.AppTool
+
+class NetworkBroadcastReceiver: BroadcastReceiver() {
+    override fun onReceive(context: Context?, intent: Intent?) {
+        if (ConnectivityManager.CONNECTIVITY_ACTION == intent!!.action) {
+            val netState = NetHelper.getInstance().getNetworkState(context)
+            if (netState == NetHelper.NETWORK_NONE) {
+                wakeUpAndUnlock()
+            }
+        }
+    }
+
+}

+ 34 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/di/HomeComponent.kt

@@ -0,0 +1,34 @@
+package com.wdkl.ncs.android.component.home.di
+
+import com.wdkl.ncs.android.component.home.activity.*
+import com.wdkl.ncs.android.component.home.service.WdKeepAliveService
+import com.wdkl.ncs.android.middleware.di.ApplicationComponent
+import dagger.Component
+
+/**
+ * @author LDD
+ * @Date   2018/1/22 下午2:43
+ * @From   com.wdkl.ncs.android.component.home.di
+ * @Note   依赖注入入口
+ */
+@Component(dependencies = arrayOf(ApplicationComponent::class))
+interface HomeComponent {
+
+    fun inject(activity: WatchHome2Activity)
+
+    fun inject(activity: AppUpdateActivity)
+
+    fun inject(activity: WatchContactsActivity)
+
+    fun inject(activity: WatchCallRecordsActivity)
+
+    fun inject(activity: TakeoverActivity)
+
+    fun inject(service: WdKeepAliveService)
+
+    fun inject(activity: NewEventListActivity)
+
+    fun inject(activity: VoiceMsgActivity)
+
+    fun inject(activity: ChannelImActivity)
+}

+ 242 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/helper/AppUpdateHelper.java

@@ -0,0 +1,242 @@
+package com.wdkl.ncs.android.component.home.helper;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.util.Log;
+
+import androidx.core.content.FileProvider;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class AppUpdateHelper {
+    private final static String TAG = "AppUpdate";
+
+    /**
+     * 下载的APK文件绝对路径
+     */
+    public static final String FILE_APK_PATH = Environment.getExternalStorageDirectory() + "/CallingBed2";
+    /**
+     * 下载的APK文件的文件名
+     */
+    public static final String FILE_APK_NAME = "CallingBed2APK.apk";
+
+    public static Context mContext;
+
+    public static void updateApp(Context context, UpdateCallBack callBack) {
+        mContext = context;
+        if (checkApkExit(context)) {
+            Log.d(TAG, "文件存在");
+        } else {
+            Log.d(TAG, "文件不存在");
+            if (callBack != null) {
+                callBack.onFailed();
+            }
+            return;
+        }
+
+        String path = FILE_APK_PATH + "/" + FILE_APK_NAME;
+
+//        if (!checkApkAvailable(context, path)) {
+//            Log.d(TAG, "安装失败");
+//            return;
+//        }
+
+
+//        if (installApp(context.getPackageName(), path)) {
+//        if (rootSilenceInstall(path)) {
+//            Log.d(TAG, "安装成功");
+//            if (callBack != null) {
+//                callBack.onSuccess();
+//            }
+//        } else {
+//            Log.d(TAG, "安装失败");
+//            if (callBack != null) {
+//                callBack.onFailed();
+//            }
+//        }
+
+//        if (silentInstall(context, path)) {
+//            Log.d(TAG, "app 安装成功");
+//            if (callBack != null) {
+//                callBack.onSuccess();
+//            }
+//        }
+
+        if (installAPP(path)) {
+            Log.d(TAG, "安装成功");
+            if (callBack != null) {
+                callBack.onSuccess();
+            }
+        } else {
+            callBack.onFailed();
+        }
+    }
+
+    public static boolean installAPP(String path) {
+
+//        if (ShellUtils.checkRootPermission()){
+//            // 安装App命令:apkFilePath为待安装的软件地址
+//            String installCommand = "pm install -r " + path;
+//            // 重启密令: getPackageName:软件包名,WatchHome2Activity.class.getCanonicalName():重启APP的首页;
+//            String restartCommand = "am start -n "
+//                    + mContext.getPackageName() + "/" + WatchHome2Activity.class.getCanonicalName();
+//            String[] commands = {installCommand, restartCommand};
+//            ShellUtils.CommandResult commandResult = ShellUtils.execCommand(commands, true, true);
+//            return Strings.isNullOrEmpty(commandResult.errorMsg);
+//        } else {
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+            File apkFile = new File(path);
+            // 由于没有在Activity环境下启动Activity,设置下面的标签
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            //Android 7.0以上要使用FileProvider
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致   参数3  共享的文件
+                Uri uri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + ".provider", apkFile);
+                //添加这一句表示对目标应用临时授权该Uri所代表的文件
+//                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                intent.setDataAndType(uri, "application/vnd.android.package-archive");
+            } else {
+                intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
+            }
+            mContext.startActivity(intent);
+            return true;
+//        }
+    }
+
+
+    private static boolean checkApkExit(Context context) {
+        File file = new File(FILE_APK_PATH + "/" + FILE_APK_NAME);
+        return file.exists();
+    }
+
+    private static boolean checkApkAvailable(Context context, String path) {
+        try {
+            PackageManager pm = context.getPackageManager();
+            PackageInfo info = pm.getPackageArchiveInfo(path, 0);
+            ApplicationInfo applicationInfo = info.applicationInfo;
+            String newPkg = applicationInfo.packageName;
+            String curPkg = context.getPackageName();
+            Log.d(TAG, "new package: " + newPkg + ", cur package: " + curPkg);
+            if (curPkg.equals(newPkg)) {
+                return true;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return false;
+    }
+
+    public static boolean silentInstall(Context context, String apkPath) {
+        PackageManager packageManager = context.getPackageManager();
+        Class pmClz = packageManager.getClass();
+        try {
+            if (Build.VERSION.SDK_INT >= 21) {
+                Log.d(TAG, "apk path: " + apkPath);
+                Class aClass = Class.forName("android.app.PackageInstallObserver");
+                Constructor constructor = aClass.getDeclaredConstructor();
+                constructor.setAccessible(true);
+                Object installObserver = constructor.newInstance();
+                Method method = pmClz.getDeclaredMethod("installPackage", Uri.class, aClass, int.class, String.class);
+                method.setAccessible(true);
+                method.invoke(packageManager, Uri.fromFile(new File(apkPath)), installObserver, 2, null);
+            } else {
+                Method method = pmClz.getDeclaredMethod("installPackage", Uri.class, Class.forName("android.content.pm.IPackageInstallObserver"), int.class, String.class);
+                method.setAccessible(true);
+                method.invoke(packageManager, Uri.fromFile(new File(apkPath)), null, 2, null);
+            }
+            return true;
+        } catch (Exception e) {
+            Log.e(TAG, e.toString());
+        }
+        return false;
+    }
+
+    public static boolean installApp(String packageName, String apkPath) {
+        Process process = null;
+        BufferedReader successResult = null;
+        BufferedReader errorResult = null;
+        StringBuilder successMsg = new StringBuilder();
+        StringBuilder errorMsg = new StringBuilder();
+        Log.i(TAG, "install package: " + packageName + ", apkPath: " + apkPath);
+        try {
+            process = new ProcessBuilder("pm", "install", "-i", packageName, "-r", apkPath).start();
+            successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+            String s;
+            while ((s = successResult.readLine()) != null) {
+                successMsg.append(s);
+            }
+            while ((s = errorResult.readLine()) != null) {
+                errorMsg.append(s);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (successResult != null) {
+                    successResult.close();
+                }
+                if (errorResult != null) {
+                    errorResult.close();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            if (process != null) {
+                process.destroy();
+            }
+        }
+        Log.e(TAG, "" + errorMsg.toString());
+        //如果含有“success”认为安装成功
+        return successMsg.toString().equalsIgnoreCase("success");
+    }
+
+    public static boolean rootSilenceInstall(String path) {
+        Process process;
+        PrintWriter printWriter;
+        try {
+            process = Runtime.getRuntime().exec("su");
+            printWriter = new PrintWriter(process.getOutputStream());
+            printWriter.println("pm install -r " + path);
+            printWriter.flush();
+            printWriter.close();
+            int res = process.waitFor();
+            Log.e(TAG, "silent install res: " + res);
+            if (res == 0) {
+                return true;
+            } else {
+                return false;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "rootSilenceInstall e:" + e.getMessage());
+            return false;
+        }
+    }
+
+    public static void reboot(Context context) {
+        Intent intent = new Intent(Intent.ACTION_REBOOT);
+        intent.putExtra("nowait", 1);
+        intent.putExtra("interval", 1);
+        intent.putExtra("window", 0);
+        context.sendBroadcast(intent);
+    }
+
+    public interface UpdateCallBack {
+        void onFailed();
+
+        void onSuccess();
+    }
+}

+ 168 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/helper/HttpHelper.java

@@ -0,0 +1,168 @@
+package com.wdkl.ncs.android.component.home.helper;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+import static com.wdkl.ncs.android.component.home.helper.AppUpdateHelper.FILE_APK_NAME;
+import static com.wdkl.ncs.android.component.home.helper.AppUpdateHelper.FILE_APK_PATH;
+
+
+public class HttpHelper {
+    private static OkHttpClient okHttpClient;
+
+    /**
+     * @param url   服务器地址
+     * @param file  所要上传的文件
+     */
+    public static void upload(String url, File file, final UploadCallback callback) {
+        if (okHttpClient == null) {
+            okHttpClient = new OkHttpClient();
+        }
+
+        RequestBody requestBody = new MultipartBody.Builder()
+                .setType(MultipartBody.FORM)
+                .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file))
+                .build();
+        Request request = new Request.Builder()
+                .url(url)
+                .post(requestBody)
+                .build();
+
+        okHttpClient.newCall(request).enqueue(new Callback() {
+            @Override
+            public void onFailure(Call call, IOException e) {
+                if (callback != null) {
+                    callback.onFail();
+                }
+            }
+
+            @Override
+            public void onResponse(Call call, Response response) throws IOException {
+                Log.d("HttpHelper", "voice msg response: " + response.toString());
+               if( response.code()==200 && response.body() != null) {
+                   String data = response.body().string();
+                   //voice msg response: upload/file/202104102037715.mp3
+                   if (callback != null) {
+                       callback.onSuccess(data);
+                   }
+               } else {
+                   if (callback != null) {
+                       callback.onFail();
+                   }
+               }
+            }
+        });
+    }
+
+    public static void download(String url, final DownloadListener listener) {
+        if (okHttpClient == null) {
+            okHttpClient = new OkHttpClient();
+        }
+
+        Request request = new Request.Builder().url(url).build();
+        okHttpClient.newCall(request).enqueue(new Callback() {
+            @Override
+            public void onFailure(Call call, IOException e) {
+                Log.d("download", "onFailure==" + e.toString());
+                if (listener != null) {
+                    listener.onDownloadFailed(); // 下载失败
+                }
+            }
+
+            @Override
+            public void onResponse(Call call, Response response) {
+                Log.d("download", "response==" + response.body().contentLength());
+                InputStream is = null;
+                byte[] buf = new byte[2048];
+                int len;
+                FileOutputStream fos = null;
+                try {
+                    is = response.body().byteStream();
+                    long total = response.body().contentLength();
+                    File file = new File(isHaveExistDir(new File(FILE_APK_PATH), new File(FILE_APK_PATH + "/" + FILE_APK_NAME)), FILE_APK_NAME);
+                    fos = new FileOutputStream(file);
+                    long sum = 0;
+                    while ((len = is.read(buf)) != -1) {
+                        fos.write(buf, 0, len);
+                        sum = sum + (long) len;
+                        //int progress = (int) (sum * 1.0f / total * 100);
+                        float sp = (float) sum / (float) total;
+                        int progress = (int) (sp * 100);
+                        Log.d("download", "progress==" + progress);
+                        if (listener != null) {
+                            listener.onDownloading(progress);// 下载中
+                        }
+                    }
+                    fos.flush();
+                    if (listener != null) {
+                        listener.onDownloadSuccess(); // 下载完成
+                    }
+                } catch (Exception e) {
+                    Log.d("download", "Exception==");
+                    if (listener != null) {
+                        listener.onDownloadFailed();
+                    }
+                } finally {
+                    try {
+                        if (is != null)
+                            is.close();
+                        if (fos != null)
+                            fos.close();
+                    } catch (IOException e) {
+                        Log.d("download", "IOException==");
+                    }
+                }
+            }
+        });
+    }
+
+    private static String isHaveExistDir(File downloadFile, File sonFile) throws IOException {
+        Log.d("download", "downloadFile.mkdirs()==" + downloadFile.mkdirs());
+        Log.d("download", "sonFile.mkdir()==" + sonFile.mkdir());
+        if (!downloadFile.mkdirs()) {
+            downloadFile.createNewFile();
+        }
+        deleteAPKFile(sonFile);//只要文件名相同就可以自动替换(按道理此处不需要了,但为了保险起见还是先执行删除操作)。
+        return downloadFile.getAbsolutePath();
+    }
+
+    public static boolean deleteAPKFile(File downloadFile) {
+        return downloadFile.delete();
+    }
+
+
+    public interface UploadCallback{
+        void onFail();
+        void onSuccess(String data);
+    }
+
+    public interface DownloadListener {
+        /**
+         * 下载成功
+         */
+        void onDownloadSuccess();
+
+        /**
+         * @param progress 下载进度
+         */
+        void onDownloading(int progress);
+
+        /**
+         * 下载失败
+         */
+        void onDownloadFailed();
+    }
+}

+ 27 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/launch/HomeLaunch.kt

@@ -0,0 +1,27 @@
+package com.wdkl.ncs.android.component.home.launch
+
+import com.wdkl.ncs.android.component.home.di.DaggerHomeComponent
+import com.wdkl.ncs.android.component.home.di.HomeComponent
+import com.enation.javashop.android.jrouter.external.annotation.Router
+import com.wdkl.ncs.android.lib.base.BaseLaunch
+import com.wdkl.ncs.android.middleware.di.DaggerManager
+
+/**
+ * @author LDD
+ * @Date   2018/1/22 下午2:44
+ * @From   com.wdkl.ncs.android.component.home.launch
+ * @Note   home模块启动类 代替Application 在壳工程Application中反射调用
+ */
+@Router(path = "/home/launch")
+class HomeLaunch : BaseLaunch() {
+    companion object {
+        lateinit var component:HomeComponent
+    }
+
+    override fun moduleInit() {
+        component = DaggerHomeComponent.builder()
+                .applicationComponent(DaggerManager.APPLICATION_COMPONENT)
+                .build()
+    }
+
+}

+ 258 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/service/FloatingService.java

@@ -0,0 +1,258 @@
+package com.wdkl.ncs.android.component.home.service;
+
+
+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 android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.google.gson.Gson;
+import com.wdkl.ncs.android.component.home.R;
+import com.wdkl.ncs.android.component.home.ui.CallSingleActivity;
+import com.wdkl.ncs.android.component.home.util.ActivityStackUtil;
+import com.wdkl.ncs.android.component.home.util.MediaPlayHelper;
+import com.wdkl.ncs.android.component.home.util.RingPlayHelper;
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.lib.vo.MessageEvent;
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel;
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpAction;
+
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+
+public class FloatingService extends Service {
+    private static final String TAG = "FloatingService";
+
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams wmParams;
+    //浮动布局view
+    private View mFloatingLayout;
+    private TextView mCallView;
+
+    private long clickTime = 0;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        initWindow();//设置悬浮窗基本参数(位置、宽高等)
+        EventBus.getDefault().register(this);
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        initFloating();//悬浮框点击事件的处理
+
+        return new MyBinder();
+    }
+
+    public class MyBinder extends Binder {
+        public FloatingService getService() {
+            return FloatingService.this;
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mFloatingLayout != null) {
+            // 移除悬浮窗口
+            mWindowManager.removeView(mFloatingLayout);
+            mFloatingLayout = null;
+            Constants.Companion.setShowFloatWindow(false);
+        }
+        EventBus.getDefault().unregister(this);
+        Log.d(TAG, "floating service destroyed");
+    }
+
+    /**
+     * 设置悬浮框基本参数(位置、宽高等)
+     */
+    private void initWindow() {
+        mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
+        //设置好悬浮窗的参数
+        wmParams = getParams();
+        //得到容器,通过这个inflater来获得悬浮窗控件
+        LayoutInflater inflater = LayoutInflater.from(getApplicationContext());
+        // 获取浮动窗口视图所在布局
+        mFloatingLayout = inflater.inflate(R.layout.float_call_window_layout, null);
+        // 添加悬浮窗的视图
+        mWindowManager.addView(mFloatingLayout, wmParams);
+    }
+
+    private WindowManager.LayoutParams getParams() {
+        wmParams = new WindowManager.LayoutParams();
+        if (Build.VERSION.SDK_INT  >= Build.VERSION_CODES.O) {
+            wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        } else {
+            wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
+        }
+
+        wmParams.gravity = Gravity.LEFT | Gravity.TOP;
+        //wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        //设置悬浮窗口长宽数据
+        wmParams.width = 220;
+        wmParams.height = 248;
+        wmParams.x = 320;
+        wmParams.y = 240;
+        return wmParams;
+    }
+
+    private void initFloating() {
+        mCallView = mFloatingLayout.findViewById(R.id.float_call_view);
+        Constants.Companion.setShowFloatWindow(true);
+
+        //悬浮框触摸事件,设置悬浮框可拖动
+        mCallView.setOnTouchListener(new FloatingListener());
+        //悬浮框点击事件
+        mCallView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                //在这里实现点击重新回到Activity
+                if (System.currentTimeMillis() - clickTime > 3000) {
+                    resumeCallActivity();
+                }
+                clickTime = System.currentTimeMillis();
+            }
+        });
+    }
+
+    private void resumeCallActivity() {
+        Intent intent = new Intent(FloatingService.this, CallSingleActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
+    }
+
+    //开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
+    private int mTouchStartX, mTouchStartY, mTouchCurrentX, mTouchCurrentY;
+    //开始时的坐标和结束时的坐标(相对于自身控件的坐标)
+    private int mStartX, mStartY, mStopX, mStopY;
+    //判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件
+    private boolean isMove;
+
+    private class FloatingListener implements View.OnTouchListener {
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            int action = event.getAction();
+            switch (action) {
+                case MotionEvent.ACTION_DOWN:
+                    isMove = false;
+                    mTouchStartX = (int) event.getRawX();
+                    mTouchStartY = (int) event.getRawY();
+                    mStartX = (int) event.getX();
+                    mStartY = (int) event.getY();
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    mTouchCurrentX = (int) event.getRawX();
+                    mTouchCurrentY = (int) event.getRawY();
+                    wmParams.x += mTouchCurrentX - mTouchStartX;
+                    wmParams.y += mTouchCurrentY - mTouchStartY;
+                    mWindowManager.updateViewLayout(mFloatingLayout, wmParams);
+                    mTouchStartX = mTouchCurrentX;
+                    mTouchStartY = mTouchCurrentY;
+                    break;
+                case MotionEvent.ACTION_UP:
+                    mStopX = (int) event.getX();
+                    mStopY = (int) event.getY();
+                    if (Math.abs(mStartX - mStopX)  >= 1 || Math.abs(mStartY - mStopY)  >= 1) {
+                        isMove = true;
+                    }
+                    break;
+                default:
+                    break;
+            }
+
+            //如果是移动事件不触发OnClick事件,防止移动的时候一放手形成点击事件
+            return isMove;
+        }
+    }
+
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    public void onEvent(MessageEvent messageEvent) {
+        int tag = messageEvent.getTag();
+        if (tag == 2) {
+            TcpModel tcpModel = (TcpModel) messageEvent.getMessage();
+            Log.i(TAG, "received tcpModel: " + tcpModel.toJson());
+            int curInteractionId = -1;
+            if (tcpModel.getData() != null) {
+                InteractionVO interactionVO = new Gson().fromJson(tcpModel.getData().toString(), InteractionVO.class);
+                curInteractionId = interactionVO.getId();
+            }
+
+            if (tcpModel.getAction() == TcpAction.VoiceAction.ACCEPT) {
+                if (curInteractionId == Constants.Companion.getInteractionId()) {
+                    MediaPlayHelper.getInstance().stopMusic(true);
+                    RingPlayHelper.stopRingTone();
+                    Log.i(TAG, "对方接听电话啦");
+                    //对方接听电话,恢复到call界面
+                    resumeCallActivity();
+                }
+            } else if (tcpModel.getAction() == TcpAction.VoiceAction.HANDOFF) { //对方挂断
+                if (curInteractionId == Constants.Companion.getInteractionId()) {
+                    CallSingleActivity callSingleActivity = ActivityStackUtil.getCallSingleActivity();
+                    if (callSingleActivity != null) {
+                        callSingleActivity.finish();
+                    }
+                }
+            } else if (tcpModel.getAction() == TcpAction.VoiceAction.REJECT) {
+                if (curInteractionId == Constants.Companion.getInteractionId()) {
+                    CallSingleActivity callSingleActivity = ActivityStackUtil.getCallSingleActivity();
+                    if (callSingleActivity != null) {
+                        callSingleActivity.finish();
+                    }
+                }
+            } else if (tcpModel.getAction() == TcpAction.VoiceAction.CALLING) {
+                if (curInteractionId == Constants.Companion.getInteractionId()) {
+                    CallSingleActivity callSingleActivity = ActivityStackUtil.getCallSingleActivity();
+                    if (callSingleActivity != null) {
+                        callSingleActivity.finish();
+                    }
+                }
+            } else if (tcpModel.getAction() == TcpAction.VoiceAction.FAILED) {
+                if (curInteractionId == Constants.Companion.getInteractionId()) {
+                    CallSingleActivity callSingleActivity = ActivityStackUtil.getCallSingleActivity();
+                    if (callSingleActivity != null) {
+                        callSingleActivity.finish();
+                    }
+                }
+            } else if (tcpModel.getAction() == TcpAction.VoiceAction.CANCEL) {
+                if (curInteractionId == Constants.Companion.getInteractionId()) {
+                    CallSingleActivity callSingleActivity = ActivityStackUtil.getCallSingleActivity();
+                    if (callSingleActivity != null) {
+                        callSingleActivity.finish();
+                    }
+                }
+            }
+        } else if (tag == 1) {
+            TcpModel tcpModel = (TcpModel) messageEvent.getMessage();
+            if (tcpModel.getAction() == TcpAction.VoiceAction.SUCCESS){ //服务器返回成功
+                CallSingleActivity callSingleActivity = ActivityStackUtil.getCallSingleActivity();
+                if (callSingleActivity != null) {
+                    callSingleActivity.callOutSuccess(tcpModel);
+                }
+
+                //重新拉起通话界面
+                resumeCallActivity();
+            }
+        }
+    }
+}

+ 158 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/service/PhoneStateReceiver.java

@@ -0,0 +1,158 @@
+package com.wdkl.ncs.android.component.home.service;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.alibaba.fastjson.JSONObject;
+import com.wdkl.ncs.android.component.home.util.PhoneCallUtil;
+import com.wdkl.ncs.android.component.home.util.RecordHelper;
+import com.wdkl.ncs.android.lib.vo.MessageEvent;
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.middleware.tcp.TcpClient;
+import com.wdkl.ncs.android.middleware.tcp.channel.DeviceChannel;
+import com.wdkl.ncs.android.middleware.tcp.channel.PhoneUtil;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel;
+
+import org.greenrobot.eventbus.EventBus;
+
+import static com.wdkl.ncs.android.lib.utils.ExtendMethodsKt.showMessage;
+
+public class PhoneStateReceiver extends BroadcastReceiver {
+    private static final String TAG = "PhoneStateReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        Log.d(TAG, "PhoneStateReceiver action: " + action);
+
+        if (action.equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
+            String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
+            DeviceChannel.calling = true;
+            Log.d(TAG, "PhoneStateReceiver EXTRA_PHONE_NUMBER: " + phoneNumber);
+        } else {
+            String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
+            String extraIncomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
+            Log.d(TAG, "PhoneStateReceiver onReceive state: " + state + ", extraIncomingNumber: " + extraIncomingNumber);
+
+            if (state.equalsIgnoreCase(TelephonyManager.EXTRA_STATE_IDLE)) {
+                //待机中
+                //Log.d(TAG, "PhoneStateReceiver idle");
+                if (Constants.Companion.getPhoneState() == Constants.PHONE_OUTGOING) {
+                    //发送tcp
+                    if (Constants.Companion.getPhoneDataVO() != null) {
+                        TcpModel tcpModel = PhoneUtil.phoneHandoff(Constants.Companion.getDeviceId(), Constants.Companion.getPhoneDataVO());
+                        TcpCallback transaction = new TcpCallback(tcpModel.getTid()) {
+                            @Override
+                            public void onSuccess(JSONObject jsonObject) {
+                                //
+                            }
+
+                            @Override
+                            public void onFailed(JSONObject jsonObject) {
+                                //
+                            }
+                        };
+                        TcpClient.getInstance().sendTcp(tcpModel, true, transaction);
+
+                        /*NettyClient.Companion.getInstance().sendMsg(tcpModel.toJson()).subscribe(it -> {
+                            if (it) {
+                                Log.d(TAG, "TCP.发送消息完成");
+                            } else {
+                                Log.e(TAG, "TCP.发送消息失败");
+                                HandleTcpConnect.Companion.getInstance().tcpReConnectWithMsgShow();
+                            }
+                        });*/
+                    }
+                }
+                DeviceChannel.calling = false;
+                Constants.Companion.setPhoneState(Constants.PHONE_IDLE);
+            } else if (state.equalsIgnoreCase(TelephonyManager.EXTRA_STATE_RINGING) && extraIncomingNumber != null) {
+                //非白名单号码拦截并删除记录
+                if (Constants.Companion.getPhoneWhiteList() != null) {
+                    for (String s : Constants.Companion.getPhoneWhiteList()) {
+                        if (s.contains(extraIncomingNumber)) { //号码在白名单内修改通话状态,并退出方法
+                            //Log.d(TAG, "PhoneStateReceiver incoming call: " + extraIncomingNumber);
+                            DeviceChannel.calling = true;
+                            Constants.Companion.setPhoneState(Constants.PHONE_INCOMING);
+                            //来电时取消留言
+                            RecordHelper.getInstance().stopCancelRecordByOther(true);
+                            EventBus.getDefault().post(new MessageEvent(true, Constants.EVENT_CLEAR_IM));
+                            return;
+                        }
+                    }
+                    //循环结束,说明来电号码不在白名单内,挂断来电
+                    showMessage("非法号码:" + extraIncomingNumber);
+                    boolean res = PhoneCallUtil.endCall(context);
+                    Log.d(TAG, "end call result: " + res);
+                    PhoneCallUtil.deleteCallLog(context, extraIncomingNumber);
+                }
+            } else if (state.equalsIgnoreCase(TelephonyManager.EXTRA_STATE_OFFHOOK) && extraIncomingNumber != null) {
+                //通话中
+                //Log.d(TAG, "PhoneStateReceiver in call: " + extraIncomingNumber);
+                DeviceChannel.calling = true;
+                RecordHelper.getInstance().stopCancelRecordByOther(true);
+                EventBus.getDefault().post(new MessageEvent(true, Constants.EVENT_CLEAR_IM));
+                if (Constants.Companion.getPhoneState() == Constants.PHONE_INCOMING) {
+                    //发送tcp
+                    if (Constants.Companion.getPhoneDataVO() != null) {
+                        TcpModel tcpModel = PhoneUtil.phoneAccept(Constants.Companion.getDeviceId(), Constants.Companion.getPhoneDataVO());
+                        TcpCallback transaction = new TcpCallback(tcpModel.getTid()) {
+                            @Override
+                            public void onSuccess(JSONObject jsonObject) {
+                                //
+                            }
+
+                            @Override
+                            public void onFailed(JSONObject jsonObject) {
+                                //
+                            }
+                        };
+                        TcpClient.getInstance().sendTcp(tcpModel, true, transaction);
+
+                        /*NettyClient.Companion.getInstance().sendMsg(tcpModel.toJson()).subscribe(it -> {
+                            if (it) {
+                                Log.d(TAG, "TCP.发送消息完成");
+                            } else {
+                                Log.e(TAG, "TCP.发送消息失败");
+                                HandleTcpConnect.Companion.getInstance().tcpReConnectWithMsgShow();
+                            }
+                        });*/
+                    }
+                } else if (Constants.Companion.getPhoneState() == Constants.PHONE_IDLE) {
+                    Constants.Companion.setPhoneState(Constants.PHONE_OUTGOING);
+                } else {
+                    Constants.Companion.setPhoneState(Constants.PHONE_CALLING);
+                }
+
+
+                //非白名单号码拦截并删除记录
+                if (Constants.Companion.getPhoneWhiteList() != null) {
+                    for (String s : Constants.Companion.getPhoneWhiteList()) {
+                        if (s.contains(extraIncomingNumber)) { //号码在白名单内修改通话状态,并退出方法
+                            return;
+                        }
+                    }
+                    //循环结束,说明来电号码不在白名单内,挂断来电
+                    showMessage("非法号码:" + extraIncomingNumber);
+                    Constants.Companion.setPhoneState(Constants.PHONE_IDLE);
+                    boolean res = PhoneCallUtil.endCall(context);
+                    Log.d(TAG, "end call result: " + res);
+                    PhoneCallUtil.deleteCallLog(context, extraIncomingNumber);
+                }
+
+//                //非白名单号码拦截并删除记录
+//                if (!Constants.Companion.getPhoneWhiteList().contains(extraIncomingNumber)) {
+//                    showMessage("非法来电!");
+//                    Constants.Companion.setPhoneState(Constants.PHONE_IDLE);
+//                    boolean res = PhoneCallUtil.endCall(context);
+//                    Log.d(TAG, "end call result22222: " + res);
+//                    PhoneCallUtil.deleteCallLog(context, extraIncomingNumber);
+//                }
+            }
+        }
+    }
+}

+ 987 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/service/WdKeepAliveService.kt

@@ -0,0 +1,987 @@
+package com.wdkl.ncs.android.component.home.service
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.*
+import android.support.v4.media.session.MediaSessionCompat
+import android.text.TextUtils
+import android.util.Log
+import android.view.KeyEvent
+import com.alibaba.fastjson.JSON
+import com.google.common.base.Strings
+import com.google.gson.FieldNamingPolicy
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import com.google.gson.reflect.TypeToken
+import com.wdkl.ncs.android.component.home.R
+import com.wdkl.ncs.android.component.home.activity.AppUpdateActivity
+import com.wdkl.ncs.android.component.home.activity.NewCallListActivity
+import com.wdkl.ncs.android.component.home.activity.NewEventListActivity
+import com.wdkl.ncs.android.component.home.settingconfig.SettingConfig
+import com.wdkl.ncs.android.component.home.util.*
+import com.wdkl.ncs.android.lib.base.BaseApplication
+import com.wdkl.ncs.android.lib.utils.AppTool
+import com.wdkl.ncs.android.lib.utils.getJsonString
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.lib.vo.MessageEvent
+import com.wdkl.ncs.android.middleware.api.ApiManager
+import com.wdkl.ncs.android.middleware.common.Constants
+import com.wdkl.ncs.android.middleware.model.dos.ChannelImDO
+import com.wdkl.ncs.android.middleware.model.dos.PartSettingDO
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
+import com.wdkl.ncs.android.middleware.model.vo.WatchContactVO
+import com.wdkl.ncs.android.middleware.tcp.TcpClient
+import com.wdkl.ncs.android.middleware.tcp.TcpClientHandler
+import com.wdkl.ncs.android.middleware.tcp.channel.ChannelImUtil
+import com.wdkl.ncs.android.middleware.tcp.channel.DeviceChannel
+import com.wdkl.ncs.android.middleware.tcp.channel.ImUtil
+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.RoleTypeEnum
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpAction
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpType
+import com.wdkl.ncs.android.middleware.utils.ContactHelper
+import com.wdkl.ncs.keepbackground.work.AbsWorkService
+import com.wdkl.rtc.rtc.AudioFocusManager
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import java.util.*
+import java.util.concurrent.ConcurrentLinkedQueue
+
+class WdKeepAliveService : AbsWorkService() {
+    val TAG = "WdKeepAliveService"
+
+    companion object instance {
+        var updateLastTime: Long = System.currentTimeMillis() / 1000
+        var sosQueue: ConcurrentLinkedQueue<TcpModel> = ConcurrentLinkedQueue<TcpModel>()
+
+        var mNewEventListActive = false
+        var instanceCreated = false
+        var mNewCallListActive = false
+    }
+
+
+    private var mediaSessionCompat: MediaSessionCompat? = null
+    private lateinit var audioFocusManager: AudioFocusManager
+
+    private var mIsRunning = false
+    private lateinit var netWorkChangeReceiver: NetWorkChangeReceiver
+    lateinit var mVibrator: Vibrator
+
+    private lateinit var receiver: PhoneStateReceiver
+
+    //当前播放留言状态: 0-无,1-个人留言,2-群留言
+    private var imPlayState = 0
+    //群留言列表
+    private var channelImList = ArrayList<ChannelImDO>()
+    //当前播放的群留言id
+    private var channelImId = 0
+    private var waitingLoop = false
+    private var waitingCall = false
+
+    private var warningTips = false
+    private val keepHandler: Handler = object : Handler(Looper.getMainLooper()) {
+        override fun handleMessage(msg: Message) {
+            warningTips = false
+        }
+    }
+
+    override fun startWork() {
+        EventBus.getDefault().register(this)
+
+        //全局调用一次
+        Thread {
+            Log.d(TAG, "Constants.tcpServer: " + Constants.tcpServer)
+
+            while (TextUtils.isEmpty(Constants.tcpServer)) {
+                Thread.sleep(3000)
+            }
+            TcpClient.getInstance().init(Constants.tcpServer, Constants.tcpPort, Constants.heartBeat)
+            instanceCreated = true
+        }.start()
+
+        // 动态注册广播接收器
+        // 过滤器
+        val intentFilter = IntentFilter()
+        // 系统的网络被更改的过滤器
+        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE")
+        netWorkChangeReceiver = NetWorkChangeReceiver()
+        registerReceiver(netWorkChangeReceiver, intentFilter)
+
+        checkNetState()
+
+        //初始化TTS服务
+        SpeechUtil.getInstance().init(BaseApplication.appContext, object : ISpeechCallback {
+            override fun initFailed() {
+                Log.d(TAG, "TTS init failed")
+            }
+
+            override fun initSuccess() {
+                Log.d(TAG, "TTS init success")
+                SpeechUtil.getInstance().startSpeechThread()
+            }
+        })
+
+        SoundPoolManager.getInstance().init()
+
+        //震动
+        mVibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+
+        //监听语音通话
+        registerCallListener()
+
+        audioFocusManager = AudioFocusManager(BaseApplication.appContext)
+        initMediaSession()
+    }
+
+    fun registerCallListener() {
+        val intentFilter = IntentFilter()
+        intentFilter.addAction("android.intent.action.NEW_OUTGOING_CALL")
+        intentFilter.addAction("android.intent.action.PHONE_STATE")
+        receiver = PhoneStateReceiver()
+        registerReceiver(receiver, intentFilter)
+    }
+
+    fun unRegisterCallListener() {
+        if (receiver != null) {
+            unregisterReceiver(receiver)
+        }
+    }
+
+    override fun isWorkRunning(): Boolean {
+        return mIsRunning
+    }
+
+    override fun needStartWorkService(): Boolean {
+        return true
+    }
+
+    override fun stopWork() {
+        //注销监听
+        unRegisterCallListener()
+        releaseMediaSession()
+        keepHandler.removeCallbacksAndMessages(null)
+
+        SoundPoolManager.getInstance().release()
+        SpeechUtil.getInstance().release()
+        EventBus.getDefault().unregister(this)
+        if (netWorkChangeReceiver != null) {
+            try {
+                unregisterReceiver(netWorkChangeReceiver)
+            } catch (ex: Exception) {
+
+            }
+        }
+        instanceCreated = false
+    }
+
+    override fun onBindService(p0: Intent?, p1: Void?): IBinder {
+        return Messenger(Handler()).binder
+    }
+
+    override fun onServiceKilled() {
+        EventBus.getDefault().unregister(this)
+        if (netWorkChangeReceiver != null) {
+            try {
+                unregisterReceiver(netWorkChangeReceiver)
+            } catch (ex: Exception) {
+
+            }
+        }
+        instanceCreated = false
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        when (messageEvent.tag) {
+            //拨出成功 || 有来电
+            1 -> {
+                Util.wakeUpAndUnlock()
+
+                SpeechUtil.getInstance().stopSpeak()
+                MediaPlayHelper.getInstance().releaseMusic();
+
+                val tcpModel = messageEvent.getMessage() as TcpModel
+                if (tcpModel.type == TcpType.VOICE) {
+//                var gson = GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create()
+                    val interactionVO = Gson().fromJson(tcpModel.data.toString(), InteractionVO::class.java)
+
+                    if (tcpModel.action == TcpAction.VoiceAction.CALL) {  //有来电
+                        //取消掉语音留言录音
+                        RecordHelper.getInstance().stopCancelRecordByOther(true)
+                        //停止媒体播放,比如正在播放留言
+                        MediaPlayHelper.getInstance().stopMusic(true)
+                        RingPlayHelper.stopRingTone()
+                        Log.i(TAG, "来电:" + JSON.toJSONString(interactionVO))
+
+                        //加入呼叫列表
+                        Constants.newCallList.add(tcpModel)
+                        //语音播报
+                        if (!DeviceChannel.calling && !RecordHelper.getInstance().isRecording) {
+                            val frameName = interactionVO.fromFrameFullName.replace("-", "")
+                            SpeechUtil.getInstance().speak(frameName + "呼叫," + frameName + "呼叫")
+
+                            //更新界面
+                            if (mNewCallListActive) {
+                                EventBus.getDefault().post(MessageEvent(tcpModel, Constants.EVENT_UPDATE_CALL))
+                            } else {
+                                val intent = Intent()
+                                intent.setClass(this, NewCallListActivity::class.java)
+                                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                                startActivity(intent)
+                            }
+                        } else {
+                            if (!waitingCall) {
+                                Thread {
+                                    while (DeviceChannel.calling || RecordHelper.getInstance().isRecording) {
+                                        waitingCall = true
+                                        Thread.sleep(500)
+                                    }
+                                    waitingCall = false
+
+                                    if (Constants.newCallList.size > 0) {
+                                        //更新界面
+                                        if (mNewCallListActive) {
+                                            EventBus.getDefault().post(MessageEvent(tcpModel, Constants.EVENT_UPDATE_CALL))
+                                        } else {
+                                            val intent = Intent()
+                                            intent.setClass(this, NewCallListActivity::class.java)
+                                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                                            startActivity(intent)
+                                        }
+                                    }
+                                }.start()
+                            }
+                        }
+
+
+                        /*DeviceChannel.calling = true
+                        var roomId: String
+                        if (interactionVO.fromDeviceType == DeviceTypeEnum.SIMULATE_BED_DEVICE.value()) {
+                            //模拟分机呼叫,通话由手机端创建
+                            DeviceChannel.callId = interactionVO.fromDeviceId
+                            roomId = Constants.sipId!!
+                        } else {
+                            if (interactionVO.fromDeviceId.equals(Constants.deviceId)) {
+                                DeviceChannel.callId = interactionVO.toDeviceId
+                                roomId = interactionVO.toSipId
+                            } else {
+                                DeviceChannel.callId = interactionVO.fromDeviceId
+                                roomId = interactionVO.fromSipId
+                            }
+                        }
+
+                        //启动 activity
+                        val intent = Intent(this, CallSingleActivity::class.java)
+                        intent.putExtra(CallSingleActivity.EXTRA_ROOM_ID, roomId)
+                        intent.putExtra(CallSingleActivity.EXTRA_MO, false)
+                        intent.putExtra(CallSingleActivity.EXTRA_AUDIO_ONLY, true)
+                        intent.putExtra(CallSingleActivity.EXTRA_TCPMODEL, tcpModel)
+                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                        startActivity(intent)*/
+                    }
+                }
+            }
+
+            /*11 -> {
+                //取消掉语音留言录音
+                RecordHelper.getInstance().stopCancelRecordByOther(true)
+
+                Util.wakeUpAndUnlock()
+                SpeechUtil.getInstance().stopSpeak()
+                val tcpModel = messageEvent.getMessage() as TcpModel
+                if (tcpModel.type == TcpType.VOICE && tcpModel.action == TcpAction.VoiceAction.CALL) {
+
+                    val interactionVO = Gson().fromJson(tcpModel.data.toString(), InteractionVO::class.java)
+
+                    DeviceChannel.calling = true
+                    var roomId: String
+                    if (interactionVO.fromDeviceId.equals(Constants.deviceId)) {
+                        DeviceChannel.callId = interactionVO.toDeviceId
+                        roomId = interactionVO.toSipId
+                    } else {
+                        DeviceChannel.callId = interactionVO.fromDeviceId
+                        roomId = interactionVO.fromSipId
+                    }
+                    Log.i(TAG, "来电:" + JSON.toJSONString(interactionVO))
+
+                    //启动 activity
+                    val intent = Intent(this, CallSingleActivity::class.java)
+                    intent.putExtra(CallSingleActivity.EXTRA_ROOM_ID, roomId)
+                    intent.putExtra(CallSingleActivity.EXTRA_MO, false)
+                    intent.putExtra(CallSingleActivity.EXTRA_AUDIO_ONLY, true)
+                    intent.putExtra(CallSingleActivity.EXTRA_TCPMODEL, tcpModel);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    startActivity(intent)
+
+                }
+            }*/
+
+            Constants.EVENT_NEW_TCP -> {
+                //先点亮屏幕,然后再通知到界面
+                Util.wakeUpAndUnlock()
+
+                val tcpModel = messageEvent.getMessage() as TcpModel
+                if (tcpModel.getAction() == TcpAction.VoiceAction.SUCCESS) {
+                    EventBus.getDefault().post(MessageEvent(tcpModel, 1))
+                } else if (tcpModel.getAction() == TcpAction.VoiceAction.CALL) {
+                    EventBus.getDefault().post(MessageEvent(tcpModel, 1))
+
+                    /*if (DeviceChannel.calling) {
+                        Log.d(TAG, "通话中,来电 " + tcpModel.getFromId() + "<>" + DeviceChannel.callId)
+                        //相同来源,重新建立通话
+                        if (tcpModel.getFromId() == DeviceChannel.callId) {
+                            EventBus.getDefault().post(MessageEvent(tcpModel, 11))
+                        }
+                    } else { //得到通话
+                        EventBus.getDefault().post(MessageEvent(tcpModel, 1))
+                    }*/
+                } else if (tcpModel.getAction() == TcpAction.VoiceAction.ACCEPT //我方呼出,对方接受
+                        || tcpModel.getAction() == TcpAction.VoiceAction.REJECT //我方呼出,对方拒绝
+                        || tcpModel.getAction() == TcpAction.VoiceAction.CALLING //我方呼出,对方通话中
+                        || tcpModel.getAction() == TcpAction.VoiceAction.HANDOFF
+                        || tcpModel.getAction() == TcpAction.VoiceAction.FAILED //我方呼出,对方不在线,设备离线或其它错误
+                        || tcpModel.getAction() == TcpAction.VoiceAction.CANCEL
+                        || tcpModel.getAction() == TcpAction.VoiceAction.VOICE_OFF) {
+
+                    if (tcpModel.getAction() === TcpAction.VoiceAction.CANCEL || tcpModel.action == TcpAction.VoiceAction.VOICE_OFF) {
+                        //对方取消呼叫或者呼叫已被其他主机处理(接听或拒绝)
+                        val interactionVO = Gson().fromJson(tcpModel.data.toString(), InteractionVO::class.java)
+                        val iterator = Constants.newCallList.iterator()
+                        while (iterator.hasNext()) {
+                            val it = iterator.next()
+                            val interaction = Gson().fromJson(it.data.toString(), InteractionVO::class.java)
+                            if (interaction.id.equals(interactionVO.id)) {
+                                iterator.remove()
+                            }
+                        }
+
+                        //更新界面
+                        if (mNewCallListActive) {
+                            EventBus.getDefault().post(MessageEvent(tcpModel, Constants.EVENT_UPDATE_CALL))
+                        }
+                    }
+
+                    EventBus.getDefault().post(MessageEvent(tcpModel, 2))
+                }
+            }
+
+            //事件与语音留言通道
+            3 -> {
+                //Util.wakeUpAndUnlock()
+                val tcpModel = messageEvent.getMessage() as TcpModel
+                if (tcpModel.action == TcpAction.CHANNELIMAction.MSG) {
+                    val channelData = Gson().fromJson(tcpModel.data.toString(), ChannelImDO::class.java)
+                    if (!DeviceChannel.calling && !RecordHelper.getInstance().isRecording) {
+                        //群留言
+                        playChannelVoiceMsg(channelData)
+                    } else {
+                        //通话中或正在留言时有新的群留言进来则将其加入群留言队列中,待留言结束后自动播放
+                        channelImList.add(channelData)
+                        if (!waitingLoop) {
+                            Thread {
+                                while (DeviceChannel.calling || RecordHelper.getInstance().isRecording) {
+                                    waitingLoop = true
+                                    Thread.sleep(500)
+                                }
+                                waitingLoop = false
+                                if (channelImList.size > 0) {
+                                    startPlayChannelIm(channelImList.get(0))
+                                }
+                            }.start()
+                        }
+                    }
+                } else if (tcpModel.action == TcpAction.IMAction.MSG) {
+                    //如果当前不在通话中则直接播放语音留言,并将该记录置为已读状态或当前正在留言中则等待留言结束再切换到留言事件界面
+                    if (DeviceChannel.calling || RecordHelper.getInstance().isRecording) {
+                        val interactionVO = Gson().fromJson(tcpModel.data.toString(), InteractionVO::class.java)
+                        if (Constants.oldEvent) {
+                            Constants.eventList.clear()
+                        }
+                        Constants.oldEvent = false
+                        Constants.eventList.add(interactionVO)
+
+                        if (!mNewEventListActive) {
+                            Thread(Runnable {
+                                while (DeviceChannel.calling || RecordHelper.getInstance().isRecording) {
+                                    Thread.sleep(400)
+                                }
+
+                                Util.wakeUpAndUnlock()
+                                SoundPoolManager.getInstance().playSound(1)
+
+                                val intent = Intent()
+                                intent.setClass(this, NewEventListActivity::class.java)
+                                //intent.putExtra("tcpModelStr", tcpModel.toJson())
+                                //intent.putExtra("newEvent", true)
+                                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                                startActivity(intent)
+                            }).start()
+                        } else {
+                            Util.wakeUpAndUnlock()
+                            EventBus.getDefault().post(MessageEvent(tcpModel, Constants.EVENT_UPDATE_EVENT))
+                        }
+                    } else {
+                        val interaction = Gson().fromJson(tcpModel.data.toString(), InteractionVO::class.java)
+                        playVoiceMsg(interaction)
+                        val msgTcpModel = ImUtil.imRead(Constants.deviceId, interaction.fromDeviceId, interaction.id)
+                        TcpClient.getInstance().sendTcp(msgTcpModel, false, null)
+
+                        /*NettyClient.instance.sendMsg(msgTcpModel.toJson()).subscribe {
+                            if (it) {
+                                Log.d(TAG, "TCP.发送消息完成")
+                            } else {
+                                Log.e(TAG, "TCP.发送消息失败")
+                                HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+                            }
+                        }*/
+                    }
+                } else if (tcpModel.action == TcpAction.EventAction.KEY_CLICK) { // 收到新事件
+                    //取消掉语音留言录音
+                    RecordHelper.getInstance().stopCancelRecordByOther(true)
+
+                    //语音播报处理
+                    eventSpeechOut(tcpModel)
+                    //震动提醒
+                    handleVibrator()
+
+                    val interactionVO = Gson().fromJson(tcpModel.data.toString(), InteractionVO::class.java)
+                    if (Constants.oldEvent) {
+                        Constants.eventList.clear()
+                    }
+                    Constants.oldEvent = false
+                    Constants.eventList.add(interactionVO)
+
+                    if (!mNewEventListActive) {
+                        Thread(Runnable {
+                            while (DeviceChannel.calling) {
+                                Thread.sleep(400)
+                            }
+
+                            Util.wakeUpAndUnlock()
+
+                            val intent = Intent()
+                            intent.setClass(this, NewEventListActivity::class.java)
+                            //intent.putExtra("tcpModelStr", tcpModel.toJson())
+                            //intent.putExtra("newEvent", true)
+                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                            startActivity(intent)
+                        }).start()
+                    } else {
+                        EventBus.getDefault().post(MessageEvent(tcpModel, Constants.EVENT_UPDATE_EVENT))
+                    }
+                } else if (tcpModel.action == TcpAction.EventAction.CANCEL || tcpModel.action == TcpAction.EventAction.COMPLETED) { // 收到事件取消 或事件完成
+                    //语音播报处理
+                    eventSpeechOut(tcpModel)
+                    //震动提醒
+                    handleVibrator()
+                    //消除事件
+                    val interactionVO = Gson().fromJson(tcpModel.data.toString(), InteractionVO::class.java)
+                    var iterator = Constants.eventList.iterator()
+                    while (iterator.hasNext()) {
+                        val it = iterator.next()
+                        if (it.id.equals(interactionVO.id)) {
+                            iterator.remove()
+                        }
+                    }
+
+                    if (mNewEventListActive) {
+                        EventBus.getDefault().post(MessageEvent(tcpModel, Constants.EVENT_UPDATE_EVENT))
+                    }
+                }
+            }
+
+            4 -> { //更新通讯录,更新通话白名单列表
+                Thread(Runnable {
+                    try {
+                        var jsonStr = ""
+                        val contactVos = ApiManager.API_WatchManageDevice.getWatchContactList(Constants.deviceId).map {
+                            jsonStr = it.getJsonString()
+                            val gson = GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create()
+                            val contactList: List<WatchContactVO> = gson.fromJson(jsonStr, object : TypeToken<List<WatchContactVO>>() {}.type)
+                            return@map contactList
+                        }.blockingSingle()
+
+                        if (contactVos != null && contactVos.size > 0) {
+                            for (contactVO in contactVos) {
+                                if (!Strings.isNullOrEmpty(contactVO.name) && !Strings.isNullOrEmpty(contactVO.phoneNumber)) {
+                                    ContactHelper.setContact(this, contactVO.name, contactVO.phoneNumber)
+                                }
+                            }
+                        }
+
+                        //更新白名单列表
+                        val whiteList = ApiManager.API_WatchManageDevice.getPhoneWhiteList(Constants.deviceId).map {
+                            jsonStr = it.getJsonString()
+                            val gson = GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create()
+                            val whiteList: List<String> = gson.fromJson(jsonStr, object : TypeToken<List<String>>() {}.type)
+                            return@map whiteList
+                        }.blockingSingle()
+
+
+                        if (whiteList != null && whiteList.size > 0) {
+                            Constants.phoneWhiteList.clear()
+                            Constants.phoneWhiteList.addAll(whiteList)
+                        }
+                    } catch (ex: Exception) {
+                        ex.printStackTrace()
+                    }
+
+                }).start()
+
+
+            }
+
+            //SOS通道
+            999 -> {
+                Util.wakeUpAndUnlock()
+                val sosTcpModel = messageEvent.getMessage() as TcpModel
+                val sosInteractionVO = Gson().fromJson(sosTcpModel.data.toString(), InteractionVO::class.java)
+                if (sosTcpModel.action === TcpAction.SOSAction.CANCEL) {
+                    //先停止铃声或其他音频播放
+                    SpeechUtil.getInstance().stopSpeak()
+                    MediaPlayHelper.getInstance().stopMusic(true)
+                    RingPlayHelper.stopRingTone()
+
+                    if (mVibrator != null) {
+                        mVibrator.cancel()
+                    }
+
+                    if (!Strings.isNullOrEmpty(sosInteractionVO.toRoleName)) {
+                        showMessage(sosInteractionVO.toRoleName + " " + sosInteractionVO.toMemberName + " 已响应")
+                    } else {
+                        showMessage("已响应")
+                    }
+
+                    var iterator = Constants.eventList.iterator()
+                    while (iterator.hasNext()) {
+                        val it = iterator.next()
+                        if (it.id == sosInteractionVO.id) {
+                            Constants.eventList.remove(it)
+                        }
+                    }
+                    EventBus.getDefault().post(MessageEvent(sosTcpModel, Constants.EVENT_UPDATE_EVENT))
+
+                } else if (sosTcpModel.action === TcpAction.SOSAction.CALL) {
+                    //取消掉语音留言录音
+                    RecordHelper.getInstance().stopCancelRecordByOther(true)
+
+                    //紧急呼叫去重
+                    for (e in Constants.eventList) {
+                        if (e.id == sosInteractionVO.id) {
+                            return
+                        }
+                    }
+
+                    if (Constants.oldEvent) {
+                        Constants.eventList.clear()
+                    }
+                    Constants.oldEvent = false
+                    Constants.eventList.add(sosInteractionVO)
+                    //语音播报处理
+                    eventSpeechOut(sosTcpModel)
+                    //震动提醒
+                    handleVibrator()
+
+                    if (!mNewEventListActive) {
+                        Thread {
+                            while (DeviceChannel.calling) {
+                                Thread.sleep(400)
+                            }
+
+                            if (sosTcpModel.action == TcpAction.SOSAction.CALL) {
+                                val intent = Intent()
+                                intent.setClass(this, NewEventListActivity::class.java)
+                                //intent.putExtra("tcpModelStr", tcpModelIn.toJson())
+                                //intent.putExtra("newEvent", true)
+                                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                                startActivity(intent)
+                            }
+                        }.start()
+                    } else {
+                        EventBus.getDefault().post(MessageEvent(sosTcpModel, Constants.EVENT_UPDATE_EVENT))
+                    }
+                }
+            }
+
+            //用户切换
+            Constants.EVENT_USER_CHANGE -> {
+                Thread(Runnable {
+                    while (DeviceChannel.calling) {    //通话中不处理,一直等待至结束
+                        Thread.sleep(400)
+                    }
+                    DeviceChannel.calling = true    //置于通话状态中,不可呼叫
+                    Util.wakeUpAndUnlock()
+                    AppUtils.restartApp()
+                }).start()
+            }
+
+            //设备切换
+            Constants.EVENT_DEVICE_CHANGE -> {
+                Thread(Runnable {
+                    while (DeviceChannel.calling) {    //通话中不处理,一直等待至结束
+                        Thread.sleep(400)
+                    }
+
+                    DeviceChannel.calling = true    //置于通话状态中,不可呼叫
+                    Util.wakeUpAndUnlock()
+                    val tcpModel = messageEvent.getMessage() as TcpModel
+                    if (tcpModel.action == TcpAction.DeviceAction.RESTART) {
+                        AppUtils.restartApp()
+                    }
+                }).start()
+            }
+
+            //系统设置
+            Constants.EVENT_DEVICE_SETTING -> {
+                Thread(Runnable {
+                    var jsonStr = ""
+                    val partSettingDO = ApiManager.API_WatchDeviceInfoAndTCPMailingAddress.getDeviceSettingData(Constants.partId).map {
+                        jsonStr = it.getJsonString()
+                        val gson = GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create()
+                        val partSettingDO = gson.fromJson(jsonStr, PartSettingDO::class.java)
+                        return@map partSettingDO
+                    }.blockingSingle()
+
+                    Log.i(TAG, "收到系统设置 " + jsonStr)
+                    if (partSettingDO != null) {
+                        if (partSettingDO.transferDuration != null && partSettingDO.transferDurationLeader != null) {
+                            if (Constants.roleId != null) {
+                                if (Constants.roleId == RoleTypeEnum.NURSE_HEAD.value()) {
+                                    //护士组长
+                                    SettingConfig.setCountdownTime(application, partSettingDO.transferDurationLeader)
+                                } else if (Constants.roleId == RoleTypeEnum.NURSE.value()) {
+                                    //护士
+                                    SettingConfig.setCountdownTime(application, partSettingDO.transferDuration)
+                                }
+                            }
+                        }
+                    }
+                }).start()
+            }
+
+            //APP升级
+            Constants.EVENT_APP_UPDATE -> {
+                Thread(Runnable {
+                    while (DeviceChannel.calling) {    //通话中不处理,一直等待至结束
+                        Thread.sleep(400)
+                    }
+
+                    //DeviceChannel.calling = true    //置于通话状态中,不可呼叫
+                    Util.wakeUpAndUnlock()
+                    val tcpModel = messageEvent.getMessage() as TcpModel
+                    if (tcpModel.action == TcpAction.DeviceAction.APP_UPDATE) {
+                        if ((System.currentTimeMillis() / 1000) - updateLastTime > 10 && !AppUpdateActivity.opened) {   //大于10秒可继续升级
+                            updateLastTime = System.currentTimeMillis() / 1000
+                            val intent = Intent()
+                            intent.setClass(this, AppUpdateActivity::class.java)
+                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                            startActivity(intent)
+                        }
+                    }
+                }).start()
+            }
+
+            //网络断开
+            Constants.EVENT_TCP_BREAK -> {
+                Log.w(TAG, "EVENT TCP BREAK")
+                //MediaPlayHelper.getInstance().playResMusic(R.raw.tcp_disconnect, 1.0f, false)
+                //RingPlayHelper.playRingTone(BaseApplication.appContext, R.raw.tcp_disconnect, false)
+            }
+
+            //留言播放完成
+            Constants.EVENT_IM_PLAY_DONE -> {
+                //将播放完成的群留言从列表中删除
+                val iterator = channelImList.iterator()
+                while (iterator.hasNext()) {
+                    val it = iterator.next()
+                    if (it.id.equals(channelImId)) {
+                        iterator.remove()
+                    }
+                }
+                //群留言列表还有留言则继续播放
+                if (channelImList.size > 0) {
+                    imPlayState = 2
+                    channelImId = channelImList.get(0).id
+                    sendChannelImRead(channelImList.get(0))
+                    MediaPlayHelper.getInstance().playUrlMusic(ApiManager.urlManager.device_url + channelImList.get(0).audioPath, 1f, false)
+                } else {
+                    imPlayState = 0
+                }
+                ImPlayDialogHelper.dismissIMDialog()
+            }
+
+            //电话时停止播放留言并清除群留言列表
+            Constants.EVENT_CLEAR_IM -> {
+                val stop = messageEvent.getMessage() as Boolean
+                channelImList.clear()
+                if (stop) {
+                    MediaPlayHelper.getInstance().stopMusic(true)
+                }
+                imPlayState = 0
+            }
+        }
+    }
+
+    private fun eventSpeechOut(model: TcpModel) {
+        if (model.type == TcpType.IM && model.action == TcpAction.IMAction.MSG) {
+            SoundPoolManager.getInstance().playSound(1)
+            return
+        }
+
+        //tts未初始化或者初始化不成功时重新初始化
+        if (SpeechUtil.getInstance().textToSpeech == null || Constants.ttsState != 2) {
+            initSpeech()
+        }
+
+        //先停止铃声或其他音频播放
+        SpeechUtil.getInstance().stopSpeak()
+        MediaPlayHelper.getInstance().stopMusic(true)
+        RingPlayHelper.stopRingTone()
+
+
+        //播报语音提醒
+        val interactionVO = Gson().fromJson<InteractionVO>(model.data.toString(), InteractionVO::class.java)
+        if (model.type == TcpType.EVENT && model.action == TcpAction.EventAction.KEY_CLICK) {
+            val eventStr = Util.appendSpace(interactionVO.fromFrameFullName.replace("-", ",")) + ", " + interactionVO.data
+
+            if (Constants.ttsState == 2) {
+                SpeechUtil.getInstance().speak("您有新的事件待处理, " + eventStr)
+            } else {
+                //MediaPlayHelper.getInstance().playResMusic(R.raw.new_event, 1.0f, false)
+                RingPlayHelper.playRingTone(BaseApplication.appContext, R.raw.new_event, false)
+            }
+        } else if (model.type == TcpType.SOS && model.action == TcpAction.SOSAction.CALL) {
+            AppTool.Time.delay(500) {
+                //MediaPlayHelper.getInstance().playResMusic(R.raw.sos, 1.0f, true)
+                RingPlayHelper.playRingTone(BaseApplication.appContext, R.raw.sos, true)
+            }
+        } else if (model.type == TcpType.EVENT && model.action == TcpAction.EventAction.CANCEL) { // 事件取消
+            val eventStr = Util.appendSpace(interactionVO.fromFrameFullName.replace("-", ",")) + ",已取消 " + interactionVO.data + "事件"
+
+            if (Constants.ttsState == 2) {
+                SpeechUtil.getInstance().speak(eventStr)
+            } else {
+                AppTool.Time.delay(500) {
+                    //MediaPlayHelper.getInstance().playResMusic(R.raw.new_event, 1.0f, false)
+                    RingPlayHelper.playRingTone(BaseApplication.appContext, R.raw.new_event, false)
+                }
+            }
+        } else if (model.type == TcpType.EVENT && model.action == TcpAction.EventAction.COMPLETED) { //事件完成
+            val eventStr = Util.appendSpace(interactionVO.fromFrameFullName.replace("-", ",")) + ", " + interactionVO.data + "事件已完成"
+
+            if (Constants.ttsState == 2) {
+                SpeechUtil.getInstance().speak(eventStr)
+            } else {
+                AppTool.Time.delay(500) {
+                    //MediaPlayHelper.getInstance().playResMusic(R.raw.new_event, 1.0f, false)
+                    RingPlayHelper.playRingTone(BaseApplication.appContext, R.raw.new_event, false)
+                }
+            }
+        }
+    }
+
+    private fun handleVibrator() {
+        /*mVibrator.cancel()
+        //开启振动后 等待0.1s振动 振动2s 等待1s 振动2s 等待1s
+        val pattern = longArrayOf(100, 2000, 1000, 2000)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            mVibrator.vibrate(VibrationEffect.createWaveform(pattern, -1))
+        } else {
+            mVibrator.vibrate(pattern, -1)
+        }*/
+    }
+
+    private fun playVoiceMsg(interactionVO: InteractionVO) {
+        synchronized(this) {
+            channelImList.clear()
+            showMessage("播放留言...")
+            MediaPlayHelper.getInstance().stopMusic(true)
+            SoundPoolManager.getInstance().playSound(1)
+            AppTool.Time.delay(1000) {
+                imPlayState = 1
+                MediaPlayHelper.getInstance().playUrlMusic(ApiManager.urlManager.device_url + interactionVO.data, 1f, false)
+            }
+        }
+    }
+
+    private fun playChannelVoiceMsg(channelImDO: ChannelImDO) {
+        synchronized(this) {
+            //如果当前正在播放留言则将新的群留言加入待播放留言列表
+            if ((imPlayState == 2 || imPlayState == 1) /*&& MediaPlayHelper.getInstance().isMediaPlaying()*/) {
+                channelImList.add(channelImDO)
+            } else {
+                startPlayChannelIm(channelImDO)
+            }
+        }
+    }
+
+    private fun startPlayChannelIm(channelIm: ChannelImDO) {
+        imPlayState = 2
+        showMessage("播放群留言...")
+        MediaPlayHelper.getInstance().stopMusic(true)
+        SoundPoolManager.getInstance().playSound(4)
+        AppTool.Time.delay(1000) {
+            imPlayState = 2
+            channelImId = channelIm.id
+            sendChannelImRead(channelIm)
+            MediaPlayHelper.getInstance().playUrlMusic(ApiManager.urlManager.device_url + channelIm.audioPath, 1f, false)
+        }
+    }
+
+    @SuppressLint("CheckResult")
+    private fun sendChannelImRead(channelIm: ChannelImDO) {
+        val imTcpModel = ChannelImUtil.channelImRead(Constants.deviceId, channelIm)
+        TcpClient.getInstance().sendTcp(imTcpModel, false, null)
+
+        /*NettyClient.instance.sendMsg(imTcpModel.toJson()).subscribe {
+            if (it) {
+                Log.d(TAG, "TCP.发送消息完成")
+            } else {
+                Log.e(TAG, "TCP.发送消息失败")
+                HandleTcpConnect.instance.tcpReConnectWithMsgShow()
+            }
+        }*/
+    }
+
+    private fun initSpeech() {
+        val speechCallback = object : ISpeechCallback {
+            override fun initFailed() {
+                //重新加载一次
+                SpeechUtil.getInstance().release()
+                SpeechUtil.getInstance().init(BaseApplication.appContext, object : ISpeechCallback {
+                    override fun initFailed() {
+                        //
+                    }
+
+                    override fun initSuccess() {
+                        SpeechUtil.getInstance().startSpeechThread()
+                    }
+                })
+            }
+
+            override fun initSuccess() {
+                SpeechUtil.getInstance().startSpeechThread()
+            }
+
+        }
+        SpeechUtil.getInstance().release()
+        //TTS初始化
+        SpeechUtil.getInstance().init(BaseApplication.appContext, speechCallback)
+    }
+
+    var checkNetStateTimer: Timer? = null
+    var checkNetStateTimerTask: TimerTask? = null
+
+    /**
+     * 重连wdklRTC
+     */
+    private fun checkNetState() {
+        if (!mIsRunning) {
+            mIsRunning = true
+            if (checkNetStateTimer != null) checkNetStateTimer!!.purge()
+            if (checkNetStateTimerTask != null) checkNetStateTimerTask!!.cancel()
+            checkNetStateTimer = Timer()
+            checkNetStateTimerTask = object : TimerTask() {
+                override fun run() {
+                    if (!EventBus.getDefault().isRegistered(this@WdKeepAliveService)) {
+                        EventBus.getDefault().register(this@WdKeepAliveService)
+                    }
+                    Util.getCpuWakeLock(this@WdKeepAliveService)?.acquire(Constants.heartBeat * 1000L)
+                    reConnect()
+                }
+            }
+            checkNetStateTimer!!.schedule(checkNetStateTimerTask, 8000, Constants.heartBeat * 1000L)
+        }
+    }
+
+    fun reConnect() {
+        if (NetHelper.getInstance().getNetworkState(this@WdKeepAliveService) != NetHelper.NETWORK_NONE) {
+            if (!TcpClientHandler.getConnected()) {
+                TcpClient.getInstance().doConnect()
+            }
+        } /*else {
+            Thread(Runnable {
+                Thread.sleep(5000)
+                reConnect()
+            }).start()
+        }*/
+    }
+
+
+    //耳机线控监听
+    private fun initMediaSession() {
+        if (mediaSessionCompat != null) {
+            releaseMediaSession()
+        }
+        audioFocusManager.requestFocus()
+        mediaSessionCompat = MediaSessionCompat(BaseApplication.appContext, "MyMediaSession")
+        mediaSessionCompat!!.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS)
+        mediaSessionCompat!!.setActive(true)
+        mediaSessionCompat!!.setCallback(object : MediaSessionCompat.Callback() {
+            override fun onMediaButtonEvent(intent: Intent): Boolean {
+                val action = intent.action
+                if (action != null) {
+                    if (action.equals(Intent.ACTION_MEDIA_BUTTON)) {
+                        val keyEvent = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
+                        if (keyEvent!!.action == KeyEvent.ACTION_DOWN) {
+                            val keyCode = keyEvent.keyCode
+                            Log.i("wdkeepalive", "mediasession keycode $keyCode")
+                            when (keyCode) {
+                                KeyEvent.KEYCODE_HEADSETHOOK -> {
+                                    //Log.w("keepalive","headset hook...")
+                                    //showMessage("耳机按键按下")
+                                    //SoundPoolManager.getInstance().playSound(3)
+
+                                    //如果有呼叫或通话中按键则点亮屏幕并通知到通话界面
+                                    if (DeviceChannel.calling) {
+                                        Util.wakeUpAndUnlock()
+                                        EventBus.getDefault().post(MessageEvent("headset_hook", Constants.EVENT_HEADSET_HOOK))
+                                    }
+                                    //通话中或者某些特定界面不响应耳机按键留言,未绑定用户时也不响应按键
+                                    else if (Constants.channelId == -1) {
+                                        showMessage("请先建立频道")
+                                    } else if (!DeviceChannel.calling && Constants.allowVoiceMsg && !TextUtils.isEmpty(Constants.userName)) {
+                                        if (Constants.deviceType != 1) {
+                                            RecordHelper.getInstance().execRecordVoiceMsg()
+                                        }
+                                    } else {
+                                        //在某些特定界面不支持按键留言,如果按键的时候正在留言则停止录音并发送留言
+                                        if (RecordHelper.getInstance().isRecording) {
+                                            RecordHelper.getInstance().stopCancelRecordByOther(false)
+                                            IMDialogHelper.dismissIMDialog()
+                                        } else {
+                                            //当前不能按键留言
+                                            if (!warningTips) {
+                                                warningTips = true
+                                                keepHandler.sendEmptyMessageDelayed(0, 3000)
+                                                SoundPoolManager.getInstance().playSound(5)
+                                            }
+                                            showMessage("请使用屏幕按钮留言")
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                return super.onMediaButtonEvent(intent)
+            }
+        })
+    }
+
+    private fun releaseMediaSession() {
+        if (mediaSessionCompat != null) {
+            mediaSessionCompat!!.setCallback(null)
+            mediaSessionCompat!!.setActive(false)
+            mediaSessionCompat!!.release()
+            mediaSessionCompat = null
+        }
+        if (audioFocusManager != null) {
+            audioFocusManager.releaseAudioFocus()
+        }
+    }
+}

+ 95 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/settingconfig/SettingConfig.java

@@ -0,0 +1,95 @@
+package com.wdkl.ncs.android.component.home.settingconfig;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class SettingConfig {
+
+    private static final String SP_NAME = "SP_FUNCTION";
+
+    //呼叫转接时间设置
+    private static final String KEY_SP_COUNTDOWN_TIME = "KEY_SP_COUNTDOWN_TIME";
+    private static final int CountdownTime = 15;
+    private static final String KEY_SP_TRANSFER_CALL = "KEY_SP_TRANSFER_CALL";
+
+    //电话功能类型
+    private static final String KEY_SP_VOICE_CALL_TYPE = "KEY_SP_VOICE_CALL_TYPE";
+    public static final int SIP_CALL = 1;  //网络电话
+    public static final int VOICE_CALL = 2;  //普通电话
+
+    //默认语言
+    private static final String KEY_LANGUAGE_ID = "KEY_LANGUAGE_ID";
+    private static final String KEY_LANGUAGE_MODE = "KEY_LANGUAGE_MODE";
+
+    //是否使用sip通话
+    private static final String KEY_SP_SIP_ENABLE = "KEY_SP_SIP_ENABLE";
+
+    public static int getLanguageId(Context context) {
+        //0--auto, 1--English, 2--中文, 3--西班牙语, 4--俄语
+        return getSP(context).getInt(KEY_LANGUAGE_ID, 2);
+    }
+
+    public static void setLanguageId(Context context, int id) {
+        getEditor(context).putInt(KEY_LANGUAGE_ID, id).apply();
+    }
+
+    public static int getLanguageMode(Context context) {
+        return getSP(context).getInt(KEY_LANGUAGE_MODE, 0);
+    }
+
+    public static void setLanguageMode(Context context, int mode) {
+        getEditor(context).putInt(KEY_LANGUAGE_MODE, mode).apply();
+    }
+
+    public static boolean getSipEnabled(Context context) {
+        return getSP(context).getBoolean(KEY_SP_SIP_ENABLE, false);
+    }
+
+    public static void setSipEnable(Context context, boolean enable) {
+        getEditor(context).putBoolean(KEY_SP_SIP_ENABLE, enable).apply();
+    }
+
+    /**
+     * 获取转发时间
+     *
+     * @return
+     */
+    public static int getCountdownTime(Context context) {
+        return getSP(context).getInt(KEY_SP_COUNTDOWN_TIME, CountdownTime);
+    }
+
+    /**
+     * 设置转发时间
+     *
+     * @param value
+     */
+    public static void setCountdownTime(Context context, int value) {
+        getEditor(context).putInt(KEY_SP_COUNTDOWN_TIME, value).apply();
+    }
+
+
+    public static int getVoiceCallType(Context context) {
+        return getSP(context).getInt(KEY_SP_VOICE_CALL_TYPE, -1);
+    }
+
+    public static void setVoiceCallType(Context context, int value) {
+        getEditor(context).putInt(KEY_SP_VOICE_CALL_TYPE, value).apply();
+    }
+
+    public static int getTransCall(Context context) {
+        return getSP(context).getInt(KEY_SP_TRANSFER_CALL, -1);
+    }
+
+    public static void setTransCall(Context context, int value) {
+        getEditor(context).putInt(KEY_SP_TRANSFER_CALL, value).apply();
+    }
+
+
+    private static SharedPreferences getSP(Context context) {
+        return context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
+    }
+
+    private static SharedPreferences.Editor getEditor(Context context) {
+        return getSP(context).edit();
+    }
+}

+ 481 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/ui/CallSingleActivity.java

@@ -0,0 +1,481 @@
+package com.wdkl.ncs.android.component.home.ui;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.FragmentManager;
+
+import com.alibaba.fastjson.JSONObject;
+import com.enation.javashop.utils.base.tool.CommonTool;
+import com.enation.javashop.utils.base.widget.LoadingDialog;
+import com.google.gson.Gson;
+import com.wdkl.ncs.android.component.home.R;
+import com.wdkl.ncs.android.component.home.service.FloatingService;
+import com.wdkl.ncs.android.component.home.util.ActivityStackUtil;
+import com.wdkl.ncs.android.component.home.util.HomeWatcher;
+import com.wdkl.ncs.android.component.home.util.LogUpload;
+import com.wdkl.ncs.android.component.home.util.MediaPlayHelper;
+import com.wdkl.ncs.android.component.home.util.RecordHelper;
+import com.wdkl.ncs.android.component.home.util.RingPlayHelper;
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO;
+import com.wdkl.ncs.android.middleware.tcp.TcpClient;
+import com.wdkl.ncs.android.middleware.tcp.channel.DeviceChannel;
+import com.wdkl.ncs.android.middleware.tcp.channel.VoiceUtil;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback;
+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.rtc.entity.Room;
+import com.wdkl.rtc.janus.JanusClient;
+import com.wdkl.rtc.janus.VideoRoomCallback;
+import com.wdkl.rtc.rtc.WebRTCEngine;
+import com.wdkl.rtc.util.EnumType;
+import com.wdkl.rtc.util.ILogUpload;
+import com.wdkl.rtc.util.JanusConstant;
+
+import java.math.BigInteger;
+
+import pub.devrel.easypermissions.EasyPermissions;
+
+/**
+ * 单人通话界面
+ */
+public class CallSingleActivity extends AppCompatActivity {
+
+    public static final String EXTRA_ROOM_ID = "roomId";
+    public static final String EXTRA_MO = "isOutGoing";
+    public static final String EXTRA_AUDIO_ONLY = "audioOnly";
+    public static final String EXTRA_TCPMODEL = "tcpModel";
+    public static final String EXTRA_SHOWNAME = "showName";
+    public static final String EXTRA_RING = "ring";
+    private static final String TAG = "CallSingleActivity";
+
+    private boolean isOutgoing;
+    private BigInteger roomId;
+    private Room room;
+    //private BigInteger localUserId = BigInteger.valueOf(Constants.Companion.getMemberId());
+    private BigInteger localUserId = BigInteger.valueOf(Integer.parseInt(Constants.Companion.getSipId()));
+
+    boolean isAudioOnly = true;
+    String showName;
+    TcpModel recTcpModel;
+    JanusClient janusClient;
+    ILogUpload iLogUpload = new LogUpload();
+    VideoRoomCallback videoRoomCallback;
+    boolean isSimulateBed = false;
+    boolean ringUp = true;
+
+    private SingleCallFragment currentFragment;
+    //振动
+    Vibrator mVibrator;
+    public LoadingDialog loadingDialog;
+
+    private boolean mServiceBound = false;
+    private HomeWatcher mHomeWatcher;
+
+    boolean callConnected = false;
+
+    private ServiceConnection mCallServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Log.d(TAG, "floating service connected");
+            // 获取服务的操作对象
+            FloatingService.MyBinder binder = (FloatingService.MyBinder) service;
+            binder.getService();
+        }
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.d(TAG, "floating service disconnected");
+        }
+    };
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setStatusBarOrScreenStatus(this);
+        setContentView(R.layout.activity_single_call);
+
+        DeviceChannel.calling = true;
+
+        mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+        loadingDialog = CommonTool.createLoadingDialog(this, R.layout.custom_loading,R.id.loadding_image);
+
+        final Intent intent = getIntent();
+        roomId = new BigInteger(intent.getStringExtra(EXTRA_ROOM_ID));
+        isOutgoing = intent.getBooleanExtra(EXTRA_MO, false);
+        isAudioOnly = intent.getBooleanExtra(EXTRA_AUDIO_ONLY,true);
+        recTcpModel = (TcpModel) intent.getSerializableExtra(EXTRA_TCPMODEL);
+        showName = intent.getStringExtra(EXTRA_SHOWNAME);
+        ringUp = intent.getBooleanExtra(EXTRA_RING, true);
+
+        if (recTcpModel !=null && recTcpModel.getData() != null) {
+            Log.d(TAG, recTcpModel.toJson());
+            InteractionVO interactionVO = new Gson().fromJson(recTcpModel.getData().toString(), InteractionVO.class);
+            isSimulateBed = (interactionVO.getFromDeviceType() == DeviceTypeEnum.SIMULATE_BED_DEVICE.value());
+        }
+
+        // 权限检测
+        String[] perms;
+        if (isAudioOnly) {
+            perms = new String[]{Manifest.permission.RECORD_AUDIO};
+        } else {
+            perms = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA};
+        }
+        if (!EasyPermissions.hasPermissions(CallSingleActivity.this, perms)) {
+            EasyPermissions.requestPermissions(CallSingleActivity.this, "需要相机和录音权限",
+                    100, perms);
+        } else {
+            //初始化 engine
+            WebRTCEngine.getInstance().init(isAudioOnly, this);
+            init(roomId, isAudioOnly);
+        }
+
+        RecordHelper.getInstance().stopCancelRecordByOther(true);
+
+        //home和recent按键事件监听
+        mHomeWatcher = new HomeWatcher(this);
+        mHomeWatcher.setOnHomePressedListener(new HomeWatcher.OnHomePressedListener() {
+            @Override
+            public void onHomePressed() {
+                //如果悬浮窗没有显示 就开启服务展示悬浮窗
+                if (!Constants.Companion.getShowFloatWindow()) {
+                    startFloatingService();
+                }
+            }
+
+            @Override
+            public void onRecentAppPressed() {
+                //如果悬浮窗没有显示 就开启服务展示悬浮窗
+                if (!Constants.Companion.getShowFloatWindow()) {
+                    startFloatingService();
+                }
+            }
+        });
+        mHomeWatcher.startWatch();
+
+        ActivityStackUtil.setCallSingleActivity(this);
+    }
+
+    private void startFloatingService() {
+        //先检查权限
+        if (Build.VERSION.SDK_INT >= 23) {
+            if (!Settings.canDrawOverlays(this)) {
+                Toast.makeText(CallSingleActivity.this, "请开启悬浮窗权限", Toast.LENGTH_LONG).show();
+                return;
+            }
+        }
+
+        //最小化Activity
+        moveTaskToBack(true);
+
+        //开启服务显示悬浮框
+        Intent floatVideoIntent = new Intent(this, FloatingService.class);
+        mServiceBound = bindService(floatVideoIntent, mCallServiceConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+
+        //界面恢复,移除悬浮框
+        if (mServiceBound) {
+            unbindService(mCallServiceConnection);
+            mServiceBound = false;
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        //通话时不能按返回键,跟微信同现象,只能挂断结束或者接听
+        super.onBackPressed();
+        if (currentFragment != null) {
+            if (currentFragment instanceof FragmentAudio) {
+                ((FragmentAudio) currentFragment).onBackPressed();
+            } else if (currentFragment instanceof FragmentVideo) {
+                ((FragmentVideo) currentFragment).onBackPressed();
+            }
+        }
+    }
+
+    public SingleCallFragment getCurrentFragment() {
+        return currentFragment;
+    }
+
+    public Vibrator getmVibrator() {
+        return mVibrator;
+    }
+
+    private void init(BigInteger roomId, boolean audioOnly) {
+        SingleCallFragment fragment;
+        if (audioOnly) {
+            fragment = new FragmentAudio();
+        } else {
+            fragment = new FragmentVideo();
+        }
+        FragmentManager fragmentManager = getSupportFragmentManager();
+        currentFragment = fragment;
+        fragmentManager.beginTransaction()
+                    .replace(R.id.fragment_content, fragment)
+                    .commit();
+
+        //启动通话界面默认切换到外放模式
+        WebRTCEngine.getInstance().toggleSpeaker(true);
+
+        room = new Room(roomId);
+
+        //================ 信令
+        //初始化 janusClient
+        janusClient = new JanusClient(JanusConstant.JANUS_URL, localUserId);
+
+        //================ 信令
+        videoRoomCallback = new VideoRoomCallback(janusClient,room, localUserId, isOutgoing, iLogUpload);
+        janusClient.setJanusCallback(videoRoomCallback);
+        // 1,连接,监听 onOpen, createSession,createSession 成功时设置janusClient sessionId,回调videoRoomCallback.onCreateSession
+        // 2,在 videoRoomCallback.onCreateSession 中使用 janusClient.attachPlugin("janus.plugin.videoroom") 获得插件 handleId,回调 videoRoomCallback.onAttached
+        // 3,在 videoRoomCallback.onAttached 中设置 videoRoomHandlerId变量为handleId,并调用 janusClient.joinRoom(handleId, room.getId(), inviteUserId.toString())
+        // 4,发送 joinRoom 后,经过janusClient onMeesage处理后,由 videoRoomCallback.onMessage 处理 plugindata.data
+        // 5,处理 janus:event,plugindata.data.videoroom:joined,在 peerConnection上createOffer,设置本地 sdp,回调 offerCallback.onCreateOfferSuccess,发布 publisher
+        if (isOutgoing) {
+            //MediaPlayHelper.getInstance().playResMusic(R.raw.outgoing_call, 0.8f, true);
+            //RingPlayHelper.playRingTone(this, R.raw.ring_back2, true);
+            janusClient.setCallState(EnumType.CallState.Outgoing);
+            janusClient.connect();
+        } else {
+            //模拟分机呼叫由主机建立通话
+            if (isSimulateBed) {
+                janusClient.setCallState(EnumType.CallState.Outgoing);
+                janusClient.connect();
+            } else {
+                janusClient.setCallState(EnumType.CallState.Incoming);
+            }
+
+            //MediaPlayHelper.getInstance().playResMusic(R.raw.incoming_call, 1.0f, true);
+            //RingPlayHelper.playRingTone(this, R.raw.incoming_call, true);
+            /*long[] pattern = new long[]{100L, 2000L, 1000L, 2000L, 1000L, 2000L};
+            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+                mVibrator.vibrate(VibrationEffect.createWaveform(pattern, 0));
+            } else {
+                mVibrator.vibrate(pattern, 0);
+            }*/
+        }
+
+        // 6,处理 publishers,每个publisher 包含 id (feedId) 和 display,attach 到发布者的 handler 上,准备接收视频流,每个发布者都要 attach 一遍,然后协商 sdp, SFU,subscribeAttach 到发布者的 handle 上 [janusClient.subscribeAttach(feedId)]。将publisher 放到Room对象中的publishers, 在 janusClient.subscribeAttach(feedId) 中回调 videoRoomCallback.onSubscribeAttached
+        // 7,在 videoRoomCallback.onSubscribeAttached 中订阅发布者,如果有 publisher 的话
+    }
+
+    public void callOutSuccess(TcpModel tcpModel) {
+        if (currentFragment != null) {
+            currentFragment.callOutSuccess(tcpModel);
+        }
+    }
+
+    // 切换到语音通话
+    public void switchAudio() {
+        //init(targetId, isOutgoing, true, true);
+    }
+
+    public Room getRoom() {
+        return room;
+    }
+
+    @TargetApi(19)
+    private static int getSystemUiVisibility() {
+        int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN |
+                View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+        }
+        return flags;
+    }
+
+    /**
+     * 设置状态栏透明
+     */
+    @TargetApi(19)
+    public void setStatusBarOrScreenStatus(Activity activity) {
+        Window window = activity.getWindow();
+        //全屏+锁屏+常亮显示
+        window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
+                //WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
+                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
+                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+        window.getDecorView().setSystemUiVisibility(getSystemUiVisibility());
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
+            layoutParams.layoutInDisplayCutoutMode =
+                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+            window.setAttributes(layoutParams);
+        }
+        // 5.0以上系统状态栏透明
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            //清除透明状态栏
+            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+            //设置状态栏颜色必须添加
+            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+            window.setStatusBarColor(Color.TRANSPARENT);//设置透明
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //19
+            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+        }
+    }
+
+    @SuppressLint("CheckResult")
+    public void sendHandOffTcp(){
+        if (recTcpModel.getType() == TcpType.VOICE) {
+            InteractionVO interactionVO = null;
+            if (recTcpModel.getData()!=null){
+                interactionVO = new Gson().fromJson(recTcpModel.getData().toString(), InteractionVO.class);
+            }
+
+            if (isOutgoing && (recTcpModel.getAction() == TcpAction.VoiceAction.SUCCESS || recTcpModel.getAction() == TcpAction.VoiceAction.ACCEPT)) {
+                int toId;
+                if (interactionVO.getFromDeviceId().equals(Constants.Companion.getDeviceId())) {
+                    toId = interactionVO.getToDeviceId();
+                } else {
+                    toId = interactionVO.getFromDeviceId();
+                }
+
+                TcpModel voiceUtilTcpModel = VoiceUtil.voiceHandoff(recTcpModel.getTid(), Constants.Companion.getDeviceId(), toId, interactionVO.getId());
+                TcpCallback transaction = new TcpCallback(voiceUtilTcpModel.getTid()) {
+                    @Override
+                    public void onSuccess(JSONObject jsonObject) {
+                        //
+                    }
+
+                    @Override
+                    public void onFailed(JSONObject jsonObject) {
+                        //
+                    }
+                };
+                TcpClient.getInstance().sendTcp(voiceUtilTcpModel, false, transaction);
+
+                /*NettyClient.Companion.getInstance().sendMsg(voiceUtilTcpModel.toJson()).subscribe(it-> {
+                    if (it) {
+                        Log.d(TAG, "TCP.发送消息完成");
+                    } else {
+                        Log.e(TAG, "TCP.发送消息失败");
+                        HandleTcpConnect.Companion.getInstance().tcpReConnectWithMsgShow();
+                    }
+                });*/
+            } else if (recTcpModel.getAction() == TcpAction.VoiceAction.CALL) {
+                int toId;
+                if (interactionVO==null){
+                    toId = recTcpModel.getToId();
+                } else {
+                    if (interactionVO.getFromDeviceId().equals(Constants.Companion.getDeviceId())) {
+                        toId = interactionVO.getToDeviceId();
+                    } else {
+                        toId = interactionVO.getFromDeviceId();
+                    }
+                }
+                TcpModel voiceUtilTcpModel;
+                if (interactionVO == null){
+                    voiceUtilTcpModel = VoiceUtil.voiceCancel(recTcpModel.getTid(), Constants.Companion.getDeviceId(),recTcpModel.getToId(), null);
+                } else {
+                    if (callConnected) {
+                        voiceUtilTcpModel = VoiceUtil.voiceHandoff(recTcpModel.getTid(), Constants.Companion.getDeviceId(), toId, interactionVO.getId());
+                    } else {
+                        voiceUtilTcpModel = VoiceUtil.voiceReject(recTcpModel.getTid(), Constants.Companion.getDeviceId(), toId, interactionVO.getId());
+                    }
+                }
+                TcpClient.getInstance().sendMsg(voiceUtilTcpModel.toJson());
+
+                /*NettyClient.Companion.getInstance().sendMsg(voiceUtilTcpModel.toJson()).subscribe(it-> {
+                    if (it) {
+                        Log.d(TAG, "TCP.发送消息完成");
+                    } else {
+                        Log.e(TAG, "TCP.发送消息失败");
+                        HandleTcpConnect.Companion.getInstance().tcpReConnectWithMsgShow();
+                    }
+                });*/
+            }
+        }
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event){
+        return currentFragment.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public synchronized void finish(){
+        Log.d(TAG, "call activity end");
+        if (janusClient.getWebSocketChannel()!=null) {
+            janusClient.setCallState(EnumType.CallState.Idle);
+            if (isOutgoing){
+                if (janusClient.getCurrentHandleId()!=null) {
+                    janusClient.destroyRoom(janusClient.getCurrentHandleId(), null);
+                }
+            } else {
+                if (isSimulateBed) {
+                    if (janusClient.getCurrentHandleId()!=null) {
+                        janusClient.destroyRoom(janusClient.getCurrentHandleId(), null);
+                    }
+                } else {
+                    janusClient.leaveRoom();
+                }
+            }
+            janusClient.disConnect();
+        }
+
+        if (loadingDialog != null) {
+            loadingDialog.dismiss();
+        }
+
+        if (mVibrator != null) {
+            mVibrator.cancel();
+        }
+
+        if (!isFinishing()) {
+            super.finish();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.d(TAG, "call activity destroyed");
+        MediaPlayHelper.getInstance().stopMusic(true);
+        RingPlayHelper.stopRingTone();
+        if (mHomeWatcher != null) {
+            mHomeWatcher.stopWatch();
+        }
+
+        DeviceChannel.calling = false;
+        DeviceChannel.callId = 0;
+
+        //解绑,不显示悬浮框
+        if (mServiceBound) {
+            unbindService(mCallServiceConnection);
+            mServiceBound = false;
+        }
+
+        ActivityStackUtil.setCallSingleActivity(null);
+
+        super.onDestroy();
+    }
+
+    public boolean isOutgoing() {
+        return isOutgoing;
+    }
+}

+ 429 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/ui/FragmentAudio.java

@@ -0,0 +1,429 @@
+package com.wdkl.ncs.android.component.home.ui;
+
+import android.annotation.SuppressLint;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+
+import com.alibaba.fastjson.JSONObject;
+import com.blankj.utilcode.util.BarUtils;
+import com.enation.javashop.utils.base.tool.CommonTool;
+import com.google.gson.Gson;
+import com.wdkl.ncs.android.component.home.R;
+import com.wdkl.ncs.android.component.home.util.MediaPlayHelper;
+import com.wdkl.ncs.android.component.home.util.RingPlayHelper;
+import com.wdkl.ncs.android.lib.utils.ExtendMethodsKt;
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO;
+import com.wdkl.ncs.android.middleware.tcp.TcpClient;
+import com.wdkl.ncs.android.middleware.tcp.channel.VoiceUtil;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel;
+import com.wdkl.rtc.entity.Publisher;
+import com.wdkl.rtc.entity.Room;
+import com.wdkl.rtc.rtc.WebRTCEngine;
+import com.wdkl.rtc.util.EnumType;
+import com.wdkl.rtc.util.OSUtils;
+
+/**
+ * 语音通话控制界面
+ */
+public class FragmentAudio extends SingleCallFragment implements View.OnClickListener {
+    private static final String TAG = "FragmentAudio";
+    private ImageView muteImageView;
+    private ImageView speakerImageView;
+    private boolean micEnabled = false; // 静音
+    private boolean isSpeakerOn = false; // 扬声器
+    private boolean accepted = false;
+
+    TransHandler transHandler;
+
+    @Override
+    int getLayout() {
+        return R.layout.fragment_audio;
+    }
+
+    @Override
+    public void initView(View view) {
+        super.initView(view);
+        muteImageView = view.findViewById(R.id.muteImageView);
+        speakerImageView = view.findViewById(R.id.speakerImageView);
+        minimizeImageView.setVisibility(View.GONE);
+        outgoingHangupImageView.setOnClickListener(this);
+        incomingHangupImageView.setOnClickListener(this);
+        incomingTransImageView.setOnClickListener(this);
+        minimizeImageView.setOnClickListener(this);
+        muteImageView.setOnClickListener(this);
+        acceptImageView.setOnClickListener(this);
+        speakerImageView.setOnClickListener(this);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M || OSUtils.isMiui() || OSUtils.isFlyme()) {
+            lytParent.post(() -> {
+                RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) minimizeImageView.getLayoutParams();
+                params.topMargin = BarUtils.getStatusBarHeight();
+                minimizeImageView.setLayoutParams(params);
+            });
+        }
+    }
+
+    @Override
+    public void init() {
+        super.init();
+
+        if (callSingleActivity.loadingDialog==null){
+            callSingleActivity.loadingDialog = CommonTool.createLoadingDialog(this.getContext(), R.layout.custom_loading,R.id.loadding_image);
+        }
+        InteractionVO interactionVO = null;
+        if (callSingleActivity.recTcpModel.getData()!=null) {
+            interactionVO = new Gson().fromJson(callSingleActivity.recTcpModel.getData().toString(), InteractionVO.class);
+        }
+        transHandler = new TransHandler();
+        //是来电,且是客户来电进入延时转接
+        /*if (!isOutgoing && !callSingleActivity.ringUp && interactionVO != null && interactionVO.getFromClerkId()==null) {
+            int transSeconds = SettingConfig.getCountdownTime(this.getContext());
+            transHandler.sendEmptyMessageDelayed(TRANS, transSeconds * 1000);
+        }*/
+
+        // 如果已经接通
+        if (callSingleActivity.janusClient != null && callSingleActivity.janusClient.getCallState() == EnumType.CallState.Connected) {
+            descTextView.setVisibility(View.GONE); // 提示语
+            outgoingActionContainer.setVisibility(View.VISIBLE);
+            durationTextView.setVisibility(View.VISIBLE);
+            startRefreshTime();
+        } else {
+            // 如果未接通
+            if (isOutgoing) {
+                descTextView.setText("正在向 "+getShowName()+" 请求通话");
+                outgoingActionContainer.setVisibility(View.VISIBLE);
+                incomingActionContainer.setVisibility(View.GONE);
+                //MediaPlayHelper.getInstance().playResMusic(R.raw.outgoing_call, 0.8f, true);
+            } else {
+                descTextView.setText(getShowName() + " 请求通话");
+                outgoingActionContainer.setVisibility(View.GONE);
+                incomingActionContainer.setVisibility(View.VISIBLE);
+            }
+        }
+    }
+
+    private String getShowName(){
+        InteractionVO interactionVO = null;
+        if (callSingleActivity.recTcpModel.getData()!=null) {
+            interactionVO = new Gson().fromJson(callSingleActivity.recTcpModel.getData().toString(), InteractionVO.class);
+        }
+
+        String showName = "";
+        if (interactionVO==null){
+            showName = callSingleActivity.showName;
+        } else {
+            //是自己拨出
+            if (interactionVO.getFromDeviceId().equals(Constants.Companion.getDeviceId())) {
+                if (interactionVO.getToClerkId() == null) {
+                    showName = interactionVO.getToFrameFullName() + " " + interactionVO.getToMemberName();
+                    portraitImageView.setImageResource(R.drawable.face_customer);
+                } else {
+                    showName = interactionVO.getToMemberName();
+                    portraitImageView.setImageResource(R.drawable.face_clerk);
+                }
+            }
+            //是他人呼入
+            else {
+                //是客户
+                if (interactionVO.getFromClerkId() == null) {
+                    showName = interactionVO.getFromFrameFullName() + " " + interactionVO.getFromMemberName();
+                    portraitImageView.setImageResource(R.drawable.face_customer);
+
+                    if (!isOutgoing /*&& Constants.Companion.getUserRoleName().contains("护士") || Constants.Companion.getUserRoleName().contains("组长")*/) {
+                        //transLinearLayout.setVisibility(View.VISIBLE);
+                        hangupLinearLayout.setVisibility(View.VISIBLE);
+                    }
+                }
+                //是同事
+                else {
+                    showName = interactionVO.getFromMemberName();
+                    portraitImageView.setImageResource(R.drawable.face_clerk);
+
+                    if (!isOutgoing) {
+                        hangupLinearLayout.setVisibility(View.VISIBLE);
+                    }
+                }
+            }
+        }
+        return showName;
+    }
+
+    @Override
+    public void onBackPressed() {
+        super.onDestroy();
+    }
+
+    @Override
+    public void onDestroyView() {
+        transHandler.removeCallbacksAndMessages(null);
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (callSingleActivity != null) {
+            currentState = callSingleActivity.janusClient.getCallState();
+            if (currentState == EnumType.CallState.Connected) {
+                //通话已连接上
+                showCallConnected();
+            } else if (currentState == EnumType.CallState.Connecting){
+                //通话正在连接,稍后再次检查
+                transHandler.sendEmptyMessageDelayed(CALL, 500);
+            }
+        }
+    }
+
+    @Override
+    public void didChangeState(EnumType.CallState state) {
+        Log.d(TAG, "didChangeState, state = " + state);
+        //callSingleActivity.janusClient.setCallState(state);
+        currentState = state;
+        if (state == EnumType.CallState.Connected) {
+            showCallConnected();
+        }
+    }
+
+    private void showCallConnected() {
+        if (callSingleActivity.callConnected) {
+            return;
+        }
+        callSingleActivity.callConnected = true;
+
+        MediaPlayHelper.getInstance().stopMusic(true);
+        RingPlayHelper.stopRingTone();
+
+        runOnUiThread(() -> {
+            callSingleActivity.loadingDialog.dismiss();
+
+            incomingActionContainer.setVisibility(View.GONE);
+            outgoingActionContainer.setVisibility(View.VISIBLE);
+            //minimizeImageView.setVisibility(View.VISIBLE);
+            descTextView.setVisibility(View.GONE);
+
+            muteLinearLayout.setVisibility(View.VISIBLE);
+            speakerLinearLayout.setVisibility(View.VISIBLE);
+
+            isSpeakerOn = !isSpeakerOn;
+            if (WebRTCEngine.getInstance().isHeadphonesPlugged()) {
+                Log.d(TAG, "didChangeState 耳机" + isSpeakerOn);
+                WebRTCEngine.getInstance().toggleHeadset(isSpeakerOn);
+            } else {
+                Log.d(TAG, "didChangeState 外放切换" + isSpeakerOn);
+                WebRTCEngine.getInstance().toggleSpeaker(isSpeakerOn);
+                speakerImageView.setSelected(isSpeakerOn);
+            }
+
+            Room room = callSingleActivity.getRoom();
+            StringBuilder sb = new StringBuilder(512);
+            for (Publisher publisher : room.getPublishers()) {
+                sb.append("id:" + publisher.getId() + ",name:" + publisher.getDisplay());
+            }
+
+            nameTextView.setText(getShowName());
+
+            startRefreshTime();
+        });
+    }
+
+    public static final int TRANS = 0x03;
+    public static final int CHECK = 0x04;
+    public static final int CALL = 0x05;
+
+    class TransHandler extends Handler {
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            if (msg.what == TRANS){
+                if (callSingleActivity!=null && callSingleActivity.janusClient.getCallState()!= EnumType.CallState.Connected && !isHandoff){
+                    transCall();
+                }
+            } else if (msg.what == CHECK) {
+                if (callSingleActivity!=null && callSingleActivity.janusClient.getCallState()!= EnumType.CallState.Connected && !isHandoff){
+                    Toast.makeText(callSingleActivity, "当前网络状况不佳", Toast.LENGTH_LONG).show();
+                }
+            } else if (msg.what == CALL) {
+                currentState = callSingleActivity.janusClient.getCallState();
+                if (currentState == EnumType.CallState.Connected) {
+                    //通话已连接上
+                    showCallConnected();
+                    transHandler.removeMessages(CALL);
+                } else if (currentState == EnumType.CallState.Connecting){
+                    //通话正在连接,稍后再次检查
+                    transHandler.sendEmptyMessageDelayed(CALL, 500);
+                }
+            }
+        }
+    }
+
+    @SuppressLint("CheckResult")
+    private void transCall(){
+        callSingleActivity.loadingDialog.show();
+        MediaPlayHelper.getInstance().stopMusic(true);
+        RingPlayHelper.stopRingTone();
+
+        //给服务器发送转接 tcp
+        InteractionVO interactionVO = new Gson().fromJson(callSingleActivity.recTcpModel.getData().toString(), InteractionVO.class);
+        int toId;
+        if (interactionVO.getFromDeviceId().equals(Constants.Companion.getDeviceId())) {
+            toId = interactionVO.getToDeviceId();
+        } else {
+            toId = interactionVO.getFromDeviceId();
+        }
+        TcpModel voiceTransferTcpModel = VoiceUtil.voiceTransfer(callSingleActivity.recTcpModel.getTid(), Constants.Companion.getDeviceId(), toId, interactionVO.getId());
+        TcpClient.getInstance().sendTcp(voiceTransferTcpModel, false, null);
+
+        handler.sendEmptyMessageDelayed(FINISH_CURRENT, 500);
+
+        /*NettyClient.Companion.getInstance().sendMsg(voiceTransferTcpModel.toJson()).subscribe(it-> {
+            if (it) {
+                Log.d(TAG, "TCP.发送消息完成");
+                handler.sendEmptyMessageDelayed(FINISH_CURRENT, 500);
+            } else {
+                Log.e(TAG, "TCP.发送消息失败");
+                HandleTcpConnect.Companion.getInstance().tcpReConnectWithMsgShow();
+            }
+        });*/
+    }
+
+    public void acceptCall(){
+        if (callSingleActivity!=null){
+            //取消自动转接
+            transHandler.removeMessages(TRANS);
+            //transHandler = null;
+            accepted = true;
+
+            callSingleActivity.loadingDialog.show();
+            callSingleActivity.janusClient.connect();
+            callSingleActivity.mVibrator.cancel();
+            MediaPlayHelper.getInstance().stopMusic(true);
+            RingPlayHelper.stopRingTone();
+            //发出accept
+            InteractionVO interactionVO = new Gson().fromJson(callSingleActivity.recTcpModel.getData().toString(), InteractionVO.class);
+            int toId;
+            if (interactionVO.getFromDeviceId().equals(Constants.Companion.getDeviceId())) {
+                toId = interactionVO.getToDeviceId();
+            } else {
+                toId = interactionVO.getFromDeviceId();
+            }
+            TcpModel voiceUtilTcpModel = VoiceUtil.voiceAccept(callSingleActivity.recTcpModel.getTid(), Constants.Companion.getDeviceId(), toId, interactionVO.getId());
+            TcpCallback transaction = new TcpCallback(voiceUtilTcpModel.getTid()) {
+                @Override
+                public void onSuccess(JSONObject jsonObject) {
+                    //
+                }
+
+                @Override
+                public void onFailed(JSONObject jsonObject) {
+                    if (callSingleActivity != null) {
+                        callSingleActivity.runOnUiThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                ExtendMethodsKt.showMessage("Failed");
+                                //callSingleActivity.loadingDialog.dismiss();
+                            }
+                        });
+                    }
+                }
+            };
+            TcpClient.getInstance().sendTcp(voiceUtilTcpModel, false, transaction);
+
+            /*TcpModel voiceUtilTcpModel = VoiceUtil.voiceAccept(Constants.Companion.getDeviceId(), toId, interactionVO.getId());
+            NettyClient.Companion.getInstance().sendMsg(voiceUtilTcpModel.toJson()).subscribe(it->{
+                if (it) {
+                    Log.d(TAG, "TCP.发送消息完成");
+                } else {
+                    Log.e(TAG, "TCP.发送消息失败");
+                    callSingleActivity.loadingDialog.dismiss();
+                    HandleTcpConnect.Companion.getInstance().tcpReConnectWithMsgShow();
+                }
+            });*/
+
+            transHandler.sendEmptyMessageDelayed(CHECK, 6000);
+        }
+    }
+
+    @Override
+    protected void handleHeadsetHook() {
+        //只有来电的时候才接听
+        if (!isOutgoing) {
+            if (accepted) {
+                if (callSingleActivity != null) {
+                    isHandoff = true;
+                    transHandler.removeMessages(TRANS);
+                    callSingleActivity.sendHandOffTcp();
+                    handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+                }
+            } else {
+                acceptCall();
+            }
+        } else {
+            //去电时挂断
+            if (callSingleActivity != null) {
+                isHandoff = true;
+                transHandler.removeMessages(TRANS);
+                callSingleActivity.sendHandOffTcp();
+                handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+            }
+        }
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        Log.i(TAG, "keyup keyCode " + keyCode);
+        if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
+            handleHeadsetHook();
+        } else if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
+            return true;
+        }
+        return true;
+    }
+
+    @Override
+    public void onClick(View v) {
+        int id = v.getId();
+        // 转接
+        if (id == R.id.incomingTransImageView){
+            if (callSingleActivity!=null) {
+                transCall();
+            }
+        }
+        // 接听
+        if (id == R.id.acceptImageView) {
+            acceptCall();
+        }
+        // 挂断电话
+        if (id == R.id.incomingHangupImageView || id == R.id.outgoingHangupImageView) {
+            if (callSingleActivity != null) {
+                isHandoff = true;
+                //取消自动转接
+                transHandler.removeMessages(TRANS);
+                callSingleActivity.sendHandOffTcp();
+                handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+            }
+        }
+        // 静音
+        if (id == R.id.muteImageView) {
+            micEnabled = !micEnabled;
+            Log.d(TAG,"静音切换" + micEnabled);
+            WebRTCEngine.getInstance().muteAudio(micEnabled);
+            muteImageView.setSelected(micEnabled);
+        }
+        // 扬声器
+        if (id == R.id.speakerImageView) {
+            isSpeakerOn=!isSpeakerOn;
+            Log.d(TAG,"外放切换" + isSpeakerOn);
+            WebRTCEngine.getInstance().toggleSpeaker(isSpeakerOn);
+            speakerImageView.setSelected(isSpeakerOn);
+        }
+    }
+}

+ 286 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/ui/FragmentVideo.java

@@ -0,0 +1,286 @@
+package com.wdkl.ncs.android.component.home.ui;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.NonNull;
+
+import com.blankj.utilcode.util.BarUtils;
+import com.wdkl.ncs.android.component.home.R;
+import com.wdkl.rtc.rtc.WebRTCEngine;
+import com.wdkl.rtc.util.EnumType;
+import com.wdkl.rtc.util.OSUtils;
+
+import org.webrtc.SurfaceViewRenderer;
+
+import java.math.BigInteger;
+
+/**
+ * 视频通话控制界面
+ */
+public class FragmentVideo extends SingleCallFragment implements View.OnClickListener {
+    private static final String TAG = "FragmentVideo";
+    private ImageView outgoingAudioOnlyImageView;
+    private LinearLayout audioLayout;
+    private ImageView incomingAudioOnlyImageView;
+    private LinearLayout hangupLinearLayout;
+    private LinearLayout acceptLinearLayout;
+    private ImageView connectedAudioOnlyImageView;
+    private ImageView connectedHangupImageView;
+    private ImageView switchCameraImageView;
+    private FrameLayout fullscreenRenderer;
+    private FrameLayout pipRenderer;
+    private LinearLayout inviteeInfoContainer;
+    private SurfaceViewRenderer localSurfaceView;
+    private SurfaceViewRenderer remoteSurfaceView;
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+    }
+
+    @Override
+    int getLayout() {
+        return R.layout.fragment_video;
+    }
+
+    @Override
+    public void initView(View view) {
+        super.initView(view);
+        fullscreenRenderer = view.findViewById(R.id.fullscreen_video_view);
+        pipRenderer = view.findViewById(R.id.pip_video_view);
+        inviteeInfoContainer = view.findViewById(R.id.inviteeInfoContainer);
+        outgoingAudioOnlyImageView = view.findViewById(R.id.outgoingAudioOnlyImageView);
+        audioLayout = view.findViewById(R.id.audioLayout);
+        incomingAudioOnlyImageView = view.findViewById(R.id.incomingAudioOnlyImageView);
+        hangupLinearLayout = view.findViewById(R.id.hangupLinearLayout);
+        acceptLinearLayout = view.findViewById(R.id.acceptLinearLayout);
+        connectedAudioOnlyImageView = view.findViewById(R.id.connectedAudioOnlyImageView);
+        connectedHangupImageView = view.findViewById(R.id.connectedHangupImageView);
+        switchCameraImageView = view.findViewById(R.id.switchCameraImageView);
+        outgoingHangupImageView.setOnClickListener(this);
+        incomingHangupImageView.setOnClickListener(this);
+        minimizeImageView.setOnClickListener(this);
+        connectedHangupImageView.setOnClickListener(this);
+        acceptImageView.setOnClickListener(this);
+        switchCameraImageView.setOnClickListener(this);
+        pipRenderer.setOnClickListener(this);
+        outgoingAudioOnlyImageView.setOnClickListener(this);
+        incomingAudioOnlyImageView.setOnClickListener(this);
+        connectedAudioOnlyImageView.setOnClickListener(this);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M || OSUtils.isMiui() || OSUtils.isFlyme()) {
+            lytParent.post(() -> {
+                RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) inviteeInfoContainer.getLayoutParams();
+                params.topMargin = (int) (BarUtils.getStatusBarHeight() * 1.2);
+                inviteeInfoContainer.setLayoutParams(params);
+                RelativeLayout.LayoutParams params1 = (RelativeLayout.LayoutParams) minimizeImageView.getLayoutParams();
+                params1.topMargin = BarUtils.getStatusBarHeight();
+                minimizeImageView.setLayoutParams(params1);
+            });
+
+            pipRenderer.post(() -> {
+                FrameLayout.LayoutParams params2 = (FrameLayout.LayoutParams) pipRenderer.getLayoutParams();
+                params2.topMargin = (int) (BarUtils.getStatusBarHeight() * 1.2);
+                pipRenderer.setLayoutParams(params2);
+            });
+        }
+//        if(isOutgoing){ //测试崩溃对方是否会停止
+//            lytParent.postDelayed(() -> {
+//                int i = 1 / 0;
+//            }, 10000);
+//        }
+
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        if (EnumType.CallState.Connected == callSingleActivity.janusClient.getCallState()) {
+            incomingActionContainer.setVisibility(View.GONE);
+            outgoingActionContainer.setVisibility(View.GONE);
+            connectedActionContainer.setVisibility(View.VISIBLE);
+            inviteeInfoContainer.setVisibility(View.GONE);
+            startRefreshTime();
+        } else {
+            if (isOutgoing) {
+                incomingActionContainer.setVisibility(View.GONE);
+                outgoingActionContainer.setVisibility(View.VISIBLE);
+                connectedActionContainer.setVisibility(View.GONE);
+                descTextView.setText(R.string.av_waiting);
+            } else {
+                incomingActionContainer.setVisibility(View.VISIBLE);
+                outgoingActionContainer.setVisibility(View.GONE);
+                connectedActionContainer.setVisibility(View.GONE);
+                descTextView.setText(R.string.av_video_invite);
+                if (callSingleActivity.janusClient.getCallState() == EnumType.CallState.Incoming) {
+                    View surfaceView = WebRTCEngine.getInstance().startPreview(false);
+                    if (surfaceView != null) {
+                        localSurfaceView = (SurfaceViewRenderer) surfaceView;
+                        localSurfaceView.setZOrderMediaOverlay(false);
+                        fullscreenRenderer.addView(localSurfaceView);
+                    }
+                }
+            }
+        }
+    }
+
+    public void didChangeState(EnumType.CallState state) {
+        Log.d(TAG, "didChangeState, state = " + state);
+        //callSingleActivity.janusClient.setCallState(state);
+        currentState = state;
+        runOnUiThread(() -> {
+            if (state == EnumType.CallState.Connected) {
+                incomingActionContainer.setVisibility(View.GONE);
+                outgoingActionContainer.setVisibility(View.GONE);
+                connectedActionContainer.setVisibility(View.VISIBLE);
+                inviteeInfoContainer.setVisibility(View.GONE);
+                descTextView.setVisibility(View.GONE);
+
+                // 开启计时器
+                startRefreshTime();
+            } else {
+                // do nothing now
+            }
+        });
+    }
+
+    public void didChangeMode(Boolean isAudio) {
+        runOnUiThread(() -> callSingleActivity.switchAudio());
+    }
+
+    @Override
+    protected void handleHeadsetHook() {
+        //
+    }
+
+    @Override
+    public void didCreateLocalVideoTrack() {
+        if (localSurfaceView == null) {
+            View surfaceView = WebRTCEngine.getInstance().startPreview(true);
+            if (surfaceView != null) {
+                localSurfaceView = (SurfaceViewRenderer) surfaceView;
+            } else {
+                if (callSingleActivity != null) callSingleActivity.finish();
+                return;
+            }
+        } else {
+            localSurfaceView.setZOrderMediaOverlay(true);
+        }
+
+        if (remoteSurfaceView!=null){
+            pipRenderer.setVisibility(View.VISIBLE);
+            if (pipRenderer.getChildCount() != 0) pipRenderer.removeAllViews();
+
+            localSurfaceView.setZOrderMediaOverlay(true);
+            pipRenderer.addView(localSurfaceView);
+        } else {
+            pipRenderer.setVisibility(View.GONE);
+            if (fullscreenRenderer != null && fullscreenRenderer.getChildCount() != 0)
+                fullscreenRenderer.removeAllViews();
+            fullscreenRenderer.addView(localSurfaceView);
+        }
+
+        Log.d(TAG,
+                "didCreateLocalVideoTrack localSurfaceView == null is " + (localSurfaceView == null) + "; remoteSurfaceView == null = " + (remoteSurfaceView == null)
+        );
+    }
+
+    public void didReceiveRemoteVideoTrack(BigInteger userId) {
+        if (localSurfaceView != null) {
+            localSurfaceView.setZOrderMediaOverlay(true);
+            if (localSurfaceView.getParent() != null) {
+                ((ViewGroup) localSurfaceView.getParent()).removeView(localSurfaceView);
+            }
+            pipRenderer.setVisibility(View.VISIBLE);
+            pipRenderer.addView(localSurfaceView);
+        }
+
+        View surfaceView = WebRTCEngine.getInstance().setupRemoteVideo(userId, false);
+        if (surfaceView != null) {
+            fullscreenRenderer.setVisibility(View.VISIBLE);
+            remoteSurfaceView = (SurfaceViewRenderer) surfaceView;
+            fullscreenRenderer.removeAllViews();
+            if (remoteSurfaceView.getParent() != null) {
+                ((ViewGroup) remoteSurfaceView.getParent()).removeView(remoteSurfaceView);
+            }
+            fullscreenRenderer.addView(remoteSurfaceView);
+        }
+
+        // 开启计时器
+        startRefreshTime();
+    }
+
+
+    @Override
+    public void onClick(View v) {
+        int id = v.getId();
+        // 接听
+        if (id == R.id.acceptImageView) {
+            //todo: 接听
+        }
+        // 挂断电话
+        if (id == R.id.incomingHangupImageView || id == R.id.outgoingHangupImageView || id == R.id.connectedHangupImageView) {
+            //todo: 挂断
+            if (callSingleActivity != null) callSingleActivity.finish();
+        }
+
+        // 切换摄像头
+        if (id == R.id.switchCameraImageView) {
+            WebRTCEngine.getInstance().switchCamera();
+        }
+        if (id == R.id.pip_video_view) {
+            boolean isFullScreenRemote = fullscreenRenderer.getChildAt(0) == remoteSurfaceView;
+            fullscreenRenderer.removeAllViews();
+            pipRenderer.removeAllViews();
+            if (isFullScreenRemote) {
+                remoteSurfaceView.setZOrderMediaOverlay(true);
+                pipRenderer.addView(remoteSurfaceView);
+                localSurfaceView.setZOrderMediaOverlay(false);
+                fullscreenRenderer.addView(localSurfaceView);
+            } else {
+                localSurfaceView.setZOrderMediaOverlay(true);
+                pipRenderer.addView(localSurfaceView);
+                remoteSurfaceView.setZOrderMediaOverlay(false);
+                fullscreenRenderer.addView(remoteSurfaceView);
+            }
+        }
+
+        // 切换到语音拨打
+        if (id == R.id.outgoingAudioOnlyImageView || id == R.id.incomingAudioOnlyImageView || id == R.id.connectedAudioOnlyImageView) {
+            if (callSingleActivity != null) callSingleActivity.isAudioOnly = true;
+            // todo: 切换纯语音
+        }
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        Log.i(TAG, "keyup keyCode " + keyCode);
+        if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
+
+        } else if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
+            return true;
+        }
+        return true;
+    }
+
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        fullscreenRenderer.removeAllViews();
+        pipRenderer.removeAllViews();
+    }
+
+    @Override
+    public void onBackPressed() {
+        onDestroy();
+    }
+}

+ 607 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/ui/SingleCallFragment.java

@@ -0,0 +1,607 @@
+package com.wdkl.ncs.android.component.home.ui;
+
+import android.annotation.SuppressLint;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Chronometer;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.alibaba.fastjson.JSONObject;
+import com.google.gson.Gson;
+import com.wdkl.ncs.android.component.home.util.MediaPlayHelper;
+import com.wdkl.ncs.android.component.home.util.RingPlayHelper;
+import com.wdkl.ncs.android.lib.utils.ExtendMethodsKt;
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.lib.base.BaseApplication;
+import com.wdkl.ncs.android.lib.vo.MessageEvent;
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO;
+import com.wdkl.ncs.android.middleware.tcp.TcpClient;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel;
+import com.wdkl.ncs.android.middleware.tcp.enums.TcpAction;
+import com.wdkl.rtc.janus.JanusClient;
+import com.wdkl.rtc.util.EnumType;
+import com.wdkl.ncs.android.component.home.R;
+import com.wdkl.rtc.rtc.WebRTCEngine;
+
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+
+import java.math.BigInteger;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public abstract class SingleCallFragment extends Fragment {
+    private static final String TAG = "SingleCallFragment";
+    ImageView minimizeImageView;
+    ImageView portraitImageView;// 用户头像
+    TextView nameTextView; // 用户昵称
+    TextView descTextView;  // 状态提示用语
+    Chronometer durationTextView; // 通话时长
+
+    LinearLayout transLinearLayout;
+    LinearLayout hangupLinearLayout;
+    LinearLayout muteLinearLayout;
+    LinearLayout speakerLinearLayout;
+
+    ImageView outgoingHangupImageView;
+    ImageView incomingHangupImageView;
+    ImageView incomingTransImageView;
+    ImageView acceptImageView;
+    TextView tvStatus;
+    View outgoingActionContainer;
+    View incomingActionContainer;
+    View connectedActionContainer;
+
+    View lytParent;
+
+    boolean isOutgoing = true;
+
+    CallSingleActivity callSingleActivity;
+    CallHandler handler;
+    boolean isHandoff = false;
+
+    boolean endWithNoAnswerFlag = false;
+    public static final int WHAT_DELAY_END_CALL = 0x01;
+    public static final int FINISH_CURRENT = 0x02;
+    public static final int WHAT_DELAY_CHECK_CALL = 0x03;
+
+    EnumType.CallState currentState;
+    HeadsetPlugReceiver headsetPlugReceiver;
+
+    private boolean callSuccess = false;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setRetainInstance(true);
+
+        handler = new CallHandler();
+        EventBus.getDefault().register(this);
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(getLayout(), container, false);
+        initView(view);
+        init();
+        return view;
+    }
+
+    @Override
+    public void onViewCreated(View view, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        if (callSingleActivity != null) {
+            if (isOutgoing) {
+                RingPlayHelper.playRingTone(BaseApplication.appContext, R.raw.ring_back2, true);
+            } else {
+                if (callSingleActivity.ringUp) {
+                    RingPlayHelper.playRingTone(BaseApplication.appContext, R.raw.ring_tone, true);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onDestroyView() {
+        if (handler != null) {
+            handler.removeCallbacksAndMessages(null);
+        }
+        if (durationTextView != null)
+            durationTextView.stop();
+        refreshMessage(true);
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        EventBus.getDefault().unregister(this);
+    }
+
+    abstract int getLayout();
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        callSingleActivity = (CallSingleActivity) getActivity();
+        if (callSingleActivity != null) {
+            isOutgoing = callSingleActivity.isOutgoing();
+            headsetPlugReceiver = new HeadsetPlugReceiver();
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_HEADSET_PLUG);
+            callSingleActivity.registerReceiver(headsetPlugReceiver, filter);
+        }
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        callSingleActivity.unregisterReceiver(headsetPlugReceiver);  //注销监听
+        callSingleActivity = null;
+    }
+
+    public void initView(View view) {
+        lytParent = view.findViewById(R.id.lytParent);
+        minimizeImageView = view.findViewById(R.id.minimizeImageView);
+        portraitImageView = view.findViewById(R.id.portraitImageView);
+        nameTextView = view.findViewById(R.id.nameTextView);
+        descTextView = view.findViewById(R.id.descTextView);
+        durationTextView = view.findViewById(R.id.durationTextView);
+        outgoingHangupImageView = view.findViewById(R.id.outgoingHangupImageView);
+        incomingHangupImageView = view.findViewById(R.id.incomingHangupImageView);
+        incomingTransImageView = view.findViewById(R.id.incomingTransImageView);
+        acceptImageView = view.findViewById(R.id.acceptImageView);
+        tvStatus = view.findViewById(R.id.tvStatus);
+        outgoingActionContainer = view.findViewById(R.id.outgoingActionContainer);
+        incomingActionContainer = view.findViewById(R.id.incomingActionContainer);
+        connectedActionContainer = view.findViewById(R.id.connectedActionContainer);
+        transLinearLayout = view.findViewById(R.id.transLinearLayout);
+        hangupLinearLayout = view.findViewById(R.id.hangupLinearLayout);
+        muteLinearLayout = view.findViewById(R.id.muteLinearLayout);
+        speakerLinearLayout = view.findViewById(R.id.speakerLinearLayout);
+
+        durationTextView.setVisibility(View.GONE);
+        if (isOutgoing) {
+            //countDownTime();
+            handler.sendEmptyMessageDelayed(WHAT_DELAY_CHECK_CALL, 5000); //5s后检查是否呼叫成功
+        }
+        handler.sendEmptyMessageDelayed(WHAT_DELAY_END_CALL, 60 * 1000);//60s之后未接通,则挂断电话
+    }
+
+    public void init() {
+        if (callSingleActivity.recTcpModel.getData()!=null){
+            InteractionVO interactionVO = new Gson().fromJson(callSingleActivity.recTcpModel.getData().toString(), InteractionVO.class);
+            Constants.Companion.setInteractionId(interactionVO.getId());
+        }
+
+        if (isOutgoing) {
+            startOutCall();
+        }
+    }
+
+    @SuppressLint("CheckResult")
+    private void startOutCall() {
+        //发送tcp call
+        TcpCallback transaction = new TcpCallback(callSingleActivity.recTcpModel.getTid()) {
+            @Override
+            public void onSuccess(JSONObject jsonObject) {
+                //
+            }
+
+            @Override
+            public void onFailed(JSONObject jsonObject) {
+                if (callSingleActivity != null) {
+                    callSingleActivity.runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            ExtendMethodsKt.showMessage("Failed");
+                        }
+                    });
+                    callSingleActivity.finish();
+                }
+            }
+        };
+        TcpClient.getInstance().sendTcp(callSingleActivity.recTcpModel, false, transaction);
+
+        /*NettyClient.Companion.getInstance().sendMsg(callSingleActivity.recTcpModel.toJson()).subscribe(it -> {
+            if (it) {
+                Log.d(TAG, "TCP.发送消息完成");
+            } else {
+                Log.e(TAG, "TCP.发送消息失败");
+                HandleTcpConnect.Companion.getInstance().tcpReConnectWithMsgShow();
+                if (callSingleActivity != null) {
+                    callSingleActivity.finish();
+                }
+            }
+        });*/
+    }
+
+    public void callOutSuccess(TcpModel tcpModel) {
+        callSingleActivity.recTcpModel = tcpModel;
+        InteractionVO interactionVO = new Gson().fromJson(tcpModel.getData().toString(), InteractionVO.class);
+        Constants.Companion.setInteractionId(interactionVO.getId());
+        callSuccess = true;
+        //callSingleActivity.janusClient.connect();
+        Log.d(TAG,"voice#success =>" + callSingleActivity.recTcpModel.toJson());
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Toast.makeText(SingleCallFragment.this.getContext(), "呼叫成功", Toast.LENGTH_SHORT).show();
+            }
+        });
+    }
+
+    // ======================================界面回调================================
+    public void didCallEndWithReason(EnumType.CallEndReason callEndReason) {
+        switch (callEndReason) {
+            case Busy: {
+                tvStatus.setText("对方忙线中");
+                break;
+            }
+            case SignalError: {
+                tvStatus.setText("连接断开");
+                break;
+            }
+            case RemoteSignalError: {
+                tvStatus.setText("对方网络断开");
+                break;
+            }
+            case Hangup: {
+                tvStatus.setText("挂断");
+                break;
+            }
+            case MediaError: {
+                tvStatus.setText("媒体错误");
+                break;
+            }
+            case RemoteHangup: {
+                tvStatus.setText("对方挂断");
+                break;
+            }
+            case OpenCameraFailure: {
+                tvStatus.setText("打开摄像头错误");
+                break;
+            }
+            case Timeout: {
+                tvStatus.setText("对方未接听");
+                break;
+            }
+            case AcceptByOtherClient: {
+                tvStatus.setText("在其它设备接听");
+                break;
+            }
+        }
+        incomingActionContainer.setVisibility(View.GONE);
+        outgoingActionContainer.setVisibility(View.GONE);
+        if (connectedActionContainer != null)
+            connectedActionContainer.setVisibility(View.GONE);
+        refreshMessage(false);
+        new Handler(Looper.getMainLooper()).postDelayed(() -> {
+            if (callSingleActivity != null) {
+                callSingleActivity.finish();
+            }
+
+        }, 11);
+    }
+
+    public void didChangeState(EnumType.CallState state) {
+
+    }
+
+    public void didCreateLocalVideoTrack() {
+    }
+
+    public void didReceiveRemoteVideoTrack(BigInteger userId) {
+    }
+
+    private void refreshMessage(Boolean isForCallTime) {
+        if (callSingleActivity == null) {
+            return;
+        }
+        // 刷新消息; demo中没有消息,不用处理这儿快逻辑
+    }
+
+    public void startRefreshTime() {
+        if (durationTextView != null) {
+            leftTime = 0;
+            durationTextView.setOnChronometerTickListener(null);
+            durationTextView.stop();
+            durationTextView.setVisibility(View.VISIBLE);
+            durationTextView.setBase(SystemClock.elapsedRealtime());
+            durationTextView.start();
+        }
+    }
+
+    private long leftTime = 60;
+    public void countDownTime() {
+        if (durationTextView != null) {
+            durationTextView.setVisibility(View.VISIBLE);
+            durationTextView.setBase(SystemClock.elapsedRealtime());
+            durationTextView.start();
+            durationTextView.setOnChronometerTickListener(
+                    new Chronometer.OnChronometerTickListener() {
+                        @Override
+                        public void onChronometerTick(Chronometer chronometer) {
+                            leftTime--;
+                            Date d = new Date(leftTime * 1000);
+                            SimpleDateFormat timeFormat = new SimpleDateFormat("mm:ss");
+                            chronometer.setText(timeFormat.format(d));
+                            if (leftTime == 0) {
+                                durationTextView.stop();
+                            }
+                        }
+                    });
+        }
+    }
+
+    void runOnUiThread(Runnable runnable) {
+        if (callSingleActivity != null) {
+            callSingleActivity.runOnUiThread(runnable);
+        }
+    }
+
+    @SuppressLint("HandlerLeak")
+    class CallHandler extends Handler {
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            switch (msg.what) {
+                case WHAT_DELAY_END_CALL:
+                    if (currentState != EnumType.CallState.Connected) {
+                        endWithNoAnswerFlag = true;
+                        if (callSingleActivity != null) {
+                            callSingleActivity.sendHandOffTcp();
+                            callSingleActivity.finish();
+                        }
+                    }
+                    break;
+                case WHAT_DELAY_CHECK_CALL:
+                    if (callSingleActivity != null && !callSuccess) {
+                        Toast.makeText(SingleCallFragment.this.getContext(), "服务器无响应,请检查网络状态!", Toast.LENGTH_SHORT).show();
+                    }
+                    break;
+                case FINISH_CURRENT:
+                    if (callSingleActivity != null) {
+                        callSingleActivity.finish();
+                    }
+                    break;
+            }
+        }
+    }
+
+    public abstract void onBackPressed();
+
+    public abstract boolean onKeyUp(int keyCode, KeyEvent event);
+
+    protected abstract void handleHeadsetHook();
+
+    class HeadsetPlugReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.hasExtra("state")) {
+                if (intent.getIntExtra("state", 0) == 0) { //拔出耳机
+                    WebRTCEngine.getInstance().toggleHeadset(false);
+                } else if (intent.getIntExtra("state", 0) == 1) { //插入耳机
+                    WebRTCEngine.getInstance().toggleHeadset(true);
+                }
+            }
+        }
+    }
+
+    private int reJoinCount = 0;
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    public void onEvent(MessageEvent messageEvent) {
+        int code = messageEvent.getTag();
+        if (code == JanusClient.CALLBACK_ADD_LOCALSTREAM) {
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    callSingleActivity.getCurrentFragment().didCreateLocalVideoTrack();
+                }
+            });
+        } else if (code == JanusClient.CALLBACK_ADD_REMOTESTREAM) {
+            BigInteger remoteUserId = (BigInteger) messageEvent.getMessage();
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    callSingleActivity.getCurrentFragment().didReceiveRemoteVideoTrack(remoteUserId);
+                }
+            });
+        } else if (code == JanusClient.CALLBACK_HANDUP) {
+            if (isHandoff) {
+                return;
+            }
+            BigInteger handleId = (BigInteger)messageEvent.getMessage();
+            if (!handleId.equals(callSingleActivity.janusClient.getCurrentHandleId())){
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        Toast.makeText(SingleCallFragment.this.getContext(),"对方已离开",Toast.LENGTH_SHORT).show();
+                    }
+                });
+            }
+//            runOnUiThread(new Runnable() {
+//                @Override
+//                public void run() {
+//                    Toast.makeText(SingleCallFragment.this.getContext(),"对方挂断",Toast.LENGTH_SHORT).show();
+//                }
+//            });
+
+            handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+        } else if (code == JanusClient.CALLBACK_ROOM_ERROR) {
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    Toast.makeText(SingleCallFragment.this.getContext(), "创建通话错误", Toast.LENGTH_SHORT).show();
+                }
+            });
+
+            handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+        } else if (code == JanusClient.CALLBACK_CONNECTED) {
+            //通话连接建立,停止铃声
+            MediaPlayHelper.getInstance().stopMusic(true);
+            RingPlayHelper.stopRingTone();
+            didChangeState(EnumType.CallState.Connected);
+        } else if (code == JanusClient.CALLBACK_CONNECTED_TIMEOUT) {
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    Toast.makeText(SingleCallFragment.this.getContext(), "连接超时", Toast.LENGTH_SHORT).show();
+                }
+            });
+
+            handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+        } else if (code == JanusClient.CALLBACK_LEAVING) {
+            if (isHandoff) {
+                return;
+            }
+            if (isOutgoing){
+                handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+            } else {
+                if (reJoinCount>=3){
+                    runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            Toast.makeText(SingleCallFragment.this.getContext(), "连接超时,请重试", Toast.LENGTH_SHORT).show();
+                        }
+                    });
+
+                    handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+                } else {
+                    //未收到挂断或主动挂断 重新进入
+                    callSingleActivity.janusClient.connect();
+                    reJoinCount++;
+                }
+            }
+            //BigInteger remoteUserId = (BigInteger)messageEvent.getMessage();
+
+        } else if (code == JanusClient.CALLBACK_REMOTE_QUIT) {
+            if (isHandoff) {
+                return;
+            }
+            //BigInteger remoteUserId = (BigInteger)messageEvent.getMessage();
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    Toast.makeText(SingleCallFragment.this.getContext(), "对方离开", Toast.LENGTH_SHORT).show();
+                }
+            });
+
+            handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+        } else if (code == JanusClient.CALLBACK_SLOW_LINK){
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    Toast.makeText(SingleCallFragment.this.getContext(), "网络情况差", Toast.LENGTH_SHORT).show();
+                }
+            });
+        }
+        //TCP处理
+        else if (code == 2) {
+
+            TcpModel tcpModel = (TcpModel) messageEvent.getMessage();
+            Log.i(TAG, "收到数据222: " + tcpModel.toJson());
+            int curInteractionId = -1;
+            if (tcpModel.getData() != null) {
+                InteractionVO interactionVO = new Gson().fromJson(tcpModel.getData().toString(), InteractionVO.class);
+                curInteractionId = interactionVO.getId();
+            }
+
+            if (tcpModel.getAction() == TcpAction.VoiceAction.ACCEPT) {
+                if (curInteractionId == Constants.Companion.getInteractionId()) {
+                    MediaPlayHelper.getInstance().stopMusic(true);
+                    RingPlayHelper.stopRingTone();
+                    callSingleActivity.recTcpModel = tcpModel;
+                    Log.d(TAG, "voice#success =>" + callSingleActivity.recTcpModel.toJson());
+                    Log.i(TAG, "对方接听电话啦");
+                }
+            } else if (tcpModel.getAction() == TcpAction.VoiceAction.HANDOFF) { //对方挂断
+                if (curInteractionId == Constants.Companion.getInteractionId()) {
+                    isHandoff = true;
+                    callSingleActivity.janusClient.disConnect();
+                    runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            Toast.makeText(SingleCallFragment.this.getContext(), "对方挂断", Toast.LENGTH_SHORT).show();
+                        }
+                    });
+
+                    handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+                }
+            } else if (tcpModel.getAction() == TcpAction.VoiceAction.REJECT) {
+                if (curInteractionId == Constants.Companion.getInteractionId()) {
+                    handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+
+                    runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            Toast.makeText(SingleCallFragment.this.getContext(), "对方拒绝", Toast.LENGTH_SHORT).show();
+                        }
+                    });
+                }
+            } else if (tcpModel.getAction() == TcpAction.VoiceAction.CALLING) {
+                if (curInteractionId == Constants.Companion.getInteractionId()) {
+                    handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+                    runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            Toast.makeText(SingleCallFragment.this.getContext(), "对方通话中", Toast.LENGTH_SHORT).show();
+                        }
+                    });
+                }
+            } else if (tcpModel.getAction() == TcpAction.VoiceAction.FAILED) {
+                //if (curInteractionId == interactionId) {
+                    handler.sendEmptyMessageDelayed(FINISH_CURRENT, 1000);
+                    runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            Toast.makeText(SingleCallFragment.this.getContext(), "呼叫失败,对方可能不在线", Toast.LENGTH_SHORT).show();
+                        }
+                    });
+                //}
+            } else if (tcpModel.getAction() == TcpAction.VoiceAction.CANCEL) {
+                if (curInteractionId == Constants.Companion.getInteractionId()) {
+                    handler.sendEmptyMessageDelayed(FINISH_CURRENT, 100);
+                    runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            Toast.makeText(SingleCallFragment.this.getContext(), "取消", Toast.LENGTH_SHORT).show();
+                        }
+                    });
+                }
+            }
+        } else if (code == 1) {
+            TcpModel tcpModel = (TcpModel) messageEvent.getMessage();
+            Log.i(TAG, "收到数据111: " + tcpModel.toJson());
+            if (isOutgoing && tcpModel.getAction() == TcpAction.VoiceAction.SUCCESS){ //服务器返回成功
+                callOutSuccess(tcpModel);
+            }
+        } else if (code == Constants.EVENT_HEADSET_HOOK) {
+            //收到耳机按键
+            handleHeadsetHook();
+        }
+    }
+}

+ 15 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/ActivityStackUtil.java

@@ -0,0 +1,15 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import com.wdkl.ncs.android.component.home.ui.CallSingleActivity;
+
+public class ActivityStackUtil {
+    private static CallSingleActivity callSingleActivity = null;
+
+    public static void setCallSingleActivity(CallSingleActivity activity) {
+        callSingleActivity = activity;
+    }
+
+    public static CallSingleActivity getCallSingleActivity() {
+        return callSingleActivity;
+    }
+}

+ 233 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/AnrFcExceptionUtil.java

@@ -0,0 +1,233 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.app.AlarmManager;
+import android.app.Application;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Environment;
+import android.util.Log;
+
+import com.github.anrwatchdog.ANRError;
+import com.github.anrwatchdog.ANRWatchDog;
+import com.wdkl.ncs.android.component.home.activity.WatchHome2Activity;
+import com.wdkl.ncs.android.component.home.service.WdKeepAliveService;
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.middleware.api.UrlManager;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.FormBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+/**
+ * Created by dengzhe on 2018/4/2.
+ * //=========================FC&ANR异常处理类=========================//
+ */
+
+public class AnrFcExceptionUtil implements Thread.UncaughtExceptionHandler {
+
+    private static ANRWatchDog mANRWatchDog;
+    private Thread.UncaughtExceptionHandler mDefaultHandler;
+    public static final String TAG = "AnrFcExceptionUtil";
+    private static Application application;
+
+    private static AnrFcExceptionUtil mAnrFcExceptionUtil;
+
+    private OkHttpClient okHttpClient;
+    private UrlManager urlManager = UrlManager.Companion.build();
+
+    /**
+     * 存储异常和参数信息
+     */
+    private Map<String, String> paramsMap = new HashMap<>();
+    /**
+     * 格式化时间
+     */
+    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
+
+
+    public static AnrFcExceptionUtil getInstance(Application application) {
+        if (mAnrFcExceptionUtil == null) {
+            mAnrFcExceptionUtil = new AnrFcExceptionUtil(application);
+        }
+        return mAnrFcExceptionUtil;
+    }
+
+    private AnrFcExceptionUtil(Application application) {
+        //获取系统默认的UncaughtException处理器
+        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
+        this.application = application;
+    }
+
+    @Override
+    public void uncaughtException(Thread thread, Throwable ex) {
+        if (!handleException(ex) && mDefaultHandler != null) {
+            //如果用户没有处理则让系统默认的异常处理器来处理
+            mDefaultHandler.uncaughtException(thread, ex);
+        } else {
+            try {
+                Thread.sleep(2000);
+            } catch (InterruptedException e) {
+                Log.e(TAG, "error : ", e);
+            }
+
+            restartApp();
+        }
+    }
+
+    private void restartApp() {
+        //停止服务
+        Intent serviceIntent = new Intent(application.getApplicationContext(), WdKeepAliveService.class);
+        application.getApplicationContext().stopService(serviceIntent);
+
+        //重新启动app
+        Intent mStartActivity = new Intent(application.getApplicationContext(), WatchHome2Activity.class);
+        int mPendingIntentId = 123456;
+        PendingIntent mPendingIntent = PendingIntent.getActivity(application.getApplicationContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
+        AlarmManager mgr = (AlarmManager) application.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
+        mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1500, mPendingIntent);
+
+        android.os.Process.killProcess(android.os.Process.myPid());
+        System.exit(0);
+    }
+
+    /**
+     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
+     *
+     * @param ex
+     * @return true:如果处理了该异常信息;否则返回false.
+     */
+    private boolean handleException(Throwable ex) {
+        if (ex == null) {
+            return false;
+        }
+
+        saveCrashInfo2File(ex);
+
+        return true;
+    }
+
+    private void uploadingErrorLog(String class_name, String err_msg, String exception_name, String method_name, String stack_trace) {
+        if(okHttpClient == null){
+            okHttpClient = new OkHttpClient();
+        }
+
+        FormBody.Builder formBody = new FormBody.Builder();
+        formBody.add("device_id", ""+Constants.Companion.getDeviceId());
+        formBody.add("class_name",class_name);
+        formBody.add("method_name",method_name);
+        formBody.add("exception_name",exception_name);
+        formBody.add("err_msg",err_msg);
+        formBody.add("stack_trace",stack_trace);
+        Log.w(TAG,urlManager.getDevice_url());
+        Request request  = new Request.Builder()
+                .url(urlManager.getDevice_url() + "device/error_log")
+                .post(formBody.build())
+                .build();
+
+        okHttpClient.newCall(request).enqueue(new Callback() {
+            @Override
+            public void onFailure(Call call, IOException e) {
+                Log.e(TAG,"错误日志上传失败"+e.getMessage());
+            }
+
+            @Override
+            public void onResponse(Call call, Response response) throws IOException {
+                Log.d(TAG,"错误日志上传成功");
+                String data = response.body().string();
+                Log.d(TAG,"错误日志数据 data "+data);
+            }
+        });
+    }
+
+    /**
+     * 保存错误信息到文件中
+     *
+     * @param ex
+     * @return 返回文件名称
+     */
+    private String saveCrashInfo2File(Throwable ex) {
+        StringBuffer sb = new StringBuffer();
+        for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            sb.append(key + "=" + value + "\n");
+        }
+
+        Writer writer = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(writer);
+        ex.printStackTrace(printWriter);
+        Throwable cause = ex.getCause();
+        while (cause != null) {
+            cause.printStackTrace(printWriter);
+            cause = cause.getCause();
+        }
+        printWriter.close();
+        String result = writer.toString();
+        sb.append(result);
+
+        //上传错误日志
+        uploadingErrorLog(application.getPackageName(), ex.getMessage(), ex.getClass().getSimpleName(), ex.getStackTrace()[0].getMethodName(), sb.toString());
+
+        try {
+            long timestamp = System.currentTimeMillis();
+            String time = format.format(new Date());
+            String fileName = "crash-" + time + "-" + timestamp + ".txt";
+            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+                String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/crash/";
+                File dir = new File(path);
+                if (!dir.exists()) {
+                    dir.mkdirs();
+                }
+                FileOutputStream fos = new FileOutputStream(path + fileName);
+                fos.write(sb.toString().getBytes());
+                Log.e(TAG, "saveCrashInfo2File: "+sb.toString());
+                fos.close();
+            }
+            return fileName;
+        } catch (Exception e) {
+            Log.e(TAG, "an error occured while writing file...", e);
+        }
+        return null;
+    }
+
+    /**
+     * ===================================================崩溃异常处理===================================================
+     */
+    public void initFCException() {
+        //设置该CrashHandler为程序的默认处理器
+        AnrFcExceptionUtil catchExcep = AnrFcExceptionUtil.getInstance(application);
+        Thread.setDefaultUncaughtExceptionHandler(catchExcep);
+        mANRWatchDog = new ANRWatchDog(8000);
+        mANRWatchDog.setInterruptionListener(new ANRWatchDog.InterruptionListener() {
+            @Override
+            public void onInterrupted(InterruptedException exception) {
+            }
+        }).setIgnoreDebugger(true).setANRListener(new ANRWatchDog.ANRListener() {
+            @Override
+            public void onAppNotResponding(ANRError error) {
+                //DaemonEnv.sendStopWorkBroadcast(application.getBaseContext());
+                //NettyClient.Companion.getInstance().disConnect();
+                Log.e("anr", "Anr restart app..." + error.toString());
+                //AppUpdateHelper.reboot(application);
+
+                restartApp();
+            }
+        }).start();
+
+    }
+}

+ 33 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/AppUtils.java

@@ -0,0 +1,33 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.annotation.SuppressLint;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.wdkl.ncs.android.component.home.activity.WatchHome2Activity;
+import com.wdkl.ncs.android.lib.base.BaseApplication;
+import com.wdkl.ncs.keepbackground.work.DaemonEnv;
+
+public class AppUtils {
+
+    public static void restartApp() {
+        //DaemonEnv.sendStopWorkBroadcast(BaseApplication.appContext);
+        try {
+            Log.i("AppUtil", "restartApp ======================");
+            Intent intent = new Intent(BaseApplication.appContext, WatchHome2Activity.class);
+            @SuppressLint("WrongConstant") PendingIntent mPendingIntent = PendingIntent.getActivity(
+                    BaseApplication.appContext, 0, intent,
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            AlarmManager mgr = (AlarmManager) BaseApplication.appContext.getSystemService(Context.ALARM_SERVICE);
+            mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1500, mPendingIntent);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        android.os.Process.killProcess(android.os.Process.myPid());
+        System.exit(0);
+    }
+}

+ 187 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/AsyncPlayer.java

@@ -0,0 +1,187 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+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;
+        int resId;
+        boolean looping;
+        int stream;
+        long requestTime;
+
+        public String toString() {
+            return "{ code=" + code + " looping=" + looping + " stream=" + stream + " resId=" + resId + " }";
+        }
+    }
+
+    private final LinkedList mCmdQueue = new LinkedList();
+
+    private void startSound(Command cmd) {
+
+        try {
+            //MediaPlayer player = new MediaPlayer();
+            MediaPlayer player = MediaPlayer.create(cmd.context, cmd.resId);
+            player.setAudioStreamType(cmd.stream);
+            //player.setDataSource(cmd.context, cmd.uri);
+            player.setLooping(cmd.looping);
+            player.setVolume(1.0f, 1.0f);
+            //player.prepare();
+            player.start();
+            if (mPlayer != null) {
+                mPlayer.release();
+            }
+            mPlayer = player;
+            Log.w(mTag, "start sound " + cmd.resId);
+        } catch (Exception e) {
+            Log.w(mTag, "error loading sound for " + cmd.resId, 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.reset();
+                            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, int res, boolean looping, int stream) {
+        Command cmd = new Command();
+        cmd.requestTime = SystemClock.uptimeMillis();
+        cmd.code = PLAY;
+        cmd.context = context;
+        cmd.resId = res;
+        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();
+        }
+    }
+}

+ 14 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/EthernetWifiCallBack.java

@@ -0,0 +1,14 @@
+package com.wdkl.ncs.android.component.home.util;
+
+/**
+ * ======================以太网和wifi状态接口=====================
+ * Created by dengzhe on 2018/2/7.
+ */
+
+public interface EthernetWifiCallBack {
+
+    boolean ethernetStatus(boolean status);//以太网状态
+
+    boolean wifiStatus(boolean status);//wifi状态
+
+}

+ 89 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/HomeWatcher.java

@@ -0,0 +1,89 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+public class HomeWatcher {
+    private static final String TAG = "HomeWatcher";
+
+    public interface OnHomePressedListener {
+        void onHomePressed();
+        void onRecentAppPressed();
+    }
+
+    /** 上下文 */
+    private Context mContext;
+    /** 过滤器 */
+    private IntentFilter mFilter;
+    /** 接口 */
+    private OnHomePressedListener mListener;
+    /** 广播接收者 */
+    private HomeReceiver mRecevier;
+
+    public HomeWatcher(Context context) {
+        mContext = context;
+        mRecevier = new HomeReceiver();
+        mFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+    }
+
+    /**
+     * 设置监听
+     *
+     * @param listener
+     */
+    public void setOnHomePressedListener(OnHomePressedListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * 开始监听,注册广播
+     */
+
+    public void startWatch() {
+        if (mRecevier != null) {
+            mContext.registerReceiver(mRecevier, mFilter);
+        }
+    }
+
+    /**
+     * 停止监听,注销广播
+     */
+    public void stopWatch() {
+        if (mRecevier != null) {
+            mContext.unregisterReceiver(mRecevier);
+        }
+    }
+
+    /**
+     * 广播接收者
+     */
+    private class HomeReceiver extends BroadcastReceiver {
+        final String SYSTEM_DIALOG_REASON_KEY = "reason";
+        final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
+        final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+        final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
+                String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
+                if (reason != null) {
+                    Log.i(TAG, "action:" + action + ",reason:" + reason);
+                    if (mListener != null) {
+                        if (reason.equals(SYSTEM_DIALOG_REASON_HOME_KEY)) {
+                            //home按键
+                            mListener.onHomePressed();
+                        } else if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
+                            //recent按键
+                            mListener.onRecentAppPressed();
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 58 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/IMDialogHelper.java

@@ -0,0 +1,58 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+
+import com.wdkl.ncs.android.component.home.R;
+
+
+public class IMDialogHelper {
+
+    private static AlertDialog imDialog;
+
+    public static void showIMDialog(Activity activity) {
+        View contentView = LayoutInflater.from(activity).inflate(R.layout.im_dialog_lay, null);
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setView(contentView);
+
+        Button confirm = contentView.findViewById(R.id.btn_im_confirm);
+        confirm.setOnClickListener(v -> {
+            RecordHelper.getInstance().stopCancelRecordByOther(false);
+            dismissIMDialog();
+        });
+
+
+        imDialog = builder.create();
+        imDialog.setCanceledOnTouchOutside(false);
+        imDialog.setCancelable(false);
+
+        //设置dialog宽高及位置
+        try {
+            Window window = imDialog.getWindow();
+            window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+            imDialog.show();
+            WindowManager.LayoutParams lp = window.getAttributes();
+            lp.width = 580;
+            lp.height = 400;
+            lp.gravity = Gravity.CENTER;
+            //lp.alpha = 0.8f;//设置透明度
+            window.setAttributes(lp);
+
+            window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void dismissIMDialog() {
+        if (imDialog != null && imDialog.isShowing()) {
+            imDialog.dismiss();
+        }
+    }
+}

+ 6 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/ISpeechCallback.java

@@ -0,0 +1,6 @@
+package com.wdkl.ncs.android.component.home.util;
+
+public interface ISpeechCallback {
+    void initSuccess();
+    void initFailed();
+}

+ 77 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/ImPlayDialogHelper.java

@@ -0,0 +1,77 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.Chronometer;
+import android.widget.TextView;
+
+import com.wdkl.ncs.android.component.home.R;
+
+public class ImPlayDialogHelper {
+
+    private static AlertDialog imPlayDialog;
+
+    public static void showImPlayDialog(Activity activity, String text, String time) {
+        View contentView = LayoutInflater.from(activity).inflate(R.layout.im_play_dialog_lay, null);
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setView(contentView);
+
+        Button confirm = contentView.findViewById(R.id.btn_im_stop);
+        confirm.setOnClickListener(v -> {
+            MediaPlayHelper.getInstance().stopMusic(false);
+            dismissIMDialog();
+        });
+        TextView textView = contentView.findViewById(R.id.tv_im_play_name);
+        textView.setText(text);
+        TextView timeView = contentView.findViewById(R.id.tv_im_time);
+        timeView.setText(time);
+        Chronometer chronometer = contentView.findViewById(R.id.im_play_time);
+        chronometer.setBase(SystemClock.elapsedRealtime());
+        chronometer.start();
+
+
+        imPlayDialog = builder.create();
+        imPlayDialog.setCanceledOnTouchOutside(false);
+        imPlayDialog.setCancelable(false);
+        imPlayDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+            @Override
+            public void onDismiss(DialogInterface dialog) {
+                if (chronometer != null) {
+                    chronometer.stop();
+                }
+            }
+        });
+
+        //设置dialog宽高及位置
+        try {
+            Window window = imPlayDialog.getWindow();
+            window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+            imPlayDialog.show();
+            WindowManager.LayoutParams lp = window.getAttributes();
+            lp.width = 580;
+            lp.height = 540;
+            lp.gravity = Gravity.CENTER;
+            //lp.alpha = 0.8f;//设置透明度
+            window.setAttributes(lp);
+
+            window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void dismissIMDialog() {
+        if (imPlayDialog != null && imPlayDialog.isShowing()) {
+            imPlayDialog.dismiss();
+        }
+    }
+}

+ 83 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/LocaleMangerUtils.java

@@ -0,0 +1,83 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.wdkl.ncs.android.component.home.R;
+import com.wdkl.ncs.android.component.home.settingconfig.SettingConfig;
+import com.wdkl.ncs.android.lib.base.BaseApplication;
+
+import java.util.Locale;
+
+public class LocaleMangerUtils {
+
+    public static Locale getSystemLocale() {
+        return Locale.getDefault();
+    }
+
+    //获取当前语言id: 0--auto, 1--English, 2--中文, 3--西班牙语, 4--俄语
+    public static int getCurrentLocaleIndex() {
+        int languageSize = BaseApplication.appContext.getResources().getStringArray(R.array.language_list).length;
+        int index = SettingConfig.getLanguageId(BaseApplication.appContext);
+        //Log.d("wzl", "current language index: " + index);
+        if (index >= 0 && index <languageSize) {
+            return index;
+        } else {
+            return 0;
+        }
+    }
+
+    public static void setApplicationLanguageByIndex(Context context, int index) {
+        Locale locale = Locale.getDefault();
+        switch (index) {
+            case 0:
+                //
+                break;
+            case 1:
+                locale = Locale.ENGLISH;
+                break;
+            case 2:
+                locale = Locale.CHINESE;
+                break;
+            case 3:
+                locale = new Locale("es");
+                break;
+            case 4:
+                locale = new Locale("ru");
+                break;
+        }
+
+        setApplicationLanguage(context, locale);
+    }
+
+    public static void setApplicationLanguage(Context context, Locale locale) {
+        Log.d("locale", "set locale language: " + locale.getLanguage());
+        Configuration configuration = context.getResources().getConfiguration();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            configuration.setLocale(locale);
+        } else {
+            configuration.locale = locale;
+        }
+        // 更新context中的语言设置
+        Resources resources = context.getResources();
+        DisplayMetrics dm = resources.getDisplayMetrics();
+        resources.updateConfiguration(configuration, dm);
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    public static Locale getApplicationLocale() {
+        Configuration config = BaseApplication.appContext.getResources().getConfiguration();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            return config.locale;
+        } else {
+            return config.getLocales().get(0);
+        }
+    }
+
+}

+ 52 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/LogUpload.java

@@ -0,0 +1,52 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.util.Log;
+
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.middleware.api.UrlManager;
+import com.wdkl.rtc.util.ILogUpload;
+
+import java.io.IOException;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.FormBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class LogUpload implements ILogUpload {
+    private final static String TAG = LogUpload.class.getSimpleName();
+    private static OkHttpClient okHttpClient = new OkHttpClient();
+    private static UrlManager urlManager = UrlManager.Companion.build();
+
+    public void errorLog(String class_name, String err_msg, String method_name, String stack_trace) {
+
+        FormBody.Builder formBody = new FormBody.Builder();
+        formBody.add("device_id", ""+ Constants.Companion.getDeviceId());
+        formBody.add("class_name",class_name);
+        formBody.add("method_name",method_name);
+        formBody.add("exception_name",TAG);
+        formBody.add("err_msg",err_msg);
+        formBody.add("stack_trace",stack_trace);
+        Log.w(TAG,urlManager.getDevice_url());
+        Request request  = new Request.Builder()
+                .url(urlManager.getDevice_url() + "device/error_log")
+                .post(formBody.build())
+                .build();
+
+        okHttpClient.newCall(request).enqueue(new Callback() {
+            @Override
+            public void onFailure(Call call, IOException e) {
+                Log.e(TAG,"错误日志上传失败"+e.getMessage());
+            }
+
+            @Override
+            public void onResponse(Call call, Response response) throws IOException {
+                Log.d(TAG,"错误日志上传成功");
+                String data = response.body().string();
+                Log.d(TAG,"错误日志数据 data "+data);
+            }
+        });
+    }
+}

+ 291 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/MediaPlayHelper.java

@@ -0,0 +1,291 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.lib.base.BaseApplication;
+import com.wdkl.ncs.android.lib.vo.MessageEvent;
+import com.wdkl.rtc.rtc.AudioFocusManager;
+
+import org.greenrobot.eventbus.EventBus;
+
+public class MediaPlayHelper {
+    private static String TAG = MediaPlayHelper.class.getCanonicalName();
+
+    private static MediaPlayHelper sInstance = null;
+    private MediaPlayer mediaPlayer;
+    private HandlerThread playHandlerThread;
+    private Handler playHandler;
+    private MediaSessionCompat mediaSessionCompat;
+    private AudioFocusManager audioFocusManager;
+
+    private int mResId;
+    private String mUrl;
+    private float mVolume;
+    private boolean mLoop;
+
+    /**
+     * 播放res资源
+     */
+    public static final int PLAY_RES = 100;
+    /**
+     * 播放url资源
+     */
+    public static final int PLAY_URL = 101;
+    /**
+     * 停止
+     */
+    public static final int STOP = 102;
+    /**
+     * 释放
+     */
+    public static final int RELEASE = 103;
+
+
+    private MediaPlayHelper() {
+        createHandlerThread();
+        audioFocusManager = new AudioFocusManager(BaseApplication.appContext);
+    }
+
+    public static MediaPlayHelper getInstance() {
+        if (sInstance == null) {
+            synchronized (MediaPlayHelper.class) {
+                if (sInstance == null) {
+                    sInstance = new MediaPlayHelper();
+                }
+            }
+        }
+        return sInstance;
+    }
+
+    private void createHandlerThread() {
+        if (playHandlerThread == null) {
+            playHandlerThread = new HandlerThread("playHandlerThread");
+            playHandlerThread.start();
+        }
+
+        if (playHandler == null) {
+            playHandler = new Handler(playHandlerThread.getLooper()) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case PLAY_RES:
+                            playResMusicNow();
+                            break;
+                        case PLAY_URL:
+                            playUrlMusicNow();
+                            break;
+                        case STOP:
+                            stopMusicNow();
+                            break;
+                        case RELEASE:
+                            releaseMediaNow();
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            };
+        }
+    }
+
+    public void playResMusic(int id, float vol, boolean loop) {
+        mResId = id;
+        mVolume = vol;
+        mLoop = loop;
+        playHandler.sendEmptyMessage(PLAY_RES);
+    }
+
+    public void playUrlMusic(String url, float vol, boolean loop) {
+        mUrl = url;
+        mVolume = vol;
+        mLoop = loop;
+        playHandler.sendEmptyMessage(PLAY_URL);
+    }
+
+    public void stopMusic(boolean dismiss) {
+        playHandler.sendEmptyMessage(STOP);
+        if (dismiss) {
+            ImPlayDialogHelper.dismissIMDialog();
+        }
+    }
+
+    public void releaseMusic() {
+        playHandler.sendEmptyMessage(RELEASE);
+    }
+
+    //播放本地res音频资源
+    private void playResMusicNow() {
+        if (mediaPlayer != null) {
+            stopMusicNow();
+        }
+        mediaPlayer = MediaPlayer.create(BaseApplication.appContext, mResId);
+        try {
+            mediaPlayer.setLooping(mLoop);
+            mediaPlayer.setVolume(mVolume, mVolume);
+            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            mediaPlayer.start();
+            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+                @Override
+                public void onCompletion(MediaPlayer player) {
+//                    playMusicComplete();
+                    stopMusicNow();
+                }
+            });
+            mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+                @Override
+                public boolean onError(MediaPlayer player, int what, int extra) {
+                    //playMusicError();
+                    stopMusicNow();
+                    releaseMediaSession();
+                    return false;
+                }
+            });
+//            initMediaSession();
+        } catch (Exception e) {
+            //playMusicError();
+            e.printStackTrace();
+        }
+    }
+
+    //播放远程或本地存储音频资源
+    private void playUrlMusicNow() {
+        if (mediaPlayer != null) {
+            mediaPlayer.stop();
+        }
+        mediaPlayer = new MediaPlayer();
+        try {
+            mediaPlayer.reset();
+            mediaPlayer.setDataSource(mUrl);
+            mediaPlayer.setLooping(mLoop);
+            mediaPlayer.setVolume(mVolume, mVolume);
+            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+                @Override
+                public void onPrepared(MediaPlayer player) {
+                    if (mediaPlayer != null) {
+                        mediaPlayer.start();
+                    }
+                }
+            });
+            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+                @Override
+                public void onCompletion(MediaPlayer player) {
+                    //playMusicComplete();
+                    //stopMusicNow();
+                    EventBus.getDefault().post(new MessageEvent("im_done", Constants.EVENT_IM_PLAY_DONE));
+                }
+            });
+            mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+                @Override
+                public boolean onError(MediaPlayer player, int what, int extra) {
+                    //playMusicError();
+                    stopMusicNow();
+                    EventBus.getDefault().post(new MessageEvent("im_done", Constants.EVENT_IM_PLAY_DONE));
+                    return false;
+                }
+            });
+            mediaPlayer.prepareAsync();
+        } catch (Exception e) {
+            //playMusicError();
+            e.printStackTrace();
+        }
+    }
+
+    private void stopMusicNow() {
+        if (mediaPlayer != null) {
+            mediaPlayer.setOnPreparedListener(null);
+            mediaPlayer.setOnCompletionListener(null);
+            try {
+                if (mediaPlayer.isPlaying()) {
+                    mediaPlayer.stop();
+                }
+                mediaPlayer.reset();
+                mediaPlayer.release();
+            } catch (IllegalStateException e) {
+                e.printStackTrace();
+            }
+        }
+        mediaPlayer = null;
+        releaseMediaSession();
+    }
+
+    private void releaseMediaNow() {
+        if (mediaPlayer != null) {
+            mediaPlayer.setOnPreparedListener(null);
+            mediaPlayer.setOnCompletionListener(null);
+            try {
+                mediaPlayer.stop();
+                mediaPlayer.reset();
+                mediaPlayer.release();
+            } catch (IllegalStateException e) {
+                e.printStackTrace();
+            }
+        }
+        mediaPlayer = null;
+        releaseMediaSession();
+    }
+
+    public boolean isMediaPlaying() {
+        if (mediaPlayer != null) {
+            return mediaPlayer.isPlaying();
+        }
+        return false;
+    }
+
+    private void initMediaSession(){
+        if (mediaSessionCompat!=null){
+            releaseMediaSession();
+        }
+
+        audioFocusManager.requestFocus();
+
+        mediaSessionCompat = new MediaSessionCompat(BaseApplication.appContext,TAG);
+        mediaSessionCompat.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);
+        mediaSessionCompat.setActive(true);
+        mediaSessionCompat.setCallback(new MediaSessionCompat.Callback(){
+            @Override
+            public boolean onMediaButtonEvent(Intent intent) {
+                String action = intent.getAction();
+                if (action != null) {
+                    if (TextUtils.equals(action, Intent.ACTION_MEDIA_BUTTON)) {
+                        KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+                        if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+                            Integer keyCode = keyEvent.getKeyCode();
+                            Log.i(TAG, "mediasession keycode " + keyCode);
+                            switch (keyCode) {
+                                case KeyEvent.KEYCODE_MEDIA_PLAY:
+                                case KeyEvent.KEYCODE_MEDIA_PAUSE:
+                                    EventBus.getDefault().post(new MessageEvent(1, Constants.EVENT_BLUETOOTH_ACCEPT_CALL));
+                                    break;
+                                default:
+                                    break;
+                            }
+                        }
+                    }
+                }
+                return super.onMediaButtonEvent(intent);
+            }
+        });
+    }
+
+    private void releaseMediaSession(){
+        if (mediaSessionCompat!=null){
+            mediaSessionCompat.setCallback(null);
+            mediaSessionCompat.setActive(false);
+            mediaSessionCompat.release();
+            mediaSessionCompat = null;
+
+            audioFocusManager.releaseAudioFocus();
+        }
+    }
+}

+ 115 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/MediaPlayer.kt

@@ -0,0 +1,115 @@
+package com.wdkl.ncs.android.component.nursehome.util
+
+import android.media.AudioManager
+import android.media.MediaPlayer
+import android.util.Log
+import com.umeng.weixin.umengwx.e
+import java.io.IOException
+import java.lang.IllegalStateException
+
+class MediaPlayer {
+    private val TAG = MediaPlayer::class.java.getSimpleName()
+
+    var mMediaPlayer: MediaPlayer? = null
+
+    init {
+        Log.i(TAG, "初始化播放器")
+        mMediaPlayer = MediaPlayer()
+        //这样设置 不能控制本音乐的音量大小 也就是setVolume无效
+//        mMediaPlayer!!.setAudioStreamType(AudioManager.STREAM_MUSIC)
+        mMediaPlayer!!.setAudioStreamType(AudioManager.STREAM_VOICE_CALL)
+        mMediaPlayer!!.setOnCompletionListener {
+            Log.i(TAG, "播放完成")
+        }
+
+        mMediaPlayer!!.setOnPreparedListener {
+            Log.i(TAG, "加载完长")
+            mMediaPlayer!!.start()
+        }
+        mMediaPlayer!!.setOnErrorListener { mp, what, extra ->
+            Log.e(TAG, "异常")
+            false
+        }
+
+    }
+
+    //启动播放
+    fun startMediaPlayer(url: String) {
+        if ((mMediaPlayer != null && mMediaPlayer!!.isPlaying())) {
+            mMediaPlayer!!.stop()
+        }
+        mMediaPlayer!!.reset()
+        //设置音频文件到MediaPlayer对象中
+        //测试地址 "http://file.kuyinyun.com/group1/M00/90/B7/rBBGdFPXJNeAM-nhABeMElAM6bY151.mp3"
+        try {
+            mMediaPlayer!!.setDataSource(url)
+            //让MediaPlayer对象准备,用这个方法防止加载时耗时导致anr
+            mMediaPlayer!!.prepareAsync()
+        } catch (e: IOException) {
+            e.printStackTrace()
+            Log.e(TAG, "播放异常")
+        }catch (e:IllegalStateException) {
+            e.message
+            Log.e(TAG, "IllegalStateException+播放异常");
+        }
+
+    }
+
+    /**
+     * 暂停播放
+     */
+    fun pauseMediaPlayer() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer!!.pause()
+        }
+
+    }
+
+    /**
+     * 继续播放
+     */
+    fun continueToPlayMediaPlayer() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer!!.start()
+        }
+
+    }
+
+    /**
+     * 停止播放
+     */
+    fun stopMediaPlayer() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer!!.stop()
+        }
+    }
+
+    /**
+     * 释放播放器资源
+     */
+    fun releaseMediaPlayer() {
+        if (mMediaPlayer != null) {
+            try {
+                mMediaPlayer!!.stop()
+                mMediaPlayer!!.release()
+            } catch (e: IllegalStateException) {
+
+            }
+
+        }
+        mMediaPlayer = null
+    }
+
+    /**
+     * 音量设置
+     *setDataSource()方法之后设置才有效
+     * @param volume
+     */
+    fun setVolume(volume: Float) {
+        if (mMediaPlayer != null) {
+            mMediaPlayer!!.setVolume(volume, volume)
+        }
+    }
+
+
+}

+ 600 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/NetHelper.java

@@ -0,0 +1,600 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.wdkl.ncs.android.lib.base.BaseApplication;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Timer;
+
+
+public class NetHelper {
+    private WifiManager wifiManager;
+    private ConnectivityManager connManager;
+    private static NetHelper sInstance = null;
+
+    private static Timer timerNetStatus = null;
+    private static final int SCHEDULE_TIME = 20000;
+    public static boolean NetConn = false;
+
+    public static final int NETWORK_NONE = -1;
+    public static final int NETWORK_WIFI = 1;
+    public static final int NETWORK_2G = 2;
+    public static final int NETWORK_3G = 3;
+    public static final int NETWORK_4G = 4;
+    public static final int NETWORK_MOBILE = 5;
+
+    /**
+     * 以太网是否ping成功
+     */
+    public static final String ETHERNETSTATUS = "ethernetStatus";
+
+    public NetHelper() {
+    }
+
+    public static NetHelper getInstance() {
+        if (sInstance == null) {
+            synchronized (NetHelper.class) {
+                if (sInstance == null) {
+                    sInstance = new NetHelper();
+                }
+            }
+        }
+        return sInstance;
+    }
+
+    public void init() {
+        wifiManager = (WifiManager) BaseApplication.appContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+        connManager = (ConnectivityManager) BaseApplication.appContext.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+    }
+
+    /**
+     * ping 网络
+     *
+     * @param host
+     * @param pingCount
+     * @param stringBuffer
+     * @return
+     */
+    public static boolean ping(String host, int pingCount, StringBuffer stringBuffer) {
+        String line = null;
+        Process process = null;
+        BufferedReader successReader = null;
+        String command = "ping -c " + pingCount + " " + host;
+        boolean isSuccess = false;
+        try {
+            process = Runtime.getRuntime().exec(command);
+            if (process == null) {
+                append(stringBuffer, "ping fail:process is null.");
+                return false;
+            }
+            successReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            while ((line = successReader.readLine()) != null) {
+                append(stringBuffer, line);
+            }
+            int status = process.waitFor();
+            if (status == 0) {
+                append(stringBuffer, "exec cmd success:" + command);
+                isSuccess = true;
+            } else {
+                append(stringBuffer, "exec cmd fail.");
+                isSuccess = false;
+            }
+            append(stringBuffer, "exec finished.");
+        } catch (IOException e) {
+        } catch (InterruptedException e) {
+        } finally {
+            if (process != null) {
+                process.destroy();
+            }
+            if (successReader != null) {
+                try {
+                    successReader.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+
+        return isSuccess;
+    }
+
+    private static void append(StringBuffer stringBuffer, String text) {
+        if (stringBuffer != null) {
+            stringBuffer.append(text + "\n");
+        }
+    }
+
+    /**
+     * 获取网关  Waderson
+     * <p>
+     * 1 WIFI情况下获取网关 2 有线网络下的DHCP模式连接 3 有线网络其他连接方式:比如静态ip、pppoe拨号、ipoe拨号等
+     */
+    public static String getLocalElement(int type) {
+        String e = "";
+        if (1 == type) {
+            //e = getLocalElementByWifi();
+        } else if (2 == type) {
+            e = getLocalElementByDhcp();
+        } else if (3 == type) {
+            e = getLocalElementByIp();
+        }
+        if (!TextUtils.isEmpty(e) && e.length() >= 11 && e.length() <= 15) {
+            return e;
+        } else {
+            return "192.168.101.1";
+        }
+    }
+
+
+    /**
+     * 有线网络下的DHCP模式连接
+     */
+    public static String getLocalElementByDhcp() {
+        BufferedReader bufferedReader = null;
+        String str2 = "";
+        String str3 = "getprop dhcp.eth0.gateway";
+        Process exec;
+        BufferedReader bufferedReader2 = null;
+        try {
+            exec = Runtime.getRuntime().exec(str3);
+            try {
+                bufferedReader2 = new BufferedReader(new InputStreamReader(exec.getInputStream()));
+            } catch (Throwable th3) {
+                if (bufferedReader != null) {
+                    bufferedReader.close();
+                }
+                if (exec != null) {
+                    exec.exitValue();
+                }
+            }
+            try {
+                str3 = bufferedReader2.readLine();
+                if (str3 != null) {
+                    TextUtils.isEmpty(str3);
+                }
+                try {
+                    bufferedReader2.close();
+                } catch (IOException iOException222) {
+                    iOException222.printStackTrace();
+                }
+                if (exec != null) {
+                    try {
+                        exec.exitValue();
+                    } catch (Exception e5) {
+                    }
+                }
+            } catch (IOException e6) {
+                str3 = str2;
+                if (bufferedReader2 != null) {
+                    bufferedReader2.close();
+                }
+                if (exec != null) {
+                    exec.exitValue();
+                }
+                return str3;
+            }
+        } catch (IOException e62) {
+            bufferedReader2 = null;
+            exec = null;
+            str3 = str2;
+            if (bufferedReader2 != null) {
+                try {
+                    bufferedReader2.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (exec != null) {
+                exec.exitValue();
+            }
+            return str3;
+        } catch (Throwable th4) {
+            exec = null;
+            if (bufferedReader != null) {
+                try {
+                    bufferedReader.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (exec != null) {
+                exec.exitValue();
+            }
+        }
+        return str3;
+    }
+
+    /**
+     * 有线网络其他连接方式:比如静态ip、pppoe拨号、ipoe拨号等
+     */
+    public static String getLocalElementByIp() {
+        BufferedReader bufferedReader = null;
+        String result = "";
+        String str2 = "";
+        String str3 = "ip route list table 0";
+        Process exec;
+        BufferedReader bufferedReader2 = null;
+        try {
+            exec = Runtime.getRuntime().exec(str3);
+            try {
+                bufferedReader2 = new BufferedReader(new InputStreamReader(exec.getInputStream()));
+            } catch (Throwable th3) {
+                if (bufferedReader != null) {
+                    bufferedReader.close();
+                }
+                if (exec != null) {
+                    exec.exitValue();
+                }
+            }
+            try {
+                str2 = bufferedReader2.readLine();
+                if (str2 != null) {
+                    str2 = str2.trim();
+                    String[] strings = str2.split("\\s+");
+                    if (strings.length > 3) {
+                        result = strings[2];
+                    }
+                }
+                try {
+                    bufferedReader2.close();
+                } catch (IOException iOException222) {
+                    iOException222.printStackTrace();
+                }
+                if (exec != null) {
+                    try {
+                        exec.exitValue();
+                    } catch (Exception e5) {
+                    }
+                }
+            } catch (IOException e6) {
+                if (bufferedReader2 != null) {
+                    bufferedReader2.close();
+                }
+                if (exec != null) {
+                    exec.exitValue();
+                }
+                return result;
+            }
+        } catch (IOException e62) {
+            bufferedReader2 = null;
+            exec = null;
+            if (bufferedReader2 != null) {
+                try {
+                    bufferedReader2.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (exec != null) {
+                exec.exitValue();
+            }
+            return result;
+        } catch (Throwable th4) {
+            exec = null;
+            if (bufferedReader != null) {
+                try {
+                    bufferedReader.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (exec != null) {
+                exec.exitValue();
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 得到MAC
+     *
+     * @return String
+     */
+    public String getMacAddress() {
+        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N){//小于安卓7 走这里
+            String mac = "";
+            try {
+//                mac = getLocalMacAddressFromIp();
+//                if (TextUtils.isEmpty(mac)) {
+//                    mac = getNetworkMac();
+//                }
+                if (TextUtils.isEmpty(mac)) {
+                    mac = tryGetWifiMac();
+                }
+            } catch (Exception e) {
+            }
+            return mac;
+        }else {//大于等于安卓7走这里
+            List<NetworkInterface> interfaces = null;
+            try {
+                interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+                for (NetworkInterface networkInterface : interfaces) {
+                    if (networkInterface != null && TextUtils.isEmpty(networkInterface.getName()) == false) {
+                        if ("wlan0".equalsIgnoreCase(networkInterface.getName())) {
+                            byte[] macBytes = networkInterface.getHardwareAddress();
+                            if (macBytes != null && macBytes.length > 0) {
+                                StringBuilder str = new StringBuilder();
+                                for (byte b : macBytes) {
+                                    str.append(String.format("%02X:", b));
+                                }
+                                if (str.length() > 0) {
+                                    str.deleteCharAt(str.length() - 1);
+                                }
+                                return str.toString();
+                            }
+                        }
+                    }
+                }
+            } catch (SocketException e) {
+                e.printStackTrace();
+            }
+            return "unknown";
+
+        }
+    }
+
+    /**
+     * 兼容安卓5-10 获取Mac地址
+     * @return
+     */
+    public String getMacAddress2() {
+        List<NetworkInterface> interfaces = null;
+        try {
+            interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+            for (NetworkInterface networkInterface : interfaces) {
+                if (networkInterface != null && TextUtils.isEmpty(networkInterface.getName()) == false) {
+                    if ("wlan0".equalsIgnoreCase(networkInterface.getName())) {
+                        byte[] macBytes = networkInterface.getHardwareAddress();
+                        if (macBytes != null && macBytes.length > 0) {
+                            StringBuilder str = new StringBuilder();
+                            for (byte b : macBytes) {
+                                str.append(String.format("%02X:", b));
+                            }
+                            if (str.length() > 0) {
+                                str.deleteCharAt(str.length() - 1);
+                            }
+                            return str.toString();
+                        }
+                    }
+                }
+            }
+        } catch (SocketException e) {
+            e.printStackTrace();
+        }
+        return "unknown";
+    }
+
+
+    /**
+     * 通过WiFiManager获取mac地址
+     * 这个方法Android 7.0是获取不到的,返回的是null,其实是返回“02:00:00:00:00:00”
+     *
+     * @return
+     */
+    public String tryGetWifiMac() {
+        String mac;
+        WifiInfo wi = wifiManager.getConnectionInfo();
+        if (wi == null || wi.getMacAddress() == null) {
+            mac = null;
+        }
+        if ("02:00:00:00:00:00".equals(wi.getMacAddress().trim())) {
+            mac = null;
+        } else {
+            mac = wi.getMacAddress().trim();
+        }
+        return mac;
+    }
+
+    /**
+     * 通过网络接口获取
+     *
+     * @return
+     */
+    public static String getNetworkMac() {
+        try {
+            List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
+            for (NetworkInterface nif : all) {
+                // if (!nif.getName().equalsIgnoreCase("wlan0") && !nif.getName().equalsIgnoreCase("eth0") && !nif.getName().equalsIgnoreCase("eth1"))
+                if (!nif.getName().equalsIgnoreCase("eth0") && !nif.getName().equalsIgnoreCase("eth1") && !nif.getName().equalsIgnoreCase("eth2"))
+                    continue;
+                byte[] macBytes = nif.getHardwareAddress();
+                if (macBytes == null) {
+                    return null;
+                }
+                StringBuilder res1 = new StringBuilder();
+                for (byte b : macBytes) {
+                    res1.append(String.format("%02X:", b));
+                }
+                if (res1.length() > 0) {
+                    res1.deleteCharAt(res1.length() - 1);
+                }
+                return res1.toString();
+            }
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+        return null;
+    }
+
+
+    /**
+     * 根据IP地址获取MAC地址
+     *
+     * @return
+     */
+    @SuppressLint("NewApi")
+    public static String getLocalMacAddressFromIp() {
+        String strMacAddr = null;
+        try {
+            // 获得IpD地址
+            InetAddress ip = getLocalInetAddress();
+            byte[] b = NetworkInterface.getByInetAddress(ip)
+                    .getHardwareAddress();
+            StringBuffer buffer = new StringBuffer();
+            for (int i = 0; i < b.length; i++) {
+                if (i != 0) {
+                    buffer.append(':');
+                }
+                String str = Integer.toHexString(b[i] & 0xFF);
+                buffer.append(str.length() == 1 ? 0 + str : str);
+            }
+            strMacAddr = buffer.toString().toUpperCase();
+
+        } catch (Exception e) {
+
+        }
+
+        return strMacAddr;
+    }
+
+    /**
+     * 获取移动设备本地IP
+     *
+     * @return
+     */
+    public static InetAddress getLocalInetAddress() {
+        InetAddress ip = null;
+        try {
+            // 列举
+            Enumeration<NetworkInterface> en_netInterface = NetworkInterface
+                    .getNetworkInterfaces();
+            while (en_netInterface.hasMoreElements()) {// 是否还有元素
+                NetworkInterface ni = (NetworkInterface) en_netInterface
+                        .nextElement();// 得到下一个元素
+                Enumeration<InetAddress> en_ip = ni.getInetAddresses();// 得到一个ip地址的列举
+                while (en_ip.hasMoreElements()) {
+                    ip = en_ip.nextElement();
+                    if (!ip.isLoopbackAddress()
+                            && ip.getHostAddress().indexOf(":") == -1)
+                        break;
+                    else
+                        ip = null;
+                }
+
+                if (ip != null) {
+                    break;
+                }
+            }
+        } catch (SocketException e) {
+
+            e.printStackTrace();
+        }
+        return ip;
+    }
+
+    //获取本地ip地址
+    public String getLocalIP() {
+        try {
+            for (Enumeration<NetworkInterface> enNetI = NetworkInterface.getNetworkInterfaces(); enNetI.hasMoreElements(); ) {
+                NetworkInterface netI = enNetI.nextElement();
+                for (Enumeration<InetAddress> enumIpAddr = netI.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
+                    InetAddress inetAddress = enumIpAddr.nextElement();
+                    if (inetAddress instanceof Inet4Address && !inetAddress.isLoopbackAddress()) {
+                        return inetAddress.getHostAddress();
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public int getNetworkType() {
+        if (connManager != null && connManager.getActiveNetworkInfo() != null) {
+            return connManager.getActiveNetworkInfo().getType();
+        }
+
+        return -1;
+    }
+
+    /**
+     * 获取当前网络连接的类型
+     *
+     * @param context context
+     * @return int
+     */
+    public int getNetworkState(Context context) {
+        ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); // 获取网络服务
+        if (null == connManager) { // 为空则认为无网络
+            return NETWORK_NONE;
+        }
+        // 获取网络类型,如果为空,返回无网络
+        NetworkInfo activeNetInfo = connManager.getActiveNetworkInfo();
+        if (activeNetInfo == null || !activeNetInfo.isAvailable()) {
+            return NETWORK_NONE;
+        }
+        // 判断是否为WIFI
+        NetworkInfo wifiInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+        if (null != wifiInfo) {
+            NetworkInfo.State state = wifiInfo.getState();
+            if (null != state) {
+                if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
+                    return NETWORK_WIFI;
+                }
+            }
+        }
+        // 若不是WIFI,则去判断是2G、3G、4G网
+        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        int networkType = telephonyManager.getNetworkType();
+        switch (networkType) {
+            /*
+             GPRS : 2G(2.5) General Packet Radia Service 114kbps
+             EDGE : 2G(2.75G) Enhanced Data Rate for GSM Evolution 384kbps
+             UMTS : 3G WCDMA 联通3G Universal Mobile Telecommunication System 完整的3G移动通信技术标准
+             CDMA : 2G 电信 Code Division Multiple Access 码分多址
+             EVDO_0 : 3G (EVDO 全程 CDMA2000 1xEV-DO) Evolution - Data Only (Data Optimized) 153.6kps - 2.4mbps 属于3G
+             EVDO_A : 3G 1.8mbps - 3.1mbps 属于3G过渡,3.5G
+             1xRTT : 2G CDMA2000 1xRTT (RTT - 无线电传输技术) 144kbps 2G的过渡,
+             HSDPA : 3.5G 高速下行分组接入 3.5G WCDMA High Speed Downlink Packet Access 14.4mbps
+             HSUPA : 3.5G High Speed Uplink Packet Access 高速上行链路分组接入 1.4 - 5.8 mbps
+             HSPA : 3G (分HSDPA,HSUPA) High Speed Packet Access
+             IDEN : 2G Integrated Dispatch Enhanced Networks 集成数字增强型网络 (属于2G,来自维基百科)
+             EVDO_B : 3G EV-DO Rev.B 14.7Mbps 下行 3.5G
+             LTE : 4G Long Term Evolution FDD-LTE 和 TDD-LTE , 3G过渡,升级版 LTE Advanced 才是4G
+             EHRPD : 3G CDMA2000向LTE 4G的中间产物 Evolved High Rate Packet Data HRPD的升级
+             HSPAP : 3G HSPAP 比 HSDPA 快些
+             */
+            // 2G网络
+            case TelephonyManager.NETWORK_TYPE_GPRS:
+            case TelephonyManager.NETWORK_TYPE_CDMA:
+            case TelephonyManager.NETWORK_TYPE_EDGE:
+            case TelephonyManager.NETWORK_TYPE_1xRTT:
+            case TelephonyManager.NETWORK_TYPE_IDEN:
+                return NETWORK_2G;
+            // 3G网络
+            case TelephonyManager.NETWORK_TYPE_EVDO_A:
+            case TelephonyManager.NETWORK_TYPE_UMTS:
+            case TelephonyManager.NETWORK_TYPE_EVDO_0:
+            case TelephonyManager.NETWORK_TYPE_HSDPA:
+            case TelephonyManager.NETWORK_TYPE_HSUPA:
+            case TelephonyManager.NETWORK_TYPE_HSPA:
+            case TelephonyManager.NETWORK_TYPE_EVDO_B:
+            case TelephonyManager.NETWORK_TYPE_EHRPD:
+            case TelephonyManager.NETWORK_TYPE_HSPAP:
+                return NETWORK_3G;
+            // 4G网络
+            case TelephonyManager.NETWORK_TYPE_LTE:
+                return NETWORK_4G;
+            default:
+                return NETWORK_MOBILE;
+        }
+    }
+}

+ 81 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/NetWorkChangeReceiver.kt

@@ -0,0 +1,81 @@
+package com.wdkl.ncs.android.component.home.util
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkInfo
+import android.os.Build
+import android.util.Log
+import com.wdkl.ncs.android.component.home.service.WdKeepAliveService
+import com.wdkl.ncs.android.middleware.tcp.TcpClient
+import com.wdkl.ncs.android.middleware.tcp.TcpClientHandler
+
+class NetWorkChangeReceiver : BroadcastReceiver() {
+    val TAG = NetWorkChangeReceiver::class.simpleName
+
+    override fun onReceive(context: Context?, intent: Intent?) {
+        // API版本23以上使用
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            // 链接管理器(获取系统链接服务)
+            val manager: ConnectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+            //获取所有网络连接的信息
+            val networks: Array<Network> = manager.allNetworks
+            var networkInfo: NetworkInfo? = null
+            //通过循环将网络信息逐个取出来
+            for (network in networks) {
+                networkInfo = manager.getNetworkInfo(network)
+            }
+
+            // 判断网络是否可用
+            if (null != networkInfo && networkInfo.isAvailable) {
+                //当前网络可用
+            }
+            // 判断网络是否链接
+            if (null != networkInfo && networkInfo.isConnected) {
+                //当前网络已经链接
+                /*if (!NettyClient.instance.isConnect() && WdKeepAliveService.instanceCreated){
+                    Log.w(TAG,"TCP.进入重新连接")
+                    HandleTcpConnect.instance.tcpReConnect()
+                }*/
+
+                if (!TcpClientHandler.getConnected()) {
+                    TcpClient.getInstance().doConnect()
+                }
+            }
+            // 判断网络是否正在链接
+            if (null != networkInfo && networkInfo.isConnectedOrConnecting) {
+                //当前网络正在链接
+            }
+            // 判断网络链接失败
+            if (null != networkInfo && networkInfo.isFailover) {
+                //当前网络链接失败
+            }
+            // 判断网络是否赞漫游
+            if (null != networkInfo && networkInfo.isRoaming) {
+                //当前处于漫游网络
+            }
+        } else { // API版本23以下使用
+            // 链接管理器(获取系统链接服务)
+            val manager: ConnectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+
+            //获取ConnectivityManager对象对应的NetworkInfo对象
+            //获取WIFI连接的信息
+            val wifiNetworkInfo: NetworkInfo? = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI)
+            //获取移动数据连接的信息
+            val dataNetworkInfo: NetworkInfo? = manager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
+            if ((wifiNetworkInfo!=null && wifiNetworkInfo.isConnected) || (dataNetworkInfo!=null&&dataNetworkInfo.isConnected)) {
+                //WIFI已连接 或 移动数据已连接
+                Log.w(TAG,"TCP.进入重新连接")
+                /*if (!NettyClient.instance.isConnect()){
+                    HandleTcpConnect.instance.tcpReConnect()
+                }*/
+
+                if (!TcpClientHandler.getConnected()) {
+                    TcpClient.getInstance().doConnect()
+                }
+            }
+        }
+    }
+}

+ 112 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/PhoneCallUtil.java

@@ -0,0 +1,112 @@
+package com.wdkl.ncs.android.component.home.util;
+
+
+import android.Manifest;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import androidx.core.app.ActivityCompat;
+
+import com.android.internal.telephony.ITelephony;
+
+import java.lang.reflect.Method;
+
+public class PhoneCallUtil {
+    private static final String TAG = "PhoneUtil";
+
+    /**
+     * 挂断电话
+     *
+     * @param context
+     * @return
+     */
+    public static boolean endCall(Context context) {
+        boolean callSuccess = false;
+        try {
+            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+                // >=Android 9,需打开 Manifest.permission.ANSWER_PHONE_CALLS 权限
+                TelecomManager telecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+                if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ANSWER_PHONE_CALLS) != PackageManager.PERMISSION_GRANTED) {
+                    // TODO: Consider calling
+                    //    ActivityCompat#requestPermissions
+                    // here to request the missing permissions, and then overriding
+                    //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
+                    //                                          int[] grantResults)
+                    // to handle the case where the user grants the permission. See the documentation
+                    // for ActivityCompat#requestPermissions for more details.
+                    return false;
+                }
+                telecomManager.endCall();
+                callSuccess = true;
+                Log.d(TAG, "telecomManager.endCall() finish");
+            } else {
+                // 1.获取TelephonyManager
+                // 2.获取TelephonyManager.class
+                // 3.反射调用TelephonyManager的 getITelephony方法获取ITelephony
+                // 4.挂断电话ITelephony.endCall
+                TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+                Class c = Class.forName(tm.getClass().getName());
+                Method m = c.getDeclaredMethod("getITelephony");
+                m.setAccessible(true);
+                ITelephony telephonyService = (ITelephony) m.invoke(tm);
+                if (telephonyService != null) {
+                    callSuccess = telephonyService.endCall();
+                    Log.d(TAG, " telephonyService.endCall finish");
+                }
+            }
+        } catch (Exception e) {
+            Log.d(TAG, "Exception   error: " + e.getMessage());
+            callSuccess = disconnectCall();
+            ;
+            e.printStackTrace();
+        }
+        return callSuccess;
+    }
+
+    // 删除通话记录
+    public static void deleteCallLog(Context context, String incomingNumber) {
+        // 1.获取内容解析者
+        final ContentResolver resolver = context.getContentResolver();
+        // 2.获取内容提供者地址 call_log calls表的地址:calls
+        // 3.获取执行操作路径
+        final Uri uri = Uri.parse("content://call_log/calls");
+        // 4.删除操作
+        // 通过内容观察者观察内容提供者内容,如果变化,就去执行删除操作
+        // notifyForDescendents : 匹配规则,true : 精确匹配 false:模糊匹配
+        resolver.registerContentObserver(uri, true, new ContentObserver(new Handler()) {
+            // 内容提供者内容变化的时候调用
+            @Override
+            public void onChange(boolean selfChange) {
+                super.onChange(selfChange);
+                try {
+                    // 删除通话记录
+                    resolver.delete(uri, "number=?", new String[]{incomingNumber});
+                    // 注销内容观察者
+                    resolver.unregisterContentObserver(this);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+    }
+
+    private static boolean disconnectCall() {
+        try {
+            Log.d(TAG, "input keyevent " + KeyEvent.KEYCODE_ENDCALL);
+            Runtime.getRuntime().exec("input keyevent " + KeyEvent.KEYCODE_ENDCALL); //KEYCODE_HEADSETHOOK
+        } catch (Exception exc) {
+            Log.d(TAG, "exc.printStackTrace");
+            exc.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+}

+ 246 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/RecordHelper.java

@@ -0,0 +1,246 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.annotation.SuppressLint;
+import android.media.MediaRecorder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.alibaba.fastjson.JSONObject;
+import com.wdkl.ncs.android.component.home.R;
+import com.wdkl.ncs.android.component.home.helper.HttpHelper;
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.middleware.api.UrlManager;
+import com.wdkl.ncs.android.middleware.model.dos.ChannelImDO;
+import com.wdkl.ncs.android.middleware.tcp.TcpClient;
+import com.wdkl.ncs.android.middleware.tcp.channel.ChannelImUtil;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpCallback;
+import com.wdkl.ncs.android.middleware.tcp.dto.TcpModel;
+
+import java.io.File;
+import java.io.IOException;
+
+import static com.wdkl.ncs.android.lib.utils.ExtendMethodsKt.showMessage;
+
+public class RecordHelper {
+    private static final String VOICE_MSG_FILE_PATH = Environment.getExternalStorageDirectory().getPath() + "/VoiceMsg";
+    private String audiofilePath;
+    private MediaRecorder mediaRecorder;
+    private static RecordHelper sInstance = null;
+    private boolean recording = false;
+
+    private long pressDownTime = 0;
+    private long pressUpTime = 0;
+    private long clickTime = 0;
+    private boolean recordRunning = false;
+    private UrlManager urlManager = UrlManager.Companion.build();
+
+    private final static Object lock = new Object();
+
+    private final Handler handler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            //录音超时,自动停止
+            stopCancelRecordByOther(false);
+        }
+    };
+
+    public RecordHelper() {
+    }
+
+    public static RecordHelper getInstance() {
+        if (sInstance == null) {
+            synchronized (RecordHelper.class) {
+                if (sInstance == null) {
+                    sInstance = new RecordHelper();
+                }
+            }
+        }
+        return sInstance;
+    }
+
+    public void init() {
+        //创建音频文件路径
+        File file = new File(VOICE_MSG_FILE_PATH);
+        if (!file.exists()) {
+            file.mkdir();
+        }
+    }
+
+    public void startRecord() {
+        //如果正在录音则先停止录音
+        if (recording) {
+            stopRecord();
+        }
+
+        audiofilePath = VOICE_MSG_FILE_PATH + "/" + System.currentTimeMillis() + "_voice.mp3";
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                synchronized (lock) {
+                    try {
+                        mediaRecorder = new MediaRecorder();
+                        mediaRecorder.setOutputFile(audiofilePath);
+                        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置MediaRecorder的音频源为麦克风
+                        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);// 设置MediaRecorder录制的音频格式
+                        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);// 设置MediaRecorder录制音频的编码为amr
+                        mediaRecorder.prepare();
+                        mediaRecorder.start();
+                        recording = true;
+                    } catch (IOException e) {
+                        Log.i("error", "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
+                    }
+                }
+            }
+        }).start();
+    }
+
+    public void stopRecord() {
+        synchronized (lock) {
+            try {
+                if (mediaRecorder != null) {
+                    mediaRecorder.stop();
+                    mediaRecorder.release();
+                    mediaRecorder = null;
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                recording = false;
+            }
+        }
+    }
+
+    public void deleteAudioFile(String path) {
+        synchronized (lock) {
+            if (path != null) {
+                File file = new File(path);
+                if (file.exists()) {
+                    file.delete();
+                }
+            }
+        }
+    }
+
+    public String getAudiofilePath() {
+        return audiofilePath;
+    }
+
+
+    //耳机线控按键双击留言,单击结束
+    public void execRecordVoiceMsg() {
+        //连续点击2次开始留言
+        long curTime = System.currentTimeMillis();
+        if (curTime - clickTime < 1000 && curTime - pressDownTime > 3500 && curTime - pressUpTime > 2500) {
+            showMessage("开始留言");
+            //先停止其他语音或铃声
+            RingPlayHelper.stopRingTone();
+            SpeechUtil.getInstance().stopSpeak();
+            MediaPlayHelper.getInstance().stopMusic(true);
+            //播放提示
+            SoundPoolManager.getInstance().playSound(2);
+            recordRunning = true;
+            pressDownTime = curTime;
+            handler.postDelayed(this::startRecord, 1200);
+            handler.sendEmptyMessageDelayed(1024, 45000);
+        } else {
+            if (recordRunning) {
+                pressUpTime = curTime;
+                stopRecord();
+                handler.removeCallbacksAndMessages(null);
+                if (Math.abs(pressUpTime - pressDownTime) <= 2000) {
+                    deleteAudioFile(audiofilePath);
+                    showMessage("说话时间太短");
+                } else {
+                    sendChannelVoiceMsg(audiofilePath);
+
+                    //showMessage("发送留言");
+                    //MediaPlayHelper.getInstance().playUrlMusic(audiofilePath, 1.0f, false);
+                }
+            }
+            recordRunning = false;
+        }
+
+        clickTime = curTime;
+    }
+
+    private void sendChannelVoiceMsg(String file) {
+        //上传语音留言
+        if (file != null) {
+            HttpHelper.upload( urlManager.getDevice_url()+ "ncs/upload/file", new File(file), new HttpHelper.UploadCallback() {
+                @Override
+                public void onFail() {
+                    showMessage("留言失败");
+                }
+
+                @SuppressLint("CheckResult")
+                @Override
+                public void onSuccess(String data) {
+                    //上传完成后删除本地文件
+                    deleteAudioFile(file);
+                    SoundPoolManager.getInstance().playSound(3);
+                    ChannelImDO channelImDO = new ChannelImDO();
+                    channelImDO.setChannelId(Constants.Companion.getChannelId());
+                    channelImDO.setSenderMemberId(Constants.Companion.getMemberId());
+                    channelImDO.setAudioPath(data);
+                    TcpModel tcpModel = ChannelImUtil.channelImMsg(Constants.Companion.getDeviceId(), channelImDO);
+                    TcpCallback transaction = new TcpCallback(tcpModel.getTid()) {
+                        @Override
+                        public void onSuccess(JSONObject jsonObject) {
+                            //if (Locale.CHINESE.getLanguage().equals(language)) {
+                                SoundPoolManager.getInstance().playSound(3);
+                            //}
+                            showMessage(R.string.str_voice_msg_send_success);
+                        }
+
+                        @Override
+                        public void onFailed(JSONObject jsonObject) {
+                            // 这里写发送失败的方法
+                            SoundPoolManager.getInstance().playSound(6);
+                            String callbackString = jsonObject.getString(CALLBACK);
+                            showMessage(R.string.str_voice_msg_send_fail + callbackString);
+                        }
+                    };
+                    TcpClient.getInstance().sendTcp(tcpModel, false, transaction);
+
+                    /*NettyClient.Companion.getInstance().sendMsg(tcpModel.toJson()).subscribe(it-> {
+                        if (it){
+                            showMessage("留言发送成功!");
+                        } else {
+                            showMessage("留言发送失败!");
+                        }
+                    });*/
+                }
+            });
+        }
+    }
+
+    //停止录音或取消录音
+    public void stopCancelRecordByOther(boolean cancel) {
+        if (recordRunning) {
+            pressUpTime = System.currentTimeMillis();
+            stopRecord();
+            recordRunning = false;
+
+            if (cancel) {
+                showMessage("留言取消");
+                deleteAudioFile(audiofilePath);
+            } else {
+                sendChannelVoiceMsg(audiofilePath);
+
+                //showMessage("播放留言");
+                //MediaPlayHelper.getInstance().playUrlMusic(audiofilePath, 1.0f, false);
+            }
+        } else if (recording) {
+            stopRecord();
+        }
+    }
+
+    public boolean isRecording() {
+        return recording || recordRunning;
+    }
+}

+ 22 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/RingPlayHelper.java

@@ -0,0 +1,22 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+public class RingPlayHelper {
+
+    private static AsyncPlayer ringPlayer;
+
+    public static void playRingTone(Context context, int res, boolean loop) {
+        if (ringPlayer == null) {
+            ringPlayer = new AsyncPlayer(null);
+        }
+        ringPlayer.play(context, res, loop, AudioManager.STREAM_MUSIC);
+    }
+
+    public static void stopRingTone() {
+        if (ringPlayer != null) {
+            ringPlayer.stop();
+        }
+    }
+}

+ 72 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/SoundPoolManager.java

@@ -0,0 +1,72 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.os.Build;
+
+import com.wdkl.ncs.android.component.home.R;
+import com.wdkl.ncs.android.lib.base.BaseApplication;
+
+import java.util.HashMap;
+
+public class SoundPoolManager {
+
+    private static SoundPoolManager instance;
+    private SoundPool soundPool;
+    private HashMap<Integer, Integer> soundID = new HashMap<Integer, Integer>();
+    private boolean isLoaded = false;
+
+    public static SoundPoolManager getInstance() {
+        if (instance == null) {
+            instance = new SoundPoolManager();
+        }
+        return instance;
+    }
+
+    public void init() {
+        if(Build.VERSION.SDK_INT > 21){
+            SoundPool.Builder builder = new SoundPool.Builder();
+            //传入音频数量
+            builder.setMaxStreams(7);
+            //AudioAttributes是一个封装音频各种属性的方法
+            AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder();
+            //设置音频流的合适的属性
+            attrBuilder.setLegacyStreamType(AudioManager.STREAM_MUSIC);//STREAM_MUSIC
+            //加载一个AudioAttributes
+            builder.setAudioAttributes(attrBuilder.build());
+            soundPool = builder.build();
+        }else{
+            soundPool = new SoundPool(7, AudioManager.STREAM_SYSTEM, 0);
+        }
+
+        soundID.put(1, soundPool.load(BaseApplication.appContext, R.raw.new_msg, 1));
+        soundID.put(2, soundPool.load(BaseApplication.appContext, R.raw.voice_msg_start, 1));
+        soundID.put(3, soundPool.load(BaseApplication.appContext, R.raw.voice_msg_send, 1));
+        soundID.put(4, soundPool.load(BaseApplication.appContext, R.raw.ding, 1));
+        soundID.put(5, soundPool.load(BaseApplication.appContext, R.raw.im_disable, 1));
+        soundID.put(6, soundPool.load(BaseApplication.appContext, R.raw.dong, 1));
+        soundID.put(7, soundPool.load(BaseApplication.appContext, R.raw.im_start, 1));
+
+        soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
+            @Override
+            public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
+                isLoaded = true;
+            }
+        });
+    }
+
+    public void playSound(int index) {
+        try {
+            if (isLoaded) {
+                soundPool.play(soundID.get(index), 1.0f, 1.0f, 0, 0, 1);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void release() {
+        soundPool.release();
+    }
+}

+ 239 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/SpeechUtil.java

@@ -0,0 +1,239 @@
+package com.wdkl.ncs.android.component.home.util;
+
+import android.content.Context;
+import android.os.Build;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.UtteranceProgressListener;
+import android.text.TextUtils;
+import android.util.Log;
+
+
+import com.wdkl.ncs.android.middleware.common.Constants;
+import com.wdkl.ncs.android.lib.base.BaseApplication;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class SpeechUtil {
+    private static final String TAG = "tts_SpeechUtil";
+
+    private TextToSpeech textToSpeech = null;
+    private static SpeechUtil speech;
+    private int speakIndex = 0;
+    private int loopCount = 1;
+    private boolean isStop = true;
+    public volatile static ArrayList<String> speechTextList = new ArrayList<>();
+    private Thread speechThread;
+    private boolean isSpeechLoop = true;
+    private String speakSpeech;
+    private final Object lockObject = new Object();
+
+    public static SpeechUtil getInstance() {
+        if (speech == null) {
+            synchronized (SpeechUtil.class) {
+                if (speech == null) {
+                    speech = new SpeechUtil();
+                }
+            }
+        }
+        return speech;
+    }
+
+    public TextToSpeech getTextToSpeech() {
+        return textToSpeech;
+    }
+
+    public void init(Context context, ISpeechCallback callback) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+            textToSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
+                @Override
+                public void onInit(int status) {
+                    if (status == TextToSpeech.SUCCESS) {
+                        int supported = textToSpeech.setLanguage(Locale.CHINESE);
+                        if ((supported != TextToSpeech.LANG_AVAILABLE) && (supported != TextToSpeech.LANG_COUNTRY_AVAILABLE)) {
+                            Constants.Companion.setTtsState(1);
+                            Log.d(TAG, "onInit: 当前不支持中文");
+                            if (callback != null) {
+                                callback.initFailed();
+                            }
+                        } else {
+                            Constants.Companion.setTtsState(2);
+                            Log.d(TAG, "onInit: 支持中文");
+                            if (callback != null) {
+                                callback.initSuccess();
+                            }
+                        }
+                        Log.d(TAG, "onInit: TTS引擎初始化成功");
+                    } else {
+                        Constants.Companion.setTtsState(0);
+                        Log.d(TAG, "onInit: TTS引擎初始化失败");
+                        if (callback != null) {
+                            callback.initFailed();
+                        }
+                    }
+                }
+            }, "com.iflytek.speechcloud");
+        } else {
+            textToSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
+                @Override
+                public void onInit(int status) {
+                    if (status == TextToSpeech.SUCCESS) {
+                        int supported = textToSpeech.setLanguage(Locale.CHINESE);
+                        if ((supported != TextToSpeech.LANG_AVAILABLE) && (supported != TextToSpeech.LANG_COUNTRY_AVAILABLE)) {
+                            Constants.Companion.setTtsState(1);
+                            Log.d(TAG, "onInit: 当前不支持中文");
+                            if (callback != null) {
+                                callback.initFailed();
+                            }
+                        } else {
+                            Constants.Companion.setTtsState(2);
+                            Log.d(TAG, "onInit: 支持中文");
+                            if (callback != null) {
+                                callback.initSuccess();
+                            }
+                        }
+                        Log.d(TAG, "onInit: TTS引擎初始化成功");
+                    } else {
+                        Constants.Companion.setTtsState(0);
+                        Log.d(TAG, "onInit: TTS引擎初始化失败");
+                        if (callback != null) {
+                            callback.initFailed();
+                        }
+                    }
+                }
+            });
+        }
+        textToSpeech.setSpeechRate(1f);
+    }
+
+    public void newSpeech(String text, boolean emergency) {
+        synchronized (lockObject) {
+            if (Constants.Companion.getTtsState() == 2) {
+                if (emergency) {
+                    speechTextList.add(0, text);
+                } else {
+                    speechTextList.add(text);
+                }
+                startSpeechThread();
+            }
+        }
+    }
+
+    public synchronized void speak(final String text) {
+        if (textToSpeech==null){
+            return;
+        }
+        isStop = false;
+        int speakStatus = textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, "uniqueId");
+        if(speakStatus != TextToSpeech.SUCCESS){
+            SpeechUtil.getInstance().init(BaseApplication.appContext, null);
+            SpeechUtil.getInstance().startSpeechThread();
+            textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, "uniqueId");
+        }
+
+        textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
+            @Override
+            public void onStart(String utteranceId) {
+                //LogUtil.d(TAG, "speak onStart..." + utteranceId);
+            }
+
+            @Override
+            public void onDone(String utteranceId) {
+                speakIndex++;
+                //LogUtil.d(TAG, "speak onDone...index: " + speakIndex + ", loop: " + loopCount);
+                if (speakIndex < loopCount) {
+                    //循环播报
+                    speak(text);
+                } else {
+                    //语音播报完毕
+                    speakIndex = 0;
+                    isStop = true;
+                }
+            }
+
+            @Override
+            public void onError(String utteranceId) {
+                isStop = true;
+                Log.d(TAG, "speak onError..." + utteranceId);
+            }
+        });
+    }
+
+    public void stopSpeak() {
+        if (textToSpeech==null){
+            return;
+        }
+        if (textToSpeech.isSpeaking()) {
+            textToSpeech.stop();
+            speechTextList.clear();
+            isStop = true;
+            speakIndex = 0;
+            Log.d(TAG, "stop speak");
+        }
+    }
+
+    public void removeSpeak(String text) {
+        synchronized (lockObject) {
+            if (!TextUtils.isEmpty(text) && !TextUtils.isEmpty(speakSpeech)) {
+                if (text.equals(speakSpeech) && textToSpeech.isSpeaking()) {
+                    textToSpeech.stop();
+                    speechTextList.remove(text);
+                    isStop = true;
+                    speakIndex = 0;
+                } else {
+                    speechTextList.remove(text);
+                }
+            }
+        }
+    }
+
+    public void setSpeechLoopCount(int count) {
+        loopCount = count;
+    }
+
+    public void release() {
+        speechTextList.clear();
+        isStop = true;
+        speakIndex = 0;
+        //Constants.Companion.setTtsState(0);
+        if (textToSpeech != null) {
+            textToSpeech.stop();
+            textToSpeech.shutdown();
+            textToSpeech = null;
+        }
+    }
+
+    public void startSpeechThread() {
+        if (null == speechThread) {
+            speechThread = new Thread(new SpeechRunnable());
+            speechThread.start();
+        } else if (!speechThread.isAlive()) {
+            speechThread.start();
+        }
+    }
+
+    public class SpeechRunnable implements Runnable {
+        public void run() {
+            while (isSpeechLoop) {
+                //synchronized (lockObject) {
+                    if (speechTextList.size() > 0 && isStop) {
+                        speakSpeech = speechTextList.get(0);
+                        Log.d(TAG, "speakSpeech: " + speakSpeech);
+                        speak(speakSpeech);
+
+                        //if (speechTextList.contains(speakSpeech)) {
+                            speechTextList.remove(speakSpeech);
+                        //}
+                    }
+                //}
+
+                try {
+                    Thread.sleep(50);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+}

+ 30 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/TimeTransition.kt

@@ -0,0 +1,30 @@
+package com.wdkl.ncs.android.component.home.util
+
+import com.umeng.weixin.handler.s
+import java.text.SimpleDateFormat
+import java.util.*
+
+
+class TimeTransition {
+
+    fun stampToDate(s:Long):String{
+        return TimeStampToTime(s,"MM-dd HH:mm")
+    }
+
+    fun stampToTime(s:Long):String{
+        return TimeStampToTime(s,"MM-dd HH:mm:ss")
+    }
+    /**
+     * 时间戳转时间
+     */
+    fun TimeStampToTime(s:Long, timestamp:String):String{
+        val res: String
+        val simpleDateFormat = SimpleDateFormat(timestamp)
+        val lt = s
+        val date = Date(lt)
+        res = simpleDateFormat.format(date)
+        return res
+}
+
+
+}

+ 168 - 0
android_mobile/src/main/yd_watch_2/code/com/wdkl/ncs/android/component/home/util/Util.kt

@@ -0,0 +1,168 @@
+package com.wdkl.ncs.android.component.home.util
+
+import android.annotation.SuppressLint
+import android.app.KeyguardManager
+import android.content.Context
+import android.content.Context.KEYGUARD_SERVICE
+import android.os.Build
+import android.os.Environment
+import android.os.PowerManager
+import android.telephony.TelephonyManager
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import com.enation.javashop.utils.base.tool.SystemTool
+import com.google.common.base.Strings
+import com.wdkl.ncs.android.lib.base.BaseApplication
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.IOException
+import java.util.*
+
+
+object Util {
+
+    var IMEI = ""
+    var UUID_FILE_NAME = "wdkl_uuid.txt"
+
+    /*
+      激活获取IMEI用
+    */
+    @SuppressLint("MissingPermission", "PrivateApi")
+    fun getIMEI(context: Context): String {
+        if (Build.VERSION.SDK_INT >= 29) {
+            val UUIDStr = readSD()
+            if (UUIDStr == null || UUIDStr.isEmpty()) { //如果为空或者空字符串就生成UUID创建文件并写入UUID
+                val uuid: String = Date().getTime().toString()
+                writeSD(uuid)
+                IMEI = uuid
+            } else {
+                IMEI = UUIDStr
+            }
+            return IMEI
+        }
+
+        IMEI = SystemTool.getPhoneImei(context)
+        if (Strings.isNullOrEmpty(IMEI)) {
+            val tm = (context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager)
+            try {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                    IMEI = tm.imei
+                } else {
+                    IMEI = tm.deviceId
+                }
+            }catch (e:Exception){
+            }
+
+            if (Strings.isNullOrEmpty(IMEI)) {
+                IMEI = SystemTool.getLocalMacAddress()
+//                IMEI = "35" + //we make this look like a valid IMEI
+//                        Build.BOARD.length % 10 + Build.BRAND.length % 10 + Build.CPU_ABI.length % 10 + Build.DEVICE.length % 10 + Build.DISPLAY.length % 10 + Build.HOST.length % 10 + Build.ID.length % 10 + Build.MANUFACTURER.length % 10 + Build.MODEL.length % 10 + Build.PRODUCT.length % 10 + Build.TAGS.length % 10 + Build.TYPE.length % 10 + Build.USER.length % 10 //13 digits
+            }
+        }
+        return IMEI
+    }
+
+    /**
+     * 亮屏并解锁
+     */
+    @SuppressLint("InvalidWakeLockTag")
+    fun wakeUpAndUnlock(): PowerManager.WakeLock? {
+        val pm = BaseApplication.appContext.getSystemService(Context.POWER_SERVICE) as PowerManager
+        var wakeLock: PowerManager.WakeLock? = null
+
+        val screenOn = pm.isInteractive
+        if (!screenOn) { //如果是熄灭状态
+            wakeLock = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_DIM_WAKE_LOCK, "TAG")
+            wakeLock.acquire(10000)//亮屏
+        }
+
+        // 屏幕解锁
+        val keyguardManager = BaseApplication.appContext.getSystemService(KEYGUARD_SERVICE) as KeyguardManager
+        val keyguardLock = keyguardManager.newKeyguardLock("unLock")
+        // 屏幕锁定
+        keyguardLock.reenableKeyguard()
+        keyguardLock.disableKeyguard() // 解锁
+        return wakeLock
+    }
+
+    @SuppressLint("InvalidWakeLockTag")
+    fun getCpuWakeLock(context: Context): PowerManager.WakeLock? {
+        val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
+        val cpuWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                context.javaClass.canonicalName)
+        return cpuWakeLock
+    }
+
+    fun appendSpace(para: String): String? {
+        val length = para.length
+        val value = CharArray(length shl 1)
+        var i = 0
+        var j = 0
+        while (i < length) {
+            value[j] = para[i]
+            value[1 + j] = ' '
+            ++i
+            j = i shl 1
+        }
+        return String(value)
+    }
+
+    fun writeSD(content: String): String? {
+        //文件输出流
+        var out: FileOutputStream? = null
+        //设置文件路径
+        val file = File(Environment.getExternalStorageDirectory(), UUID_FILE_NAME)
+        return try {
+            out = FileOutputStream(file)
+            out.write(content.toByteArray())
+            content
+        } catch (e: Exception) {
+            e.printStackTrace()
+            ""
+        } finally {
+            try {
+                if (out != null) {
+                    out.close()
+                }
+            } catch (e: IOException) {
+                e.printStackTrace()
+            }
+        }
+    }
+
+    fun readSD(): String? {
+        //文件输入流
+        var `in`: FileInputStream? = null
+        //设置文件路径
+        val file = File(Environment.getExternalStorageDirectory(), UUID_FILE_NAME)
+        return try {
+            `in` = FileInputStream(file)
+            //使用缓冲来读
+            val buf = ByteArray(1024) //每1024字节读一次
+            val builder = StringBuilder()
+            while (`in`.read(buf) !== -1) {
+                builder.append(String(buf).trim { it <= ' ' })
+            }
+            builder.toString()
+        } catch (e: java.lang.Exception) {
+            e.printStackTrace()
+            ""
+        } finally {
+            try {
+                if (`in` != null) {
+                    `in`.close()
+                }
+            } catch (e: IOException) {
+                e.printStackTrace()
+            }
+        }
+    }
+
+    fun getPixelsFromDp(context: Context, size: Int): Int {
+        val metrics = DisplayMetrics()
+        val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+        wm.getDefaultDisplay().getMetrics(metrics)
+        return size * metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT
+    }
+}

BIN
android_mobile/src/main/yd_watch_2/res/drawable-hdpi/hu_jiao.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-hdpi/hu_jiao_bg.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-hdpi/wei_chu_li_bg.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-hdpi/yi_chu_li_bg.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-hdpi/yu_yin_wei_chu_li.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/bao_ma_tou_xiang.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/dian_liang.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/geng_duo.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/gu_ke_lie_biao.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_chu_wei_jie.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_chu_yi_jie.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_jiao.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_jiao_bg.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_ru_wei_jie.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_ru_yi_jie.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/hu_shi_to_xiang.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/ju_jie.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/sip_lian_jie.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/tong_hu_ji_lu.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/voice_recorder.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/wei_bo_fang.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/wei_chu_li.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/wei_chu_li_bg.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/wifi_lian_jie.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yi_bo_fang.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yi_chu_li.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yi_chu_li_bg.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yu_yin_gua_duan.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yu_yin_jie_ting.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/yu_yin_wei_chu_li.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable-mdpi/zhe_die.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable/av_audio_answer.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable/av_audio_answer_hover.png


+ 10 - 0
android_mobile/src/main/yd_watch_2/res/drawable/av_audio_answer_selector.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:drawable="@drawable/av_audio_answer" android:state_focused="false" android:state_pressed="false" android:state_selected="false" />
+    <item android:drawable="@drawable/av_audio_answer_hover" android:state_pressed="false" android:state_selected="true" />
+    <!-- Focused states -->
+    <item android:drawable="@drawable/av_audio_answer_hover" android:state_focused="true" android:state_pressed="false" android:state_selected="false" />
+    <!-- Pressed -->
+    <item android:drawable="@drawable/av_audio_answer_hover" android:state_pressed="true" />
+</selector>

BIN
android_mobile/src/main/yd_watch_2/res/drawable/av_camera.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable/av_camera_hover.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable/av_default_header.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable/av_float_audio.png


+ 16 - 0
android_mobile/src/main/yd_watch_2/res/drawable/av_float_bg.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <solid android:color="#ffffff" />
+
+    <stroke
+        android:width="0.5px"
+        android:color="#ffffff" />
+
+    <corners android:radius="3dp" />
+
+    <size
+        android:width="60dp"
+        android:height="80dp" />
+</shape>

BIN
android_mobile/src/main/yd_watch_2/res/drawable/av_handfree.png


BIN
android_mobile/src/main/yd_watch_2/res/drawable/av_handfree_hover.png


+ 0 - 0
android_mobile/src/main/yd_watch_2/res/drawable/av_hang_up.png


Some files were not shown because too many files changed in this diff