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