浏览代码

增加sip通话支持

weizhengliang 1 年之前
父节点
当前提交
55f1b00490
共有 24 个文件被更改,包括 1678 次插入46 次删除
  1. 3 0
      android_door/build.gradle
  2. 199 0
      android_door/src/main/common/java/com/wdkl/ncs/host/activity/CallActivity.java
  3. 45 0
      android_door/src/main/common/java/com/wdkl/ncs/host/activity/SipTestActivity.kt
  4. 330 0
      android_door/src/main/common/java/com/wdkl/ncs/host/service/WdklSipService.java
  5. 170 0
      android_door/src/main/common/java/com/wdkl/ncs/host/util/AudioRouteUtils.kt
  6. 50 0
      android_door/src/main/common/res/layout/activity_sip_test.xml
  7. 34 0
      android_door/src/main/common/res/layout/call.xml
  8. 20 0
      android_door/src/main/common/res/raw/linphonerc_default
  9. 34 0
      android_door/src/main/common/res/raw/linphonerc_factory
  10. 7 0
      android_door/src/main/h10_3128/AndroidManifest.xml
  11. 131 19
      android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/activity/CallingdoorActivity.kt
  12. 514 0
      android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/fragment/SipCallFragment.kt
  13. 2 0
      android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/helper/AppInfoDialogHelper.java
  14. 12 4
      android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/helper/AppUpdateHelper.java
  15. 6 0
      android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/helper/WarningDialogHelper.java
  16. 11 0
      android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/settings/SettingConfig.java
  17. 5 3
      android_door/src/main/h10_3128/res/layout/view_title_layout.xml
  18. 5 3
      android_door/src/main/h10_3128/res/layout/view_title_layout_rk3288.xml
  19. 4 1
      build.gradle
  20. 3 0
      common/build.gradle
  21. 二进制
      common/libs/linphone-sdk-android-5.2.10.aar
  22. 7 0
      middleware/src/main/code/com/wdkl/ncs/android/middleware/common/Constant.java
  23. 85 16
      middleware/src/main/code/com/wdkl/ncs/android/middleware/model/ServerInfo.java
  24. 1 0
      middleware/src/main/code/com/wdkl/ncs/android/middleware/utils/CommonUtils.java

+ 3 - 0
android_door/build.gradle

@@ -41,6 +41,9 @@ android {
 
     sourceSets {
         println 'config app_device_type ===== ' + app_device_type
+        main.java.srcDirs += 'src/main/common/java'
+        main.res.srcDirs += 'src/main/common/res'
+
         if ("mk_h10_z_3128_1" == app_device_type) {
             main.java.srcDirs += 'src/main/h10_3128/java'
             main.res.srcDirs += 'src/main/h10_3128/res'

+ 199 - 0
android_door/src/main/common/java/com/wdkl/ncs/host/activity/CallActivity.java

@@ -0,0 +1,199 @@
+package com.wdkl.ncs.host.activity;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.TextureView;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.Nullable;
+
+import com.wdkl.app.ncs.callingdoor.R;
+import com.wdkl.ncs.host.service.WdklSipService;
+import com.wdkl.ncs.host.util.AudioRouteUtils;
+
+import org.linphone.core.Call;
+import org.linphone.core.Core;
+import org.linphone.core.CoreListenerStub;
+import org.linphone.core.VideoDefinition;
+import org.linphone.mediastream.Version;
+
+public class CallActivity extends Activity {
+    private static final String TAG = "CallActivity";
+    // 远程视频
+    private TextureView mVideoView;
+    // 本地视频
+    private TextureView mCaptureView;
+
+    private CoreListenerStub mCoreListener;
+
+    private AudioManager audioManager;
+    private Core core;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.call);
+
+        mVideoView = findViewById(R.id.videoSurface);
+        mCaptureView = findViewById(R.id.videoCaptureSurface);
+
+        core = WdklSipService.getCore();
+        // 配置核心视频层渲染
+        core.setNativeVideoWindowId(mVideoView);
+        core.setNativePreviewWindowId(mCaptureView);
+
+        audioManager = (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
+
+        // 配置通话状态结束监听
+        mCoreListener = new CoreListenerStub() {
+            @Override
+            public void onCallStateChanged(Core core, Call call, Call.State state, String message) {
+                if (state == Call.State.End || state == Call.State.Released) {
+                    finish();
+                }
+            }
+        };
+
+        findViewById(R.id.toggle_speaker).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                speakerOn();
+            }
+        });
+
+        findViewById(R.id.terminate_call).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Core core = WdklSipService.getCore();
+                if (core.getCallsNb() > 0) {
+                    Call call = core.getCurrentCall();
+                    if (call == null) {
+                        call = core.getCalls()[0];
+                    }
+                    call.terminate();
+                }
+            }
+        });
+    }
+
+    private void speakerOn(){
+        Log.i(TAG,">>>>>>>>>>>>>>"+audioManager.isSpeakerphoneOn());
+        //5.0以上
+        audioManager.setMode(AudioManager.MODE_IN_CALL);
+        //设置音量,解决有些机型切换后没声音或者声音突然变大的问题
+        audioManager.setStreamVolume(
+                AudioManager.STREAM_MUSIC,
+                audioManager.getStreamVolume(AudioManager.STREAM_MUSIC),
+                AudioManager.FX_KEY_CLICK
+        );
+        audioManager.setSpeakerphoneOn(true);
+        Log.i(TAG,">>>>>>>>>>>>>>"+audioManager.isSpeakerphoneOn());
+
+        if (core != null) {
+            AudioRouteUtils.Companion.routeAudioToSpeaker(core, null, false);
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        WdklSipService.getCore().addListener(mCoreListener);
+        resizePreview();
+    }
+
+    @Override
+    protected void onPause() {
+        WdklSipService.getCore().removeListener(mCoreListener);
+
+        super.onPause();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
+    @TargetApi(24)
+    @Override
+    public void onUserLeaveHint() {
+        // 判断设备是否支持画中画
+        boolean supportsPip =
+                getPackageManager()
+                        .hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
+        Log.i(TAG,"[Call] Is picture in picture supported: " + supportsPip);
+        if (supportsPip && Version.sdkAboveOrEqual(24)) {
+            enterPictureInPictureMode();
+        }
+    }
+
+    @Override
+    public void onPictureInPictureModeChanged(
+            boolean isInPictureInPictureMode, Configuration newConfig) {
+        if (isInPictureInPictureMode) {
+            // Currently nothing to do has we only display video
+            // But if we had controls or other UI elements we should hide them
+        } else {
+            // If we did hide something, let's make them visible again
+        }
+    }
+
+    private void resizePreview() {
+        Core core = WdklSipService.getCore();
+        if (core.getCallsNb() > 0) {
+            Call call = core.getCurrentCall();
+            if (call == null) {
+                call = core.getCalls()[0];
+            }
+            if (call == null) return;
+
+            DisplayMetrics metrics = new DisplayMetrics();
+            getWindowManager().getDefaultDisplay().getMetrics(metrics);
+            int screenHeight = metrics.heightPixels;
+            int maxHeight =
+                    screenHeight / 4; // 本地1/4高
+
+            VideoDefinition videoSize =
+                    call.getCurrentParams()
+                            .getSentVideoDefinition();
+            if (videoSize.getWidth() == 0 || videoSize.getHeight() == 0) {
+                Log.w(TAG,
+                        "[Video] 无法使用发送端视频定义, 使用默认");
+                videoSize = core.getPreferredVideoDefinition();
+            }
+            int width = videoSize.getWidth();
+            int height = videoSize.getHeight();
+
+            Log.d(TAG,"[Video] Video height is " + height + ", width is " + width);
+            width = width * maxHeight / height;
+            height = maxHeight;
+
+            if (mCaptureView == null) {
+                Log.e(TAG,"[Video] 本地视频流 is null !");
+                return;
+            }
+
+            RelativeLayout.LayoutParams newLp = new RelativeLayout.LayoutParams(width, height);
+            newLp.addRule(
+                    RelativeLayout.ALIGN_PARENT_BOTTOM,
+                    1); // Clears the rule, as there is no removeRule until API 17.
+            newLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 1);
+            mCaptureView.setLayoutParams(newLp);
+            Log.d(TAG,"[Video] 远程视频 size set to " + width + "x" + height);
+        }
+    }
+}

+ 45 - 0
android_door/src/main/common/java/com/wdkl/ncs/host/activity/SipTestActivity.kt

@@ -0,0 +1,45 @@
+package com.wdkl.ncs.host.activity
+
+import android.os.Bundle
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import com.wdkl.app.ncs.callingdoor.R
+import com.wdkl.ncs.host.service.WdklSipService
+import kotlinx.android.synthetic.main.activity_sip_test.*
+import org.linphone.core.Core
+
+class SipTestActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_sip_test)
+
+        val core: Core = WdklSipService.getCore()
+        WdklSipService.sipTesting = true
+
+        sip_test_id.text = core.identity
+
+        sip_test_back.setOnClickListener {
+            finish()
+        }
+
+        sip_test_call.setOnClickListener{
+            val sipNo = callout_sipno.text
+            if (sipNo.equals("")){
+                Toast.makeText(this, "sip no must input", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+
+            val addressToCall = core.interpretUrl(sipNo.toString())
+            val params = core.createCallParams(null)
+            params?.isVideoEnabled  = false
+            if (addressToCall != null) {
+                core.inviteAddressWithParams(addressToCall, params!!)
+            }
+        }
+    }
+
+    override fun finish() {
+        WdklSipService.sipTesting = false
+        super.finish()
+    }
+}

+ 330 - 0
android_door/src/main/common/java/com/wdkl/ncs/host/service/WdklSipService.java

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

+ 170 - 0
android_door/src/main/common/java/com/wdkl/ncs/host/util/AudioRouteUtils.kt

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

+ 50 - 0
android_door/src/main/common/res/layout/activity_sip_test.xml

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activity.SipTestActivity">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:padding="@dimen/activity_vertical_margin">
+
+        <TextView
+            android:id="@+id/sip_test_id"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="@dimen/activity_vertical_margin"/>
+
+        <EditText
+            android:id="@+id/callout_sipno"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:hint="test call out sip no"
+            android:layout_margin="@dimen/activity_vertical_margin"
+            tools:ignore="MissingConstraints" />
+
+        <Button
+            android:id="@+id/sip_test_back"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="BACK"
+            android:layout_margin="@dimen/activity_vertical_margin"
+            tools:ignore="MissingConstraints" />
+
+        <Button
+            android:id="@+id/sip_test_call"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="CALL"
+            android:layout_margin="@dimen/activity_vertical_margin"
+            tools:ignore="MissingConstraints" />
+    </LinearLayout>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

+ 34 - 0
android_door/src/main/common/res/layout/call.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextureView
+        android:id="@+id/videoSurface"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <TextureView
+        android:id="@+id/videoCaptureSurface"
+        android:layout_width="300dp"
+        android:layout_height="200dp"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentBottom="true" />
+
+    <Button
+        android:id="@+id/toggle_speaker"
+        android:text="切换外放"
+        android:layout_marginBottom="30dp"
+        android:layout_centerHorizontal="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <Button
+        android:id="@+id/terminate_call"
+        android:text="挂断"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</RelativeLayout>

+ 20 - 0
android_door/src/main/common/res/raw/linphonerc_default

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

+ 34 - 0
android_door/src/main/common/res/raw/linphonerc_factory

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

+ 7 - 0
android_door/src/main/h10_3128/AndroidManifest.xml

@@ -22,6 +22,13 @@
             android:screenOrientation="landscape"
             android:launchMode="singleTask"/>
 
+        <activity android:name="com.wdkl.ncs.host.activity.SipTestActivity" />
+        <activity android:name="com.wdkl.ncs.host.activity.CallActivity" />
+
+        <service
+            android:name="com.wdkl.ncs.host.service.WdklSipService"
+            android:label="@string/javashop_app_name" />
+
         <provider
             android:name="androidx.core.content.FileProvider"
             android:authorities="${applicationId}.provider"

+ 131 - 19
android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/activity/CallingdoorActivity.kt

@@ -54,6 +54,7 @@ import com.wdkl.ncs.android.middleware.utils.AppUtil
 import com.wdkl.ncs.android.middleware.utils.CommonUtils
 import com.wdkl.ncs.android.middleware.utils.StringUtil
 import com.wdkl.ncs.android.middleware.utils.Util
+import com.wdkl.ncs.host.service.WdklSipService
 import com.wdkl.ncs.janus.util.JanusConstant
 import kotlinx.android.synthetic.main.callingdoor_main_lay.*
 import kotlinx.android.synthetic.main.callingdoor_main_lay.app_version
@@ -73,6 +74,8 @@ import okhttp3.Request
 import org.greenrobot.eventbus.EventBus
 import org.greenrobot.eventbus.Subscribe
 import org.greenrobot.eventbus.ThreadMode
+import org.linphone.core.AccountCreator
+import org.linphone.core.TransportType
 import serialporttest.utils.SerialPortUtil
 import serialporttest.utils.SerialPortUtil433
 import serialporttest.utils.StringUtils
@@ -128,6 +131,8 @@ class CallingdoorActivity :BaseActivity<CallingdoorActivityPresenter, Callingdoo
 
     private var trans433Data: Trans433Data? = null
 
+    private var mAccountCreator: AccountCreator? = null
+
     //网络异常计数
     private var netErrCount : Int = 0
 
@@ -167,6 +172,20 @@ class CallingdoorActivity :BaseActivity<CallingdoorActivityPresenter, Callingdoo
             Constant.DEVICE_REGISTER_ID = Constant.LOCAL_MAC
         }
 
+        if (SettingConfig.getSipEnabled(activity)) {
+            //启动sip服务
+            val serviceIntent = Intent(BaseApplication.appContext, WdklSipService::class.java)
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                //android8.0以上通过startForegroundService启动service
+                startForegroundService(serviceIntent)
+            } else {
+                startService(serviceIntent)
+            }
+            view_title_layout_tv_point.text = "sip"
+        } else {
+            view_title_layout_tv_point.text = "rtc"
+        }
+
         //presenter.loadTcpServerHost()
         //注册广播
         regReceiver()
@@ -762,6 +781,35 @@ class CallingdoorActivity :BaseActivity<CallingdoorActivityPresenter, Callingdoo
         //开启TCP连接
         startTcp()
         showMessage("tcp connect...host: " + Constant.TCP_SERVER_URL + ", port: " + Constant.TCP_PORT)
+        WarningDialogHelper.dismiss()
+
+        if (data.sipIp != null) {
+            Constant.sip_ip = data.sipIp
+        }
+        if (data.sipPort != null) {
+            Constant.sip_port = data.sipPort
+        }
+
+        if (data.voiceType != null) {
+            val orgSipEnable = SettingConfig.getSipEnabled(activity)
+            if ("sip".equals(data.voiceType)) {
+                //使用sip通话
+                SettingConfig.setSipEnable(activity, true)
+                if (!orgSipEnable) {
+                    handler.postDelayed({
+                        AppUpdateHelper.reboot(activity)
+                    }, 8000)
+                }
+            } else {
+                //使用webrtc通话
+                SettingConfig.setSipEnable(activity, false)
+                if (orgSipEnable) {
+                    handler.postDelayed({
+                        AppUpdateHelper.reboot(activity)
+                    }, 8000)
+                }
+            }
+        }
 
         Thread(Runnable {
             while (!initialized) {
@@ -821,7 +869,9 @@ class CallingdoorActivity :BaseActivity<CallingdoorActivityPresenter, Callingdoo
             switchFragment(R.id.callingdoor_main_frame, MainFragment(), mainFragment)
         }
 
-        Constant.SIP_ID = deviceInfo.sipId
+        if (deviceInfo.sipId != null) {
+            Constant.SIP_ID = deviceInfo.sipId
+        }
         Constant.DEVICE_ID = deviceInfo.id
         Constant.PART_ID = deviceInfo.partId
         Constant.ROOM_NAME = deviceInfo.fullName
@@ -937,6 +987,39 @@ class CallingdoorActivity :BaseActivity<CallingdoorActivityPresenter, Callingdoo
         updateSettings(true)
 
         EventBus.getDefault().post(MessageEvent("updateCustom", Constant.EVENT_UPDATE_CUSTOM))
+
+
+        //配置sip账号并连接sip服务器
+        if (SettingConfig.getSipEnabled(activity)) {
+            //配置sip账户
+            if (WdklSipService.getCore() != null) {
+                mAccountCreator = WdklSipService.getCore().createAccountCreator(null)
+                // 以下三项必须
+                if (!TextUtils.isEmpty(Constant.SIP_ID) && !TextUtils.isEmpty(Constant.sip_ip)) {
+                    Log.e(TAG, "sip connect: ${Constant.SIP_ID}@${Constant.sip_ip}:${Constant.sip_port}")
+                    mAccountCreator!!.setDomain(Constant.sip_ip)
+                    mAccountCreator!!.setUsername(Constant.SIP_ID)
+                    mAccountCreator!!.setPassword(Constant.SIP_ID)
+                    //默认使用udp
+                    mAccountCreator!!.transport = TransportType.Udp
+
+                    // 这里会自动创建代理配置、认证信息到 SIP核心
+                    val cfg = mAccountCreator!!.createProxyConfig()
+                    // 确保新创建的是最新
+                    WdklSipService.getCore().defaultProxyConfig = cfg
+
+                    /*if (Constants.sip_port != null) {
+                        var transports = WdklSipService.getCore().transports
+                        transports.udpPort = Constants.sip_port!!
+                        transports.tcpPort = Constants.sip_port!!
+                        transports.tlsPort = -1
+                        WdklSipService.getCore().transports = transports
+                    }*/
+                } else {
+                    showMessage("SIP empty")
+                }
+            }
+        }
     }
 
     override fun loadAppVersion(appInfo: AppVersionDO) {
@@ -1114,11 +1197,20 @@ class CallingdoorActivity :BaseActivity<CallingdoorActivityPresenter, Callingdoo
                 Constant.CALL_TYPE = Constant.VOICE_CALL
             }
             Constant.CALL_STATE = Constant.CALL_OUTGOING
-            var fragment = SkyCallFragment()
-            var bundle = Bundle()
-            bundle.putInt("call_state", 0)
-            fragment.arguments = bundle
-            addCallFragment(fragment)
+
+            if (SettingConfig.getSipEnabled(activity)) {
+                var fragment = SipCallFragment()
+                var bundle = Bundle()
+                bundle.putInt("call_state", 0)
+                fragment.arguments = bundle
+                addCallFragment(fragment)
+            } else {
+                var fragment = SkyCallFragment()
+                var bundle = Bundle()
+                bundle.putInt("call_state", 0)
+                fragment.arguments = bundle
+                addCallFragment(fragment)
+            }
         } else {
             showMessage(R.string.net_error)
         }
@@ -1135,12 +1227,22 @@ class CallingdoorActivity :BaseActivity<CallingdoorActivityPresenter, Callingdoo
                 Constant.CALL_TYPE = Constant.VOICE_CALL
             }
             Constant.CALL_STATE = Constant.CALL_OUTGOING
-            var fragment = SkyCallFragment()
-            var bundle = Bundle()
-            bundle.putInt("call_state", 2)
-            bundle.putInt("bed_id", bedId)
-            fragment.arguments = bundle
-            addCallFragment(fragment)
+
+            if (SettingConfig.getSipEnabled(activity)) {
+                var fragment = SipCallFragment()
+                var bundle = Bundle()
+                bundle.putInt("call_state", 2)
+                bundle.putInt("bed_id", bedId)
+                fragment.arguments = bundle
+                addCallFragment(fragment)
+            } else {
+                var fragment = SkyCallFragment()
+                var bundle = Bundle()
+                bundle.putInt("call_state", 2)
+                bundle.putInt("bed_id", bedId)
+                fragment.arguments = bundle
+                addCallFragment(fragment)
+            }
         } else {
             showMessage(R.string.net_error)
         }
@@ -1181,13 +1283,23 @@ class CallingdoorActivity :BaseActivity<CallingdoorActivityPresenter, Callingdoo
                             Constant.CALL_STATE = Constant.CALL_INCOMING
                             if (Constant.TCP_CONNECTED && !TextUtils.isEmpty(Constant.SIP_ID)) {
                                 //来电界面
-                                var fragment = SkyCallFragment()
-                                var bundle = Bundle()
-                                bundle.putInt("call_state", 1)
-                                bundle.putString("tcp_tid", tcpModel.tid)
-                                bundle.putSerializable("tcp_model", tcpModel)
-                                fragment.arguments = bundle
-                                addCallFragment(fragment)
+                                if (SettingConfig.getSipEnabled(activity)) {
+                                    var fragment = SipCallFragment()
+                                    var bundle = Bundle()
+                                    bundle.putInt("call_state", 1)
+                                    bundle.putString("tcp_tid", tcpModel.tid)
+                                    bundle.putSerializable("tcp_model", tcpModel)
+                                    fragment.arguments = bundle
+                                    addCallFragment(fragment)
+                                } else {
+                                    var fragment = SkyCallFragment()
+                                    var bundle = Bundle()
+                                    bundle.putInt("call_state", 1)
+                                    bundle.putString("tcp_tid", tcpModel.tid)
+                                    bundle.putSerializable("tcp_model", tcpModel)
+                                    fragment.arguments = bundle
+                                    addCallFragment(fragment)
+                                }
                             } else {
                                 showMessage(R.string.call_init_error)
                                 Constant.CALL_STATE = Constant.CALL_STANDBY

+ 514 - 0
android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/fragment/SipCallFragment.kt

@@ -0,0 +1,514 @@
+package com.wdkl.app.ncs.callingdoor.fragment
+
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.os.SystemClock
+import android.text.TextUtils
+import android.util.Log
+import android.view.View
+import android.widget.SeekBar
+import com.alibaba.fastjson.JSONObject
+import com.google.gson.Gson
+import com.wdkl.app.ncs.callingdoor.R
+import com.wdkl.app.ncs.callingdoor.activity.CallingdoorActivity
+import com.wdkl.app.ncs.callingdoor.helper.DoorLightHelper
+import com.wdkl.app.ncs.callingdoor.helper.RingPlayHelper
+import com.wdkl.app.ncs.callingdoor.helper.VoiceManagerUtil
+import com.wdkl.app.ncs.callingdoor.settings.SettingConfig
+import com.wdkl.ncs.android.lib.utils.AppTool
+import com.wdkl.ncs.android.lib.utils.showMessage
+import com.wdkl.ncs.android.middleware.common.Constant
+import com.wdkl.ncs.android.middleware.common.MessageEvent
+import com.wdkl.ncs.android.middleware.model.vo.InteractionVO
+import com.wdkl.ncs.android.middleware.tcp.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.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.host.service.WdklSipService
+import com.wdkl.ncs.host.util.AudioRouteUtils
+import com.wdkl.ncs.janus.entity.Room
+import com.wdkl.ncs.janus.util.EnumType
+import kotlinx.android.synthetic.main.sky_voice_call_layout.*
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import org.linphone.core.Core
+
+class SipCallFragment: BaseCallFragment() {
+    private val TAG = "SipCallFragment"
+
+    //来电设备id
+    var fromId: Int = -1
+    private var interactionVO: InteractionVO? = null
+
+    private var callEnded: Boolean = false
+    private var outGoing: Boolean = false
+    private var audioCall: Boolean = true
+    private var callSuccess: Boolean = false
+
+    private var sipCore: Core? = null
+    private var volume = 60
+
+    private val handler = Handler(Looper.getMainLooper())
+
+    override fun getLayId(): Int {
+        if ("rk3288".equals(Build.MODEL)) {
+            return R.layout.sky_voice_call_layout_rk3288
+        } else {
+            return R.layout.sky_voice_call_layout
+        }
+    }
+
+    override fun init() {
+        //初始化计时器
+        initCountDownTimer(sky_voice_call_timeout)
+
+        sipCore = WdklSipService.getCore()
+
+        /*volume = SettingConfig.getExtensionCallVolume(activity)
+        if (volume < 0 || volume > 100) {
+            volume = 60
+        }
+        call_volume_bar.progress = volume/10
+        tv_volume.text = "" + volume/10
+        VoiceManagerUtil.setCallVoice(activity, volume)*/
+
+        //tcp参数
+        if (tcpModel != null) {
+            fromId = tcpModel!!.fromId
+            interactionVO = Gson().fromJson(tcpModel!!.data.toString(), InteractionVO::class.java)
+        }
+
+        //Log.e(TAG, "udpPort: ${sipCore!!.transports.udpPort}, tcpPort: ${sipCore!!.transports.tcpPort}")
+        //Log.d(TAG, "callState: $callState, local sip: ${Constants.sip_id}, target sip: ${Constants.targetSipId}")
+        when (callState) {
+            0 -> {
+                //去电
+                outGoing = true
+                startOutgoing()
+                Constant.CALL_STATE = Constant.CALL_OUTGOING
+                RingPlayHelper.playRingTone(baseActivity, R.raw.ring_back2, true)
+            }
+
+            1 -> {
+                //来电
+                outGoing = false
+                showIncomingCall()
+                RingPlayHelper.playRingTone(baseActivity, R.raw.ring_tone, true)
+            }
+
+            2 -> {
+                //呼叫分机
+                outGoing = true
+                startOutgoing()
+                Constant.CALL_STATE = Constant.CALL_OUTGOING
+                RingPlayHelper.playRingTone(baseActivity, R.raw.ring_back2, true)
+            }
+        }
+    }
+
+    override fun bindEvent() {
+        //通话挂断
+        sky_voice_call_hangup.setOnClickListener {
+            RingPlayHelper.stopRingTone()
+            Constant.CALL_STATE = Constant.CALL_STANDBY
+            if (Constant.CALL_STATE == Constant.CALL_CALLING) {
+                //结束通话
+                if (sky_voice_call_timer != null) {
+                    sky_voice_call_timer.stop()
+                }
+                callEnd(true)
+            } else {
+                if (callState == 0) {
+                    voiceCancel()
+                } else if (callState == 2) {
+                    if (bedId != -1) {
+                        voiceCancelBed()
+                    }
+                }
+                cancelCall()
+            }
+        }
+
+        //来电拒绝
+        sky_voice_call_ring_reject.setOnClickListener {
+            RingPlayHelper.stopRingTone()
+            Constant.CALL_STATE = Constant.CALL_STANDBY
+            voiceReject()
+            callEnd(false)
+        }
+
+        //来电接听
+        sky_voice_call_ring_pickup_audio.setOnClickListener {
+            RingPlayHelper.stopRingTone()
+            Constant.CALL_STATE = Constant.CALL_INCOMING
+            voiceAccept()
+            acceptCall()
+        }
+
+        /*sky_voice_call_mute.setOnClickListener {
+            val micEnable = sipCore!!.isMicEnabled
+            Log.d(TAG,"mic enable: $micEnable")
+
+            if (micEnable) {
+                sipCore!!.isMicEnabled = false
+                sky_voice_call_mute.isSelected = true
+            } else {
+                sipCore!!.isMicEnabled = true
+                sky_voice_call_mute.isSelected = false
+            }
+        }
+
+        call_volume_bar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
+            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
+                tv_volume.text = "" + progress
+                if (seekBar!!.progress <= 1) {
+                    tv_volume.text = "1"
+                } else {
+                    tv_volume.text = "" + progress
+                }
+            }
+
+            override fun onStartTrackingTouch(seekBar: SeekBar?) {
+                //
+            }
+
+            override fun onStopTrackingTouch(seekBar: SeekBar?) {
+                if (seekBar!!.progress <= 2) {
+                    VoiceManagerUtil.setCallVoice(activity, 20)
+                } else {
+                    VoiceManagerUtil.setCallVoice(activity, seekBar.progress*10)
+                }
+            }
+        })*/
+    }
+
+    private fun callTerminate() {
+        if (sipCore != null && sipCore!!.callsNb > 0) {
+            var call = sipCore!!.currentCall
+            if (call == null) {
+                call = sipCore!!.calls[0]
+            }
+            call!!.terminate()
+        }
+    }
+
+    override fun destroy() {
+        cancelTimer()
+        Constant.CALL_STATE = Constant.CALL_STANDBY
+        if (sky_voice_call_timer != null) {
+            sky_voice_call_timer.stop()
+        }
+        RingPlayHelper.stopRingTone()
+        handler.removeCallbacksAndMessages(null)
+
+        //如果当前在护理中则不操作门灯,如果不在护理中则重置门灯
+        if (("rk3128".equals(Build.MODEL) || "rk3368".equals(Build.MODEL)) && !Constant.inNursing && !Constant.sosOn) {
+            //SerialPortHelper.setDoorLight(1, "111") //白色
+            if (SettingConfig.getDoorLightAlwaysOn(activity) == 0) {
+                DoorLightHelper.resetDoorLight()
+            }
+        }
+    }
+
+    private fun voiceAccept() {
+        val callTcp = VoiceUtil.voiceAccept(tid, Constant.DEVICE_ID, fromId, interactionVO?.id)
+        val transaction = object : TcpCallback(callTcp.tid) {
+            override fun onSuccess(jsonObject: JSONObject) {
+                //
+            }
+
+            override fun onFailed(jsonObject: JSONObject) {
+                // 这里写发送失败的方法
+                val callbackString = jsonObject.getString(CALLBACK)
+                handler.post {
+                    showMessage("accept fail: $callbackString")
+                }
+            }
+        }
+        TcpClient.getInstance().sendTcp(callTcp, false, transaction)
+    }
+
+    private fun voiceReject() {
+        val callTcp = VoiceUtil.voiceReject(tid, Constant.DEVICE_ID, fromId, interactionVO?.id)
+        TcpClient.getInstance().sendMsg(callTcp.toJson())
+    }
+
+    private fun startOutgoing() {
+        if (callState == 0) {
+            val outCallTcp = VoiceUtil.voiceCall(Constant.DEVICE_ID, RoleTypeEnum.NURSE.name)
+
+            Constant.CALL_STATE = Constant.CALL_OUTGOING
+            sky_voice_call_outgoing.visibility = View.VISIBLE
+            sky_voice_call_incoming.visibility = View.GONE
+            sky_voice_call_timeout.visibility = View.VISIBLE
+            sky_voice_call_timer.visibility = View.GONE
+            startTimer()
+
+            val transaction: TcpCallback = object : TcpCallback(outCallTcp!!.tid) {
+                override fun onSuccess(jsonObject: JSONObject) {
+                    Constant.CALL_STATE = Constant.CALL_OUTGOING
+                }
+
+                override fun onFailed(jsonObject: JSONObject) {
+                    // 这里写发送失败的方法
+                    val callbackString = jsonObject.getString(CALLBACK)
+                    RingPlayHelper.stopRingTone()
+
+                    handler.post {
+                        cancelCall()
+                        showMessage("outgoing call failed: $callbackString")
+                    }
+                }
+            }
+            TcpClient.getInstance().sendTcp(outCallTcp, false, transaction)
+        } else if (callState == 2) {
+            if (bedId != -1) {
+                val outCallTcp = VoiceUtil.voiceCallBed(Constant.DEVICE_ID, bedId)
+
+                Constant.CALL_STATE = Constant.CALL_OUTGOING
+                sky_voice_call_outgoing.visibility = View.VISIBLE
+                sky_voice_call_incoming.visibility = View.GONE
+                sky_voice_call_timeout.visibility = View.VISIBLE
+                sky_voice_call_timer.visibility = View.GONE
+                startTimer()
+
+                val transaction: TcpCallback = object : TcpCallback(outCallTcp!!.tid) {
+                    override fun onSuccess(jsonObject: JSONObject) {
+                        Constant.CALL_STATE = Constant.CALL_OUTGOING
+                    }
+
+                    override fun onFailed(jsonObject: JSONObject) {
+                        // 这里写发送失败的方法
+                        val callbackString = jsonObject.getString(CALLBACK)
+                        RingPlayHelper.stopRingTone()
+
+                        handler.post {
+                            cancelCall()
+                            showMessage("outgoing call failed: $callbackString")
+                        }
+                    }
+                }
+                TcpClient.getInstance().sendTcp(outCallTcp, false, transaction)
+            } else {
+                showMessage("bed_id null")
+                Constant.CALL_STATE = Constant.CALL_STANDBY
+            }
+        }
+    }
+
+    //去电界面
+    private fun showOutgoingCall() {
+        sky_voice_call_calling_text.setText(R.string.call_success)
+
+        if (!audioCall) {
+            //显示视频画面
+            fullscreen_video_frame.visibility = View.VISIBLE
+            pip_video_frame.visibility = View.VISIBLE
+        }
+    }
+
+    //来电界面
+    private fun showIncomingCall() {
+        Constant.CALL_STATE = Constant.CALL_INCOMING
+        sky_voice_call_calling_text.setText(R.string.call_incoming)
+        sky_voice_call_outgoing.visibility = View.GONE
+        sky_voice_call_incoming.visibility = View.VISIBLE
+        sky_voice_call_timeout.visibility = View.GONE
+        sky_voice_call_timer.visibility = View.GONE
+        cancelTimer()
+    }
+
+    //开始接听
+    private fun acceptCall() {
+        sky_voice_call_calling_text.setText(R.string.call_connecting)
+        sky_voice_call_outgoing.visibility = View.VISIBLE
+        sky_voice_call_incoming.visibility = View.GONE
+        sky_voice_call_timeout.visibility = View.GONE
+        sky_voice_call_timer.visibility = View.GONE
+        cancelTimer()
+    }
+
+    //呼叫取消
+    private fun cancelCall() {
+        cancelTimer()
+        Constant.CALL_STATE = Constant.CALL_STANDBY
+        if (sky_voice_call_timer != null) {
+            sky_voice_call_timer.stop()
+        }
+        callEnd(false)
+    }
+
+    private fun showCalling(audioOnly: Boolean) {
+        if (callEnded) {
+            return
+        }
+
+        if (audioOnly) {
+            ll_voice_call.visibility = View.VISIBLE
+        } else {
+            //显示视频画面
+            fullscreen_video_frame.visibility = View.VISIBLE
+            pip_video_frame.visibility = View.VISIBLE
+            ll_voice_call.visibility = View.GONE
+        }
+
+        Constant.CALL_STATE = Constant.CALL_CALLING
+        sky_voice_call_calling_text.setText(R.string.call_in_call)
+        sky_voice_call_timeout.visibility = View.GONE
+        cancelTimer()
+        sky_voice_call_timer.visibility = View.VISIBLE
+        sky_voice_call_timer.base = SystemClock.elapsedRealtime()
+        sky_voice_call_timer.start()
+        //sky_voice_call_mute.visibility = View.VISIBLE
+        //ll_voice_volume_bar.visibility = View.VISIBLE
+    }
+
+    //通话结束
+    override fun callEnd(handoff: Boolean) {
+        Log.e(TAG, ">>>>>>>>>>> call end !!!!!!!!!!!!!!!!!!")
+        RingPlayHelper.stopRingTone()
+        countDownTimer.cancel()
+
+        synchronized(this) {
+            if (callEnded) {
+                return
+            }
+            callEnded = true
+
+            if (sky_voice_call_timer != null) {
+                sky_voice_call_timer.stop()
+            }
+
+            callTerminate()
+
+            if (handoff) {
+                val callTcp = VoiceUtil.voiceHandoff(tid, Constant.DEVICE_ID, fromId, Constant.interactionId)
+                TcpClient.getInstance().sendMsg(callTcp.toJson())
+            }
+
+            Constant.CALL_STATE = Constant.CALL_STANDBY
+            Constant.interactionId = null
+
+            backToMain()
+        }
+    }
+
+    private fun toggleSpeaker(enable: Boolean) {
+        Log.d(TAG, "toggle speaker: $enable, sipCore: $sipCore")
+        if ( sipCore == null) {
+            return
+        }
+
+        if (enable) {
+            AudioRouteUtils.routeAudioToSpeaker(sipCore!!)
+        } else {
+            AudioRouteUtils.routeAudioToEarpiece(sipCore!!)
+        }
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMoonEvent(messageEvent: MessageEvent) {
+        when (messageEvent.getType()) {
+            Constant.EVENT_TCP_MSG -> {
+                if (messageEvent.message is TcpModel) {
+                    val curTcpModel = messageEvent.message as TcpModel
+                    if (curTcpModel.type == TcpType.VOICE) {
+                        if (curTcpModel.data != null) {
+                            val curInteractionVO = Gson().fromJson(curTcpModel.data.toString(), InteractionVO::class.java)
+                            if (curTcpModel.getAction() == TcpAction.VoiceAction.ACCEPT) {
+                                //我方呼出,对方接受
+                                RingPlayHelper.stopRingTone()
+                                Constant.interactionId = curInteractionVO.id
+                                fromId = curTcpModel.fromId
+                                acceptCall()
+
+                                if (sipCore == null || TextUtils.isEmpty(curInteractionVO.toSipId)) {
+                                    //通话失败,重置并返回主界面
+                                    showMessage("sip_core targetSipId empty!")
+                                    Constant.CALL_STATE = Constant.CALL_STANDBY
+                                    if (sky_voice_call_timer != null) {
+                                        sky_voice_call_timer.stop()
+                                    }
+                                    callEnd(true)
+                                } else {
+                                    val addressToCall = sipCore!!.interpretUrl(curInteractionVO.toSipId)
+                                    val params = sipCore!!.createCallParams(null)
+                                    params?.isVideoEnabled = false
+                                    if (addressToCall != null) {
+                                        sipCore!!.inviteAddressWithParams(addressToCall, params!!)
+                                        Log.d(TAG, ">>>>>>>>>>> invite address: " + addressToCall.asString())
+                                    }
+                                }
+                            } else if (curTcpModel.getAction() == TcpAction.VoiceAction.REJECT) {
+                                //我方呼出,对方拒绝
+                                showMessage(R.string.call_reject)
+                                RingPlayHelper.stopRingTone()
+                                cancelCall()
+                            } else if (curTcpModel.getAction() == TcpAction.VoiceAction.CALLING) {
+                                //我方呼出,对方通话中
+                                showMessage(R.string.call_busy)
+                                AppTool.Time.delay(2000) {
+                                    RingPlayHelper.stopRingTone()
+                                    cancelCall()
+                                }
+                            } else if (curTcpModel.getAction() == TcpAction.VoiceAction.SUCCESS) {
+                                //呼叫成功
+                                showOutgoingCall()
+                                //本机呼叫的时候tcpModel为空,只有呼叫成功的时候才能获得对应tcp相关数据
+                                callSuccess = true
+                                interactionVO = curInteractionVO
+                                fromId = curTcpModel.fromId
+                                tid = curTcpModel.tid
+                                Constant.interactionId = curInteractionVO.id
+                            } else if (curTcpModel.getAction() == TcpAction.VoiceAction.FAILED) {
+                                //我方呼出,对方不在线,设备离线或其它错误
+                                callSuccess = true
+                                showMessage(R.string.call_failed)
+                                AppTool.Time.delay(2000) {
+                                    RingPlayHelper.stopRingTone()
+                                    cancelCall()
+                                }
+                            } else if (curTcpModel.getAction() == TcpAction.VoiceAction.HANDOFF) {
+                                //对方挂断,不论我方呼出或呼入
+                                if (Constant.interactionId == curInteractionVO.id) {
+                                    RingPlayHelper.stopRingTone()
+                                    cancelCall()
+                                }
+                            } else if (curTcpModel.getAction() == TcpAction.VoiceAction.CANCEL) {
+                                //对方呼叫时取消
+                                if (Constant.interactionId == curInteractionVO.id) {
+                                    RingPlayHelper.stopRingTone()
+                                    cancelCall()
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            Constant.EVENT_END_CALL -> {
+                Log.d(TAG, ">>>>>>>>>>>>>> EVENT_END_CALL")
+                if (messageEvent.getMessage() is String) {
+                    val str = messageEvent.message as String
+                    if (str.equals("cancel")) {
+                        voiceCancel()
+                        cancelCall()
+                    } else {
+                        callEnd(true)
+                    }
+                }
+            }
+
+            Constant.SIP_CONNECTED -> {
+                sipCore!!.isMicEnabled = true
+                showCalling(true)
+            }
+        }
+    }
+
+}

+ 2 - 0
android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/helper/AppInfoDialogHelper.java

@@ -27,6 +27,8 @@ public class AppInfoDialogHelper {
         stringBuilder.append(Constant.DEVICE_ID);
         stringBuilder.append("\nIP: ");
         stringBuilder.append(ipAddr);
+        stringBuilder.append("\nServer: ");
+        stringBuilder.append(CommonUtils.getUrl(BaseApplication.appContext));
         stringBuilder.append("\nMAC: ");
         stringBuilder.append(macAddr);
         stringBuilder.append("\nIdentifier: ");

+ 12 - 4
android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/helper/AppUpdateHelper.java

@@ -14,7 +14,9 @@ import android.util.Log;
 
 import androidx.core.content.FileProvider;
 
+import com.wdkl.app.ncs.callingdoor.settings.SettingConfig;
 import com.wdkl.ncs.android.component.welcome.activity.WelcomeActivity;
+import com.wdkl.ncs.host.service.WdklSipService;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -209,15 +211,15 @@ public class AppUpdateHelper {
 
     public static void reboot(Context context) {
         try {
+            if ("rk3128".equals(Build.MODEL)) {
+                SerialPortHelper.resetDevice();
+            }
+
             Intent intent = new Intent(Intent.ACTION_REBOOT);
             intent.putExtra("nowait", 1);
             intent.putExtra("interval", 1);
             intent.putExtra("window", 0);
             context.sendBroadcast(intent);
-
-            if ("rk3128".equals(Build.MODEL)) {
-                SerialPortHelper.resetDevice();
-            }
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -225,6 +227,12 @@ public class AppUpdateHelper {
     }
 
     public static void restartApp(Context context) {
+        if (SettingConfig.getSipEnabled(context)) {
+            //停止sip服务
+            Intent serviceIntent = new Intent(context, WdklSipService.class);
+            context.stopService(serviceIntent);
+        }
+
         //重新启动app
         Intent mStartActivity = new Intent(context.getApplicationContext(), WelcomeActivity.class);
         mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

+ 6 - 0
android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/helper/WarningDialogHelper.java

@@ -47,4 +47,10 @@ public class WarningDialogHelper {
             e.printStackTrace();
         }
     }
+
+    public static void dismiss() {
+        if (dialog != null) {
+            dialog.dismiss();
+        }
+    }
 }

+ 11 - 0
android_door/src/main/h10_3128/java/com/wdkl/app/ncs/callingdoor/settings/SettingConfig.java

@@ -67,6 +67,9 @@ public class SettingConfig {
     //呼叫时门灯是否持续亮,需要手动按护理按键切换
     private static final String KEY_DOOR_LIGHT_ALWAYS_ON = "KEY_DOOR_LIGHT_ALWAYS_ON";
 
+    //是否使用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);
@@ -109,6 +112,14 @@ public class SettingConfig {
         getEditor(context).putInt(KEY_DOOR_LIGHT_ALWAYS_ON, alwaysOn).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();
+    }
+
 
     /**
      * 获取白天亮度

+ 5 - 3
android_door/src/main/h10_3128/res/layout/view_title_layout.xml

@@ -8,11 +8,13 @@
     <!--SIP状态图标-->
     <TextView
         android:id="@+id/view_title_layout_tv_point"
-        android:layout_width="12dp"
-        android:layout_height="12dp"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
         android:layout_centerVertical="true"
         android:layout_marginLeft="20dp"
-        android:background="@color/red_color"/>
+        android:background="@color/red_color"
+        android:gravity="center"
+        android:textColor="@color/text_room_color"/>
 
     <!--医院名称-->
     <TextView

+ 5 - 3
android_door/src/main/h10_3128/res/layout/view_title_layout_rk3288.xml

@@ -8,11 +8,13 @@
     <!--SIP状态图标-->
     <TextView
         android:id="@+id/view_title_layout_tv_point"
-        android:layout_width="20dp"
-        android:layout_height="20dp"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
         android:layout_centerVertical="true"
         android:layout_marginLeft="10dp"
-        android:background="@color/red_color"/>
+        android:background="@color/red_color"
+        android:gravity="center"
+        android:textColor="@color/text_room_color"/>
 
     <!--医院名称-->
     <TextView

+ 4 - 1
build.gradle

@@ -44,7 +44,7 @@ buildscript {
      */
     ext.support_library_version = "28.0.0"
 
-    ext.app_device_type = "mk_h10_w_a133_1_chile"
+    ext.app_device_type = "mk_h10_z_3128_1"
 
     if (app_device_type == "mk_h10_z_3128_1") {
         //rk3128 10寸门口机
@@ -124,6 +124,9 @@ allprojects {
         maven { url 'https://jitpack.io' }
         //maven { url 'https://dl.bintray.com/geamtear/maven' }
 
+        flatDir {
+            dirs '../common/libs' //申明本地库
+        }
     }
     tasks.withType(Javadoc) { // 新增
         options.addStringOption('Xdoclint:none', '-quiet')

+ 3 - 0
common/build.gradle

@@ -239,6 +239,9 @@ dependencies {
      * google
      */
     compile 'com.google.guava:guava:23.0'
+
+    //linphone sip sdk
+    api(name: 'linphone-sdk-android-5.2.10', ext: 'aar')
 }
 
 //tasks.withType(JavaCompile) {

二进制
common/libs/linphone-sdk-android-5.2.10.aar


+ 7 - 0
middleware/src/main/code/com/wdkl/ncs/android/middleware/common/Constant.java

@@ -90,6 +90,9 @@ public class Constant {
     public static String SIP_ID = "";
     public static String TARGET_SIP = "";
 
+    public static String sip_ip = "";  //sip
+    public static int sip_port = 5060;
+
     public static String SIP_ROOM_ID = "";
 
     public static int TTS_STATUS = 0;
@@ -221,4 +224,8 @@ public class Constant {
     public static final int EVENT_CLEAR_CALLS = 0x15;
 
     public static final int EVENT_SERIAL_EVENT = 0x16;
+
+    public static final int SIP_CONNECTED = 0x17;
+
+    public static final int EVENT_END_CALL = 0x18;
 }

+ 85 - 16
middleware/src/main/code/com/wdkl/ncs/android/middleware/model/ServerInfo.java

@@ -13,59 +13,54 @@ public class ServerInfo implements Serializable {
     @ApiModelProperty(notes = "局域网TCP服务器IP")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String tcpLocalIp;
-
     @ApiModelProperty(notes = "互联网TCP服务器IP")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String tcpPublicIp;
-
     @ApiModelProperty(notes = "TCP端口")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Integer tcpPort;
-
     @ApiModelProperty(notes = "TCP体征数据库端口")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Integer tcpVsPort;
-
     @ApiModelProperty(notes = "TCP心跳间隔,单位:秒")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Integer tcpIdleSeconds;
-
     @ApiModelProperty(notes = "TCP心跳是否开启")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Boolean tcpIdleLogEnabled;
+    @ApiModelProperty(notes = "平行主机模式是否开启")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private Boolean openHostDeviceCombinedUse;
 
     @ApiModelProperty(notes = "局域网HTTP服务器IP")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String httpLocalIp;
-
     @ApiModelProperty(notes = "互联网HTTP服务器IP")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String httpPublicIp;
-
     @ApiModelProperty(notes = "HTTP端口")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Integer httpPort;
-
     @ApiModelProperty(notes = "HTTP System端口")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Integer httpSystemPort;
 
+    @ApiModelProperty(notes = "互联网HTTP服务器IP")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private String voiceType;
+
     @ApiModelProperty(notes = "媒体服务是否启用")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Boolean mediaEnable;
-
     @ApiModelProperty(notes = "每个科室限制的广播数")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Integer mediaPartLimit;
-
     @ApiModelProperty(notes = "局域网媒体服务IP")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String mediaLocalIp;
-
     @ApiModelProperty(notes = "互联网媒体服务IP")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String mediaPublicIp;
-
     @ApiModelProperty(notes = "媒体服务端口")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Integer mediaPort;
@@ -73,19 +68,32 @@ public class ServerInfo implements Serializable {
     @ApiModelProperty(notes = "RTC Local IP")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String rtcLocalIp;
-
     @ApiModelProperty(notes = "RTC Public IP")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String rtcPublicIp;
-
     @ApiModelProperty(notes = "RTC 端口")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Integer rtcPort;
 
+    @ApiModelProperty(notes = "SIP LOCAL IP")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private String sipIp;
+    @ApiModelProperty(notes = "SIP LOCAL 端口")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private Integer sipPort;
+    @ApiModelProperty(notes = "SIP PUBLIC IP")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private String sipPublicIp;
+    @ApiModelProperty(notes = "SIP LOCAL 端口")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private Integer sipPublicPort;
+    @ApiModelProperty(notes = "SIP LOCAL 端口")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private Integer sipBroadcastStart;
+
     @ApiModelProperty(notes = "stun地址", example = "stun:xxx.xxx.xxx.xxx:3478")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String stunServer;
-
     @ApiModelProperty(notes = "turn地址", example = "turn:xxx.xxx.xxx.xxx:3478|username|password")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String[] turnServer;
@@ -97,17 +105,22 @@ public class ServerInfo implements Serializable {
     @ApiModelProperty(notes = "led屏控制,true就是服务器控制,false就是护士主机控制")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Boolean ledControl;
+    @ApiModelProperty(notes = "true就是科室控制点阵屏,false就是医院控制点阵屏")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private Boolean ledControlPart;
 
     @ApiModelProperty(notes = "录像文件的网关")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String recordGateway;
-
     @ApiModelProperty(notes = "录像文件的存储路径")
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private String recordDir;
 
     private Long serverTime;
 
+
+    private String appName;
+
     public String getTcpLocalIp() {
         return tcpLocalIp;
     }
@@ -156,6 +169,14 @@ public class ServerInfo implements Serializable {
         this.tcpIdleLogEnabled = tcpIdleLogEnabled;
     }
 
+    public Boolean getOpenHostDeviceCombinedUse() {
+        return openHostDeviceCombinedUse;
+    }
+
+    public void setOpenHostDeviceCombinedUse(Boolean openHostDeviceCombinedUse) {
+        this.openHostDeviceCombinedUse = openHostDeviceCombinedUse;
+    }
+
     public String getHttpLocalIp() {
         return httpLocalIp;
     }
@@ -212,6 +233,22 @@ public class ServerInfo implements Serializable {
         this.rtcPort = rtcPort;
     }
 
+    public String getSipIp() {
+        return sipIp;
+    }
+
+    public void setSipIp(String sipIp) {
+        this.sipIp = sipIp;
+    }
+
+    public Integer getSipPort() {
+        return sipPort;
+    }
+
+    public void setSipPort(Integer sipPort) {
+        this.sipPort = sipPort;
+    }
+
     public String getStunServer() {
         return stunServer;
     }
@@ -292,6 +329,14 @@ public class ServerInfo implements Serializable {
         this.ledControl = ledControl;
     }
 
+    public Boolean getLedControlPart() {
+        return ledControlPart;
+    }
+
+    public void setLedControlPart(Boolean ledControlPart) {
+        this.ledControlPart = ledControlPart;
+    }
+
     public String getRecordGateway() {
         return recordGateway;
     }
@@ -307,4 +352,28 @@ public class ServerInfo implements Serializable {
     public void setRecordDir(String recordDir) {
         this.recordDir = recordDir;
     }
+
+    public String getVoiceType() {
+        return voiceType;
+    }
+
+    public String getSipPublicIp() {
+        return sipPublicIp;
+    }
+
+    public Integer getSipPublicPort() {
+        return sipPublicPort;
+    }
+
+    public Integer getSipBroadcastStart() {
+        return sipBroadcastStart;
+    }
+
+    public String getAppName() {
+        return appName;
+    }
+
+    public void setAppName(String appName) {
+        this.appName = appName;
+    }
 }

+ 1 - 0
middleware/src/main/code/com/wdkl/ncs/android/middleware/utils/CommonUtils.java

@@ -25,6 +25,7 @@ public class CommonUtils {
     //private static final String DEFAULT_URL = "192.168.9.200";
     //private static final String DEFAULT_URL = "192.168.8.5";
     //private static final String DEFAULT_URL = "192.168.101.1";
+    //private static final String DEFAULT_URL = "47.106.137.131";  //新云服
     private static final String DEFAULT_URL_PORT = "8006";
     private static final String DEFAULT_SIP_PORT = "8188";