Switched account creator to FlexiAPI backend instead of XMLRPC

This commit is contained in:
Sylvain Berfini 2023-02-24 14:53:50 +01:00
parent f551398652
commit 6bb6daadba
30 changed files with 653 additions and 179 deletions

View file

@ -23,6 +23,8 @@ Group changes to describe their impact on the project, as follows:
- SIP URI in call can be selected using long press - SIP URI in call can be selected using long press
### Changed ### Changed
- Switched Account Creator backend from XMLRPC to FlexiAPI, it now requires to be able to receive a push notification
- Email account creation form is now only available if TELEPHONY feature is not available, not related to screen size anymore
- Account EXPIRES is now set to 1 month instead of 1 year for sip.linphone.org accounts - Account EXPIRES is now set to 1 month instead of 1 year for sip.linphone.org accounts
- Replaced voice recordings file name by localized placeholder text, like for video conferences invitations - Replaced voice recordings file name by localized placeholder text, like for video conferences invitations
- Decline incoming calls with Busy reason if there is at least another active call - Decline incoming calls with Busy reason if there is at least another active call

View file

@ -42,11 +42,11 @@ activation_code_length=4
prefer_basic_chat_room=1 prefer_basic_chat_room=1
record_aware=1 record_aware=1
[assistant]
xmlrpc_url=https://subscribe.linphone.org:444/wizard.php
[account_creator] [account_creator]
backend=0 backend=1
# 1 means FlexiAPI, 0 is XMLRPC
url=https://subscribe.linphone.org/api/
# replace above URL by https://staging-subscribe.linphone.org/api/ for testing
[lime] [lime]
lime_update_threshold=86400 lime_update_threshold=86400

View file

@ -1057,6 +1057,16 @@ internal fun WelcomeFragment.navigateToPhoneAccountCreation() {
} }
} }
internal fun WelcomeFragment.navigateToNoPushWarning() {
if (findNavController().currentDestination?.id == R.id.welcomeFragment) {
findNavController().navigate(
R.id.action_welcomeFragment_to_noPushWarningFragment,
null,
popupTo()
)
}
}
internal fun WelcomeFragment.navigateToAccountLogin() { internal fun WelcomeFragment.navigateToAccountLogin() {
if (findNavController().currentDestination?.id == R.id.welcomeFragment) { if (findNavController().currentDestination?.id == R.id.welcomeFragment) {
findNavController().navigate( findNavController().navigate(

View file

@ -28,6 +28,7 @@ import org.linphone.activities.GenericFragment
import org.linphone.activities.assistant.viewmodels.AbstractPhoneViewModel import org.linphone.activities.assistant.viewmodels.AbstractPhoneViewModel
import org.linphone.compatibility.Compatibility import org.linphone.compatibility.Compatibility
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.PermissionHelper import org.linphone.utils.PermissionHelper
import org.linphone.utils.PhoneNumberUtils import org.linphone.utils.PhoneNumberUtils
@ -55,7 +56,8 @@ abstract class AbstractPhoneFragment<T : ViewDataBinding> : GenericFragment<T>()
} }
protected fun checkPermissions() { protected fun checkPermissions() {
if (!resources.getBoolean(R.bool.isTablet)) { // Only ask for phone number related permission on devices that have TELEPHONY feature && if push notifications are available
if (requireContext().packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) && LinphoneUtils.isPushNotificationAvailable()) {
if (!PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) { if (!PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) {
Log.i("[Assistant] Asking for READ_PHONE_STATE/READ_PHONE_NUMBERS permission") Log.i("[Assistant] Asking for READ_PHONE_STATE/READ_PHONE_NUMBERS permission")
Compatibility.requestReadPhoneStateOrNumbersPermission( Compatibility.requestReadPhoneStateOrNumbersPermission(

View file

@ -58,10 +58,6 @@ class AccountLoginFragment : AbstractPhoneFragment<AssistantAccountLoginFragment
)[AccountLoginViewModel::class.java] )[AccountLoginViewModel::class.java]
binding.viewModel = viewModel binding.viewModel = viewModel
if (resources.getBoolean(R.bool.isTablet)) {
viewModel.loginWithUsernamePassword.value = true
}
binding.setInfoClickListener { binding.setInfoClickListener {
showPhoneNumberInfoDialog() showPhoneNumberInfoDialog()
} }

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.activities.assistant.fragments
import android.os.Bundle
import android.view.View
import org.linphone.R
import org.linphone.activities.GenericFragment
import org.linphone.databinding.AssistantNoPushWarningFragmentBinding
class NoPushWarningFragment : GenericFragment<AssistantNoPushWarningFragmentBinding>() {
override fun getLayoutId(): Int = R.layout.assistant_no_push_warning_fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
}
}

View file

@ -20,6 +20,7 @@
package org.linphone.activities.assistant.fragments package org.linphone.activities.assistant.fragments
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.SpannableString import android.text.SpannableString
@ -30,6 +31,7 @@ import android.view.View
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import java.util.UnknownFormatConversionException import java.util.UnknownFormatConversionException
import java.util.regex.Pattern import java.util.regex.Pattern
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R import org.linphone.R
import org.linphone.activities.* import org.linphone.activities.*
@ -39,6 +41,7 @@ import org.linphone.activities.navigateToEmailAccountCreation
import org.linphone.activities.navigateToRemoteProvisioning import org.linphone.activities.navigateToRemoteProvisioning
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.databinding.AssistantWelcomeFragmentBinding import org.linphone.databinding.AssistantWelcomeFragmentBinding
import org.linphone.utils.LinphoneUtils
class WelcomeFragment : GenericFragment<AssistantWelcomeFragmentBinding>() { class WelcomeFragment : GenericFragment<AssistantWelcomeFragmentBinding>() {
private lateinit var viewModel: WelcomeViewModel private lateinit var viewModel: WelcomeViewModel
@ -54,11 +57,28 @@ class WelcomeFragment : GenericFragment<AssistantWelcomeFragmentBinding>() {
binding.viewModel = viewModel binding.viewModel = viewModel
binding.setCreateAccountClickListener { binding.setCreateAccountClickListener {
if (resources.getBoolean(R.bool.isTablet)) { if (LinphoneUtils.isPushNotificationAvailable()) {
Log.i("[Assistant] Core says push notifications are available")
val deviceHasTelephonyFeature = coreContext.context.packageManager.hasSystemFeature(
PackageManager.FEATURE_TELEPHONY
)
if (!deviceHasTelephonyFeature) {
Log.i(
"[Assistant] Device doesn't have TELEPHONY feature, showing email based account creation"
)
navigateToEmailAccountCreation() navigateToEmailAccountCreation()
} else { } else {
Log.i(
"[Assistant] Device has TELEPHONY feature, showing phone based account creation"
)
navigateToPhoneAccountCreation() navigateToPhoneAccountCreation()
} }
} else {
Log.w(
"[Assistant] Failed to get push notification info, showing warning instead of phone based account creation"
)
navigateToNoPushWarning()
}
} }
binding.setAccountLoginClickListener { binding.setAccountLoginClickListener {

View file

@ -21,15 +21,15 @@
package org.linphone.activities.assistant.viewmodels package org.linphone.activities.assistant.viewmodels
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import kotlinx.coroutines.*
import org.linphone.activities.assistant.fragments.CountryPickerFragment import org.linphone.activities.assistant.fragments.CountryPickerFragment
import org.linphone.core.AccountCreator import org.linphone.core.AccountCreator
import org.linphone.core.DialPlan import org.linphone.core.DialPlan
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.PhoneNumberUtils import org.linphone.utils.PhoneNumberUtils
abstract class AbstractPhoneViewModel(val accountCreator: AccountCreator) : abstract class AbstractPhoneViewModel(accountCreator: AccountCreator) :
ViewModel(), AbstractPushTokenViewModel(accountCreator),
CountryPickerFragment.CountryPickedListener { CountryPickerFragment.CountryPickedListener {
val prefix = MutableLiveData<String>() val prefix = MutableLiveData<String>()
@ -71,7 +71,7 @@ abstract class AbstractPhoneViewModel(val accountCreator: AccountCreator) :
} }
private fun getCountryNameFromPrefix(prefix: String?) { private fun getCountryNameFromPrefix(prefix: String?) {
if (prefix != null && prefix.isNotEmpty()) { if (!prefix.isNullOrEmpty()) {
val countryCode = if (prefix.first() == '+') prefix.substring(1) else prefix val countryCode = if (prefix.first() == '+') prefix.substring(1) else prefix
val dialPlan = PhoneNumberUtils.getDialPlanFromCountryCallingPrefix(countryCode) val dialPlan = PhoneNumberUtils.getDialPlanFromCountryCallingPrefix(countryCode)
Log.i("[Assistant] Found dial plan $dialPlan from country code: $countryCode") Log.i("[Assistant] Found dial plan $dialPlan from country code: $countryCode")

View file

@ -0,0 +1,143 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.linphone.activities.assistant.viewmodels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONException
import org.json.JSONObject
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.AccountCreator
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.tools.Log
abstract class AbstractPushTokenViewModel(val accountCreator: AccountCreator) : ViewModel() {
private var waitingForPushToken = false
private var waitForPushJob: Job? = null
private val coreListener = object : CoreListenerStub() {
override fun onPushNotificationReceived(core: Core, payload: String?) {
Log.i("[Assistant] Push received: [$payload]")
val data = payload.orEmpty()
if (data.isNotEmpty()) {
try {
// This is because JSONObject.toString() done by the SDK will result in payload looking like {"custom-payload":"{\"token\":\"value\"}"}
val cleanPayload = data.replace("\\\"", "\"").replace("\"{", "{").replace(
"}\"",
"}"
)
Log.i("[Assistant] Cleaned payload is: [$cleanPayload]")
val json = JSONObject(cleanPayload)
val customPayload = json.getJSONObject("custom-payload")
if (customPayload.has("token")) {
waitForPushJob?.cancel()
waitingForPushToken = false
val token = customPayload.getString("token")
if (token.isNotEmpty()) {
Log.i("[Assistant] Extracted token [$token] from push payload")
accountCreator.token = token
onFlexiApiTokenReceived()
} else {
Log.e("[Assistant] Push payload JSON object has an empty 'token'!")
onFlexiApiTokenRequestError()
}
} else {
Log.e("[Assistant] Push payload JSON object has no 'token' key!")
onFlexiApiTokenRequestError()
}
} catch (e: JSONException) {
Log.e("[Assistant] Exception trying to parse push payload as JSON: [$e]")
onFlexiApiTokenRequestError()
}
} else {
Log.e("[Assistant] Push payload is null or empty, can't extract auth token!")
onFlexiApiTokenRequestError()
}
}
}
init {
coreContext.core.addListener(coreListener)
}
override fun onCleared() {
coreContext.core.removeListener(coreListener)
waitForPushJob?.cancel()
}
abstract fun onFlexiApiTokenReceived()
abstract fun onFlexiApiTokenRequestError()
protected fun requestFlexiApiToken() {
if (!coreContext.core.isPushNotificationAvailable) {
Log.e(
"[Assistant] Core says push notification aren't available, can't request a token from FlexiAPI"
)
onFlexiApiTokenRequestError()
return
}
val pushConfig = coreContext.core.pushNotificationConfig
if (pushConfig != null) {
Log.i(
"[Assistant] Found push notification info: provider [${pushConfig.provider}], param [${pushConfig.param}] and prid [${pushConfig.prid}]"
)
accountCreator.pnProvider = pushConfig.provider
accountCreator.pnParam = pushConfig.param
accountCreator.pnPrid = pushConfig.prid
// Request an auth token, will be sent by push
val result = accountCreator.requestAuthToken()
if (result == AccountCreator.Status.RequestOk) {
val waitFor = 5000
waitingForPushToken = true
waitForPushJob?.cancel()
Log.i("[Assistant] Waiting push with auth token for $waitFor ms")
waitForPushJob = viewModelScope.launch {
withContext(Dispatchers.IO) {
delay(waitFor.toLong())
}
withContext(Dispatchers.Main) {
if (waitingForPushToken) {
waitingForPushToken = false
Log.e("[Assistant] Auth token wasn't received by push in $waitFor ms")
onFlexiApiTokenRequestError()
}
}
}
} else {
Log.e("[Assistant] Failed to require a push with an auth token: [$result]")
onFlexiApiTokenRequestError()
}
} else {
Log.e("[Assistant] No push configuration object in Core, shouldn't happen!")
onFlexiApiTokenRequestError()
}
}
}

View file

@ -19,12 +19,13 @@
*/ */
package org.linphone.activities.assistant.viewmodels package org.linphone.activities.assistant.viewmodels
import android.content.pm.PackageManager
import androidx.lifecycle.* import androidx.lifecycle.*
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.core.* import org.linphone.core.*
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.PhoneNumberUtils import org.linphone.utils.PhoneNumberUtils
class AccountLoginViewModelFactory(private val accountCreator: AccountCreator) : class AccountLoginViewModelFactory(private val accountCreator: AccountCreator) :
@ -51,6 +52,8 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
val displayName = MutableLiveData<String>() val displayName = MutableLiveData<String>()
val forceLoginUsingUsernameAndPassword = MutableLiveData<Boolean>()
val leaveAssistantEvent: MutableLiveData<Event<Boolean>> by lazy { val leaveAssistantEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>() MutableLiveData<Event<Boolean>>()
} }
@ -84,17 +87,16 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
} }
} }
private var proxyConfigToCheck: ProxyConfig? = null private var accountToCheck: Account? = null
private val coreListener = object : CoreListenerStub() { private val coreListener = object : CoreListenerStub() {
@Deprecated("Deprecated in Java") override fun onAccountRegistrationStateChanged(
override fun onRegistrationStateChanged(
core: Core, core: Core,
cfg: ProxyConfig, account: Account,
state: RegistrationState, state: RegistrationState?,
message: String message: String
) { ) {
if (cfg == proxyConfigToCheck) { if (account == accountToCheck) {
Log.i("[Assistant] [Account Login] Registration state is $state: $message") Log.i("[Assistant] [Account Login] Registration state is $state: $message")
if (state == RegistrationState.Ok) { if (state == RegistrationState.Ok) {
waitForServerAnswer.value = false waitForServerAnswer.value = false
@ -112,7 +114,12 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
init { init {
accountCreator.addListener(listener) accountCreator.addListener(listener)
loginWithUsernamePassword.value = coreContext.context.resources.getBoolean(R.bool.isTablet) val pushAvailable = LinphoneUtils.isPushNotificationAvailable()
val deviceHasTelephonyFeature = coreContext.context.packageManager.hasSystemFeature(
PackageManager.FEATURE_TELEPHONY
)
loginWithUsernamePassword.value = !deviceHasTelephonyFeature || !pushAvailable
forceLoginUsingUsernameAndPassword.value = !pushAvailable
loginEnabled.value = false loginEnabled.value = false
loginEnabled.addSource(prefix) { loginEnabled.addSource(prefix) {
@ -140,23 +147,31 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
super.onCleared() super.onCleared()
} }
override fun onFlexiApiTokenReceived() {
Log.i("[Assistant] [Account Login] Using FlexiAPI auth token [${accountCreator.token}]")
loginWithPhoneNumber()
}
override fun onFlexiApiTokenRequestError() {
Log.e("[Assistant] [Account Login] Failed to get an auth token from FlexiAPI")
waitForServerAnswer.value = false
onErrorEvent.value = Event("Error: Failed to get an auth token from account manager server")
}
fun removeInvalidProxyConfig() { fun removeInvalidProxyConfig() {
val cfg = proxyConfigToCheck val account = accountToCheck
cfg ?: return account ?: return
val authInfo = cfg.findAuthInfo() val authInfo = account.findAuthInfo()
if (authInfo != null) coreContext.core.removeAuthInfo(authInfo) if (authInfo != null) coreContext.core.removeAuthInfo(authInfo)
coreContext.core.removeProxyConfig(cfg) coreContext.core.removeAccount(account)
proxyConfigToCheck = null accountToCheck = null
} }
fun continueEvenIfInvalidCredentials() { fun continueEvenIfInvalidCredentials() {
leaveAssistantEvent.value = Event(true) leaveAssistantEvent.value = Event(true)
} }
fun login() { private fun loginWithUsername() {
accountCreator.displayName = displayName.value
if (loginWithUsernamePassword.value == true) {
val result = accountCreator.setUsername(username.value) val result = accountCreator.setUsername(username.value)
if (result != AccountCreator.UsernameStatus.Ok) { if (result != AccountCreator.UsernameStatus.Ok) {
Log.e( Log.e(
@ -176,12 +191,14 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
waitForServerAnswer.value = true waitForServerAnswer.value = true
coreContext.core.addListener(coreListener) coreContext.core.addListener(coreListener)
if (!createProxyConfig()) { if (!createAccountAndAuthInfo()) {
waitForServerAnswer.value = false waitForServerAnswer.value = false
coreContext.core.removeListener(coreListener) coreContext.core.removeListener(coreListener)
onErrorEvent.value = Event("Error: Failed to create account object") onErrorEvent.value = Event("Error: Failed to create account object")
} }
} else { }
private fun loginWithPhoneNumber() {
val result = AccountCreator.PhoneNumberStatus.fromInt( val result = AccountCreator.PhoneNumberStatus.fromInt(
accountCreator.setPhoneNumber(phoneNumber.value, prefix.value) accountCreator.setPhoneNumber(phoneNumber.value, prefix.value)
) )
@ -212,6 +229,25 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
onErrorEvent.value = Event("Error: ${status.name}") onErrorEvent.value = Event("Error: ${status.name}")
} }
} }
fun login() {
accountCreator.displayName = displayName.value
if (loginWithUsernamePassword.value == true) {
loginWithUsername()
} else {
val token = accountCreator.token.orEmpty()
if (token.isNotEmpty()) {
Log.i(
"[Assistant] [Account Login] We already have an auth token from FlexiAPI [$token], continue"
)
onFlexiApiTokenReceived()
} else {
Log.i("[Assistant] [Account Login] Requesting an auth token from FlexiAPI")
waitForServerAnswer.value = true
requestFlexiApiToken()
}
}
} }
private fun isLoginButtonEnabled(): Boolean { private fun isLoginButtonEnabled(): Boolean {
@ -222,33 +258,33 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
} }
} }
private fun createProxyConfig(): Boolean { private fun createAccountAndAuthInfo(): Boolean {
val proxyConfig: ProxyConfig? = accountCreator.createProxyConfig() val account = accountCreator.createAccountInCore()
proxyConfigToCheck = proxyConfig accountToCheck = account
if (proxyConfig == null) { if (account == null) {
Log.e("[Assistant] [Account Login] Account creator couldn't create proxy config") Log.e("[Assistant] [Account Login] Account creator couldn't create account")
onErrorEvent.value = Event("Error: Failed to create account object") onErrorEvent.value = Event("Error: Failed to create account object")
return false return false
} }
proxyConfig.isPushNotificationAllowed = true val params = account.params.clone()
params.pushNotificationAllowed = true
if (proxyConfig.dialPrefix.isNullOrEmpty()) { if (params.internationalPrefix.isNullOrEmpty()) {
val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(coreContext.context) val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(coreContext.context)
if (dialPlan != null) { if (dialPlan != null) {
Log.i( Log.i(
"[Assistant] [Account Login] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}" "[Assistant] [Account Login] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}"
) )
proxyConfig.edit() params.internationalPrefix = dialPlan.countryCallingCode
proxyConfig.dialPrefix = dialPlan.countryCallingCode
proxyConfig.done()
} else { } else {
Log.w("[Assistant] [Account Login] Failed to find dial plan") Log.w("[Assistant] [Account Login] Failed to find dial plan")
} }
} }
Log.i("[Assistant] [Account Login] Proxy config created") account.params = params
Log.i("[Assistant] [Account Login] Account created")
return true return true
} }
} }

View file

@ -39,7 +39,9 @@ class EmailAccountCreationViewModelFactory(private val accountCreator: AccountCr
} }
} }
class EmailAccountCreationViewModel(val accountCreator: AccountCreator) : ViewModel() { class EmailAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPushTokenViewModel(
accountCreator
) {
val username = MutableLiveData<String>() val username = MutableLiveData<String>()
val usernameError = MutableLiveData<String>() val usernameError = MutableLiveData<String>()
@ -70,7 +72,7 @@ class EmailAccountCreationViewModel(val accountCreator: AccountCreator) : ViewMo
status: AccountCreator.Status, status: AccountCreator.Status,
response: String? response: String?
) { ) {
Log.i("[Assistant] [Account Creation] onIsAccountExist status is $status") Log.i("[Account Creation] onIsAccountExist status is $status")
when (status) { when (status) {
AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> { AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> {
waitForServerAnswer.value = false waitForServerAnswer.value = false
@ -146,18 +148,40 @@ class EmailAccountCreationViewModel(val accountCreator: AccountCreator) : ViewMo
super.onCleared() super.onCleared()
} }
override fun onFlexiApiTokenReceived() {
Log.i("[Account Creation] Using FlexiAPI auth token [${accountCreator.token}]")
waitForServerAnswer.value = true
val status = accountCreator.isAccountExist
Log.i("[Account Creation] Account exists returned $status")
if (status != AccountCreator.Status.RequestOk) {
waitForServerAnswer.value = false
onErrorEvent.value = Event("Error: ${status.name}")
}
}
override fun onFlexiApiTokenRequestError() {
Log.e("[Account Creation] Failed to get an auth token from FlexiAPI")
waitForServerAnswer.value = false
onErrorEvent.value = Event("Error: Failed to get an auth token from account manager server")
}
fun create() { fun create() {
accountCreator.username = username.value accountCreator.username = username.value
accountCreator.password = password.value accountCreator.password = password.value
accountCreator.email = email.value accountCreator.email = email.value
accountCreator.displayName = displayName.value accountCreator.displayName = displayName.value
val token = accountCreator.token.orEmpty()
if (token.isNotEmpty()) {
Log.i(
"[Account Creation] We already have an auth token from FlexiAPI [$token], continue"
)
onFlexiApiTokenReceived()
} else {
Log.i("[Account Creation] Requesting an auth token from FlexiAPI")
waitForServerAnswer.value = true waitForServerAnswer.value = true
val status = accountCreator.isAccountExist requestFlexiApiToken()
Log.i("[Assistant] [Account Creation] Account exists returned $status")
if (status != AccountCreator.Status.RequestOk) {
waitForServerAnswer.value = false
onErrorEvent.value = Event("Error: ${status.name}")
} }
} }

View file

@ -22,11 +22,12 @@ package org.linphone.activities.assistant.viewmodels
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import org.linphone.LinphoneApplication import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.core.AccountCreator import org.linphone.core.AccountCreator
import org.linphone.core.AccountCreatorListenerStub import org.linphone.core.AccountCreatorListenerStub
import org.linphone.core.ProxyConfig
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.AppUtils
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.PhoneNumberUtils import org.linphone.utils.PhoneNumberUtils
@ -61,14 +62,16 @@ class EmailAccountValidationViewModel(val accountCreator: AccountCreator) : View
when (status) { when (status) {
AccountCreator.Status.AccountActivated -> { AccountCreator.Status.AccountActivated -> {
if (createProxyConfig()) { if (createAccountAndAuthInfo()) {
leaveAssistantEvent.value = Event(true) leaveAssistantEvent.value = Event(true)
} else { } else {
onErrorEvent.value = Event("Error: ${status.name}") onErrorEvent.value = Event("Error: ${status.name}")
} }
} }
AccountCreator.Status.AccountNotActivated -> { AccountCreator.Status.AccountNotActivated -> {
onErrorEvent.value = Event("Error: ${status.name}") onErrorEvent.value = Event(
AppUtils.getString(R.string.assistant_create_email_account_not_validated)
)
} }
else -> { else -> {
onErrorEvent.value = Event("Error: ${status.name}") onErrorEvent.value = Event("Error: ${status.name}")
@ -97,34 +100,33 @@ class EmailAccountValidationViewModel(val accountCreator: AccountCreator) : View
} }
} }
private fun createProxyConfig(): Boolean { private fun createAccountAndAuthInfo(): Boolean {
val proxyConfig: ProxyConfig? = accountCreator.createProxyConfig() val account = accountCreator.createAccountInCore()
if (proxyConfig == null) { if (account == null) {
Log.e("[Assistant] [Account Validation] Account creator couldn't create proxy config") Log.e("[Assistant] [Account Validation] Account creator couldn't create account")
onErrorEvent.value = Event("Error: Failed to create account object") onErrorEvent.value = Event("Error: Failed to create account object")
return false return false
} }
proxyConfig.isPushNotificationAllowed = true val params = account.params.clone()
params.pushNotificationAllowed = true
if (proxyConfig.dialPrefix.isNullOrEmpty()) { if (params.internationalPrefix.isNullOrEmpty()) {
val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry( val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(coreContext.context)
LinphoneApplication.coreContext.context
)
if (dialPlan != null) { if (dialPlan != null) {
Log.i( Log.i(
"[Assistant] [Account Validation] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}" "[Assistant] [Account Validation] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}"
) )
proxyConfig.edit() params.internationalPrefix = dialPlan.countryCallingCode
proxyConfig.dialPrefix = dialPlan.countryCallingCode
proxyConfig.done()
} else { } else {
Log.w("[Assistant] [Account Validation] Failed to find dial plan") Log.w("[Assistant] [Account Validation] Failed to find dial plan")
} }
} }
Log.i("[Assistant] [Account Validation] Proxy config created") account.params = params
Log.i("[Assistant] [Account Validation] Account created")
return true return true
} }
} }

View file

@ -62,17 +62,16 @@ class GenericLoginViewModel(private val accountCreator: AccountCreator) : ViewMo
MutableLiveData<Event<String>>() MutableLiveData<Event<String>>()
} }
private var proxyConfigToCheck: ProxyConfig? = null private var accountToCheck: Account? = null
private val coreListener = object : CoreListenerStub() { private val coreListener = object : CoreListenerStub() {
@Deprecated("Deprecated in Java") override fun onAccountRegistrationStateChanged(
override fun onRegistrationStateChanged(
core: Core, core: Core,
cfg: ProxyConfig, account: Account,
state: RegistrationState, state: RegistrationState?,
message: String message: String
) { ) {
if (cfg == proxyConfigToCheck) { if (account == accountToCheck) {
Log.i("[Assistant] [Generic Login] Registration state is $state: $message") Log.i("[Assistant] [Generic Login] Registration state is $state: $message")
if (state == RegistrationState.Ok) { if (state == RegistrationState.Ok) {
waitForServerAnswer.value = false waitForServerAnswer.value = false
@ -107,19 +106,19 @@ class GenericLoginViewModel(private val accountCreator: AccountCreator) : ViewMo
} }
fun removeInvalidProxyConfig() { fun removeInvalidProxyConfig() {
val cfg = proxyConfigToCheck val account = accountToCheck
cfg ?: return account ?: return
val authInfo = cfg.findAuthInfo() val authInfo = account.findAuthInfo()
if (authInfo != null) coreContext.core.removeAuthInfo(authInfo) if (authInfo != null) coreContext.core.removeAuthInfo(authInfo)
coreContext.core.removeProxyConfig(cfg) coreContext.core.removeAccount(account)
proxyConfigToCheck = null accountToCheck = null
} }
fun continueEvenIfInvalidCredentials() { fun continueEvenIfInvalidCredentials() {
leaveAssistantEvent.value = Event(true) leaveAssistantEvent.value = Event(true)
} }
fun createProxyConfig() { fun createAccountAndAuthInfo() {
waitForServerAnswer.value = true waitForServerAnswer.value = true
coreContext.core.addListener(coreListener) coreContext.core.addListener(coreListener)
@ -129,18 +128,18 @@ class GenericLoginViewModel(private val accountCreator: AccountCreator) : ViewMo
accountCreator.displayName = displayName.value accountCreator.displayName = displayName.value
accountCreator.transport = transport.value accountCreator.transport = transport.value
val proxyConfig: ProxyConfig? = accountCreator.createProxyConfig() val account = accountCreator.createAccountInCore()
proxyConfigToCheck = proxyConfig accountToCheck = account
if (proxyConfig == null) { if (account == null) {
Log.e("[Assistant] [Generic Login] Account creator couldn't create proxy config") Log.e("[Assistant] [Generic Login] Account creator couldn't create account")
coreContext.core.removeListener(coreListener) coreContext.core.removeListener(coreListener)
onErrorEvent.value = Event("Error: Failed to create account object") onErrorEvent.value = Event("Error: Failed to create account object")
waitForServerAnswer.value = false waitForServerAnswer.value = false
return return
} }
Log.i("[Assistant] [Generic Login] Proxy config created") Log.i("[Assistant] [Generic Login] Account created")
} }
private fun isLoginButtonEnabled(): Boolean { private fun isLoginButtonEnabled(): Boolean {

View file

@ -71,6 +71,36 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
Log.i("[Phone Account Creation] onIsAccountExist status is $status") Log.i("[Phone Account Creation] onIsAccountExist status is $status")
when (status) { when (status) {
AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> { AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> {
waitForServerAnswer.value = false
usernameError.value = AppUtils.getString(
R.string.assistant_error_username_already_exists
)
}
AccountCreator.Status.AccountNotExist -> {
waitForServerAnswer.value = false
checkPhoneNumber()
}
else -> {
waitForServerAnswer.value = false
onErrorEvent.value = Event("Error: ${status.name}")
}
}
}
override fun onIsAliasUsed(
creator: AccountCreator,
status: AccountCreator.Status,
response: String?
) {
Log.i("[Phone Account Creation] onIsAliasUsed status is $status")
when (status) {
AccountCreator.Status.AliasExist -> {
waitForServerAnswer.value = false
phoneNumberError.value = AppUtils.getString(
R.string.assistant_error_phone_number_already_exists
)
}
AccountCreator.Status.AliasIsAccount -> {
waitForServerAnswer.value = false waitForServerAnswer.value = false
if (useUsername.value == true) { if (useUsername.value == true) {
usernameError.value = AppUtils.getString( usernameError.value = AppUtils.getString(
@ -82,7 +112,7 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
) )
} }
} }
AccountCreator.Status.AccountNotExist -> { AccountCreator.Status.AliasNotExist -> {
val createAccountStatus = creator.createAccount() val createAccountStatus = creator.createAccount()
Log.i("[Phone Account Creation] createAccount returned $createAccountStatus") Log.i("[Phone Account Creation] createAccount returned $createAccountStatus")
if (createAccountStatus != AccountCreator.Status.RequestOk) { if (createAccountStatus != AccountCreator.Status.RequestOk) {
@ -150,16 +180,31 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
super.onCleared() super.onCleared()
} }
fun create() { override fun onFlexiApiTokenReceived() {
Log.i("[Phone Account Creation] Using FlexiAPI auth token [${accountCreator.token}]")
accountCreator.displayName = displayName.value accountCreator.displayName = displayName.value
accountCreator.setPhoneNumber(phoneNumber.value, prefix.value) accountCreator.setPhoneNumber(phoneNumber.value, prefix.value)
if (useUsername.value == true) { if (useUsername.value == true) {
accountCreator.username = username.value accountCreator.username = username.value
} else { } else {
accountCreator.username = accountCreator.phoneNumber accountCreator.username = accountCreator.phoneNumber
} }
waitForServerAnswer.value = true if (useUsername.value == true) {
checkUsername()
} else {
checkPhoneNumber()
}
}
override fun onFlexiApiTokenRequestError() {
Log.e("[Phone Account Creation] Failed to get an auth token from FlexiAPI")
waitForServerAnswer.value = false
onErrorEvent.value = Event("Error: Failed to get an auth token from account manager server")
}
private fun checkUsername() {
val status = accountCreator.isAccountExist val status = accountCreator.isAccountExist
Log.i("[Phone Account Creation] isAccountExist returned $status") Log.i("[Phone Account Creation] isAccountExist returned $status")
if (status != AccountCreator.Status.RequestOk) { if (status != AccountCreator.Status.RequestOk) {
@ -168,6 +213,29 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
} }
} }
private fun checkPhoneNumber() {
val status = accountCreator.isAliasUsed
Log.i("[Phone Account Creation] isAliasUsed returned $status")
if (status != AccountCreator.Status.RequestOk) {
waitForServerAnswer.value = false
onErrorEvent.value = Event("Error: ${status.name}")
}
}
fun create() {
val token = accountCreator.token.orEmpty()
if (token.isNotEmpty()) {
Log.i(
"[Phone Account Creation] We already have an auth token from FlexiAPI [$token], continue"
)
onFlexiApiTokenReceived()
} else {
Log.i("[Phone Account Creation] Requesting an auth token from FlexiAPI")
waitForServerAnswer.value = true
requestFlexiApiToken()
}
}
private fun isCreateButtonEnabled(): Boolean { private fun isCreateButtonEnabled(): Boolean {
val usernameRegexp = corePreferences.config.getString( val usernameRegexp = corePreferences.config.getString(
"assistant", "assistant",

View file

@ -120,12 +120,11 @@ class PhoneAccountLinkingViewModel(accountCreator: AccountCreator) : AbstractPho
super.onCleared() super.onCleared()
} }
fun link() { override fun onFlexiApiTokenReceived() {
accountCreator.setPhoneNumber(phoneNumber.value, prefix.value) accountCreator.setPhoneNumber(phoneNumber.value, prefix.value)
accountCreator.username = username.value accountCreator.username = username.value
Log.i("[Assistant] [Phone Account Linking] Phone number is ${accountCreator.phoneNumber}") Log.i("[Phone Account Linking] Phone number is ${accountCreator.phoneNumber}")
waitForServerAnswer.value = true
val status: AccountCreator.Status = accountCreator.isAliasUsed val status: AccountCreator.Status = accountCreator.isAliasUsed
Log.i("[Phone Account Linking] isAliasUsed returned $status") Log.i("[Phone Account Linking] isAliasUsed returned $status")
if (status != AccountCreator.Status.RequestOk) { if (status != AccountCreator.Status.RequestOk) {
@ -134,6 +133,17 @@ class PhoneAccountLinkingViewModel(accountCreator: AccountCreator) : AbstractPho
} }
} }
override fun onFlexiApiTokenRequestError() {
Log.e("[Phone Account Linking] Failed to get an auth token from FlexiAPI")
waitForServerAnswer.value = false
}
fun link() {
Log.i("[Phone Account Linking] Requesting an auth token from FlexiAPI")
waitForServerAnswer.value = true
requestFlexiApiToken()
}
fun skip() { fun skip() {
leaveAssistantEvent.value = Event(true) leaveAssistantEvent.value = Event(true)
} }

View file

@ -24,7 +24,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import org.linphone.core.AccountCreator import org.linphone.core.AccountCreator
import org.linphone.core.AccountCreatorListenerStub import org.linphone.core.AccountCreatorListenerStub
import org.linphone.core.ProxyConfig
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.Event import org.linphone.utils.Event
@ -66,7 +65,7 @@ class PhoneAccountValidationViewModel(val accountCreator: AccountCreator) : View
waitForServerAnswer.value = false waitForServerAnswer.value = false
if (status == AccountCreator.Status.RequestOk) { if (status == AccountCreator.Status.RequestOk) {
if (createProxyConfig()) { if (createAccountAndAuthInfo()) {
leaveAssistantEvent.value = Event(true) leaveAssistantEvent.value = Event(true)
} else { } else {
onErrorEvent.value = Event("Error: Failed to create account object") onErrorEvent.value = Event("Error: Failed to create account object")
@ -103,7 +102,7 @@ class PhoneAccountValidationViewModel(val accountCreator: AccountCreator) : View
waitForServerAnswer.value = false waitForServerAnswer.value = false
if (status == AccountCreator.Status.AccountActivated) { if (status == AccountCreator.Status.AccountActivated) {
if (createProxyConfig()) { if (createAccountAndAuthInfo()) {
leaveAssistantEvent.value = Event(true) leaveAssistantEvent.value = Event(true)
} else { } else {
onErrorEvent.value = Event("Error: Failed to create account object") onErrorEvent.value = Event("Error: Failed to create account object")
@ -143,18 +142,21 @@ class PhoneAccountValidationViewModel(val accountCreator: AccountCreator) : View
} }
} }
private fun createProxyConfig(): Boolean { private fun createAccountAndAuthInfo(): Boolean {
val proxyConfig: ProxyConfig? = accountCreator.createProxyConfig() val account = accountCreator.createAccountInCore()
if (proxyConfig == null) { if (account == null) {
Log.e( Log.e(
"[Assistant] [Phone Account Validation] Account creator couldn't create proxy config" "[Assistant] [Phone Account Validation] Account creator couldn't create account"
) )
return false return false
} }
proxyConfig.isPushNotificationAllowed = true val params = account.params.clone()
Log.i("[Assistant] [Phone Account Validation] Proxy config created") params.pushNotificationAllowed = true
account.params = params
Log.i("[Assistant] [Phone Account Validation] Account created")
return true return true
} }
} }

View file

@ -27,6 +27,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R import org.linphone.R
import org.linphone.activities.GenericActivity
import org.linphone.activities.main.MainActivity import org.linphone.activities.main.MainActivity
import org.linphone.activities.main.chat.viewmodels.ChatRoomCreationViewModel import org.linphone.activities.main.chat.viewmodels.ChatRoomCreationViewModel
import org.linphone.activities.main.fragments.SecureFragment import org.linphone.activities.main.fragments.SecureFragment
@ -74,7 +75,7 @@ class ChatRoomCreationFragment : SecureFragment<ChatRoomCreationFragmentBinding>
AppUtils.getDividerDecoration(requireContext(), layoutManager) AppUtils.getDividerDecoration(requireContext(), layoutManager)
) )
binding.back.visibility = if (resources.getBoolean(R.bool.isTablet)) View.INVISIBLE else View.VISIBLE binding.back.visibility = if ((requireActivity() as GenericActivity).isTablet()) View.INVISIBLE else View.VISIBLE
binding.setAllContactsToggleClickListener { binding.setAllContactsToggleClickListener {
viewModel.sipContactsSelected.value = false viewModel.sipContactsSelected.value = false

View file

@ -38,6 +38,7 @@ import org.linphone.BuildConfig
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R import org.linphone.R
import org.linphone.activities.GenericActivity
import org.linphone.activities.main.MainActivity import org.linphone.activities.main.MainActivity
import org.linphone.activities.main.dialer.viewmodels.DialerViewModel import org.linphone.activities.main.dialer.viewmodels.DialerViewModel
import org.linphone.activities.main.fragments.SecureFragment import org.linphone.activities.main.fragments.SecureFragment
@ -213,7 +214,7 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (resources.getBoolean(R.bool.isTablet)) { if ((requireActivity() as GenericActivity).isTablet()) {
coreContext.core.nativePreviewWindowId = binding.videoPreviewWindow coreContext.core.nativePreviewWindowId = binding.videoPreviewWindow
} }

View file

@ -31,6 +31,7 @@ import org.linphone.activities.main.settings.SettingListenerStub
import org.linphone.core.* import org.linphone.core.*
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
class AccountSettingsViewModelFactory(private val identity: String) : class AccountSettingsViewModelFactory(private val identity: String) :
ViewModelProvider.NewInstanceFactory() { ViewModelProvider.NewInstanceFactory() {
@ -408,7 +409,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
override fun onTextValueChanged(newValue: String) { override fun onTextValueChanged(newValue: String) {
val params = account.params.clone() val params = account.params.clone()
Log.i( Log.i(
"[Account Settings] Forcing conference factory on proxy config ${params.identityAddress?.asString()} to value: $newValue" "[Account Settings] Forcing conference factory on account ${params.identityAddress?.asString()} to value: $newValue"
) )
params.conferenceFactoryUri = newValue params.conferenceFactoryUri = newValue
account.params = params account.params = params
@ -421,7 +422,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
val params = account.params.clone() val params = account.params.clone()
val uri = coreContext.core.interpretUrl(newValue, false) val uri = coreContext.core.interpretUrl(newValue, false)
Log.i( Log.i(
"[Account Settings] Forcing audio/video conference factory on proxy config ${params.identityAddress?.asString()} to value: $newValue" "[Account Settings] Forcing audio/video conference factory on account ${params.identityAddress?.asString()} to value: $newValue"
) )
params.audioVideoConferenceFactoryAddress = uri params.audioVideoConferenceFactoryAddress = uri
account.params = params account.params = params
@ -500,7 +501,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
domain.value = params.identityAddress?.domain domain.value = params.identityAddress?.domain
disable.value = !params.isRegisterEnabled disable.value = !params.isRegisterEnabled
pushNotification.value = params.pushNotificationAllowed pushNotification.value = params.pushNotificationAllowed
pushNotificationsAvailable.value = core.isPushNotificationAvailable pushNotificationsAvailable.value = LinphoneUtils.isPushNotificationAvailable()
proxy.value = params.serverAddress?.asStringUriOnly() proxy.value = params.serverAddress?.asStringUriOnly()
outboundProxy.value = params.isOutboundProxyEnabled outboundProxy.value = params.isOutboundProxyEnabled
stunServer.value = params.natPolicy?.stunServer stunServer.value = params.natPolicy?.stunServer

View file

@ -501,7 +501,7 @@ class CoreContext(
val newExpire = 2629800 // 1 month val newExpire = 2629800 // 1 month
if (account.params.expires != newExpire) { if (account.params.expires != newExpire) {
Log.i( Log.i(
"[Context] Updating expire on proxy config ${params.identityAddress?.asString()} from ${account.params.expires} to newExpire" "[Context] Updating expire on account ${params.identityAddress?.asString()} from ${account.params.expires} to newExpire"
) )
params.expires = newExpire params.expires = newExpire
paramsChanged = true paramsChanged = true
@ -510,7 +510,7 @@ class CoreContext(
// Enable presence publish/subscribe for new feature // Enable presence publish/subscribe for new feature
if (!account.params.isPublishEnabled) { if (!account.params.isPublishEnabled) {
Log.i( Log.i(
"[Context] Enabling presence publish on proxy config ${params.identityAddress?.asString()}" "[Context] Enabling presence publish on account ${params.identityAddress?.asString()}"
) )
params.isPublishEnabled = true params.isPublishEnabled = true
params.publishExpires = 120 params.publishExpires = 120
@ -518,23 +518,23 @@ class CoreContext(
} }
} }
// Ensure conference factory URI is set on sip.linphone.org proxy configs // Ensure conference factory URI is set on sip.linphone.org accounts
if (account.params.conferenceFactoryUri == null) { if (account.params.conferenceFactoryUri == null) {
val uri = corePreferences.conferenceServerUri val uri = corePreferences.conferenceServerUri
Log.i( Log.i(
"[Context] Setting conference factory on proxy config ${params.identityAddress?.asString()} to default value: $uri" "[Context] Setting conference factory on account ${params.identityAddress?.asString()} to default value: $uri"
) )
params.conferenceFactoryUri = uri params.conferenceFactoryUri = uri
paramsChanged = true paramsChanged = true
} }
// Ensure audio/video conference factory URI is set on sip.linphone.org proxy configs // Ensure audio/video conference factory URI is set on sip.linphone.org accounts
if (account.params.audioVideoConferenceFactoryAddress == null) { if (account.params.audioVideoConferenceFactoryAddress == null) {
val uri = corePreferences.audioVideoConferenceServerUri val uri = corePreferences.audioVideoConferenceServerUri
val address = core.interpretUrl(uri, false) val address = core.interpretUrl(uri, false)
if (address != null) { if (address != null) {
Log.i( Log.i(
"[Context] Setting audio/video conference factory on proxy config ${params.identityAddress?.asString()} to default value: $uri" "[Context] Setting audio/video conference factory on account ${params.identityAddress?.asString()} to default value: $uri"
) )
params.audioVideoConferenceFactoryAddress = address params.audioVideoConferenceFactoryAddress = address
paramsChanged = true paramsChanged = true
@ -546,7 +546,7 @@ class CoreContext(
// Enable Bundle mode by default // Enable Bundle mode by default
if (!account.params.isRtpBundleEnabled) { if (!account.params.isRtpBundleEnabled) {
Log.i( Log.i(
"[Context] Enabling RTP bundle mode on proxy config ${params.identityAddress?.asString()}" "[Context] Enabling RTP bundle mode on account ${params.identityAddress?.asString()}"
) )
params.isRtpBundleEnabled = true params.isRtpBundleEnabled = true
paramsChanged = true paramsChanged = true

View file

@ -274,6 +274,20 @@ class LinphoneUtils {
return true // Legacy behavior return true // Legacy behavior
} }
fun isPushNotificationAvailable(): Boolean {
val core = coreContext.core
if (!core.isPushNotificationAvailable) {
return false
}
val pushConfig = core.pushNotificationConfig ?: return false
if (pushConfig.provider.isNullOrEmpty()) return false
if (pushConfig.param.isNullOrEmpty()) return false
if (pushConfig.prid.isNullOrEmpty()) return false
return true
}
fun hashPassword( fun hashPassword(
userId: String, userId: String,
password: String, password: String,

View file

@ -84,9 +84,9 @@
android:onClick="@{infoClickListener}" android:onClick="@{infoClickListener}"
android:contentDescription="@string/content_description_phone_number_use" android:contentDescription="@string/content_description_phone_number_use"
android:src="@drawable/info" android:src="@drawable/info"
android:layout_marginTop="10dp" android:layout_alignParentEnd="true"
android:layout_below="@id/phone_number_desc" android:layout_alignTop="@id/select_country_label"
android:layout_alignParentRight="true" android:layout_above="@id/select_country"
android:layout_width="20dp" android:layout_width="20dp"
android:layout_height="20dp"/> android:layout_height="20dp"/>
@ -222,7 +222,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center" android:gravity="center"
android:paddingTop="10dp"> android:paddingTop="10dp"
android:visibility="@{viewModel.forceLoginUsingUsernameAndPassword ? View.GONE : View.VISIBLE}">
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.switchmaterial.SwitchMaterial
android:checked="@={viewModel.loginWithUsernamePassword}" android:checked="@={viewModel.loginWithUsernamePassword}"

View file

@ -164,7 +164,7 @@
</RadioGroup> </RadioGroup>
<TextView <TextView
android:onClick="@{() -> viewModel.createProxyConfig()}" android:onClick="@{() -> viewModel.createAccountAndAuthInfo()}"
android:enabled="@{viewModel.loginEnabled, default=false}" android:enabled="@{viewModel.loginEnabled, default=false}"
android:text="@string/assistant_login" android:text="@string/assistant_login"
android:background="@drawable/assistant_button" android:background="@drawable/assistant_button"

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View"/>
<variable
name="understoodClickListener"
type="android.view.View.OnClickListener"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/top_bar_fragment"
android:name="org.linphone.activities.assistant.fragments.TopBarFragment"
android:layout_width="match_parent"
android:layout_height="@dimen/main_activity_top_bar_size"
android:layout_alignParentTop="true"
tools:layout="@layout/assistant_top_bar_fragment" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/top_bar_fragment"
android:orientation="vertical"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:gravity="center_horizontal">
<TextView
android:id="@+id/warning_text"
style="@style/standard_small_text_font"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginTop="20dp"
android:text="@string/assistant_no_push_warning"/>
<TextView
android:textColor="?attr/accentColor"
android:textSize="16sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:autoLink="web"
android:gravity="center"
android:paddingTop="10dp"
android:textColorLink="@color/primary_color"
android:text="@string/assistant_forgotten_password_link" />
</LinearLayout>
</RelativeLayout>
</layout>

View file

@ -44,7 +44,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:text="@string/assistant_create_account" android:text="@string/assistant_create_account"
android:paddingTop="10dp" android:layout_marginTop="10dp"
android:textAllCaps="true" /> android:textAllCaps="true" />
<TextView <TextView
@ -53,7 +53,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:layout_gravity="top" android:layout_gravity="top"
android:paddingTop="20dp" android:layout_marginTop="10dp"
android:text="@string/assistant_create_account_part_1" /> android:text="@string/assistant_create_account_part_1" />
<RelativeLayout <RelativeLayout
@ -65,8 +65,8 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView <TextView
android:id="@+id/phone_number_label" android:id="@+id/select_country_label"
android:text="@string/phone_number" android:text="@string/select_your_country"
style="@style/assistant_input_field_header_font" style="@style/assistant_input_field_header_font"
android:textAllCaps="true" android:textAllCaps="true"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -76,7 +76,9 @@
android:onClick="@{infoClickListener}" android:onClick="@{infoClickListener}"
android:contentDescription="@string/content_description_phone_number_use" android:contentDescription="@string/content_description_phone_number_use"
android:src="@drawable/info" android:src="@drawable/info"
android:layout_alignParentRight="true" android:layout_alignParentEnd="true"
android:layout_alignTop="@id/select_country_label"
android:layout_above="@id/select_country"
android:layout_width="20dp" android:layout_width="20dp"
android:layout_height="20dp"/> android:layout_height="20dp"/>
@ -92,7 +94,7 @@
android:padding="10dp" android:padding="10dp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:gravity="center" android:gravity="center"
android:layout_below="@id/phone_number_label"/> android:layout_below="@id/select_country_label"/>
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
@ -217,6 +219,35 @@
android:layout_margin="20dp" android:layout_margin="20dp"
android:padding="10dp"/> android:padding="10dp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="10dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:background="?attr/dividerColor" />
<TextView
style="@style/standard_small_text_font"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="top"
android:layout_marginTop="20dp"
android:text="@string/assistant_alternative_way_create_account" />
<TextView
android:textColor="?attr/accentColor"
android:textSize="16sp"
android:textColorLink="@color/primary_color"
android:autoLink="web"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="top"
android:paddingTop="5dp"
android:text="@string/assistant_forgotten_password_link" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View file

@ -80,8 +80,8 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView <TextView
android:id="@+id/phone_number_label" android:id="@+id/select_country_label"
android:text="@string/phone_number" android:text="@string/select_your_country"
style="@style/assistant_input_field_header_font" style="@style/assistant_input_field_header_font"
android:textAllCaps="true" android:textAllCaps="true"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -91,7 +91,9 @@
android:onClick="@{infoClickListener}" android:onClick="@{infoClickListener}"
android:contentDescription="@string/content_description_phone_number_use" android:contentDescription="@string/content_description_phone_number_use"
android:src="@drawable/info" android:src="@drawable/info"
android:layout_alignParentRight="true" android:layout_alignParentEnd="true"
android:layout_alignTop="@id/select_country_label"
android:layout_above="@id/select_country"
android:layout_width="20dp" android:layout_width="20dp"
android:layout_height="20dp"/> android:layout_height="20dp"/>
@ -107,7 +109,7 @@
android:padding="10dp" android:padding="10dp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:gravity="center" android:gravity="center"
android:layout_below="@id/phone_number_label"/> android:layout_below="@id/select_country_label"/>
<LinearLayout <LinearLayout
android:orientation="horizontal" android:orientation="horizontal"

View file

@ -137,6 +137,7 @@
layout="@layout/settings_widget_basic" layout="@layout/settings_widget_basic"
linphone:title="@{@string/account_settings_link_phone_number_title}" linphone:title="@{@string/account_settings_link_phone_number_title}"
linphone:listener="@{viewModel.linkPhoneNumberListener}" linphone:listener="@{viewModel.linkPhoneNumberListener}"
linphone:enabled="@{viewModel.pushNotificationsAvailable}"
android:visibility="@{viewModel.hideLinkPhoneNumber ? View.GONE : View.VISIBLE}"/> android:visibility="@{viewModel.hideLinkPhoneNumber ? View.GONE : View.VISIBLE}"/>
<include <include

View file

@ -24,6 +24,9 @@
<action <action
android:id="@+id/action_welcomeFragment_to_remoteProvisioningFragment" android:id="@+id/action_welcomeFragment_to_remoteProvisioningFragment"
app:destination="@id/remoteProvisioningFragment" /> app:destination="@id/remoteProvisioningFragment" />
<action
android:id="@+id/action_welcomeFragment_to_noPushWarningFragment"
app:destination="@id/noPushWarningFragment" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/accountLoginFragment" android:id="@+id/accountLoginFragment"
@ -162,4 +165,9 @@
android:id="@+id/action_phoneAccountLinkingFragment_to_echoCancellerCalibrationFragment" android:id="@+id/action_phoneAccountLinkingFragment_to_echoCancellerCalibrationFragment"
app:destination="@id/echoCancellerCalibrationFragment" /> app:destination="@id/echoCancellerCalibrationFragment" />
</fragment> </fragment>
<fragment
android:id="@+id/noPushWarningFragment"
android:name="org.linphone.activities.assistant.fragments.NoPushWarningFragment"
android:label="NoPushWarningFragment"
tools:layout="@layout/assistant_no_push_warning_fragment"/>
</navigation> </navigation>

View file

@ -176,7 +176,7 @@
<string name="assistant_phone_number_link_info_content_already_account">Vous ne pouvez associer votre numéro qu\'à un seul compte &appName;.\n\nSi vous avez déjà associé votre numéro à un autre compte mais préférez utiliser ce compte-ci, suivez la procédure d\'association et votre numéro sera automatiquement transféré à ce compte.</string> <string name="assistant_phone_number_link_info_content_already_account">Vous ne pouvez associer votre numéro qu\'à un seul compte &appName;.\n\nSi vous avez déjà associé votre numéro à un autre compte mais préférez utiliser ce compte-ci, suivez la procédure d\'association et votre numéro sera automatiquement transféré à ce compte.</string>
<string name="assistant_error_phone_number_invalid_characters">Entrez uniquement des chiffres</string> <string name="assistant_error_phone_number_invalid_characters">Entrez uniquement des chiffres</string>
<string name="assistant_error_username_already_exists">Ce nom d\'utilisateur est déjà utilisé</string> <string name="assistant_error_username_already_exists">Ce nom d\'utilisateur est déjà utilisé</string>
<string name="assistant_error_phone_number_already_exists">Ce nom d\'utilisateur est déjà pris</string> <string name="assistant_error_phone_number_already_exists">Ce numéro de téléphone est déjà utilisé</string>
<string name="assistant_error_username_invalid_characters">Caractères invalides</string> <string name="assistant_error_username_invalid_characters">Caractères invalides</string>
<string name="assistant_error_passwords_dont_match">Les mots de passe ne correspondent pas</string> <string name="assistant_error_passwords_dont_match">Les mots de passe ne correspondent pas</string>
<string name="assistant_error_invalid_email_address">L\'adresse email est invalide</string> <string name="assistant_error_invalid_email_address">L\'adresse email est invalide</string>
@ -782,4 +782,7 @@
<string name="settings_password_protection_dialog_invalid_input">Le mot de passe est invalide !</string> <string name="settings_password_protection_dialog_invalid_input">Le mot de passe est invalide !</string>
<string name="account_setting_disable_bundle_mode_title">Désactiver le mode bundle</string> <string name="account_setting_disable_bundle_mode_title">Désactiver le mode bundle</string>
<string name="account_setting_disable_bundle_mode_summary"></string> <string name="account_setting_disable_bundle_mode_summary"></string>
<string name="assistant_alternative_way_create_account">Pour créer un compte avec votre email :</string>
<string name="assistant_no_push_warning">Votre périphérique ne semble pas supporter les notifications \'push\'.\n\nVous ne pourrez donc pas créer des comptes dans l\'application mais vous pouvez toujours le faire sur notre site internet :</string>
<string name="assistant_create_email_account_not_validated">Votre compte n\'est pas activé, veuillez cliquer sur le lien que vous avez reçu par courriel</string>
</resources> </resources>

View file

@ -410,6 +410,8 @@
<string name="assistant_generic_account_warning">Some features require a &appName; account, such as group messaging or ephemeral messaging.\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.</string> <string name="assistant_generic_account_warning">Some features require a &appName; account, such as group messaging or ephemeral messaging.\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.</string>
<string name="assistant_generic_account_warning_contact_link" translatable="false">https://www.linphone.org/contact</string> <string name="assistant_generic_account_warning_contact_link" translatable="false">https://www.linphone.org/contact</string>
<string name="assistant_generic_account_warning_continue_button_text">I understand</string> <string name="assistant_generic_account_warning_continue_button_text">I understand</string>
<string name="assistant_alternative_way_create_account">To create an account using your email:</string>
<string name="assistant_no_push_warning">Your device doesn\'t seem to be able to receive push notifications.\n\nAs we now require them for the account creation process, you won\'t be able to create an account inside the app, but you can create it on our website:</string>
<!-- Assistant errors --> <!-- Assistant errors -->
<string name="assistant_error_phone_number_invalid_characters">Only digits are expected here</string> <string name="assistant_error_phone_number_invalid_characters">Only digits are expected here</string>
@ -424,7 +426,7 @@
<!-- Assistant login --> <!-- Assistant login -->
<string name="assistant_linphone_account">Use your &appName; account</string> <string name="assistant_linphone_account">Use your &appName; account</string>
<string name="assistant_create_account_part_1">Please confirm your country code and enter your phone number</string> <string name="assistant_create_account_part_1">Please confirm your country code and enter your phone number</string>
<string name="assistant_linphone_login_desc">Please enter your username and password of &appName; account</string> <string name="assistant_linphone_login_desc">Please enter your &appName; account username and password</string>
<string name="assistant_login_with_username">Use your username and password instead of your phone number</string> <string name="assistant_login_with_username">Use your username and password instead of your phone number</string>
<string name="assistant_login_forgotten_password">Forgot your password?</string> <string name="assistant_login_forgotten_password">Forgot your password?</string>
<string name="assistant_forgotten_password_link" translatable="false">https://subscribe.linphone.org/</string> <string name="assistant_forgotten_password_link" translatable="false">https://subscribe.linphone.org/</string>
@ -448,6 +450,7 @@
<string name="assistant_create_account_part_3">To complete your phone number verification, please enter the 4 digit code below:\n</string> <string name="assistant_create_account_part_3">To complete your phone number verification, please enter the 4 digit code below:\n</string>
<string name="assistant_link_phone_number">You will link with your phone number the following username:</string> <string name="assistant_link_phone_number">You will link with your phone number the following username:</string>
<string name="assistant_link_phone_number_skip">Skip</string> <string name="assistant_link_phone_number_skip">Skip</string>
<string name="assistant_create_email_account_not_validated">Your account has not been activated yet, please click on the link you received by email</string>
<!-- Assistant link --> <!-- Assistant link -->
<string name="assistant_link_account">Link account</string> <string name="assistant_link_account">Link account</string>
@ -575,7 +578,7 @@
<string name="chat_settings_launcher_shortcuts_title">Create shortcuts to chat rooms in launcher</string> <string name="chat_settings_launcher_shortcuts_title">Create shortcuts to chat rooms in launcher</string>
<string name="chat_settings_launcher_shortcuts_summary">Will be replaced by contacts shortcuts if enabled</string> <string name="chat_settings_launcher_shortcuts_summary">Will be replaced by contacts shortcuts if enabled</string>
<string name="chat_settings_hide_empty_rooms_title">Hide empty chat rooms</string> <string name="chat_settings_hide_empty_rooms_title">Hide empty chat rooms</string>
<string name="chat_settings_hide_rooms_removed_proxies_title">Hide chat rooms from removed proxy configs</string> <string name="chat_settings_hide_rooms_removed_proxies_title">Hide chat rooms from removed accounts</string>
<string name="chat_settings_hide_rooms_removed_proxies_summary">If you are missing chat rooms, try to uncheck this setting</string> <string name="chat_settings_hide_rooms_removed_proxies_summary">If you are missing chat rooms, try to uncheck this setting</string>
<string name="chat_settings_go_to_android_notification_settings">Android notification settings</string> <string name="chat_settings_go_to_android_notification_settings">Android notification settings</string>
<string name="chat_settings_use_in_app_file_viewer_title">Always open files inside this app</string> <string name="chat_settings_use_in_app_file_viewer_title">Always open files inside this app</string>
@ -691,7 +694,7 @@
<string name="account_settings_delete_title">Delete</string> <string name="account_settings_delete_title">Delete</string>
<string name="account_settings_advanced_title">Advanced</string> <string name="account_settings_advanced_title">Advanced</string>
<string name="account_settings_push_notification_title">Allow push notification</string> <string name="account_settings_push_notification_title">Allow push notification</string>
<string name="account_settings_push_notification_summary">Proxy config won\'t unregister</string> <string name="account_settings_push_notification_summary">Account won\'t unregister</string>
<string name="account_settings_transport_title">Transport</string> <string name="account_settings_transport_title">Transport</string>
<string name="account_settings_transport_udp">UDP</string> <string name="account_settings_transport_udp">UDP</string>
<string name="account_settings_transport_tcp">TCP</string> <string name="account_settings_transport_tcp">TCP</string>