/**
* Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
* This file is part of CSipSimple.
*
* CSipSimple is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* If you own a pjsip commercial license you can also redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as an android library.
*
* CSipSimple is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CSipSimple. If not, see <http://www.gnu.org/licenses/>.
*/
package com.csipsimple.service;
import android.app.Activity;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.DetailedState;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Parcelable;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.view.SurfaceView;
import android.widget.Toast;
import com.csipsimple.R;
import com.csipsimple.api.ISipConfiguration;
import com.csipsimple.api.ISipService;
import com.csipsimple.api.MediaState;
import com.csipsimple.api.SipCallSession;
import com.csipsimple.api.SipConfigManager;
import com.csipsimple.api.SipManager;
import com.csipsimple.api.SipManager.PresenceStatus;
import com.csipsimple.api.SipMessage;
import com.csipsimple.api.SipProfile;
import com.csipsimple.api.SipProfileState;
import com.csipsimple.api.SipUri;
import com.csipsimple.db.DBProvider;
import com.csipsimple.models.Filter;
import com.csipsimple.pjsip.PjSipCalls;
import com.csipsimple.pjsip.PjSipService;
import com.csipsimple.pjsip.UAStateReceiver;
import com.csipsimple.service.receiver.DynamicReceiver4;
import com.csipsimple.service.receiver.DynamicReceiver5;
import com.csipsimple.ui.incall.InCallMediaControl;
import com.csipsimple.utils.Compatibility;
import com.csipsimple.utils.CustomDistribution;
import com.csipsimple.utils.ExtraPlugins;
import com.csipsimple.utils.ExtraPlugins.DynActivityPlugin;
import com.csipsimple.utils.Log;
import com.csipsimple.utils.PreferencesProviderWrapper;
import com.csipsimple.utils.PreferencesWrapper;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SipService extends Service {
// static boolean creating = false;
private static final String THIS_FILE = "SIP SRV";
private SipWakeLock sipWakeLock;
private boolean autoAcceptCurrent = false;
public boolean supportMultipleCalls = false;
// For video testing -- TODO : remove
private static SipService singleton = null;
// Implement public interface for the service
private final ISipService.Stub binder = new ISipService.Stub() {
/**
* {@inheritDoc}
*/
@Override
public void sipStart() throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
Log.d(THIS_FILE, "Start required from third party app/serv");
getExecutor().execute(new StartRunnable());
}
/**
* {@inheritDoc}
*/
@Override
public void sipStop() throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
getExecutor().execute(new StopRunnable());
}
/**
* {@inheritDoc}
*/
@Override
public void forceStopService() throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
Log.d(THIS_FILE, "Try to force service stop");
cleanStop();
//stopSelf();
}
/**
* {@inheritDoc}
*/
@Override
public void askThreadedRestart() throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
Log.d(THIS_FILE, "Restart required from third part app/serv");
getExecutor().execute(new RestartRunnable());
};
/**
* {@inheritDoc}
*/
@Override
public void addAllAccounts() throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
getExecutor().execute(new SipRunnable() {
@Override
public void doRun() throws SameThreadException {
SipService.this.addAllAccounts();
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void removeAllAccounts() throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
getExecutor().execute(new SipRunnable() {
@Override
public void doRun() throws SameThreadException {
SipService.this.unregisterAllAccounts(true);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void reAddAllAccounts() throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
getExecutor().execute(new SipRunnable() {
@Override
public void doRun() throws SameThreadException {
SipService.this.reAddAllAccounts();
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void setAccountRegistration(int accountId, int renew) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
final SipProfile acc = getAccount(accountId);
if(acc != null) {
final int ren = renew;
getExecutor().execute(new SipRunnable() {
@Override
public void doRun() throws SameThreadException {
SipService.this.setAccountRegistration(acc, ren, false);
}
});
}
}
/**
* {@inheritDoc}
*/
@Override
public SipProfileState getSipProfileState(int accountId) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
return SipService.this.getSipProfileState(accountId);
}
/**
* {@inheritDoc}
*/
@Override
public void switchToAutoAnswer() throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
Log.d(THIS_FILE, "Switch to auto answer");
setAutoAnswerNext(true);
}
/**
* {@inheritDoc}
*/
@Override
public void makeCall(final String callee, final int accountId) throws RemoteException {
makeCallWithOptions(callee, accountId, null);
}
@Override
public void makeCallWithOptions(final String callee, final int accountId, final Bundle options)
throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
//We have to ensure service is properly started and not just binded
SipService.this.startService(new Intent(SipService.this, SipService.class));
if(pjService == null) {
Log.e(THIS_FILE, "Can't place call if service not started");
// TODO - we should return a failing status here
return;
}
if(!supportMultipleCalls) {
// Check if there is no ongoing calls if so drop this request by alerting user
SipCallSession activeCall = pjService.getActiveCallInProgress();
if(activeCall != null) {
if(!CustomDistribution.forceNoMultipleCalls()) {
notifyUserOfMessage(R.string.not_configured_multiple_calls);
}
return;
}
}
Intent intent = new Intent(SipManager.ACTION_SIP_CALL_LAUNCH);
intent.putExtra(SipProfile.FIELD_ID, accountId);
intent.putExtra(SipManager.EXTRA_SIP_CALL_TARGET, callee);
intent.putExtra(SipManager.EXTRA_SIP_CALL_OPTIONS, options);
sendOrderedBroadcast (intent , SipManager.PERMISSION_USE_SIP, mPlaceCallResultReceiver, null, Activity.RESULT_OK, null, null);
}
private BroadcastReceiver mPlaceCallResultReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, final Intent intent) {
final Bundle extras = intent.getExtras();
final String action = intent.getAction();
if(extras == null) {
Log.e(THIS_FILE, "No data in intent retrieved for call");
return;
}
if(!SipManager.ACTION_SIP_CALL_LAUNCH.equals(action)) {
Log.e(THIS_FILE, "Received invalid action " + action);
return;
}
final int accountId = extras.getInt(SipProfile.FIELD_ID, -2);
final String callee = extras.getString(SipManager.EXTRA_SIP_CALL_TARGET);
final Bundle options = extras.getBundle(SipManager.EXTRA_SIP_CALL_OPTIONS);
if(accountId == -2 || callee == null) {
Log.e(THIS_FILE, "Invalid rewrite " + accountId);
return;
}
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.makeCall(callee, accountId, options);
}
});
}
};
/**
* {@inheritDoc}
*/
@Override
public void sendMessage(final String message, final String callee, final long accountId) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
//We have to ensure service is properly started and not just binded
SipService.this.startService(new Intent(SipService.this, SipService.class));
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
Log.d(THIS_FILE, "will sms " + callee);
if(pjService != null) {
ToCall called = pjService.sendMessage(callee, message, accountId);
if(called!=null) {
SipMessage msg = new SipMessage(SipMessage.SELF,
SipUri.getCanonicalSipContact(callee), SipUri.getCanonicalSipContact(called.getCallee()),
message, "text/plain", System.currentTimeMillis(),
SipMessage.MESSAGE_TYPE_QUEUED, called.getCallee());
msg.setRead(true);
getContentResolver().insert(SipMessage.MESSAGE_URI, msg.getContentValues());
Log.d(THIS_FILE, "Inserted "+msg.getTo());
}else {
SipService.this.notifyUserOfMessage( getString(R.string.invalid_sip_uri)+ " : "+callee );
}
}else {
SipService.this.notifyUserOfMessage( getString(R.string.connection_not_valid) );
}
}
});
}
/**
* {@inheritDoc}
*/
@Override
public int answer(final int callId, final int status) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
ReturnRunnable action = new ReturnRunnable() {
@Override
protected Object runWithReturn() throws SameThreadException {
return (Integer) pjService.callAnswer(callId, status);
}
};
getExecutor().execute(action);
//return (Integer) action.getResult();
return SipManager.SUCCESS;
}
/**
* {@inheritDoc}
*/
@Override
public int hangup(final int callId, final int status) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
ReturnRunnable action = new ReturnRunnable() {
@Override
protected Object runWithReturn() throws SameThreadException {
return (Integer) pjService.callHangup(callId, status);
}
};
getExecutor().execute(action);
//return (Integer) action.getResult();
return SipManager.SUCCESS;
}
/**
* {@inheritDoc}
*/
@Override
public int xfer(final int callId, final String callee) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
Log.d(THIS_FILE, "XFER");
ReturnRunnable action = new ReturnRunnable() {
@Override
protected Object runWithReturn() throws SameThreadException {
return (Integer) pjService.callXfer(callId, callee);
}
};
getExecutor().execute(action);
return (Integer) action.getResult();
}
/**
* {@inheritDoc}
*/
@Override
public int xferReplace(final int callId, final int otherCallId, final int options) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
Log.d(THIS_FILE, "XFER-replace");
ReturnRunnable action = new ReturnRunnable() {
@Override
protected Object runWithReturn() throws SameThreadException {
return (Integer) pjService.callXferReplace(callId, otherCallId, options);
}
};
getExecutor().execute(action);
return (Integer) action.getResult();
}
/**
* {@inheritDoc}
*/
@Override
public int sendDtmf(final int callId, final int keyCode) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
ReturnRunnable action = new ReturnRunnable() {
@Override
protected Object runWithReturn() throws SameThreadException {
return (Integer) pjService.sendDtmf(callId, keyCode);
}
};
getExecutor().execute(action);
return (Integer) action.getResult();
}
/**
* {@inheritDoc}
*/
@Override
public int hold(final int callId) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
Log.d(THIS_FILE, "HOLDING");
ReturnRunnable action = new ReturnRunnable() {
@Override
protected Object runWithReturn() throws SameThreadException {
return (Integer) pjService.callHold(callId);
}
};
getExecutor().execute(action);
return (Integer) action.getResult();
}
/**
* {@inheritDoc}
*/
@Override
public int reinvite(final int callId, final boolean unhold) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
Log.d(THIS_FILE, "REINVITING");
ReturnRunnable action = new ReturnRunnable() {
@Override
protected Object runWithReturn() throws SameThreadException {
return (Integer) pjService.callReinvite(callId, unhold);
}
};
getExecutor().execute(action);
return (Integer) action.getResult();
}
/**
* {@inheritDoc}
*/
@Override
public SipCallSession getCallInfo(final int callId) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
return new SipCallSession(pjService.getCallInfo(callId));
}
/**
* {@inheritDoc}
*/
@Override
public void setBluetoothOn(final boolean on) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.setBluetoothOn(on);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void setMicrophoneMute(final boolean on) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.setMicrophoneMute(on);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void setSpeakerphoneOn(final boolean on) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.setSpeakerphoneOn(on);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public SipCallSession[] getCalls() throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
if(pjService != null) {
SipCallSession[] listOfCallsImpl = pjService.getCalls();
SipCallSession[] result = new SipCallSession[listOfCallsImpl.length];
for(int sessIdx = 0; sessIdx < listOfCallsImpl.length; sessIdx++) {
result[sessIdx] = new SipCallSession(listOfCallsImpl[sessIdx]);
}
return result;
}
return new SipCallSession[0];
}
/**
* {@inheritDoc}
*/
@Override
public void confAdjustTxLevel(final int port, final float value) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
if(pjService == null) {
return;
}
pjService.confAdjustTxLevel(port, value);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void confAdjustRxLevel(final int port, final float value) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
if(pjService == null) {
return;
}
pjService.confAdjustRxLevel(port, value);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void adjustVolume(SipCallSession callInfo, int direction, int flags) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
if(pjService == null) {
return;
}
boolean ringing = callInfo.isIncoming() && callInfo.isBeforeConfirmed();
// Mode ringing
if(ringing) {
// What is expected here is to silence ringer
//pjService.adjustStreamVolume(AudioManager.STREAM_RING, direction, AudioManager.FLAG_SHOW_UI);
pjService.silenceRinger();
}else {
// Mode in call
if(prefsWrapper.getPreferenceBooleanValue(SipConfigManager.USE_SOFT_VOLUME)) {
Intent adjustVolumeIntent = new Intent(SipService.this, InCallMediaControl.class);
adjustVolumeIntent.putExtra(Intent.EXTRA_KEY_EVENT, direction);
adjustVolumeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(adjustVolumeIntent);
}else {
pjService.adjustStreamVolume(Compatibility.getInCallStream(pjService.mediaManager.doesUserWantBluetooth()), direction, flags);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void setEchoCancellation(final boolean on) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
if(pjService == null) {
return;
}
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.setEchoCancellation(on);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void startRecording(final int callId, final int way) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
if (pjService == null) {
return;
}
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.startRecording(callId, way);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void stopRecording(final int callId) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
if (pjService == null) {
return;
}
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.stopRecording(callId);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public boolean isRecording(int callId) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
if(pjService == null) {
return false;
}
SipCallSession info = pjService.getCallInfo(callId);
if(info != null) {
return info.isRecording();
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean canRecord(int callId) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
if(pjService == null) {
return false;
}
return pjService.canRecord(callId);
}
/**
* {@inheritDoc}
*/
@Override
public void playWaveFile(final String filePath, final int callId, final int way) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
if(pjService == null) {
return;
}
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.playWaveFile(filePath, callId, way);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void setPresence(final int presenceInt, final String statusText, final long accountId) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
if(pjService == null) {
return;
}
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
presence = PresenceStatus.values()[presenceInt];
pjService.setPresence(presence, statusText, accountId);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public int getPresence(long accountId) throws RemoteException {
// TODO Auto-generated method stub
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public String getPresenceStatus(long accountId) throws RemoteException {
// TODO Auto-generated method stub
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void zrtpSASVerified(final int callId) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.zrtpSASVerified(callId);
pjService.zrtpReceiver.updateZrtpInfos(callId);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void zrtpSASRevoke(final int callId) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.zrtpSASRevoke(callId);
pjService.zrtpReceiver.updateZrtpInfos(callId);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public MediaState getCurrentMediaState() throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_USE_SIP, null);
MediaState ms = new MediaState();
if(pjService != null && pjService.mediaManager != null) {
ms = pjService.mediaManager.getMediaState();
}
return ms;
}
/**
* {@inheritDoc}
*/
@Override
public int getVersion() throws RemoteException {
return SipManager.CURRENT_API;
}
/**
* {@inheritDoc}
*/
@Override
public String showCallInfosDialog(final int callId) throws RemoteException {
ReturnRunnable action = new ReturnRunnable() {
@Override
protected Object runWithReturn() throws SameThreadException {
String infos = PjSipCalls.dumpCallInfo(callId);
Log.d(THIS_FILE, infos);
return infos;
}
};
getExecutor().execute(action);
return (String) action.getResult();
}
/**
* {@inheritDoc}
*/
@Override
public int startLoopbackTest() throws RemoteException {
if(pjService == null) {
return SipManager.ERROR_CURRENT_NETWORK;
}
SipRunnable action = new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.startLoopbackTest();
}
};
getExecutor().execute(action);
return SipManager.SUCCESS;
}
/**
* {@inheritDoc}
*/
@Override
public int stopLoopbackTest() throws RemoteException {
if(pjService == null) {
return SipManager.ERROR_CURRENT_NETWORK;
}
SipRunnable action = new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.stopLoopbackTest();
}
};
getExecutor().execute(action);
return SipManager.SUCCESS;
}
/**
* {@inheritDoc}
*/
@Override
public long confGetRxTxLevel(final int port) throws RemoteException {
ReturnRunnable action = new ReturnRunnable() {
@Override
protected Object runWithReturn() throws SameThreadException {
return (Long) pjService.getRxTxLevel(port);
}
};
getExecutor().execute(action);
return (Long) action.getResult();
}
/**
* {@inheritDoc}
*/
@Override
public void ignoreNextOutgoingCallFor(String number) throws RemoteException {
OutgoingCall.ignoreNext = number;
}
/**
* {@inheritDoc}
*/
@Override
public void updateCallOptions(final int callId, final Bundle options) throws RemoteException {
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.updateCallOptions(callId, options);
}
});
}
/**
* {@inheritDoc}
*/
@Override
public String getLocalNatType() throws RemoteException {
ReturnRunnable action = new ReturnRunnable() {
@Override
protected Object runWithReturn() throws SameThreadException {
return (String) pjService.getDetectedNatType();
}
};
getExecutor().execute(action);
return (String) action.getResult();
}
};
private final ISipConfiguration.Stub binderConfiguration = new ISipConfiguration.Stub() {
@Override
public void setPreferenceBoolean(String key, boolean value) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_CONFIGURE_SIP, null);
prefsWrapper.setPreferenceBooleanValue(key, value);
}
@Override
public void setPreferenceFloat(String key, float value) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_CONFIGURE_SIP, null);
prefsWrapper.setPreferenceFloatValue(key, value);
}
@Override
public void setPreferenceString(String key, String value) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_CONFIGURE_SIP, null);
prefsWrapper.setPreferenceStringValue(key, value);
}
@Override
public String getPreferenceString(String key) throws RemoteException {
return prefsWrapper.getPreferenceStringValue(key);
}
@Override
public boolean getPreferenceBoolean(String key) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_CONFIGURE_SIP, null);
return prefsWrapper.getPreferenceBooleanValue(key);
}
@Override
public float getPreferenceFloat(String key) throws RemoteException {
SipService.this.enforceCallingOrSelfPermission(SipManager.PERMISSION_CONFIGURE_SIP, null);
return prefsWrapper.getPreferenceFloatValue(key);
}
};
private WakeLock wakeLock;
private WifiLock wifiLock;
private DynamicReceiver4 deviceStateReceiver;
private PreferencesProviderWrapper prefsWrapper;
private ServicePhoneStateReceiver phoneConnectivityReceiver;
private TelephonyManager telephonyManager;
// private ConnectivityManager connectivityManager;
public SipNotifications notificationManager;
private SipServiceExecutor mExecutor;
private static PjSipService pjService;
private static HandlerThread executorThread;
private AccountStatusContentObserver statusObserver = null;
//public PresenceManager presenceMgr;
private BroadcastReceiver serviceReceiver;
class AccountStatusContentObserver extends ContentObserver {
public AccountStatusContentObserver(Handler h) {
super(h);
}
public void onChange(boolean selfChange) {
Log.d(THIS_FILE, "Accounts status.onChange( " + selfChange + ")");
updateRegistrationsState();
}
}
public SipServiceExecutor getExecutor() {
// create mExecutor lazily
if (mExecutor == null) {
mExecutor = new SipServiceExecutor(this);
}
return mExecutor;
}
private class ServicePhoneStateReceiver extends PhoneStateListener {
//private boolean ignoreFirstConnectionState = true;
private boolean ignoreFirstCallState = true;
/*
@Override
public void onDataConnectionStateChanged(final int state) {
if(!ignoreFirstConnectionState) {
Log.d(THIS_FILE, "Data connection state changed : " + state);
Thread t = new Thread("DataConnectionDetach") {
@Override
public void run() {
if(deviceStateReceiver != null) {
deviceStateReceiver.onChanged("MOBILE", state == TelephonyManager.DATA_CONNECTED);
}
}
};
t.start();
}else {
ignoreFirstConnectionState = false;
}
super.onDataConnectionStateChanged(state);
}
*/
@Override
public void onCallStateChanged(final int state, final String incomingNumber) {
if(!ignoreFirstCallState) {
Log.d(THIS_FILE, "Call state has changed !" + state + " : " + incomingNumber);
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
if(pjService != null) {
pjService.onGSMStateChanged(state, incomingNumber);
}
}
});
}else {
ignoreFirstCallState = false;
}
super.onCallStateChanged(state, incomingNumber);
}
}
@Override
public void onCreate() {
super.onCreate();
singleton = this;
Log.i(THIS_FILE, "Create SIP Service");
prefsWrapper = new PreferencesProviderWrapper(this);
Log.setLogLevel(prefsWrapper.getLogLevel());
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
// connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
notificationManager = new SipNotifications(this);
notificationManager.onServiceCreate();
sipWakeLock = new SipWakeLock((PowerManager) getSystemService(Context.POWER_SERVICE));
boolean hasSetup = prefsWrapper.getPreferenceBooleanValue(PreferencesProviderWrapper.HAS_ALREADY_SETUP_SERVICE, false);
Log.d(THIS_FILE, "Service has been setup ? "+ hasSetup);
//presenceMgr = new PresenceManager();
registerServiceBroadcasts();
if(!hasSetup) {
Log.e(THIS_FILE, "RESET SETTINGS !!!!");
prefsWrapper.resetAllDefaultValues();
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(THIS_FILE, "Destroying SIP Service");
unregisterBroadcasts();
unregisterServiceBroadcasts();
notificationManager.onServiceDestroy();
getExecutor().execute(new FinalizeDestroyRunnable());
}
public void cleanStop () {
getExecutor().execute(new DestroyRunnable());
}
private void applyComponentEnablingState(boolean active) {
int enableState = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
if(active && prefsWrapper.getPreferenceBooleanValue(SipConfigManager.INTEGRATE_TEL_PRIVILEGED) ) {
// Check whether we should register for stock tel: intents
// Useful for devices without gsm
enableState = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
}
PackageManager pm = getPackageManager();
ComponentName cmp = new ComponentName(this, "com.csipsimple.ui.PrivilegedOutgoingCallBroadcaster");
try {
if (pm.getComponentEnabledSetting(cmp) != enableState) {
pm.setComponentEnabledSetting(cmp, enableState, PackageManager.DONT_KILL_APP);
}
} catch (IllegalArgumentException e) {
Log.d(THIS_FILE,
"Current manifest has no PrivilegedOutgoingCallBroadcaster -- you can ignore this if voluntary", e);
}
}
private void registerServiceBroadcasts() {
if(serviceReceiver == null) {
IntentFilter intentfilter = new IntentFilter();
intentfilter.addAction(SipManager.ACTION_DEFER_OUTGOING_UNREGISTER);
intentfilter.addAction(SipManager.ACTION_OUTGOING_UNREGISTER);
serviceReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(action.equals(SipManager.ACTION_OUTGOING_UNREGISTER)){
unregisterForOutgoing((ComponentName) intent.getParcelableExtra(SipManager.EXTRA_OUTGOING_ACTIVITY));
} else if(action.equals(SipManager.ACTION_DEFER_OUTGOING_UNREGISTER)){
deferUnregisterForOutgoing((ComponentName) intent.getParcelableExtra(SipManager.EXTRA_OUTGOING_ACTIVITY));
}
}
};
registerReceiver(serviceReceiver, intentfilter);
}
}
private void unregisterServiceBroadcasts() {
if(serviceReceiver != null) {
unregisterReceiver(serviceReceiver);
serviceReceiver = null;
}
}
/**
* Register broadcast receivers.
*/
private void registerBroadcasts() {
// Register own broadcast receiver
if (deviceStateReceiver == null) {
IntentFilter intentfilter = new IntentFilter();
intentfilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
intentfilter.addAction(SipManager.ACTION_SIP_ACCOUNT_CHANGED);
intentfilter.addAction(SipManager.ACTION_SIP_ACCOUNT_DELETED);
intentfilter.addAction(SipManager.ACTION_SIP_CAN_BE_STOPPED);
intentfilter.addAction(SipManager.ACTION_SIP_REQUEST_RESTART);
intentfilter.addAction(DynamicReceiver4.ACTION_VPN_CONNECTIVITY);
if(Compatibility.isCompatible(5)) {
deviceStateReceiver = new DynamicReceiver5(this);
}else {
deviceStateReceiver = new DynamicReceiver4(this);
}
registerReceiver(deviceStateReceiver, intentfilter);
deviceStateReceiver.startMonitoring();
}
// Telephony
if (phoneConnectivityReceiver == null) {
Log.d(THIS_FILE, "Listen for phone state ");
phoneConnectivityReceiver = new ServicePhoneStateReceiver();
telephonyManager.listen(phoneConnectivityReceiver, /*PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
| */PhoneStateListener.LISTEN_CALL_STATE );
}
// Content observer
if(statusObserver == null) {
statusObserver = new AccountStatusContentObserver(serviceHandler);
getContentResolver().registerContentObserver(SipProfile.ACCOUNT_STATUS_URI, true, statusObserver);
}
}
/**
* Remove registration of broadcasts receiver.
*/
private void unregisterBroadcasts() {
if(deviceStateReceiver != null) {
try {
Log.d(THIS_FILE, "Stop and unregister device receiver");
deviceStateReceiver.stopMonitoring();
unregisterReceiver(deviceStateReceiver);
deviceStateReceiver = null;
} catch (IllegalArgumentException e) {
// This is the case if already unregistered itself
// Python style usage of try ;) : nothing to do here since it could
// be a standard case
// And in this case nothing has to be done
Log.d(THIS_FILE, "Has not to unregister telephony receiver");
}
}
if (phoneConnectivityReceiver != null) {
Log.d(THIS_FILE, "Unregister telephony receiver");
telephonyManager.listen(phoneConnectivityReceiver, PhoneStateListener.LISTEN_NONE);
phoneConnectivityReceiver = null;
}
if(statusObserver != null) {
getContentResolver().unregisterContentObserver(statusObserver);
statusObserver = null;
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("deprecation")
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
if(intent != null) {
Parcelable p = intent.getParcelableExtra(SipManager.EXTRA_OUTGOING_ACTIVITY);
if(p != null) {
ComponentName outActivity = (ComponentName) p;
registerForOutgoing(outActivity);
}
}
// Check connectivity, else just finish itself
if (!isConnectivityValid()) {
notifyUserOfMessage(R.string.connection_not_valid);
Log.d(THIS_FILE, "Harakiri... we are not needed since no way to use self");
cleanStop();
return;
}
// Autostart the stack - make sure started and that receivers are there
// NOTE : the stack may also be autostarted cause of phoneConnectivityReceiver
if (!loadStack()) {
return;
}
//if(directConnect) {
Log.d(THIS_FILE, "Direct sip start");
getExecutor().execute(new StartRunnable());
/*
}else {
Log.d(THIS_FILE, "Defered SIP start !!");
NetworkInfo netInfo = (NetworkInfo) connectivityManager.getActiveNetworkInfo();
if(netInfo != null) {
String type = netInfo.getTypeName();
NetworkInfo.State state = netInfo.getState();
if(state == NetworkInfo.State.CONNECTED) {
Log.d(THIS_FILE, ">> on changed connected");
deviceStateReceiver.onChanged(type, true);
}else if(state == NetworkInfo.State.DISCONNECTED) {
Log.d(THIS_FILE, ">> on changed disconnected");
deviceStateReceiver.onChanged(type, false);
}
}else {
deviceStateReceiver.onChanged(null, false);
Log.d(THIS_FILE, ">> on changed disconnected");
}
}
*/
}
private List<ComponentName> activitiesForOutgoing = new ArrayList<ComponentName>();
private List<ComponentName> deferedUnregisterForOutgoing = new ArrayList<ComponentName>();
public void registerForOutgoing(ComponentName activityKey) {
if(!activitiesForOutgoing.contains(activityKey)) {
activitiesForOutgoing.add(activityKey);
}
}
public void unregisterForOutgoing(ComponentName activityKey) {
activitiesForOutgoing.remove(activityKey);
if(!isConnectivityValid()) {
cleanStop();
}
}
public void deferUnregisterForOutgoing(ComponentName activityKey) {
if(!deferedUnregisterForOutgoing.contains(activityKey)) {
deferedUnregisterForOutgoing.add(activityKey);
}
}
public void treatDeferUnregistersForOutgoing() {
for(ComponentName cmp : deferedUnregisterForOutgoing) {
activitiesForOutgoing.remove(cmp);
}
deferedUnregisterForOutgoing.clear();
if(!isConnectivityValid()) {
cleanStop();
}
}
public boolean isConnectivityValid() {
if(prefsWrapper.getPreferenceBooleanValue(PreferencesWrapper.HAS_BEEN_QUIT, false)) {
return false;
}
boolean valid = prefsWrapper.isValidConnectionForIncoming();
if(activitiesForOutgoing.size() > 0) {
valid |= prefsWrapper.isValidConnectionForOutgoing();
}
return valid;
}
private boolean loadStack() {
//Ensure pjService exists
if(pjService == null) {
pjService = new PjSipService();
}
pjService.setService(this);
if (pjService.tryToLoadStack()) {
return true;
}
return false;
}
@Override
public IBinder onBind(Intent intent) {
String serviceName = intent.getAction();
Log.d(THIS_FILE, "Action is " + serviceName);
if (serviceName == null || serviceName.equalsIgnoreCase(SipManager.INTENT_SIP_SERVICE)) {
Log.d(THIS_FILE, "Service returned");
return binder;
} else if (serviceName.equalsIgnoreCase(SipManager.INTENT_SIP_CONFIGURATION)) {
Log.d(THIS_FILE, "Conf returned");
return binderConfiguration;
}
Log.d(THIS_FILE, "Default service (SipService) returned");
return binder;
}
//private KeepAliveTimer kaAlarm;
// This is always done in SipExecutor thread
private void startSipStack() throws SameThreadException {
//Cache some prefs
supportMultipleCalls = prefsWrapper.getPreferenceBooleanValue(SipConfigManager.SUPPORT_MULTIPLE_CALLS);
if(!isConnectivityValid()) {
notifyUserOfMessage(R.string.connection_not_valid);
Log.e(THIS_FILE, "No need to start sip");
return;
}
Log.d(THIS_FILE, "Start was asked and we should actually start now");
if(pjService == null) {
Log.d(THIS_FILE, "Start was asked and pjService in not there");
if(!loadStack()) {
Log.e(THIS_FILE, "Unable to load SIP stack !! ");
return;
}
}
Log.d(THIS_FILE, "Ask pjservice to start itself");
//presenceMgr.startMonitoring(this);
if(pjService.sipStart()) {
// This should be done after in acquire resource
// But due to http://code.google.com/p/android/issues/detail?id=21635
// not a good idea
applyComponentEnablingState(true);
registerBroadcasts();
Log.d(THIS_FILE, "Add all accounts");
addAllAccounts();
}
}
/**
* Safe stop the sip stack
* @return true if can be stopped, false if there is a pending call and the sip service should not be stopped
*/
public boolean stopSipStack() throws SameThreadException {
Log.d(THIS_FILE, "Stop sip stack");
boolean canStop = true;
if(pjService != null) {
canStop &= pjService.sipStop();
/*
if(canStop) {
pjService = null;
}
*/
}
if(canStop) {
//if(presenceMgr != null) {
// presenceMgr.stopMonitoring();
//}
// Due to http://code.google.com/p/android/issues/detail?id=21635
// exclude 14 and upper from auto disabling on stop.
if(!Compatibility.isCompatible(14)) {
applyComponentEnablingState(false);
}
unregisterBroadcasts();
releaseResources();
}
return canStop;
}
public void restartSipStack() throws SameThreadException {
if(stopSipStack()) {
startSipStack();
}else {
Log.e(THIS_FILE, "Can't stop ... so do not restart ! ");
}
}
/**
* Notify user from a message the sip stack wants to transmit.
* For now it shows a toaster
* @param msg String message to display
*/
public void notifyUserOfMessage(String msg) {
serviceHandler.sendMessage(serviceHandler.obtainMessage(TOAST_MESSAGE, msg));
}
/**
* Notify user from a message the sip stack wants to transmit.
* For now it shows a toaster
* @param resStringId The id of the string resource to display
*/
public void notifyUserOfMessage(int resStringId) {
serviceHandler.sendMessage(serviceHandler.obtainMessage(TOAST_MESSAGE, resStringId, 0));
}
private boolean hasSomeActiveAccount = false;
/**
* Add accounts from database
*/
private void addAllAccounts() throws SameThreadException {
Log.d(THIS_FILE, "We are adding all accounts right now....");
boolean hasSomeSuccess = false;
Cursor c = getContentResolver().query(SipProfile.ACCOUNT_URI, DBProvider.ACCOUNT_FULL_PROJECTION,
SipProfile.FIELD_ACTIVE + "=?", new String[] {"1"}, null);
if (c != null) {
try {
int index = 0;
if(c.getCount() > 0) {
c.moveToFirst();
do {
SipProfile account = new SipProfile(c);
if (pjService != null && pjService.addAccount(account) ) {
hasSomeSuccess = true;
}
index ++;
} while (c.moveToNext() && index < 10);
}
} catch (Exception e) {
Log.e(THIS_FILE, "Error on looping over sip profiles", e);
} finally {
c.close();
}
}
hasSomeActiveAccount = hasSomeSuccess;
if (hasSomeSuccess) {
acquireResources();
} else {
releaseResources();
if (notificationManager != null) {
notificationManager.cancelRegisters();
}
}
}
public boolean setAccountRegistration(SipProfile account, int renew, boolean forceReAdd) throws SameThreadException {
boolean status = false;
if(pjService != null) {
status = pjService.setAccountRegistration(account, renew, forceReAdd);
}
return status;
}
/**
* Remove accounts from database
*/
private void unregisterAllAccounts(boolean cancelNotification) throws SameThreadException {
releaseResources();
Log.d(THIS_FILE, "Remove all accounts");
Cursor c = getContentResolver().query(SipProfile.ACCOUNT_URI, DBProvider.ACCOUNT_FULL_PROJECTION, null, null, null);
if (c != null) {
try {
c.moveToFirst();
do {
SipProfile account = new SipProfile(c);
setAccountRegistration(account, 0, false);
} while (c.moveToNext() );
} catch (Exception e) {
Log.e(THIS_FILE, "Error on looping over sip profiles", e);
} finally {
c.close();
}
}
if (notificationManager != null && cancelNotification) {
notificationManager.cancelRegisters();
}
}
private void reAddAllAccounts() throws SameThreadException {
Log.d(THIS_FILE, "RE REGISTER ALL ACCOUNTS");
unregisterAllAccounts(false);
addAllAccounts();
}
public SipProfileState getSipProfileState(int accountDbId) {
final SipProfile acc = getAccount(accountDbId);
if(pjService != null && acc != null) {
return pjService.getProfileState(acc);
}
return null;
}
public void updateRegistrationsState() {
Log.d(THIS_FILE, "Update registration state");
ArrayList<SipProfileState> activeProfilesState = new ArrayList<SipProfileState>();
Cursor c = getContentResolver().query(SipProfile.ACCOUNT_STATUS_URI, null, null, null, null);
if (c != null) {
try {
if(c.getCount() > 0) {
c.moveToFirst();
do {
SipProfileState ps = new SipProfileState(c);
if(ps.isValidForCall()) {
activeProfilesState.add(ps);
}
} while ( c.moveToNext() );
}
} catch (Exception e) {
Log.e(THIS_FILE, "Error on looping over sip profiles", e);
} finally {
c.close();
}
}
Collections.sort(activeProfilesState, SipProfileState.getComparator());
// Handle status bar notification
if (activeProfilesState.size() > 0 &&
prefsWrapper.getPreferenceBooleanValue(SipConfigManager.ICON_IN_STATUS_BAR, true)) {
// Testing memory / CPU leak as per issue 676
// for(int i=0; i < 10; i++) {
// Log.d(THIS_FILE, "Notify ...");
notificationManager.notifyRegisteredAccounts(activeProfilesState, prefsWrapper.getPreferenceBooleanValue(SipConfigManager.ICON_IN_STATUS_BAR_NBR));
// try {
// Thread.sleep(6000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
} else {
notificationManager.cancelRegisters();
}
if(hasSomeActiveAccount) {
acquireResources();
}else {
releaseResources();
}
}
/**
* Get the currently instanciated prefsWrapper (to be used by
* UAStateReceiver)
*
* @return the preferenceWrapper instanciated
*/
public PreferencesProviderWrapper getPrefs() {
// Is never null when call so ok, just not check...
return prefsWrapper;
}
//Binders for media manager to sip stack
/**
* Adjust tx software sound level
* @param speakVolume volume 0.0 - 1.0
*/
public void confAdjustTxLevel(float speakVolume) throws SameThreadException {
if(pjService != null) {
pjService.confAdjustTxLevel(0, speakVolume);
}
}
/**
* Adjust rx software sound level
* @param speakVolume volume 0.0 - 1.0
*/
public void confAdjustRxLevel(float speakVolume) throws SameThreadException {
if(pjService != null) {
pjService.confAdjustRxLevel(0, speakVolume);
}
}
/**
* Add a buddy to list
* @param buddyUri the sip uri of the buddy to add
*/
public int addBuddy(String buddyUri) throws SameThreadException {
int retVal = -1;
if(pjService != null) {
Log.d(THIS_FILE, "Trying to add buddy " + buddyUri);
retVal = pjService.addBuddy(buddyUri);
}
return retVal;
}
/**
* Remove a buddy from buddies
* @param buddyUri the sip uri of the buddy to remove
*/
public void removeBuddy(String buddyUri) throws SameThreadException {
if(pjService != null) {
pjService.removeBuddy(buddyUri);
}
}
private boolean holdResources = false;
/**
* Ask to take the control of the wifi and the partial wake lock if
* configured
*/
private synchronized void acquireResources() {
if(holdResources) {
return;
}
// Add a wake lock for CPU if necessary
if (prefsWrapper.getPreferenceBooleanValue(SipConfigManager.USE_PARTIAL_WAKE_LOCK)) {
PowerManager pman = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (wakeLock == null) {
wakeLock = pman.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "com.csipsimple.SipService");
wakeLock.setReferenceCounted(false);
}
// Extra check if set reference counted is false ???
if (!wakeLock.isHeld()) {
wakeLock.acquire();
}
}
// Add a lock for WIFI if necessary
WifiManager wman = (WifiManager) getSystemService(Context.WIFI_SERVICE);
if (wifiLock == null) {
int mode = WifiManager.WIFI_MODE_FULL;
if(Compatibility.isCompatible(9) && prefsWrapper.getPreferenceBooleanValue(SipConfigManager.LOCK_WIFI_PERFS)) {
mode = 0x3; // WIFI_MODE_FULL_HIGH_PERF
}
wifiLock = wman.createWifiLock(mode, "com.csipsimple.SipService");
wifiLock.setReferenceCounted(false);
}
if (prefsWrapper.getPreferenceBooleanValue(SipConfigManager.LOCK_WIFI) && !wifiLock.isHeld()) {
WifiInfo winfo = wman.getConnectionInfo();
if (winfo != null) {
DetailedState dstate = WifiInfo.getDetailedStateOf(winfo.getSupplicantState());
// We assume that if obtaining ip addr, we are almost connected
// so can keep wifi lock
if (dstate == DetailedState.OBTAINING_IPADDR || dstate == DetailedState.CONNECTED) {
if (!wifiLock.isHeld()) {
wifiLock.acquire();
}
}
}
}
holdResources = true;
}
private synchronized void releaseResources() {
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
if (wifiLock != null && wifiLock.isHeld()) {
wifiLock.release();
}
holdResources = false;
}
private static final int TOAST_MESSAGE = 0;
private Handler serviceHandler = new ServiceHandler(this);
private static class ServiceHandler extends Handler {
WeakReference<SipService> s;
public ServiceHandler(SipService sipService) {
s = new WeakReference<SipService>(sipService);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
SipService sipService = s.get();
if(sipService == null) {
return;
}
if (msg.what == TOAST_MESSAGE) {
if (msg.arg1 != 0) {
Toast.makeText(sipService, msg.arg1, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(sipService, (String) msg.obj, Toast.LENGTH_LONG).show();
}
}
}
};
public UAStateReceiver getUAStateReceiver() {
return pjService.userAgentReceiver;
}
public int getGSMCallState() {
return telephonyManager.getCallState();
}
public static final class ToCall {
private Integer pjsipAccountId;
private String callee;
private String dtmf;
public ToCall(Integer acc, String uri) {
pjsipAccountId = acc;
callee = uri;
}
public ToCall(Integer acc, String uri, String dtmfChars) {
pjsipAccountId = acc;
callee = uri;
dtmf = dtmfChars;
}
/**
* @return the pjsipAccountId
*/
public Integer getPjsipAccountId() {
return pjsipAccountId;
}
/**
* @return the callee
*/
public String getCallee() {
return callee;
}
/**
* @return the dtmf sequence to automatically dial for this call
*/
public String getDtmf() {
return dtmf;
}
};
public SipProfile getAccount(long accountId) {
// TODO : create cache at this point to not requery each time as far as it's a service query
return SipProfile.getProfileFromDbId(this, accountId, DBProvider.ACCOUNT_FULL_PROJECTION);
}
// Auto answer feature
public void setAutoAnswerNext(boolean auto_response) {
autoAcceptCurrent = auto_response;
}
/**
* Should a current incoming call be answered.
* A call to this method will reset internal state
* @param remContact The remote contact to test
* @param acc The incoming guessed account
* @return the sip code to auto-answer with. If > 0 it means that an auto answer must be fired
*/
public int shouldAutoAnswer(String remContact, SipProfile acc, Bundle extraHdr) {
Log.d(THIS_FILE, "Search if should I auto answer for " + remContact);
int shouldAutoAnswer = 0;
if(autoAcceptCurrent) {
Log.d(THIS_FILE, "I should auto answer this one !!! ");
autoAcceptCurrent = false;
return 200;
}
if(acc != null) {
Pattern p = Pattern.compile("^(?:\")?([^<\"]*)(?:\")?[ ]*(?:<)?sip(?:s)?:([^@]*@[^>]*)(?:>)?", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(remContact);
String number = remContact;
if (m.matches()) {
number = m.group(2);
}
shouldAutoAnswer = Filter.isAutoAnswerNumber(this, acc.id, number, extraHdr);
}else {
Log.e(THIS_FILE, "Oupps... that come from an unknown account...");
// If some user need to auto hangup if comes from unknown account, just needed to add local account and filter on it.
}
return shouldAutoAnswer;
}
// Media direct binders
public void setNoSnd() throws SameThreadException {
if (pjService != null) {
pjService.setNoSnd();
}
}
public void setSnd() throws SameThreadException {
if (pjService != null) {
pjService.setSnd();
}
}
private static Looper createLooper() {
// synchronized (executorThread) {
if(executorThread == null) {
Log.d(THIS_FILE, "Creating new handler thread");
// ADT gives a fake warning due to bad parse rule.
executorThread = new HandlerThread("SipService.Executor");
executorThread.start();
}
// }
return executorThread.getLooper();
}
// Executes immediate tasks in a single executorThread.
// Hold/release wake lock for running tasks
public static class SipServiceExecutor extends Handler {
WeakReference<SipService> handlerService;
SipServiceExecutor(SipService s) {
super(createLooper());
handlerService = new WeakReference<SipService>(s);
}
public void execute(Runnable task) {
SipService s = handlerService.get();
if(s != null) {
s.sipWakeLock.acquire(task);
}
Message.obtain(this, 0/* don't care */, task).sendToTarget();
}
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof Runnable) {
executeInternal((Runnable) msg.obj);
} else {
Log.w(THIS_FILE, "can't handle msg: " + msg);
}
}
private void executeInternal(Runnable task) {
try {
task.run();
} catch (Throwable t) {
Log.e(THIS_FILE, "run task: " + task, t);
} finally {
SipService s = handlerService.get();
if(s != null) {
s.sipWakeLock.release(task);
}
}
}
}
class StartRunnable extends SipRunnable {
@Override
protected void doRun() throws SameThreadException {
startSipStack();
}
}
class SyncStartRunnable extends ReturnRunnable {
@Override
protected Object runWithReturn() throws SameThreadException {
startSipStack();
return null;
}
}
class StopRunnable extends SipRunnable {
@Override
protected void doRun() throws SameThreadException {
stopSipStack();
}
}
class SyncStopRunnable extends ReturnRunnable {
@Override
protected Object runWithReturn() throws SameThreadException {
stopSipStack();
return null;
}
}
class RestartRunnable extends SipRunnable {
@Override
protected void doRun() throws SameThreadException {
if(stopSipStack()) {
startSipStack();
}else {
Log.e(THIS_FILE, "Can't stop ... so do not restart ! ");
}
}
}
class SyncRestartRunnable extends ReturnRunnable {
@Override
protected Object runWithReturn() throws SameThreadException {
if(stopSipStack()) {
startSipStack();
}else {
Log.e(THIS_FILE, "Can't stop ... so do not restart ! ");
}
return null;
}
}
class DestroyRunnable extends SipRunnable {
@Override
protected void doRun() throws SameThreadException {
if(stopSipStack()) {
stopSelf();
}
}
}
class FinalizeDestroyRunnable extends SipRunnable {
@Override
protected void doRun() throws SameThreadException {
mExecutor = null;
Log.d(THIS_FILE, "Destroy sip stack");
sipWakeLock.reset();
if(stopSipStack()) {
notificationManager.cancelAll();
notificationManager.cancelCalls();
}else {
Log.e(THIS_FILE, "Somebody has stopped the service while there is an ongoing call !!!");
}
/* If we activate that we can get two concurrent executorThread
synchronized (executorThread) {
HandlerThread currentHandlerThread = executorThread;
executorThread = null;
System.gc();
// This is a little bit crappy, we are cutting were we sit.
Threading.stopHandlerThread(currentHandlerThread, false);
}
*/
// We will not go longer
Log.i(THIS_FILE, "--- SIP SERVICE DESTROYED ---");
}
}
// Enforce same thread contract to ensure we do not call from somewhere else
public class SameThreadException extends Exception {
private static final long serialVersionUID = -905639124232613768L;
public SameThreadException() {
super("Should be launched from a single worker thread");
}
}
public abstract static class SipRunnable implements Runnable {
protected abstract void doRun() throws SameThreadException;
public void run() {
try {
doRun();
}catch(SameThreadException e) {
Log.e(THIS_FILE, "Not done from same thread");
}
}
}
public abstract class ReturnRunnable extends SipRunnable {
private Semaphore runSemaphore;
private Object resultObject;
public ReturnRunnable() {
super();
runSemaphore = new Semaphore(0);
}
public Object getResult() {
try {
runSemaphore.acquire();
} catch (InterruptedException e) {
Log.e(THIS_FILE, "Can't acquire run semaphore... problem...");
}
return resultObject;
}
protected abstract Object runWithReturn() throws SameThreadException;
@Override
public void doRun() throws SameThreadException {
setResult(runWithReturn());
}
private void setResult(Object obj) {
resultObject = obj;
runSemaphore.release();
}
}
private static String UI_CALL_PACKAGE = null;
public static Intent buildCallUiIntent(Context ctxt, SipCallSession callInfo) {
// Resolve the package to handle call.
if(UI_CALL_PACKAGE == null) {
UI_CALL_PACKAGE = ctxt.getPackageName();
try {
Map<String, DynActivityPlugin> callsUis = ExtraPlugins.getDynActivityPlugins(ctxt, SipManager.ACTION_SIP_CALL_UI);
String preferredPackage = SipConfigManager.getPreferenceStringValue(ctxt, SipConfigManager.CALL_UI_PACKAGE, UI_CALL_PACKAGE);
String packageName = null;
boolean foundPref = false;
for(String activity : callsUis.keySet()) {
packageName = activity.split("/")[0];
if(preferredPackage.equalsIgnoreCase(packageName)) {
UI_CALL_PACKAGE = packageName;
foundPref = true;
break;
}
}
if(!foundPref && !TextUtils.isEmpty(packageName)) {
UI_CALL_PACKAGE = packageName;
}
}catch(Exception e) {
Log.e(THIS_FILE, "Error while resolving package", e);
}
}
SipCallSession toSendInfo = new SipCallSession(callInfo);
Intent intent = new Intent(SipManager.ACTION_SIP_CALL_UI);
intent.putExtra(SipManager.EXTRA_CALL_INFO, toSendInfo);
intent.setPackage(UI_CALL_PACKAGE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
return intent;
}
public static void setVideoWindow(int callId, SurfaceView window, boolean local) {
if(singleton != null) {
if(local) {
singleton.setCaptureVideoWindow(window);
}else {
singleton.setRenderVideoWindow(callId, window);
}
}
}
private void setRenderVideoWindow(final int callId, final SurfaceView window) {
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.setVideoAndroidRenderer(callId, window);
}
});
}
private void setCaptureVideoWindow(final SurfaceView window) {
getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
pjService.setVideoAndroidCapturer(window);
}
});
}
private PresenceStatus presence = SipManager.PresenceStatus.ONLINE;
/**
* Get current last status for the user
* @return
*/
public PresenceStatus getPresence() {
return presence;
}
}