I have been implementing VoIP Calling feature in an android app for a while now, most of the work is done but when a user makes a call to another user through the app, most of the time same person is receiving the call from where it was initialized.
For example, There are two users ABC and CBA. When ABC makes a call to CBA, it is done properly but when the case is reversed i.e when CBA makes a call to ABC, in the incoming method of the Linphone it receives the remote username as CBA, which means that CBA is getting calls to itself.
How to solve this problem? Am I applying a method in a wrong way?
I have used the Linphone Calling SDK for Android completely not even changed the methods from it.
Below is the code for the LinphoneService which receives the Incoming Call.
public final class LinphoneService extends Service {
/* Listener needs to be implemented in the Service as it calls
* setLatestEventInfo and startActivity() which needs a context.
*/
public static final String START_LINPHONE_LOGS = " ==== Phone information dump ====";
private static LinphoneService instance;
private final static int NOTIF_ID = 1;
private final static int INCALL_NOTIF_ID = 2;
private final static int MESSAGE_NOTIF_ID = 3;
private final static int SAS_NOTIF_ID = 6;
public static boolean isReady() {
return instance != null && instance.mTestDelayElapsed;
}
/**
* @throws RuntimeException service not instantiated
*/
public static LinphoneService instance() {
if (isReady()) return instance;
throw new RuntimeException("LinphoneService not instantiated yet");
}
public Handler mHandler = new Handler();
// private boolean mTestDelayElapsed; // add a timer for testing
private boolean mTestDelayElapsed = true; // no timer
private NotificationManager mNM;
private String mNotificationTitle;
private boolean mDisableRegistrationStatus;
private LinphoneCoreListenerBase mListener;
public static int notifcationsPriority = (Version.sdkAboveOrEqual(Version.API16_JELLY_BEAN_41) ? Notification.PRIORITY_MIN : 0);
private WindowManager mWindowManager;
private Application.ActivityLifecycleCallbacks activityCallbacks;
/*Believe me or not, but knowing the application visibility state on Android is a nightmare.
After two days of hard work I ended with the following class, that does the job more or less reliabily.
*/
class ActivityMonitor implements Application.ActivityLifecycleCallbacks {
private ArrayList<Activity> activities = new ArrayList<Activity>();
private boolean mActive = false;
private int mRunningActivities = 0;
class InactivityChecker implements Runnable {
private boolean isCanceled;
public void cancel() {
isCanceled = true;
}
@Override
public void run() {
synchronized (LinphoneService.this) {
if (!isCanceled) {
if (LinphoneService.ActivityMonitor.this.mRunningActivities == 0 && mActive) {
mActive = false;
LinphoneService.this.onBackgroundMode();
}
}
}
}
}
;
private LinphoneService.ActivityMonitor.InactivityChecker mLastChecker;
@Override
public synchronized void onActivityCreated(Activity activity, Bundle savedInstanceState) {
Log.i("Activity created:" + activity);
if (!activities.contains(activity))
activities.add(activity);
}
@Override
public void onActivityStarted(Activity activity) {
Log.i("Activity started:" + activity);
}
@Override
public synchronized void onActivityResumed(Activity activity) {
Log.i("Activity resumed:" + activity);
if (activities.contains(activity)) {
mRunningActivities++;
Log.i("runningActivities=" + mRunningActivities);
checkActivity();
}
}
@Override
public synchronized void onActivityPaused(Activity activity) {
Log.i("Activity paused:" + activity);
if (activities.contains(activity)) {
mRunningActivities--;
Log.i("runningActivities=" + mRunningActivities);
checkActivity();
}
}
@Override
public void onActivityStopped(Activity activity) {
Log.i("Activity stopped:" + activity);
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public synchronized void onActivityDestroyed(Activity activity) {
Log.i("Activity destroyed:" + activity);
if (activities.contains(activity)) {
activities.remove(activity);
}
}
void startInactivityChecker() {
if (mLastChecker != null) mLastChecker.cancel();
LinphoneService.this.mHandler.postDelayed(
(mLastChecker = new LinphoneService.ActivityMonitor.InactivityChecker()), 2000);
}
void checkActivity() {
if (mRunningActivities == 0) {
if (mActive) startInactivityChecker();
} else if (mRunningActivities > 0) {
if (!mActive) {
mActive = true;
LinphoneService.this.onForegroundMode();
}
if (mLastChecker != null) {
mLastChecker.cancel();
mLastChecker = null;
}
}
}
}
protected void onBackgroundMode() {
Log.i("App has entered background mode");
if (LinphonePreferences.instance() != null && LinphonePreferences.instance().isFriendlistsubscriptionEnabled()) {
if (LinphoneManager.isInstanciated())
LinphoneManager.getInstance().subscribeFriendList(false);
}
}
protected void onForegroundMode() {
Log.i("App has left background mode");
if (LinphonePreferences.instance() != null && LinphonePreferences.instance().isFriendlistsubscriptionEnabled()) {
if (LinphoneManager.isInstanciated())
LinphoneManager.getInstance().subscribeFriendList(true);
}
}
private void setupActivityMonitor() {
if (activityCallbacks != null) return;
getApplication().registerActivityLifecycleCallbacks(activityCallbacks = new LinphoneService.ActivityMonitor());
}
@SuppressWarnings("unchecked")
@Override
public void onCreate() {
super.onCreate();
setupActivityMonitor();
// In case restart after a crash. Main in LinphoneActivity
mNotificationTitle = getString(R.string.service_name);
// Needed in order for the two next calls to succeed, libraries must have been loaded first
LinphonePreferences.instance().setContext(getBaseContext());
LinphoneCoreFactory.instance().setLogCollectionPath(getFilesDir().getAbsolutePath());
boolean isDebugEnabled = LinphonePreferences.instance().isDebugEnabled();
LinphoneCoreFactory.instance().enableLogCollection(isDebugEnabled);
LinphoneCoreFactory.instance().setDebugMode(isDebugEnabled, getString(R.string.app_name));
// Dump some debugging information to the logs
Log.i(START_LINPHONE_LOGS);
dumpDeviceInformation();
dumpInstalledLinphoneInformation();
//Disable service notification for Android O
if ((Version.sdkAboveOrEqual(Version.API26_O_80))) {
LinphonePreferences.instance().setServiceNotificationVisibility(false);
mDisableRegistrationStatus = true;
}
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNM.cancel(INCALL_NOTIF_ID); // in case of crash the icon is not removed
if (!LinphoneManager.isInstanciated())
LinphoneManager.createAndStart(LinphoneService.this);
instance = this; // instance is ready once linphone manager has been created
incomingReceivedActivityName = LinphonePreferences.instance().getActivityToLaunchOnIncomingReceived();
android.util.Log.d("linphoneCalling", "Service Strated");
LinphoneManager.getLc().addListener(mListener = new LinphoneCoreListenerBase() {
@Override
public void callState(LinphoneCore lc, LinphoneCall call, LinphoneCall.State state, String message) {
if (instance == null) {
Log.i("Service not ready, discarding call state change to ", state.toString());
return;
}
if (state == LinphoneCall.State.IncomingReceived) {
android.util.Log.d("ServiceStarted", "IncomingCall Status");
onIncomingReceived2(call);
}
if (state == State.CallEnd || state == State.CallReleased || state == State.Error) {
if (LinphoneManager.isInstanciated() && LinphoneManager.getLc() != null && LinphoneManager.getLc().getCallsNb() == 0) {
}
}
if (state == State.CallEnd && call.getCallLog().getStatus() == CallStatus.Missed) {
int missedCallCount = LinphoneManager.getLcIfManagerNotDestroyedOrNull().getMissedCallsCount();
String body;
if (missedCallCount > 1) {
body = getString(R.string.missed_calls_notif_body).replace("%i", String.valueOf(missedCallCount));
} else {
LinphoneAddress address = call.getRemoteAddress();
body = address.getDisplayName();
if (body == null) {
body = address.asStringUriOnly();
}
}
}
if (state == State.StreamsRunning) {
// Workaround bug current call seems to be updated after state changed to streams running
if (getResources().getBoolean(R.bool.enable_call_notification))
refreshIncallIcon(call);
} else {
if (getResources().getBoolean(R.bool.enable_call_notification))
refreshIncallIcon(LinphoneManager.getLc().getCurrentCall());
}
}
@Override
public void globalState(LinphoneCore lc, LinphoneCore.GlobalState state, String message) {
}
@Override
public void registrationState(LinphoneCore lc, LinphoneProxyConfig cfg, LinphoneCore.RegistrationState state, String smessage) {
}
});
try {
mStartForeground = getClass().getMethod("startForeground", mStartFgSign);
mStopForeground = getClass().getMethod("stopForeground", mStopFgSign);
} catch (NoSuchMethodException e) {
Log.e(e, "Couldn't find startForeground or stopForeground");
}
/* if (displayServiceNotification()) {
startForegroundCompat(NOTIF_ID, mNotif);
}*/
if (!mTestDelayElapsed) {
// Only used when testing. Simulates a 5 seconds delay for launching service
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTestDelayElapsed = true;
}
}, 5000);
}
//make sure the application will at least wakes up every 10 mn
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
}
private enum IncallIconState {INCALL, PAUSE, VIDEO, IDLE}
private LinphoneService.IncallIconState mCurrentIncallIconState = LinphoneService.IncallIconState.IDLE;
public void refreshIncallIcon(LinphoneCall currentCall) {
LinphoneCore lc = LinphoneManager.getLc();
}
public void removeSasNotification() {
mNM.cancel(SAS_NOTIF_ID);
}
private static final Class<?>[] mSetFgSign = new Class[]{boolean.class};
private static final Class<?>[] mStartFgSign = new Class[]{
int.class, Notification.class};
private static final Class<?>[] mStopFgSign = new Class[]{boolean.class};
private Method mSetForeground;
private Method mStartForeground;
private Method mStopForeground;
private Object[] mSetForegroundArgs = new Object[1];
private Object[] mStartForegroundArgs = new Object[2];
private Object[] mStopForegroundArgs = new Object[1];
private String incomingReceivedActivityName;
void invokeMethod(Method method, Object[] args) {
try {
method.invoke(this, args);
} catch (InvocationTargetException e) {
// Should not happen.
Log.w(e, "Unable to invoke method");
} catch (IllegalAccessException e) {
// Should not happen.
Log.w(e, "Unable to invoke method");
}
}
/**
* This is a wrapper around the new startForeground method, using the older
* APIs if it is not available.
*/
void startForegroundCompat(int id, Notification notification) {
// If we have the new startForeground API, then use it.
if (mStartForeground != null) {
mStartForegroundArgs[0] = Integer.valueOf(id);
mStartForegroundArgs[1] = notification;
invokeMethod(mStartForeground, mStartForegroundArgs);
return;
}
// Fall back on the old API.
if (mSetForeground != null) {
mSetForegroundArgs[0] = Boolean.TRUE;
invokeMethod(mSetForeground, mSetForegroundArgs);
// continue
}
notifyWrapper(id, notification);
}
/**
* This is a wrapper around the new stopForeground method, using the older
* APIs if it is not available.
*/
void stopForegroundCompat(int id) {
// If we have the new stopForeground API, then use it.
if (mStopForeground != null) {
mStopForegroundArgs[0] = Boolean.TRUE;
invokeMethod(mStopForeground, mStopForegroundArgs);
return;
}
// Fall back on the old API. Note to cancel BEFORE changing the
// foreground state, since we could be killed at that point.
mNM.cancel(id);
if (mSetForeground != null) {
mSetForegroundArgs[0] = Boolean.FALSE;
invokeMethod(mSetForeground, mSetForegroundArgs);
}
}
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 + ", ");
}
sb.append("\n");
Log.i(sb.toString());
}
private void dumpInstalledLinphoneInformation() {
PackageInfo info = null;
try {
info = getPackageManager().getPackageInfo(getPackageName(), 0);
} catch (NameNotFoundException nnfe) {
}
if (info != null) {
Log.i("Linphone version is ", info.versionName + " (" + info.versionCode + ")");
} else {
Log.i("Linphone version is unknown");
}
}
/**
* Wrap notifier to avoid setting the linphone icons while the service
* is stopping. When the (rare) bug is triggered, the linphone icon is
* present despite the service is not running. To trigger it one could
* stop linphone as soon as it is started. Transport configured with TLS.
*/
private synchronized void notifyWrapper(int id, Notification notification) {
if (instance != null && notification != null) {
mNM.notify(id, notification);
} else {
Log.i("Service not ready, discarding notification");
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onTaskRemoved(Intent rootIntent) {
android.util.Log.d("TaskRemoved","True");
if (getResources().getBoolean(R.bool.kill_service_with_task_manager)) {
Log.d("Task removed, stop service");
// If push is enabled, don't unregister account, otherwise do unregister
if (LinphonePreferences.instance().isPushNotificationEnabled()) {
LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) lc.setNetworkReachable(false);
}
stopSelf();
}
super.onTaskRemoved(rootIntent);
}
@Override
public synchronized void onDestroy() {
if (activityCallbacks != null) {
getApplication().unregisterActivityLifecycleCallbacks(activityCallbacks);
activityCallbacks = null;
}
LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.removeListener(mListener);
}
instance = null;
LinphoneManager.destroy();
// Make sure our notification is gone.
stopForegroundCompat(NOTIF_ID);
mNM.cancel(INCALL_NOTIF_ID);
mNM.cancel(MESSAGE_NOTIF_ID);
super.onDestroy();
}
private void onIncomingReceived2(LinphoneCall call) {
String name = call.getRemoteAddress().asString().substring(call.getRemoteAddress().asString().indexOf("\"")+1,call.getRemoteAddress().asString().lastIndexOf("\""));
String myname = MyApplication.getInstance().getPrefManager().getUserName()+" "+MyApplication.getInstance().getPrefManager().getUserLastName();
android.util.Log.d("ServiceStarted", " Values "+ name+" "+myname + call.getUserData());
if (!name.equals(myname)) {
Intent intent = new Intent(getApplicationContext(), TwilioIncomingCallActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
When there is any Incoming Call the listener in the service initialized by the name LinphoneManager.getLc().addListener
in this it listens for the incoming call where the If the statement is mentioned as
if (state == LinphoneCall.State.IncomingReceived) {
android.util.Log.d("ServiceStarted", "IncomingCall Status");
onIncomingReceived2(call);
}
Now at the bottom of the code there is a method onIncomingReceived2(LinphoneCall call)
. So when I checked from the default method i.e. call.getRemoteAddress(), I am getting the same name of the user from which the call of Initialized.
So when I make the call it is received on the same device. Would really appreciate if anybody can help in this. Thanks in advance.