package com.hdl.hdllinphonesdk.core.service;
|
|
import android.app.AlarmManager;
|
import android.app.PendingIntent;
|
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.os.SystemClock;
|
|
|
import com.hdl.hdllinphonesdk.HDLLinphoneKit;
|
import com.hdl.hdllinphonesdk.R;
|
import com.hdl.hdllinphonesdk.core.callback.PhoneCallback;
|
import com.hdl.hdllinphonesdk.core.callback.RegistrationCallback;
|
import com.hdl.hdllinphonesdk.core.linphone.KeepAliveHandler;
|
import com.hdl.hdllinphonesdk.core.linphone.LinphoneUtils;
|
|
import org.linphone.core.Call;
|
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.core.ToneID;
|
import org.linphone.core.VideoActivationPolicy;
|
import org.linphone.core.VideoDefinition;
|
import org.linphone.core.tools.Log;
|
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;
|
|
import static com.hdl.hdllinphonesdk.HDLLinphoneKit.HDLLinphoneKitNAME;
|
|
public class HDLLinphoneService extends Service {
|
private static final String START_LINPHONE_LOGS = " ==== HDLLinphoneService ====";
|
// Keep a static reference to the Service so we can access it from anywhere in the app
|
private static HDLLinphoneService sInstance;
|
|
private PendingIntent mKeepAlivePendingIntent;
|
private static PhoneCallback sPhoneCallback;//通话状态回调
|
private static RegistrationCallback sRegistrationCallback;//账号注册登录状态回调
|
private String mRingSoundFile,mPauseSoundFile = null;
|
|
private Handler mHandler;
|
private Timer mTimer;
|
|
private Core mCore;
|
private CoreListenerStub mCoreListener;
|
private Call.State currentCallState;//当前通话状态
|
|
public static boolean isReady() {
|
return sInstance != null;
|
}
|
|
public static HDLLinphoneService getInstance() {
|
return sInstance;
|
}
|
|
public static Boolean getCoreIsNotNull() {
|
return sInstance.mCore != null;
|
}
|
|
public static Core getCore() {
|
return sInstance.mCore;
|
}
|
|
|
@Override
|
public IBinder onBind(Intent intent) {
|
return null;
|
}
|
|
public static void addPhoneCallback(PhoneCallback phoneCallback) {
|
sPhoneCallback = phoneCallback;
|
}
|
|
public static void removePhoneCallback() {
|
if (sPhoneCallback != null) {
|
sPhoneCallback = null;
|
}
|
}
|
|
public static void addRegistrationCallback(RegistrationCallback registrationCallback) {
|
sRegistrationCallback = registrationCallback;
|
}
|
|
public static void removeRegistrationCallback() {
|
if (sRegistrationCallback != null) {
|
sRegistrationCallback = null;
|
}
|
}
|
|
public void removeAllCallback() {
|
removePhoneCallback();
|
removeRegistrationCallback();
|
}
|
|
@Override
|
public void onCreate() {
|
super.onCreate();
|
|
initLinphone();
|
|
Intent intent = new Intent(this, KeepAliveHandler.class);
|
mKeepAlivePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
((AlarmManager) this.getSystemService(Context.ALARM_SERVICE)).setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
SystemClock.elapsedRealtime() + 60000, 60000, mKeepAlivePendingIntent);
|
|
}
|
|
|
@Override
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
super.onStartCommand(intent, flags, startId);
|
|
// If our Service is already running, no need to continue
|
if (sInstance != null) {
|
return START_STICKY;
|
}
|
|
// Our Service has been started, we can keep our reference on it
|
// From now one the Launcher will be able to call onServiceReady()
|
sInstance = this;
|
|
// Core must be started after being created and configured
|
mCore.start();
|
mCore.enableKeepAlive(true);
|
// We also MUST call the iterate() method of the Core on a regular basis
|
TimerTask lTask =
|
new TimerTask() {
|
@Override
|
public void run() {
|
mHandler.post(
|
new Runnable() {
|
@Override
|
public void run() {
|
if (mCore != null) {
|
mCore.iterate();
|
}
|
}
|
});
|
}
|
};
|
mTimer = new Timer("Linphone scheduler");
|
mTimer.schedule(lTask, 0, 20);
|
|
return START_STICKY;
|
}
|
|
@Override
|
public void onDestroy() {
|
removeAllCallback();
|
|
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;
|
|
((AlarmManager) this.getSystemService(Context.ALARM_SERVICE)).cancel(mKeepAlivePendingIntent);
|
|
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);
|
}
|
|
/**
|
* 初始化Linphone
|
*/
|
private void initLinphone() {
|
|
String basePath = getFilesDir().getAbsolutePath();
|
Factory.instance().setLogCollectionPath(basePath);
|
Factory.instance().enableLogCollection(LogCollectionState.Enabled);
|
Factory.instance().setDebugMode(false, getString(R.string.app_name));
|
// Dump some useful information about the device we're running on
|
Log.i(START_LINPHONE_LOGS);
|
dumpDeviceInformation();
|
dumpInstalledLinphoneInformation();
|
mHandler = new Handler();
|
// This will be our main Core listener, it will change activities depending on events
|
initCoreListener();
|
|
copyAssetsFromPackage(basePath);
|
|
// Create the Core and add our listener
|
mCore = Factory.instance()
|
.createCore(basePath + "/.linphonerc", basePath + "/linphonerc", this);
|
mCore.addListener(mCoreListener);
|
// Core is ready to be configured
|
configureCore();
|
}
|
|
private void initCoreListener() {
|
mCoreListener = new CoreListenerStub() {
|
@Override
|
public void onCallStateChanged(Core core, Call linphoneCall, Call.State state, String message) {
|
Log.e(START_LINPHONE_LOGS, "callState: " + state.toString());
|
currentCallState = state;
|
if (sPhoneCallback != null) {
|
if (state == Call.State.IncomingReceived) {
|
sPhoneCallback.incomingCall(linphoneCall);
|
} else if (state == Call.State.OutgoingInit) {
|
sPhoneCallback.outgoingInit();
|
} else if (state == Call.State.Connected) {
|
sPhoneCallback.callConnected();
|
} else if (state == Call.State.Error) {
|
sPhoneCallback.error();
|
} else if (state == Call.State.End) {
|
sPhoneCallback.callEnd();
|
} else if (state == Call.State.Released) {
|
sPhoneCallback.callReleased();
|
}
|
}
|
}
|
|
@Override
|
public void onRegistrationStateChanged(Core core, ProxyConfig proxyConfig, RegistrationState registrationState, String message) {
|
|
String state = registrationState.toString();
|
Log.i(START_LINPHONE_LOGS, "registrationState: " + state);
|
if (sRegistrationCallback != null) {
|
|
if (state.equals(RegistrationState.None.toString())) {
|
sRegistrationCallback.registrationNone();
|
} else if (state.equals(RegistrationState.Progress.toString())) {
|
sRegistrationCallback.registrationProgress();
|
} else if (state.equals(RegistrationState.Ok.toString())) {
|
sRegistrationCallback.registrationOk();
|
} else if (state.equals(RegistrationState.Cleared.toString())) {
|
sRegistrationCallback.registrationCleared();
|
} else if (state.equals(RegistrationState.Failed.toString())) {
|
sRegistrationCallback.registrationFailed();
|
}
|
}
|
}
|
};
|
}
|
|
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(userCerts + " can't be created.");
|
}
|
}
|
setUserAgent();
|
|
mCore.setUserCertificatesPath(userCerts);
|
// mCore.setNativeRingingEnabled(true);
|
//
|
// mCore.setRemoteRingbackTone(mRingSoundFile);
|
// mCore.setTone(ToneID.CallWaiting, mRingSoundFile);
|
mCore.setRing(mRingSoundFile);
|
// mCore.setPlayFile(mPauseSoundFile);
|
|
// mCore.enableVideoCapture(false);//禁用手机摄像头视频采集
|
|
mCore.setNetworkReachable(true);
|
//回声消除
|
// boolean isEchoCancellation = (boolean) SPUtils.get(mServiceContext, "echo_cancellation", true);
|
//回声消除
|
mCore.enableEchoCancellation(true);
|
mCore.enableEchoLimiter(true);
|
|
//自适应码率控制
|
// boolean isAdaptiveRateControl = (boolean) SPUtils.get(mServiceContext, "adaptive_rate_control", true);
|
mCore.enableAdaptiveRateControl(true);
|
//audio 码率设置
|
mCore.getConfig().setInt("audio", "codec_bitrate_limit", 36);
|
|
VideoDefinition preferredVideoDefinition =
|
Factory.instance().createVideoDefinitionFromName("qvga");
|
mCore.setPreferredVideoDefinition(preferredVideoDefinition);
|
|
mCore.setUploadBandwidth(1536);
|
mCore.setDownloadBandwidth(1536);
|
|
mCore.getConfig().setBool("app", "open_h264_download_enabled", true);
|
VideoActivationPolicy vap = mCore.getVideoActivationPolicy();
|
vap.setAutomaticallyInitiate(true);
|
mCore.setVideoActivationPolicy(vap);
|
|
// 设置编码格式
|
setCodecMime();
|
}
|
|
private void setCodecMime() {
|
PayloadType[] ptList = mCore.getAudioPayloadTypes();
|
for (PayloadType pt : ptList) {
|
|
org.linphone.mediastream.Log.d("payloadaudio", pt.getMimeType());
|
if (pt.getMimeType().equals("PCMA") && pt.getClockRate() == 8000) {
|
pt.enable(true);
|
|
} else {
|
pt.enable(false);
|
}
|
}
|
mCore.setAudioPayloadTypes(ptList);
|
|
PayloadType[] ptVideoList = mCore.getVideoPayloadTypes();
|
for (PayloadType pt : ptVideoList) {
|
pt.enable(true);
|
}
|
mCore.setVideoPayloadTypes(ptVideoList);
|
}
|
|
public void enableDeviceRingtone(boolean use) {
|
if (use) {
|
mCore.setRing(null);
|
} else {
|
mCore.setRing(mRingSoundFile);
|
}
|
}
|
|
private void setUserAgent() {
|
try {
|
String versionName = this.getPackageManager().getPackageInfo(this.getPackageName(),
|
0).versionName;
|
if (versionName == null) {
|
versionName = String.valueOf(this.getPackageManager().getPackageInfo(this.getPackageName(), 0).versionCode);
|
}
|
mCore.setUserAgent(HDLLinphoneKitNAME, versionName);
|
} catch (PackageManager.NameNotFoundException e) {
|
e.printStackTrace();
|
}
|
}
|
|
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(sb.toString());
|
}
|
|
private void dumpInstalledLinphoneInformation() {
|
PackageInfo info = null;
|
try {
|
info = getPackageManager().getPackageInfo(getPackageName(), 0);
|
} catch (PackageManager.NameNotFoundException nnfe) {
|
Log.e(nnfe);
|
}
|
|
if (info != null) {
|
Log.i(
|
"[Service] Linphone version is ",
|
info.versionName + " (" + info.versionCode + ")");
|
} else {
|
Log.i("[Service] Linphone version is unknown");
|
}
|
}
|
|
/**
|
* copyAssetsFromPackage
|
*/
|
private void copyAssetsFromPackage(String basePath) {
|
try {
|
// Let's copy some RAW resources to the device
|
// The default config file must only be installed once (the first time)
|
copyIfNotExist(R.raw.linphonerc_default, basePath + "/.linphonerc");
|
// The factory config is used to override any other setting, let's copy it each time
|
copyFromPackage(R.raw.linphonerc_factory, "linphonerc");
|
mRingSoundFile = basePath + "/oldphone_mono.wav";
|
copyIfNotExist(R.raw.oldphone_mono, basePath + "/oldphone_mono.wav");
|
copyIfNotExist(R.raw.ringback, basePath + "/ringback.wav");
|
mPauseSoundFile = basePath + "/toy_mono.wav";
|
copyIfNotExist( R.raw.toy_mono, basePath + "/toy_mono.wav");
|
|
// LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.ringback, mRingBackSoundFile);
|
// LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.toy_mono, mPauseSoundFile);
|
// LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.linphonerc_default, mLinphoneConfigFile);
|
// LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.linphonerc_factory, new File(mLinphoneFactoryConfigFile).getName());
|
// LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.lpconfig, mLPConfigXsd);
|
// LinphoneUtils.copyIfNotExist(mServiceContext, R.raw.rootca, mLinphoneRootCaFile);
|
|
|
} catch (IOException ioe) {
|
Log.e(ioe);
|
}
|
|
}
|
|
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();
|
}
|
|
/**
|
* 获取当前通话状态
|
*/
|
public Call.State getCurrentCallState() {
|
return currentCallState;
|
}
|
}
|