diff --git a/app/src/main/java/org/linphone/ui/assistant/fragment/ThirdPartySipAccountLoginFragment.kt b/app/src/main/java/org/linphone/ui/assistant/fragment/ThirdPartySipAccountLoginFragment.kt
index d9d3d3d20..1fcdf275a 100644
--- a/app/src/main/java/org/linphone/ui/assistant/fragment/ThirdPartySipAccountLoginFragment.kt
+++ b/app/src/main/java/org/linphone/ui/assistant/fragment/ThirdPartySipAccountLoginFragment.kt
@@ -38,6 +38,7 @@ import org.linphone.databinding.AssistantThirdPartySipAccountLoginFragmentBindin
import org.linphone.ui.GenericActivity
import org.linphone.ui.GenericFragment
import org.linphone.ui.assistant.viewmodel.ThirdPartySipAccountLoginViewModel
+import org.linphone.ui.main.sso.fragment.SingleSignOnFragmentDirections
import org.linphone.utils.PhoneNumberUtils
@UiThread
@@ -119,6 +120,22 @@ class ThirdPartySipAccountLoginFragment : GenericFragment() {
}
}
+ coreContext.bearerAuthenticationRequestedEvent.observe(viewLifecycleOwner) {
+ it.consume { pair ->
+ val serverUrl = pair.first
+ val username = pair.second
+
+ Log.i(
+ "$TAG Navigating to Single Sign On Fragment with server URL [$serverUrl] and username [$username]"
+ )
+ val action = SingleSignOnFragmentDirections.actionGlobalSingleSignOnFragment(
+ serverUrl,
+ username
+ )
+ findNavController().navigate(action)
+ }
+ }
+
coreContext.postOnCoreThread {
val dialPlan = PhoneNumberUtils.getDeviceDialPlan(requireContext())
if (dialPlan != null) {
diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt
index 680b4ed65..d3442b06c 100644
--- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/ThirdPartySipAccountLoginViewModel.kt
@@ -124,9 +124,6 @@ class ThirdPartySipAccountLoginViewModel @UiThread constructor() : GenericViewMo
loginEnabled.addSource(username) {
loginEnabled.value = isLoginButtonEnabled()
}
- loginEnabled.addSource(password) {
- loginEnabled.value = isLoginButtonEnabled()
- }
loginEnabled.addSource(domain) {
loginEnabled.value = isLoginButtonEnabled()
}
@@ -143,8 +140,31 @@ class ThirdPartySipAccountLoginViewModel @UiThread constructor() : GenericViewMo
coreContext.postOnCoreThread { core ->
core.loadConfigFromXml(corePreferences.thirdPartyDefaultValuesPath)
- val user = username.value.orEmpty().trim()
+ // Remove sip: in front of domain, just in case...
val domainValue = domain.value.orEmpty().trim()
+ val domain = if (domainValue.startsWith("sip:")) {
+ domainValue.substring("sip:".length)
+ } else {
+ domainValue
+ }
+
+ // Allow to enter SIP identity instead of simply username
+ // in case identity domain doesn't match proxy domain
+ val user = username.value.orEmpty().trim()
+ val identity = if (user.startsWith("sip:")) {
+ if (user.contains("@")) {
+ user
+ } else {
+ "$user@$domain"
+ }
+ } else {
+ if (user.contains("@")) {
+ "sip:$user"
+ } else {
+ "sip:$user@$domain"
+ }
+ }
+ val identityAddress = Factory.instance().createAddress(identity)
newlyCreatedAuthInfo = Factory.instance().createAuthInfo(
user,
@@ -152,19 +172,18 @@ class ThirdPartySipAccountLoginViewModel @UiThread constructor() : GenericViewMo
password.value.orEmpty().trim(),
null,
null,
- domainValue
+ null
)
core.addAuthInfo(newlyCreatedAuthInfo)
val accountParams = core.createAccountParams()
- val identityAddress = Factory.instance().createAddress("sip:$user@$domainValue")
if (displayName.value.orEmpty().isNotEmpty()) {
identityAddress?.displayName = displayName.value.orEmpty().trim()
}
accountParams.identityAddress = identityAddress
- val serverAddress = Factory.instance().createAddress("sip:$domainValue")
+ val serverAddress = Factory.instance().createAddress("sip:$domain")
serverAddress?.transport = when (transport.value.orEmpty().trim()) {
TransportType.Tcp.name.uppercase(Locale.getDefault()) -> TransportType.Tcp
TransportType.Tls.name.uppercase(Locale.getDefault()) -> TransportType.Tls
@@ -204,6 +223,7 @@ class ThirdPartySipAccountLoginViewModel @UiThread constructor() : GenericViewMo
@UiThread
private fun isLoginButtonEnabled(): Boolean {
- return username.value.orEmpty().isNotEmpty() && password.value.orEmpty().isNotEmpty() && domain.value.orEmpty().isNotEmpty()
+ // Password isn't mandatory as authentication could be Bearer
+ return username.value.orEmpty().isNotEmpty() && domain.value.orEmpty().isNotEmpty()
}
}
diff --git a/app/src/main/java/org/linphone/ui/main/sso/fragment/SingleSignOnFragment.kt b/app/src/main/java/org/linphone/ui/main/sso/fragment/SingleSignOnFragment.kt
index e95c852c0..45a02a6d4 100644
--- a/app/src/main/java/org/linphone/ui/main/sso/fragment/SingleSignOnFragment.kt
+++ b/app/src/main/java/org/linphone/ui/main/sso/fragment/SingleSignOnFragment.kt
@@ -24,8 +24,8 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.navArgs
-import androidx.navigation.navGraphViewModels
import net.openid.appauth.AuthorizationException
import net.openid.appauth.AuthorizationResponse
import org.linphone.R
@@ -44,9 +44,7 @@ class SingleSignOnFragment : GenericMainFragment() {
private lateinit var binding: SingleSignOnFragmentBinding
- private val viewModel: SingleSignOnViewModel by navGraphViewModels(
- R.id.main_nav_graph
- )
+ private lateinit var viewModel: SingleSignOnViewModel
private val args: SingleSignOnFragmentArgs by navArgs()
@@ -62,6 +60,8 @@ class SingleSignOnFragment : GenericMainFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
+
+ viewModel = ViewModelProvider(this)[SingleSignOnViewModel::class.java]
binding.viewModel = viewModel
observeToastEvents(viewModel)
diff --git a/app/src/main/java/org/linphone/ui/main/sso/viewmodel/SingleSignOnViewModel.kt b/app/src/main/java/org/linphone/ui/main/sso/viewmodel/SingleSignOnViewModel.kt
index b79c9d4e5..a51956f29 100644
--- a/app/src/main/java/org/linphone/ui/main/sso/viewmodel/SingleSignOnViewModel.kt
+++ b/app/src/main/java/org/linphone/ui/main/sso/viewmodel/SingleSignOnViewModel.kt
@@ -114,6 +114,7 @@ class SingleSignOnViewModel : GenericViewModel() {
"$singleSignOnUrl/.well-known/openid-configuration"
}
singleSignOn()
+ return@RetrieveConfigurationCallback
} else {
onErrorEvent.postValue(Event("Failed to fetch configuration"))
return@RetrieveConfigurationCallback
@@ -158,34 +159,48 @@ class SingleSignOnViewModel : GenericViewModel() {
authService = AuthorizationService(coreContext.context)
}
+ val authStateJsonFile = File(
+ coreContext.context.filesDir.absolutePath,
+ "auth_state.json"
+ )
Log.i("$TAG Starting refresh token request")
- authService.performTokenRequest(
- authState.createTokenRefreshRequest()
- ) { resp, ex ->
- if (resp != null) {
- Log.i("$TAG Token refresh succeeded!")
+ try {
+ authService.performTokenRequest(
+ authState.createTokenRefreshRequest()
+ ) { resp, ex ->
+ if (resp != null) {
+ Log.i("$TAG Token refresh succeeded!")
- if (::authState.isInitialized) {
- Log.i("$TAG Updating AuthState object after refresh token response")
- authState.update(resp, ex)
- storeAuthStateAsJsonFile()
- }
- updateTokenInfo()
- } else {
- Log.e(
- "$TAG Failed to perform token refresh [$ex], destroying auth_state.json file"
- )
- onErrorEvent.postValue(Event(ex?.errorDescription.orEmpty()))
-
- val file = File(coreContext.context.filesDir.absolutePath, "auth_state.json")
- viewModelScope.launch {
- FileUtils.deleteFile(file.absolutePath)
- Log.w(
- "$TAG Previous auth_state.json file deleted, starting single sign on process from scratch"
+ if (::authState.isInitialized) {
+ Log.i("$TAG Updating AuthState object after refresh token response")
+ authState.update(resp, ex)
+ storeAuthStateAsJsonFile()
+ }
+ updateTokenInfo()
+ } else {
+ Log.e(
+ "$TAG Failed to perform token refresh [$ex], destroying auth_state.json file"
)
- singleSignOn()
+ onErrorEvent.postValue(Event(ex?.errorDescription.orEmpty()))
+
+ viewModelScope.launch {
+ FileUtils.deleteFile(authStateJsonFile.absolutePath)
+ Log.w(
+ "$TAG Previous auth_state.json file deleted, starting single sign on process from scratch"
+ )
+ singleSignOn()
+ }
}
}
+ } catch (ise: IllegalStateException) {
+ Log.e("$TAG Illegal state exception, clearing auth state and trying again: $ise")
+ viewModelScope.launch {
+ FileUtils.deleteFile(authStateJsonFile.absolutePath)
+ authState = getAuthState()
+ performRefreshToken()
+ }
+ } catch (e: Exception) {
+ Log.e("$TAG Failed to perform token request: $e")
}
}
}
diff --git a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml
index 51567f51d..4696816b9 100644
--- a/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml
+++ b/app/src/main/res/layout/assistant_third_party_sip_account_login_fragment.xml
@@ -70,7 +70,7 @@
android:layout_marginTop="38dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
- android:text="@{@string/username + `*`, default=`Username*`}"
+ android:text="@string/assistant_third_party_sip_account_username_or_identity"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="@id/username"/>
diff --git a/app/src/main/res/navigation/assistant_nav_graph.xml b/app/src/main/res/navigation/assistant_nav_graph.xml
index c5c79e34e..d5f86e01c 100644
--- a/app/src/main/res/navigation/assistant_nav_graph.xml
+++ b/app/src/main/res/navigation/assistant_nav_graph.xml
@@ -168,4 +168,27 @@
app:destination="@id/profileModeFragment" />
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 0d16c5500..095ac29cf 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -137,6 +137,7 @@
Certaines fonctionalités telles que les conversations de groupe, les vidéo-conférences, etc… nécessitent un compte &appName;.\n\nCes fonctionalités seront masquées si vous utilisez un compte SIP tiers.\n\nPour les activer dans un projet commercial, merci de nous contacter.
Je préfère créer un compte
J\'ai compris
+ Nom d\'utilisateur ou identité SIP*
Choisissez votre mode
Vous pourrez le changer plus tard
Continuer
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bedba33e0..a423827bc 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -172,6 +172,7 @@
Some features require a &appName; account, such as group messaging, video conferences…\n\nThese features are hidden when you register with a third party SIP account.\n\nTo enable it in a commercial project, please contact us.
I prefer to create an account
I understand
+ Username or SIP identity*
Personalize your profile mode
You may change that mode later
Continue