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
### 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
- 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

View file

@ -42,11 +42,11 @@ activation_code_length=4
prefer_basic_chat_room=1
record_aware=1
[assistant]
xmlrpc_url=https://subscribe.linphone.org:444/wizard.php
[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_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() {
if (findNavController().currentDestination?.id == R.id.welcomeFragment) {
findNavController().navigate(

View file

@ -28,6 +28,7 @@ import org.linphone.activities.GenericFragment
import org.linphone.activities.assistant.viewmodels.AbstractPhoneViewModel
import org.linphone.compatibility.Compatibility
import org.linphone.core.tools.Log
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.PermissionHelper
import org.linphone.utils.PhoneNumberUtils
@ -55,7 +56,8 @@ abstract class AbstractPhoneFragment<T : ViewDataBinding> : GenericFragment<T>()
}
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()) {
Log.i("[Assistant] Asking for READ_PHONE_STATE/READ_PHONE_NUMBERS permission")
Compatibility.requestReadPhoneStateOrNumbersPermission(

View file

@ -58,10 +58,6 @@ class AccountLoginFragment : AbstractPhoneFragment<AssistantAccountLoginFragment
)[AccountLoginViewModel::class.java]
binding.viewModel = viewModel
if (resources.getBoolean(R.bool.isTablet)) {
viewModel.loginWithUsernamePassword.value = true
}
binding.setInfoClickListener {
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
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.text.SpannableString
@ -30,6 +31,7 @@ import android.view.View
import androidx.lifecycle.ViewModelProvider
import java.util.UnknownFormatConversionException
import java.util.regex.Pattern
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.activities.*
@ -39,6 +41,7 @@ import org.linphone.activities.navigateToEmailAccountCreation
import org.linphone.activities.navigateToRemoteProvisioning
import org.linphone.core.tools.Log
import org.linphone.databinding.AssistantWelcomeFragmentBinding
import org.linphone.utils.LinphoneUtils
class WelcomeFragment : GenericFragment<AssistantWelcomeFragmentBinding>() {
private lateinit var viewModel: WelcomeViewModel
@ -54,10 +57,27 @@ class WelcomeFragment : GenericFragment<AssistantWelcomeFragmentBinding>() {
binding.viewModel = viewModel
binding.setCreateAccountClickListener {
if (resources.getBoolean(R.bool.isTablet)) {
navigateToEmailAccountCreation()
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()
} else {
Log.i(
"[Assistant] Device has TELEPHONY feature, showing phone based account creation"
)
navigateToPhoneAccountCreation()
}
} else {
navigateToPhoneAccountCreation()
Log.w(
"[Assistant] Failed to get push notification info, showing warning instead of phone based account creation"
)
navigateToNoPushWarning()
}
}

View file

@ -21,15 +21,15 @@
package org.linphone.activities.assistant.viewmodels
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.*
import org.linphone.activities.assistant.fragments.CountryPickerFragment
import org.linphone.core.AccountCreator
import org.linphone.core.DialPlan
import org.linphone.core.tools.Log
import org.linphone.utils.PhoneNumberUtils
abstract class AbstractPhoneViewModel(val accountCreator: AccountCreator) :
ViewModel(),
abstract class AbstractPhoneViewModel(accountCreator: AccountCreator) :
AbstractPushTokenViewModel(accountCreator),
CountryPickerFragment.CountryPickedListener {
val prefix = MutableLiveData<String>()
@ -71,7 +71,7 @@ abstract class AbstractPhoneViewModel(val accountCreator: AccountCreator) :
}
private fun getCountryNameFromPrefix(prefix: String?) {
if (prefix != null && prefix.isNotEmpty()) {
if (!prefix.isNullOrEmpty()) {
val countryCode = if (prefix.first() == '+') prefix.substring(1) else prefix
val dialPlan = PhoneNumberUtils.getDialPlanFromCountryCallingPrefix(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
import android.content.pm.PackageManager
import androidx.lifecycle.*
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.core.*
import org.linphone.core.tools.Log
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.PhoneNumberUtils
class AccountLoginViewModelFactory(private val accountCreator: AccountCreator) :
@ -51,6 +52,8 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
val displayName = MutableLiveData<String>()
val forceLoginUsingUsernameAndPassword = MutableLiveData<Boolean>()
val leaveAssistantEvent: MutableLiveData<Event<Boolean>> by lazy {
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() {
@Deprecated("Deprecated in Java")
override fun onRegistrationStateChanged(
override fun onAccountRegistrationStateChanged(
core: Core,
cfg: ProxyConfig,
state: RegistrationState,
account: Account,
state: RegistrationState?,
message: String
) {
if (cfg == proxyConfigToCheck) {
if (account == accountToCheck) {
Log.i("[Assistant] [Account Login] Registration state is $state: $message")
if (state == RegistrationState.Ok) {
waitForServerAnswer.value = false
@ -112,7 +114,12 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
init {
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.addSource(prefix) {
@ -140,76 +147,105 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
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() {
val cfg = proxyConfigToCheck
cfg ?: return
val authInfo = cfg.findAuthInfo()
val account = accountToCheck
account ?: return
val authInfo = account.findAuthInfo()
if (authInfo != null) coreContext.core.removeAuthInfo(authInfo)
coreContext.core.removeProxyConfig(cfg)
proxyConfigToCheck = null
coreContext.core.removeAccount(account)
accountToCheck = null
}
fun continueEvenIfInvalidCredentials() {
leaveAssistantEvent.value = Event(true)
}
private fun loginWithUsername() {
val result = accountCreator.setUsername(username.value)
if (result != AccountCreator.UsernameStatus.Ok) {
Log.e(
"[Assistant] [Account Login] Error [${result.name}] setting the username: ${username.value}"
)
usernameError.value = result.name
return
}
Log.i("[Assistant] [Account Login] Username is ${accountCreator.username}")
val result2 = accountCreator.setPassword(password.value)
if (result2 != AccountCreator.PasswordStatus.Ok) {
Log.e("[Assistant] [Account Login] Error [${result2.name}] setting the password")
passwordError.value = result2.name
return
}
waitForServerAnswer.value = true
coreContext.core.addListener(coreListener)
if (!createAccountAndAuthInfo()) {
waitForServerAnswer.value = false
coreContext.core.removeListener(coreListener)
onErrorEvent.value = Event("Error: Failed to create account object")
}
}
private fun loginWithPhoneNumber() {
val result = AccountCreator.PhoneNumberStatus.fromInt(
accountCreator.setPhoneNumber(phoneNumber.value, prefix.value)
)
if (result != AccountCreator.PhoneNumberStatus.Ok) {
Log.e(
"[Assistant] [Account Login] Error [$result] setting the phone number: ${phoneNumber.value} with prefix: ${prefix.value}"
)
phoneNumberError.value = result.name
return
}
Log.i("[Assistant] [Account Login] Phone number is ${accountCreator.phoneNumber}")
val result2 = accountCreator.setUsername(accountCreator.phoneNumber)
if (result2 != AccountCreator.UsernameStatus.Ok) {
Log.e(
"[Assistant] [Account Login] Error [${result2.name}] setting the username: ${accountCreator.phoneNumber}"
)
usernameError.value = result2.name
return
}
Log.i("[Assistant] [Account Login] Username is ${accountCreator.username}")
waitForServerAnswer.value = true
val status = accountCreator.recoverAccount()
Log.i("[Assistant] [Account Login] Recover account returned $status")
if (status != AccountCreator.Status.RequestOk) {
waitForServerAnswer.value = false
onErrorEvent.value = Event("Error: ${status.name}")
}
}
fun login() {
accountCreator.displayName = displayName.value
if (loginWithUsernamePassword.value == true) {
val result = accountCreator.setUsername(username.value)
if (result != AccountCreator.UsernameStatus.Ok) {
Log.e(
"[Assistant] [Account Login] Error [${result.name}] setting the username: ${username.value}"
)
usernameError.value = result.name
return
}
Log.i("[Assistant] [Account Login] Username is ${accountCreator.username}")
val result2 = accountCreator.setPassword(password.value)
if (result2 != AccountCreator.PasswordStatus.Ok) {
Log.e("[Assistant] [Account Login] Error [${result2.name}] setting the password")
passwordError.value = result2.name
return
}
waitForServerAnswer.value = true
coreContext.core.addListener(coreListener)
if (!createProxyConfig()) {
waitForServerAnswer.value = false
coreContext.core.removeListener(coreListener)
onErrorEvent.value = Event("Error: Failed to create account object")
}
loginWithUsername()
} else {
val result = AccountCreator.PhoneNumberStatus.fromInt(
accountCreator.setPhoneNumber(phoneNumber.value, prefix.value)
)
if (result != AccountCreator.PhoneNumberStatus.Ok) {
Log.e(
"[Assistant] [Account Login] Error [$result] setting the phone number: ${phoneNumber.value} with prefix: ${prefix.value}"
val token = accountCreator.token.orEmpty()
if (token.isNotEmpty()) {
Log.i(
"[Assistant] [Account Login] We already have an auth token from FlexiAPI [$token], continue"
)
phoneNumberError.value = result.name
return
}
Log.i("[Assistant] [Account Login] Phone number is ${accountCreator.phoneNumber}")
val result2 = accountCreator.setUsername(accountCreator.phoneNumber)
if (result2 != AccountCreator.UsernameStatus.Ok) {
Log.e(
"[Assistant] [Account Login] Error [${result2.name}] setting the username: ${accountCreator.phoneNumber}"
)
usernameError.value = result2.name
return
}
Log.i("[Assistant] [Account Login] Username is ${accountCreator.username}")
waitForServerAnswer.value = true
val status = accountCreator.recoverAccount()
Log.i("[Assistant] [Account Login] Recover account returned $status")
if (status != AccountCreator.Status.RequestOk) {
waitForServerAnswer.value = false
onErrorEvent.value = Event("Error: ${status.name}")
onFlexiApiTokenReceived()
} else {
Log.i("[Assistant] [Account Login] Requesting an auth token from FlexiAPI")
waitForServerAnswer.value = true
requestFlexiApiToken()
}
}
}
@ -222,33 +258,33 @@ class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewM
}
}
private fun createProxyConfig(): Boolean {
val proxyConfig: ProxyConfig? = accountCreator.createProxyConfig()
proxyConfigToCheck = proxyConfig
private fun createAccountAndAuthInfo(): Boolean {
val account = accountCreator.createAccountInCore()
accountToCheck = account
if (proxyConfig == null) {
Log.e("[Assistant] [Account Login] Account creator couldn't create proxy config")
if (account == null) {
Log.e("[Assistant] [Account Login] Account creator couldn't create account")
onErrorEvent.value = Event("Error: Failed to create account object")
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)
if (dialPlan != null) {
Log.i(
"[Assistant] [Account Login] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}"
)
proxyConfig.edit()
proxyConfig.dialPrefix = dialPlan.countryCallingCode
proxyConfig.done()
params.internationalPrefix = dialPlan.countryCallingCode
} else {
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
}
}

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 usernameError = MutableLiveData<String>()
@ -70,7 +72,7 @@ class EmailAccountCreationViewModel(val accountCreator: AccountCreator) : ViewMo
status: AccountCreator.Status,
response: String?
) {
Log.i("[Assistant] [Account Creation] onIsAccountExist status is $status")
Log.i("[Account Creation] onIsAccountExist status is $status")
when (status) {
AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> {
waitForServerAnswer.value = false
@ -146,18 +148,40 @@ class EmailAccountCreationViewModel(val accountCreator: AccountCreator) : ViewMo
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() {
accountCreator.username = username.value
accountCreator.password = password.value
accountCreator.email = email.value
accountCreator.displayName = displayName.value
waitForServerAnswer.value = true
val status = accountCreator.isAccountExist
Log.i("[Assistant] [Account Creation] Account exists returned $status")
if (status != AccountCreator.Status.RequestOk) {
waitForServerAnswer.value = false
onErrorEvent.value = Event("Error: ${status.name}")
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
requestFlexiApiToken()
}
}

View file

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

View file

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

View file

@ -71,6 +71,36 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
Log.i("[Phone Account Creation] onIsAccountExist status is $status")
when (status) {
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
if (useUsername.value == true) {
usernameError.value = AppUtils.getString(
@ -82,7 +112,7 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
)
}
}
AccountCreator.Status.AccountNotExist -> {
AccountCreator.Status.AliasNotExist -> {
val createAccountStatus = creator.createAccount()
Log.i("[Phone Account Creation] createAccount returned $createAccountStatus")
if (createAccountStatus != AccountCreator.Status.RequestOk) {
@ -150,16 +180,31 @@ class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPh
super.onCleared()
}
fun create() {
override fun onFlexiApiTokenReceived() {
Log.i("[Phone Account Creation] Using FlexiAPI auth token [${accountCreator.token}]")
accountCreator.displayName = displayName.value
accountCreator.setPhoneNumber(phoneNumber.value, prefix.value)
if (useUsername.value == true) {
accountCreator.username = username.value
} else {
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
Log.i("[Phone Account Creation] isAccountExist returned $status")
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 {
val usernameRegexp = corePreferences.config.getString(
"assistant",

View file

@ -120,12 +120,11 @@ class PhoneAccountLinkingViewModel(accountCreator: AccountCreator) : AbstractPho
super.onCleared()
}
fun link() {
override fun onFlexiApiTokenReceived() {
accountCreator.setPhoneNumber(phoneNumber.value, prefix.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
Log.i("[Phone Account Linking] isAliasUsed returned $status")
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() {
leaveAssistantEvent.value = Event(true)
}

View file

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

View file

@ -27,6 +27,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.activities.GenericActivity
import org.linphone.activities.main.MainActivity
import org.linphone.activities.main.chat.viewmodels.ChatRoomCreationViewModel
import org.linphone.activities.main.fragments.SecureFragment
@ -74,7 +75,7 @@ class ChatRoomCreationFragment : SecureFragment<ChatRoomCreationFragmentBinding>
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 {
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.corePreferences
import org.linphone.R
import org.linphone.activities.GenericActivity
import org.linphone.activities.main.MainActivity
import org.linphone.activities.main.dialer.viewmodels.DialerViewModel
import org.linphone.activities.main.fragments.SecureFragment
@ -213,7 +214,7 @@ class DialerFragment : SecureFragment<DialerFragmentBinding>() {
override fun onResume() {
super.onResume()
if (resources.getBoolean(R.bool.isTablet)) {
if ((requireActivity() as GenericActivity).isTablet()) {
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.tools.Log
import org.linphone.utils.Event
import org.linphone.utils.LinphoneUtils
class AccountSettingsViewModelFactory(private val identity: String) :
ViewModelProvider.NewInstanceFactory() {
@ -408,7 +409,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
override fun onTextValueChanged(newValue: String) {
val params = account.params.clone()
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
account.params = params
@ -421,7 +422,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
val params = account.params.clone()
val uri = coreContext.core.interpretUrl(newValue, false)
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
account.params = params
@ -500,7 +501,7 @@ class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel(
domain.value = params.identityAddress?.domain
disable.value = !params.isRegisterEnabled
pushNotification.value = params.pushNotificationAllowed
pushNotificationsAvailable.value = core.isPushNotificationAvailable
pushNotificationsAvailable.value = LinphoneUtils.isPushNotificationAvailable()
proxy.value = params.serverAddress?.asStringUriOnly()
outboundProxy.value = params.isOutboundProxyEnabled
stunServer.value = params.natPolicy?.stunServer

View file

@ -501,7 +501,7 @@ class CoreContext(
val newExpire = 2629800 // 1 month
if (account.params.expires != newExpire) {
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
paramsChanged = true
@ -510,7 +510,7 @@ class CoreContext(
// Enable presence publish/subscribe for new feature
if (!account.params.isPublishEnabled) {
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.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) {
val uri = corePreferences.conferenceServerUri
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
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) {
val uri = corePreferences.audioVideoConferenceServerUri
val address = core.interpretUrl(uri, false)
if (address != null) {
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
paramsChanged = true
@ -546,7 +546,7 @@ class CoreContext(
// Enable Bundle mode by default
if (!account.params.isRtpBundleEnabled) {
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
paramsChanged = true

View file

@ -274,6 +274,20 @@ class LinphoneUtils {
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(
userId: String,
password: String,

View file

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

View file

@ -164,7 +164,7 @@
</RadioGroup>
<TextView
android:onClick="@{() -> viewModel.createProxyConfig()}"
android:onClick="@{() -> viewModel.createAccountAndAuthInfo()}"
android:enabled="@{viewModel.loginEnabled, default=false}"
android:text="@string/assistant_login"
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:gravity="center"
android:text="@string/assistant_create_account"
android:paddingTop="10dp"
android:layout_marginTop="10dp"
android:textAllCaps="true" />
<TextView
@ -53,7 +53,7 @@
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="top"
android:paddingTop="20dp"
android:layout_marginTop="10dp"
android:text="@string/assistant_create_account_part_1" />
<RelativeLayout
@ -65,8 +65,8 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/phone_number_label"
android:text="@string/phone_number"
android:id="@+id/select_country_label"
android:text="@string/select_your_country"
style="@style/assistant_input_field_header_font"
android:textAllCaps="true"
android:layout_width="match_parent"
@ -76,7 +76,9 @@
android:onClick="@{infoClickListener}"
android:contentDescription="@string/content_description_phone_number_use"
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_height="20dp"/>
@ -92,7 +94,7 @@
android:padding="10dp"
android:layout_marginTop="5dp"
android:gravity="center"
android:layout_below="@id/phone_number_label"/>
android:layout_below="@id/select_country_label"/>
<LinearLayout
android:orientation="vertical"
@ -217,6 +219,35 @@
android:layout_margin="20dp"
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>
</androidx.core.widget.NestedScrollView>

View file

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

View file

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

View file

@ -24,6 +24,9 @@
<action
android:id="@+id/action_welcomeFragment_to_remoteProvisioningFragment"
app:destination="@id/remoteProvisioningFragment" />
<action
android:id="@+id/action_welcomeFragment_to_noPushWarningFragment"
app:destination="@id/noPushWarningFragment" />
</fragment>
<fragment
android:id="@+id/accountLoginFragment"
@ -162,4 +165,9 @@
android:id="@+id/action_phoneAccountLinkingFragment_to_echoCancellerCalibrationFragment"
app:destination="@id/echoCancellerCalibrationFragment" />
</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>

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_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_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_passwords_dont_match">Les mots de passe ne correspondent pas</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="account_setting_disable_bundle_mode_title">Désactiver le mode bundle</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>

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_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_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 -->
<string name="assistant_error_phone_number_invalid_characters">Only digits are expected here</string>
@ -424,7 +426,7 @@
<!-- Assistant login -->
<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_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_forgotten_password">Forgot your password?</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_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_create_email_account_not_validated">Your account has not been activated yet, please click on the link you received by email</string>
<!-- Assistant link -->
<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_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_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_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>
@ -691,7 +694,7 @@
<string name="account_settings_delete_title">Delete</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_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_udp">UDP</string>
<string name="account_settings_transport_tcp">TCP</string>