Switched account creator to FlexiAPI backend instead of XMLRPC
This commit is contained in:
parent
f551398652
commit
6bb6daadba
30 changed files with 653 additions and 179 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue