Closed ParticleCore closed 6 months ago
I just realized this might be better off asked at a different repo? https://github.com/BelledonneCommunications/linphone-sdk
If so this one can be closed.
Hi @ParticleCore,
Could you dump the content of /data/user/0/com.app/files/.linphonerc
file after you call
core.clearAccounts()
core.clearAllAuthInfo()
please?
It's the first time I hear about such issue, and as this code almost never changes I find it weird it doesn't work on your side.
Cheers,
Here is the content of the file you asked for after calling both methods (account related information has been redacted):
[misc]
empty_chat_room_deletion=1
uuid=<truncated>
[sound]
playback_dev_id=AAudio Earpiece: Pixel 6
ringer_dev_id=AAudio Earpiece: Pixel 6
capture_dev_id=AAudio Microphone: Pixel 6
media_dev_id=AAudio Earpiece: Pixel 6
remote_ring=/data/user/0/com.app/files/share/sounds/linphone/ringback.wav
playback_gain_db=0.000000
mic_gain_db=0.000000
disable_record_on_mute=0
[net]
nat_policy_ref=iNUTu4EfNSKW2XJ
mtu=1300
[sip]
root_ca=/data/user/0/com.app/files/share/linphone/rootca.pem
verify_server_certs=1
verify_server_cn=1
contact=sip:linphone@unknown-host
auto_send_ringing=1
media_encryption=none
register_only_when_network_is_up=1
supported=replaces, outbound, gruu, path
default_proxy=0
http_proxy_port=0
guess_hostname=1
inc_timeout=45
in_call_timeout=0
delayed_timeout=4
register_only_when_upnp_is_ok=1
[app]
auto_download_incoming_files_max_size=-1
auto_download_incoming_voice_recordings=1
auto_download_incoming_icalendars=1
sender_name_hidden_in_forward_message=0
record_aware=0
[nat_policy_0]
ref=iNUTu4EfNSKW2XJ
[rtp]
audio_rtp_port=-1
video_rtp_port=-1
text_rtp_port=-1
audio_jitt_comp=60
video_jitt_comp=60
nortp_timeout=30
audio_adaptive_jitt_comp_enabled=1
video_adaptive_jitt_comp_enabled=1
[video]
size=vga
display=1
capture=1
codec_priority_policy=1
[audio_codec_0]
mime=opus
rate=48000
channels=2
enabled=1
recv_fmtp=useinbandfec=1
[audio_codec_1]
mime=speex
rate=16000
channels=1
enabled=1
recv_fmtp=vbr=on
[audio_codec_2]
mime=speex
rate=8000
channels=1
enabled=1
recv_fmtp=vbr=on
[audio_codec_3]
mime=PCMU
rate=8000
channels=1
enabled=1
[audio_codec_4]
mime=PCMA
rate=8000
channels=1
enabled=1
[audio_codec_5]
mime=GSM
rate=8000
channels=1
enabled=0
[audio_codec_6]
mime=G722
rate=8000
channels=1
enabled=0
[audio_codec_7]
mime=iLBC
rate=8000
channels=1
enabled=0
recv_fmtp=mode=30
[audio_codec_8]
mime=iSAC
rate=16000
channels=1
enabled=0
[audio_codec_9]
mime=speex
rate=32000
channels=1
enabled=0
recv_fmtp=vbr=on
[audio_codec_10]
mime=BV16
rate=8000
channels=1
enabled=0
[audio_codec_11]
mime=L16
rate=44100
channels=2
enabled=0
[audio_codec_12]
mime=L16
rate=44100
channels=1
enabled=0
[video_codec_0]
mime=H265
rate=90000
enabled=1
[video_codec_1]
mime=H264
rate=90000
enabled=1
recv_fmtp=profile-level-id=42801F
[video_codec_2]
mime=AV1
rate=90000
enabled=1
[video_codec_3]
mime=VP8
rate=90000
enabled=1
[proxy_0]
reg_proxy=<sip:<truncated>;transport=udp>
reg_identity=sip:<truncated>
push_parameters=pn-prid=<truncated>;pn-provider=fcm;pn-param=539156446598;pn-silent=1;pn-timeout=0;pn-msg-str=IM_MSG;pn-call-str=IC_MSG;pn-groupchat-str=GC_MSG;pn-call-snd=notes_of_the_optimistic.caf;pn-msg-snd=msg.caf;
quality_reporting_enabled=0
quality_reporting_interval=0
reg_expires=3600
reg_sendregister=1
publish=0
avpf=-1
avpf_rr_interval=1
dial_escape_plus=0
use_dial_prefix_for_calls_and_chats=1
privacy=32768
push_notification_allowed=1
remote_push_notification_allowed=1
force_register_on_push=0
cpim_in_basic_chat_rooms_enabled=0
idkey=proxy_config_4i3~JIsh2xn1S5L
publish_expires=-1
rtp_bundle=0
rtp_bundle_assumption=0
Thanks.
Indeed the proxy_0 section should not be here after calling core.clearAccounts()
Problem is I don't reproduce it...
Could you check your code and make sure you don't add it again somewhere?
Does calling core.clearProxyConfigs()
makes it better?
Cheers,
I've tried with core.clearProxyConfig()
along with the other methods, and on its own, the result is exactly the same, there is always one account when reading the account list after.
However, I did something different; while the account was registered I deleted the .linphonerc
file directly from the device and now it started working normally. It would logout correctly and clear the account from the file, and on login it would correctly indicate there were currently 0 accounts stored and create a new registration.
So far so good, but after repeating the logout and login a few times I tried the following; while logged in, close the app, and then open it again. It correctly reads the account in the file. I then logged out, and the account information was still in the file. Logged in, and it was back with the same issue, it stated that 1 account was already stored.
So it seems like running the login logic more than once (it is executed on login, and every time the app resumes) is enough to cause the issue.
Is there something that I should not be doing in the login code logic that could explain this problem? I am already checking to see if an account already exists so it does not add multiple equal accounts:
val oldAccount = core.accountList.firstOrNull()
if (oldAccount == null) {
val account = core.createAccount(params)
core.addAccount(account)
core.addAuthInfo(authInfo)
core.defaultAccount = account
core.start()
}
Is it something that is being done before that? Is it the factory.createConfigWithFactory
or factory.createCoreWithConfig
that should only be run once? But then how can I check the account on resume without it?
I already moved everything else in that logic to after if (oldAccount == null) {
to rule out the possibility of createAuthInfo
, createAddress
, createAccountParams
possibly interfering here, so the only ones left are the factory instantiation and core instatiation methods.
val factory = Factory.instance()
val coreConfig = factory.createConfigWithFactory(
"${context.filesDir.absolutePath}/.linphonerc",
null,
)
val core = factory.createCoreWithConfig(coreConfig, context)
log.d("Core account list: ${core.accountList.size}")
core.addListener(coreListener)
val oldAccount = core.accountList.firstOrNull()
if (oldAccount != null) return
In case it might be helpful, currently this is my SipManager
object SipManager {
private const val TAG = "SipManager"
@Volatile
private var _factory: Factory? = null
@Volatile
private var _core: Core? = null
private fun getFactory() = _factory ?: synchronized(SipManager::class.java) {
_factory ?: try {
Factory.instance()?.apply {
setLoggerDomain("sip")
enableLogcatLogs(true)
loggingService.setLogLevel(LogLevel.Debug)
}
?.also { _factory = it }
} catch (e: Exception) {
Log.e(TAG, "Failed to obtain factory instance", e)
null
}
}
private fun getCore(context: Context) = _core ?: synchronized(SipManager::class.java) {
_core ?: getFactory()?.createCore(
"${context.filesDir.absolutePath}/.linphonerc",
null,
context
)?.also { _core = it }
}
private val coreListener = CoreListenerStub()
fun connect(
context: Context,
username: String,
password: String,
domain: String,
transportType: TransportType,
onError: ((message: String) -> Unit)? = null,
) {
Log.i(TAG, "Running on main thread ${Looper.getMainLooper().thread == Thread.currentThread()}")
val core = getCore(context)
if (core == null) {
AppLog.error(TAG, "Unable to initialize")
onError?.invoke("Unable to login")
return
}
Log.i(TAG, "Core account list: ${core.accountList.size}")
core.addListener(coreListener)
val oldAccount = core.accountList.firstOrNull()
if (oldAccount != null) return
val factory = getFactory()
if (factory == null) {
AppLog.error(TAG, "Unable to initialize")
onError?.invoke("Unable to login")
return
}
val authInfo = factory.createAuthInfo(username, null, password, null, null, domain, null)
val identity = factory.createAddress("sip:$username@$domain")
val address = factory.createAddress("sip:$domain")?.apply {
transport = transportType
}
val params = core.createAccountParams().apply {
identityAddress = identity
serverAddress = address
isRegisterEnabled = true
remotePushNotificationAllowed = true
}
val account = core.createAccount(params)
core.addAccount(account)
core.addAuthInfo(authInfo)
core.defaultAccount = account
core.start()
}
fun disconnect(context: Context) {
Log.i(TAG, "Disconnecting")
val core = getCore(context) ?: return
val account = core.defaultAccount ?: return
val authInfo = account.findAuthInfo()
val params = account.params
val clonedParams = params.clone().apply {
isRegisterEnabled = false
}
if (authInfo != null) {
core.removeAuthInfo(authInfo)
}
account.params = clonedParams
core.removeAccount(account)
core.accountList.firstOrNull()?.let {
val authInfo = account.findAuthInfo()
val params = account.params
val clonedParams = params.clone().apply {
isRegisterEnabled = false
}
if (authInfo != null) {
core.removeAuthInfo(authInfo)
}
it.params = clonedParams
core.removeAccount(it)
}
core.clearAccounts()
core.clearAllAuthInfo()
core.clearProxyConfig()
core.defaultAccount = null
core.refreshRegisters()
core.stop()
core.removeListener(coreListener)
_core = null
_factory = null
}
}
Connect
is called when the user logs in, and every time the app is reopened while the user is still logged in to the app. The subsequent logins when reopening the app (app dies or gets killed and is opened again) seems to be the scenario that causes the documented problem)
Disconnect
is only called when the user logs out
@Viish I've been trying to make this work without saving the account registration (passing null
for the config in createCore
) which overcomes the initial issue outlined in this case, but unfortunately this brings up another issue that cannot be ignored.
I'm working with a SIP server that has a number of limited licenses per account and (because of the above change) the app always registers again for the same account and in a different random port every time the app reconnects/is re-opened. This causes the number of registrations to pile up way sooner than older registrations have a chance of expiring, and eventually hits a license limit and after that point the app can no longer receive or make any calls. Also making the port a fixed port lead to conflicts, so the random port feature is very welcome to resolve that.
Changing the license behavior on the server side is not an option since this is a mixed environment, meaning that there are other systems connected to it that need those configurations to stay as they are.
Is there any way that this issue can be resolved? Or a workaround to the original problem? Because the only real problem here is I am unable to tell whether the device has already been registered or not to prevent registering it again.
Hi @ParticleCore,
There is obviously something "wrong" (in the sense that it does something differently than we do) in the way you use our SDK, or there is a serious issue with your device. Can you confirm the issue doesn't happen with our app from the Play Store?
Cheers,
Hi @Viish I've gone ahead and just did a small reproducible project to confirm it wasn't any interference with anything else in the main app, and the issue still happens exactly as before. I'm attaching it here so you can try it out, this app uses a demo server with everything ready so there is no configuration needed.
Steps to recreate the issue with the app:
.linphonerc
file the account is still presentI was not able to try it out with the linphone app from the Play Store yet, will try it when possible.
Thanks, I'll take a look at it asap.
Hi @ParticleCore,
There is obviously something "wrong" (in the sense that it does something differently than we do) in the way you use our SDK, or there is a serious issue with your device. Can you confirm the issue doesn't happen with our app from the Play Store?
Cheers,
@Viish I am afraid that I am not able to confirm whether or not the account is correctly removed from the config file using the play store app. I have no access to the package, nor can I find a way to access it via the app. When I delete the account it appears to be removed correctly, but I am unsure if it is using an extra logic behind the scene that causes the original problem to not surface, such as keeping a list of removed accounts in order to ignore any existing account in the config file.
Did you manage to find out what the issue is via the sample app I posted?
Did you manage to find out what the issue is via the sample app I posted?
Sorry but I haven't taken the time to take a look at it yet.
I am unsure if it is using an extra logic behind the scene that causes the original problem to not surface, such as keeping a list of removed accounts in order to ignore any existing account in the config file.
No it doesn't, you check our source code.
I am afraid that I am not able to confirm whether or not the account is correctly removed from the config file using the play store app.
You can "dial" #1234#
on the main screen and in the popup that appears, click on View config file
.
Thanks, I tried replicating the scenario with the Linphone app and the issue does not occur, the account is correctly removed from the config app even after closing and opening the app before deleting the account.
@Viish I see the tag was removed, did you manage to replicate the issue with the sample app?
Hi,
First of all, in your sample, you have forgotten to add the following before the core.stop()
in the unregister process :
val authInfo = account.findAuthInfo()
if (authInfo != null) {
core.removeAuthInfo(authInfo)
}
core.removeAccount(account)
But even with it, I could reproduce your issue. After the above code was executed, the config file did still has the proxy_config and auth_info information.
The real issue is that you never start the core using core.start()
, and for some reason accounts aren't cleaned up from config file if Core isn't started when they are removed.
Cheers,
@Viish yes, I only used bare min in the sample app, I did test with all the suggested methods before and the result was the same, but thank you for letting me know that those steps are required to unregister correctly.
As for the core.start()
note, I am afraid I am confused here. The main issue is the account is not being removed correctly from the config at all, are you saying we have to always start the core in order to get the accounts properly removed, despite already having done that process priorly?
Sure, in the sample app I provided I do not have a core.start()
in this section here:
But the fact is the code only enters in that section because there is still an account in the config when it shouldn't:
Also, I definitely do have the core.start()
called here:
Which is guaranteed to be called for the very first time the account is registered, before the call to unregister it.
Again, I am at a total loss here with your explanation, nothing about it is making sense with what is being observed.
In my head this is what should happen:
Which is not what is happening in step 5. At which point does adding another core.start()
step help fix this issue?
In the provided tutorial the core.start()
is being used in the same way as I am using it, at the end of the account creation and all the parameters: https://gitlab.linphone.org/BC/public/tutorials/-/blob/master/android/kotlin/1-AccountLogin/app/src/main/java/org/linphone/accountlogin/AccountLoginActivity.kt
The only difference is in the unregister section it is not using core.stop()
, is that what is causing the problem? Then what would be the point of having that method, or why should it cause this problem to begin with by design?
I simply added it.start()
inside
getFactory()?.createCoreWithConfig(config, appContext)?.apply {
isKeepAliveEnabled = false
isLimeX3DhEnabled = false
}?.also { _core = it
it.start()
}
I've opened a ticket on our internal bugtracker for our SDK team to check whether this can be fixed or if it is normal, then to improve the documentation about it.
I simply added
it.start()
insidegetFactory()?.createCoreWithConfig(config, appContext)?.apply { isKeepAliveEnabled = false isLimeX3DhEnabled = false }?.also { _core = it it.start() }
Thank you, this time it is working. So the core must always be started no matter what, and since there is no available feedback to check if it is already started (ex; core.isStarted
) it must be started every time we need to invoke it, which makes me concerned with any consequences of calling core.start()
multiple times on an already started core.
Still, I believe the biggest issue is the account not actually being removed from the config file when we request it to be removed:
To me, the expectation is to remove the account from both the core and config when we use core.removeAccount(...)
which is definitely not doing. Hopefully that behavior will be changed to do just that!
Thanks for the big help with this one.
You can check if core is started by looking at it's global state (https://download.linphone.org/releases/docs/liblinphone/latest/java/org/linphone/core/GlobalState.html). If it's On then it is started, if it is Ready it means Core was created and is waiting to be started. Startup means core is starting but not yet On.
You can intercept those states in https://download.linphone.org/releases/docs/liblinphone/latest/java/org/linphone/core/CoreListener.html#onGlobalStateChanged(org.linphone.core.Core,org.linphone.core.GlobalState,java.lang.String)
Hi there, this is related to what I discussed in #2129 but since it was different than the original question I thought it would be better off as a new issue.
It is not currently possible to remove an account from core, tried using the following to no avail:
When I run a new core instance the
core.defaultAccount
still returns the old account andcore.accountList
still has the account that was suppose to be removed.The following is executed when the user logs out of the app:
And on login the following is run:
Whenever an account is removed or
clearAccounts
is called no accounts should be left in the defaultAccount nor accountList in subsequent core instance uses.Please complete the following information (mandatory)
SDK logs (mandatory)
These logs represent when the user logs out, and the account is suppose to be removed
These logs represent when the user logs in after having logged out, so right after the above
Note: at
10:39:30.489
there are two custom entries representing the twolog.d(...)
in the snippet examples posted above, and it confirms the accountlist is still not empty, and that a default account is not null.Not applicable
Not applicable
Unsure if I am doing anything wrong, I could not find any documentation, readme, or tutorial explaining how to properly do something like this.