Merge pull request #7390 from thunderbird/remove-account-edit-legacy-code
Remove account edit legacy code
This commit is contained in:
commit
0fcc98f5c1
29 changed files with 31 additions and 2749 deletions
|
@ -364,6 +364,7 @@ public class MessagingController {
|
|||
put("refreshFolderList", null, () -> refreshFolderListSynchronous(account));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void refreshFolderListSynchronous(Account account) {
|
||||
try {
|
||||
if (isAuthenticationProblem(account, true)) {
|
||||
|
@ -1671,10 +1672,12 @@ public class MessagingController {
|
|||
return getBackend(account).getSupportsFolderSubscriptions();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void checkIncomingServerSettings(Account account) throws MessagingException {
|
||||
getBackend(account).checkIncomingServerSettings();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void checkOutgoingServerSettings(Account account) throws MessagingException {
|
||||
getBackend(account).checkOutgoingServerSettings();
|
||||
}
|
||||
|
@ -2440,6 +2443,7 @@ public class MessagingController {
|
|||
notificationController.removeNewMailNotification(account, messageReference);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void clearCertificateErrorNotifications(Account account, boolean incoming) {
|
||||
notificationController.clearCertificateErrorNotifications(account, incoming);
|
||||
}
|
||||
|
|
|
@ -13,8 +13,6 @@ import android.net.ConnectivityManager;
|
|||
import android.net.NetworkInfo;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
|
@ -86,38 +84,6 @@ public class Utility {
|
|||
return TextUtils.join(String.valueOf(separator), parts);
|
||||
}
|
||||
|
||||
public static boolean requiredFieldValid(TextView view) {
|
||||
return view.getText() != null && view.getText().length() > 0;
|
||||
}
|
||||
|
||||
|
||||
public static boolean domainFieldValid(EditText view) {
|
||||
if (view.getText() != null) {
|
||||
String s = view.getText().toString();
|
||||
if (s.matches("^([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)*[a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?$") &&
|
||||
s.length() <= 253) {
|
||||
return true;
|
||||
}
|
||||
if (s.matches("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO disabled this method globally. It is used in all the settings screens but I just
|
||||
* noticed that an unrelated icon was dimmed. Android must share drawables internally.
|
||||
*/
|
||||
public static void setCompoundDrawablesAlpha(TextView view, int alpha) {
|
||||
// Drawable[] drawables = view.getCompoundDrawables();
|
||||
// for (Drawable drawable : drawables) {
|
||||
// if (drawable != null) {
|
||||
// drawable.setAlpha(alpha);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the 'original' subject value, by ignoring leading
|
||||
* response/forward marker and '[XX]' formatted tags (as many mailing-list
|
||||
|
|
|
@ -67,21 +67,11 @@
|
|||
android:theme="@style/Theme.K9.Dialog.Translucent.DayNight"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".activity.setup.AccountSetupIncoming"
|
||||
android:configChanges="locale"
|
||||
android:label="@string/account_setup_incoming_title"/>
|
||||
|
||||
<activity
|
||||
android:name=".activity.setup.AccountSetupComposition"
|
||||
android:configChanges="locale"
|
||||
android:label="@string/account_settings_composition_title"/>
|
||||
|
||||
<activity
|
||||
android:name=".activity.setup.AccountSetupOutgoing"
|
||||
android:configChanges="locale"
|
||||
android:label="@string/account_setup_outgoing_title"/>
|
||||
|
||||
<activity
|
||||
android:name=".activity.ChooseAccount"
|
||||
android:configChanges="locale"
|
||||
|
@ -116,13 +106,6 @@
|
|||
android:taskAffinity=""
|
||||
android:theme="@style/Theme.K9.Dialog.Translucent.DayNight"/>
|
||||
|
||||
<!-- XXX Note: this activity is hacked to ignore config changes,
|
||||
since it doesn't currently handle them correctly in code. -->
|
||||
<activity
|
||||
android:name=".activity.setup.AccountSetupCheckSettings"
|
||||
android:configChanges="keyboardHidden|orientation|locale"
|
||||
android:label="@string/account_setup_check_settings_title"/>
|
||||
|
||||
<activity
|
||||
android:name=".ui.endtoend.AutocryptKeyTransferActivity"
|
||||
android:configChanges="locale"
|
||||
|
|
|
@ -2,12 +2,9 @@ package com.fsck.k9.featureflag
|
|||
|
||||
import app.k9mail.core.featureflag.FeatureFlag
|
||||
import app.k9mail.core.featureflag.FeatureFlagFactory
|
||||
import app.k9mail.core.featureflag.FeatureFlagKey
|
||||
|
||||
class InMemoryFeatureFlagFactory : FeatureFlagFactory {
|
||||
override fun createFeatureCatalog(): List<FeatureFlag> {
|
||||
return listOf(
|
||||
FeatureFlag(FeatureFlagKey("new_account_edit"), true),
|
||||
)
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,11 @@ import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import app.k9mail.feature.launcher.FeatureLauncherActivity
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.K9
|
||||
import com.fsck.k9.activity.MessageList
|
||||
import com.fsck.k9.activity.compose.MessageActions
|
||||
import com.fsck.k9.activity.setup.AccountSetupIncoming
|
||||
import com.fsck.k9.activity.setup.AccountSetupOutgoing
|
||||
import com.fsck.k9.controller.MessageReference
|
||||
import com.fsck.k9.helper.PendingIntentCompat.FLAG_IMMUTABLE
|
||||
import com.fsck.k9.mailstore.MessageStoreManager
|
||||
|
@ -112,12 +111,12 @@ internal class K9NotificationActionCreator(
|
|||
}
|
||||
|
||||
override fun getEditIncomingServerSettingsIntent(account: Account): PendingIntent {
|
||||
val intent = AccountSetupIncoming.intentActionEditIncomingSettings(context, account)
|
||||
val intent = FeatureLauncherActivity.getEditIncomingSettingsIntent(context, account.uuid)
|
||||
return PendingIntent.getActivity(context, account.accountNumber, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
|
||||
}
|
||||
|
||||
override fun getEditOutgoingServerSettingsIntent(account: Account): PendingIntent {
|
||||
val intent = AccountSetupOutgoing.intentActionEditOutgoingSettings(context, account)
|
||||
val intent = FeatureLauncherActivity.getEditOutgoingSettingsIntent(context, account.uuid)
|
||||
return PendingIntent.getActivity(context, account.accountNumber, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
@file:JvmName("TextInputLayoutHelper")
|
||||
|
||||
package com.fsck.k9.ui.base.extensions
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.text.method.PasswordTransformationMethod
|
||||
import android.view.WindowManager.LayoutParams.FLAG_SECURE
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.get
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
|
||||
/**
|
||||
* Configures a [TextInputLayout] so the password can only be revealed after authentication.
|
||||
*
|
||||
* **IMPORTANT**: Only call this after the instance state has been restored! Otherwise, restoring the previous state
|
||||
* after the initial state has been set will be detected as replacing the whole text. In that case showing the password
|
||||
* will be allowed without authentication.
|
||||
*/
|
||||
fun TextInputLayout.configureAuthenticatedPasswordToggle(
|
||||
activity: FragmentActivity,
|
||||
title: String,
|
||||
subtitle: String,
|
||||
needScreenLockMessage: String,
|
||||
) {
|
||||
val viewModel = ViewModelProvider(activity).get<AuthenticatedPasswordToggleViewModel>()
|
||||
viewModel.textInputLayout = this
|
||||
viewModel.activity = activity
|
||||
|
||||
fun authenticateUserAndShowPassword(activity: FragmentActivity) {
|
||||
val mainExecutor = ContextCompat.getMainExecutor(activity)
|
||||
|
||||
val context = activity.applicationContext
|
||||
val authenticationCallback = object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
// The Activity might have been recreated since this callback object was created (e.g. due to an
|
||||
// orientation change). So we fetch the (new) references from the ViewModel.
|
||||
viewModel.isAuthenticated = true
|
||||
viewModel.activity?.setSecure(true)
|
||||
viewModel.textInputLayout?.editText?.showPassword()
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
if (errorCode == BiometricPrompt.ERROR_HW_NOT_PRESENT ||
|
||||
errorCode == BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL ||
|
||||
errorCode == BiometricPrompt.ERROR_NO_BIOMETRICS
|
||||
) {
|
||||
Toast.makeText(context, needScreenLockMessage, Toast.LENGTH_SHORT).show()
|
||||
} else if (errString.isNotEmpty()) {
|
||||
Toast.makeText(context, errString, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BiometricPrompt(activity, mainExecutor, authenticationCallback).authenticate(
|
||||
BiometricPrompt.PromptInfo.Builder()
|
||||
.setAllowedAuthenticators(BIOMETRIC_STRONG or BIOMETRIC_WEAK or DEVICE_CREDENTIAL)
|
||||
.setTitle(title)
|
||||
.setSubtitle(subtitle)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
val editText = this.editText ?: error("TextInputLayout.editText == null")
|
||||
|
||||
editText.doOnTextChanged { text, _, before, count ->
|
||||
// Check if the password field is empty or if all of the previous text was replaced
|
||||
if (text != null && before > 0 && (text.isEmpty() || text.length - count == 0)) {
|
||||
viewModel.isNewPassword = true
|
||||
}
|
||||
}
|
||||
|
||||
setEndIconOnClickListener {
|
||||
if (editText.isPasswordHidden) {
|
||||
if (viewModel.isShowPasswordAllowed) {
|
||||
activity.setSecure(true)
|
||||
editText.showPassword()
|
||||
} else {
|
||||
authenticateUserAndShowPassword(activity)
|
||||
}
|
||||
} else {
|
||||
viewModel.isAuthenticated = false
|
||||
editText.hidePassword()
|
||||
activity.setSecure(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val EditText.isPasswordHidden: Boolean
|
||||
get() = transformationMethod is PasswordTransformationMethod
|
||||
|
||||
private fun EditText.showPassword() {
|
||||
transformationMethod = null
|
||||
}
|
||||
|
||||
private fun EditText.hidePassword() {
|
||||
transformationMethod = PasswordTransformationMethod.getInstance()
|
||||
}
|
||||
|
||||
private fun FragmentActivity.setSecure(secure: Boolean) {
|
||||
window.setFlags(if (secure) FLAG_SECURE else 0, FLAG_SECURE)
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
class AuthenticatedPasswordToggleViewModel : ViewModel() {
|
||||
val isShowPasswordAllowed: Boolean
|
||||
get() = isAuthenticated || isNewPassword
|
||||
|
||||
var isNewPassword = false
|
||||
var isAuthenticated = false
|
||||
var textInputLayout: TextInputLayout? = null
|
||||
var activity: FragmentActivity? = null
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
value?.lifecycle?.addObserver(
|
||||
object : DefaultLifecycleObserver {
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
textInputLayout = null
|
||||
field = null
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,500 +0,0 @@
|
|||
package com.fsck.k9.activity.setup
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.commit
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.LocalKeyStoreManager
|
||||
import com.fsck.k9.Preferences
|
||||
import com.fsck.k9.controller.MessagingController
|
||||
import com.fsck.k9.fragment.ConfirmationDialogFragment
|
||||
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener
|
||||
import com.fsck.k9.mail.AuthType
|
||||
import com.fsck.k9.mail.AuthenticationFailedException
|
||||
import com.fsck.k9.mail.CertificateValidationException
|
||||
import com.fsck.k9.mail.MailServerDirection
|
||||
import com.fsck.k9.mail.filter.Hex
|
||||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.ui.base.K9Activity
|
||||
import com.fsck.k9.ui.observe
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.cert.CertificateEncodingException
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.Executors
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Checks the given settings to make sure that they can be used to send and receive mail.
|
||||
*
|
||||
* XXX NOTE: The manifest for this app has it ignore config changes, because it doesn't correctly deal with restarting
|
||||
* while its thread is running.
|
||||
*/
|
||||
class AccountSetupCheckSettings : K9Activity(), ConfirmationDialogFragmentListener {
|
||||
private val authViewModel: AuthViewModel by viewModel()
|
||||
|
||||
private val messagingController: MessagingController by inject()
|
||||
private val preferences: Preferences by inject()
|
||||
private val localKeyStoreManager: LocalKeyStoreManager by inject()
|
||||
|
||||
private val handler = Handler(Looper.myLooper()!!)
|
||||
|
||||
private lateinit var progressBar: ProgressBar
|
||||
private lateinit var messageView: TextView
|
||||
|
||||
private lateinit var account: Account
|
||||
private lateinit var direction: CheckDirection
|
||||
|
||||
@Volatile
|
||||
private var canceled = false
|
||||
|
||||
@Volatile
|
||||
private var destroyed = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setLayout(R.layout.account_setup_check_settings)
|
||||
|
||||
messageView = findViewById(R.id.message)
|
||||
progressBar = findViewById(R.id.progress)
|
||||
findViewById<View>(R.id.cancel).setOnClickListener { onCancel() }
|
||||
|
||||
setMessage(R.string.account_setup_check_settings_retr_info_msg)
|
||||
progressBar.isIndeterminate = true
|
||||
|
||||
val accountUuid = intent.getStringExtra(EXTRA_ACCOUNT) ?: error("Missing account UUID")
|
||||
account = preferences.getAccount(accountUuid) ?: error("Could not find account")
|
||||
direction = intent.getSerializableExtra(EXTRA_CHECK_DIRECTION) as CheckDirection?
|
||||
?: error("Missing CheckDirection")
|
||||
|
||||
authViewModel.init(activityResultRegistry, lifecycle, account)
|
||||
|
||||
authViewModel.uiState.observe(this) { state ->
|
||||
when (state) {
|
||||
AuthFlowState.Idle -> {
|
||||
return@observe
|
||||
}
|
||||
AuthFlowState.Success -> {
|
||||
startCheckServerSettings()
|
||||
}
|
||||
AuthFlowState.Canceled -> {
|
||||
showErrorDialog(R.string.account_setup_failed_dlg_oauth_flow_canceled)
|
||||
}
|
||||
is AuthFlowState.Failed -> {
|
||||
showErrorDialog(R.string.account_setup_failed_dlg_oauth_flow_failed, state)
|
||||
}
|
||||
AuthFlowState.NotSupported -> {
|
||||
showErrorDialog(R.string.account_setup_failed_dlg_oauth_not_supported)
|
||||
}
|
||||
AuthFlowState.BrowserNotFound -> {
|
||||
showErrorDialog(R.string.account_setup_failed_dlg_browser_not_found)
|
||||
}
|
||||
}
|
||||
|
||||
authViewModel.authResultConsumed()
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (needsAuthorization()) {
|
||||
setMessage(R.string.account_setup_check_settings_authenticate)
|
||||
authViewModel.login()
|
||||
} else {
|
||||
startCheckServerSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun needsAuthorization(): Boolean {
|
||||
return (
|
||||
account.incomingServerSettings.authenticationType == AuthType.XOAUTH2 ||
|
||||
account.outgoingServerSettings.authenticationType == AuthType.XOAUTH2
|
||||
) &&
|
||||
!authViewModel.isAuthorized(account)
|
||||
}
|
||||
|
||||
private fun startCheckServerSettings() {
|
||||
CheckAccountTask(account).executeOnExecutor(Executors.newSingleThreadExecutor(), direction)
|
||||
}
|
||||
|
||||
private fun handleCertificateValidationException(exception: CertificateValidationException) {
|
||||
Timber.e(exception, "Error while testing settings")
|
||||
|
||||
val chain = exception.certChain
|
||||
|
||||
// Avoid NullPointerException in acceptKeyDialog()
|
||||
if (chain != null) {
|
||||
acceptKeyDialog(
|
||||
R.string.account_setup_failed_dlg_certificate_message_fmt,
|
||||
exception,
|
||||
)
|
||||
} else {
|
||||
showErrorDialog(
|
||||
R.string.account_setup_failed_dlg_server_message_fmt,
|
||||
errorMessageForCertificateException(exception)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
destroyed = true
|
||||
canceled = true
|
||||
}
|
||||
|
||||
private fun setMessage(resId: Int) {
|
||||
messageView.text = getString(resId)
|
||||
}
|
||||
|
||||
private fun acceptKeyDialog(msgResId: Int, exception: CertificateValidationException) {
|
||||
handler.post {
|
||||
if (destroyed) {
|
||||
return@post
|
||||
}
|
||||
|
||||
val errorMessage = exception.cause?.cause?.message ?: exception.cause?.message ?: exception.message
|
||||
|
||||
progressBar.isIndeterminate = false
|
||||
|
||||
val chainInfo = StringBuilder()
|
||||
val chain = exception.certChain
|
||||
|
||||
// We already know chain != null (tested before calling this method)
|
||||
for (i in chain.indices) {
|
||||
// display certificate chain information
|
||||
// TODO: localize this strings
|
||||
chainInfo.append("Certificate chain[").append(i).append("]:\n")
|
||||
chainInfo.append("Subject: ").append(chain[i].subjectDN.toString()).append("\n")
|
||||
|
||||
// display SubjectAltNames too
|
||||
// (the user may be mislead into mistrusting a certificate
|
||||
// by a subjectDN not matching the server even though a
|
||||
// SubjectAltName matches)
|
||||
try {
|
||||
val subjectAlternativeNames = chain[i].subjectAlternativeNames
|
||||
if (subjectAlternativeNames != null) {
|
||||
// TODO: localize this string
|
||||
val altNamesText = StringBuilder()
|
||||
altNamesText.append("Subject has ")
|
||||
.append(subjectAlternativeNames.size)
|
||||
.append(" alternative names\n")
|
||||
|
||||
// we need these for matching
|
||||
val incomingServerHost = account.incomingServerSettings.host!!
|
||||
val outgoingServerHost = account.outgoingServerSettings.host!!
|
||||
for (subjectAlternativeName in subjectAlternativeNames) {
|
||||
val type = subjectAlternativeName[0] as Int
|
||||
val value: Any? = subjectAlternativeName[1]
|
||||
val name: String = when (type) {
|
||||
0 -> {
|
||||
Timber.w("SubjectAltName of type OtherName not supported.")
|
||||
continue
|
||||
}
|
||||
1 -> value as String
|
||||
2 -> value as String
|
||||
3 -> {
|
||||
Timber.w("unsupported SubjectAltName of type x400Address")
|
||||
continue
|
||||
}
|
||||
4 -> {
|
||||
Timber.w("unsupported SubjectAltName of type directoryName")
|
||||
continue
|
||||
}
|
||||
5 -> {
|
||||
Timber.w("unsupported SubjectAltName of type ediPartyName")
|
||||
continue
|
||||
}
|
||||
6 -> value as String
|
||||
7 -> value as String
|
||||
else -> {
|
||||
Timber.w("unsupported SubjectAltName of unknown type")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// if some of the SubjectAltNames match the store or transport -host, display them
|
||||
if (name.equals(incomingServerHost, ignoreCase = true) ||
|
||||
name.equals(outgoingServerHost, ignoreCase = true)
|
||||
) {
|
||||
// TODO: localize this string
|
||||
altNamesText.append("Subject(alt): ").append(name).append(",...\n")
|
||||
} else if (name.startsWith("*.") &&
|
||||
(
|
||||
incomingServerHost.endsWith(name.substring(2)) ||
|
||||
outgoingServerHost.endsWith(name.substring(2))
|
||||
)
|
||||
) {
|
||||
// TODO: localize this string
|
||||
altNamesText.append("Subject(alt): ").append(name).append(",...\n")
|
||||
}
|
||||
}
|
||||
chainInfo.append(altNamesText)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// don't fail just because of subjectAltNames
|
||||
Timber.w(e, "cannot display SubjectAltNames in dialog")
|
||||
}
|
||||
|
||||
chainInfo.append("Issuer: ").append(chain[i].issuerDN.toString()).append("\n")
|
||||
for (algorithm in arrayOf("SHA-1", "SHA-256", "SHA-512")) {
|
||||
val digest = try {
|
||||
MessageDigest.getInstance(algorithm)
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
Timber.e(e, "Error while initializing MessageDigest ($algorithm)")
|
||||
null
|
||||
}
|
||||
|
||||
if (digest != null) {
|
||||
digest.reset()
|
||||
try {
|
||||
val hash = Hex.encodeHex(digest.digest(chain[i].encoded))
|
||||
chainInfo.append("Fingerprint ($algorithm): ").append("\n").append(hash).append("\n")
|
||||
} catch (e: CertificateEncodingException) {
|
||||
Timber.e(e, "Error while encoding certificate")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor with DialogFragment.
|
||||
// This is difficult because we need to pass through chain[0] for onClick()
|
||||
AlertDialog.Builder(this@AccountSetupCheckSettings)
|
||||
.setTitle(getString(R.string.account_setup_failed_dlg_invalid_certificate_title))
|
||||
.setMessage(getString(msgResId, errorMessage) + " " + chainInfo.toString())
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.account_setup_failed_dlg_invalid_certificate_accept) { _, _ ->
|
||||
acceptCertificate(chain[0])
|
||||
}
|
||||
.setNegativeButton(R.string.account_setup_failed_dlg_invalid_certificate_reject) { _, _ ->
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently accepts a certificate for the INCOMING or OUTGOING direction by adding it to the local key store.
|
||||
*/
|
||||
private fun acceptCertificate(certificate: X509Certificate) {
|
||||
try {
|
||||
localKeyStoreManager.addCertificate(account, direction.toMailServerDirection(), certificate)
|
||||
} catch (e: CertificateException) {
|
||||
showErrorDialog(R.string.account_setup_failed_dlg_certificate_message_fmt, e.message.orEmpty())
|
||||
}
|
||||
|
||||
actionCheckSettings(this@AccountSetupCheckSettings, account, direction)
|
||||
}
|
||||
|
||||
override fun onActivityResult(reqCode: Int, resCode: Int, data: Intent?) {
|
||||
if (reqCode == ACTIVITY_REQUEST_CODE) {
|
||||
setResult(resCode)
|
||||
finish()
|
||||
} else {
|
||||
super.onActivityResult(reqCode, resCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCancel() {
|
||||
canceled = true
|
||||
setMessage(R.string.account_setup_check_settings_canceling_msg)
|
||||
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun showErrorDialog(msgResId: Int, vararg args: Any) {
|
||||
handler.post {
|
||||
showDialogFragment(R.id.dialog_account_setup_error, getString(msgResId, *args))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDialogFragment(dialogId: Int, customMessage: String) {
|
||||
if (destroyed) return
|
||||
|
||||
progressBar.isIndeterminate = false
|
||||
|
||||
val fragment: DialogFragment = if (dialogId == R.id.dialog_account_setup_error) {
|
||||
ConfirmationDialogFragment.newInstance(
|
||||
dialogId,
|
||||
getString(R.string.account_setup_failed_dlg_title),
|
||||
customMessage,
|
||||
getString(R.string.account_setup_failed_dlg_edit_details_action),
|
||||
getString(R.string.account_setup_failed_dlg_continue_action),
|
||||
)
|
||||
} else {
|
||||
throw RuntimeException("Called showDialog(int) with unknown dialog id.")
|
||||
}
|
||||
|
||||
// TODO: commitAllowingStateLoss() is used to prevent https://code.google.com/p/android/issues/detail?id=23761
|
||||
// but is a bad...
|
||||
supportFragmentManager.commit(allowStateLoss = true) {
|
||||
add(fragment, getDialogTag(dialogId))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDialogTag(dialogId: Int): String {
|
||||
return String.format(Locale.US, "dialog-%d", dialogId)
|
||||
}
|
||||
|
||||
override fun doPositiveClick(dialogId: Int) {
|
||||
if (dialogId == R.id.dialog_account_setup_error) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun doNegativeClick(dialogId: Int) {
|
||||
if (dialogId == R.id.dialog_account_setup_error) {
|
||||
canceled = false
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun dialogCancelled(dialogId: Int) = Unit
|
||||
|
||||
private fun errorMessageForCertificateException(e: CertificateValidationException): String? {
|
||||
return when (e.reason) {
|
||||
CertificateValidationException.Reason.Expired -> {
|
||||
getString(R.string.client_certificate_expired, e.alias, e.message)
|
||||
}
|
||||
CertificateValidationException.Reason.MissingCapability -> {
|
||||
getString(R.string.auth_external_error)
|
||||
}
|
||||
CertificateValidationException.Reason.RetrievalFailure -> {
|
||||
getString(R.string.client_certificate_retrieval_failure, e.alias)
|
||||
}
|
||||
CertificateValidationException.Reason.UseMessage -> {
|
||||
e.message
|
||||
}
|
||||
else -> {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME: Don't use an AsyncTask to perform network operations.
|
||||
* See also discussion in https://github.com/thunderbird/thunderbird-android/pull/560
|
||||
*/
|
||||
private inner class CheckAccountTask(private val account: Account) : AsyncTask<CheckDirection, Int, Unit>() {
|
||||
override fun doInBackground(vararg params: CheckDirection) {
|
||||
val direction = params[0]
|
||||
try {
|
||||
/*
|
||||
* This task could be interrupted at any point, but network operations can block,
|
||||
* so relying on InterruptedException is not enough. Instead, check after
|
||||
* each potentially long-running operation.
|
||||
*/
|
||||
if (isCanceled()) {
|
||||
return
|
||||
}
|
||||
|
||||
clearCertificateErrorNotifications(direction)
|
||||
|
||||
checkServerSettings(direction)
|
||||
|
||||
if (isCanceled()) {
|
||||
return
|
||||
}
|
||||
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
} catch (e: AuthenticationFailedException) {
|
||||
Timber.e(e, "Error while testing settings")
|
||||
showErrorDialog(R.string.account_setup_failed_dlg_auth_message_fmt, e.messageFromServer.orEmpty())
|
||||
} catch (e: CertificateValidationException) {
|
||||
handleCertificateValidationException(e)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error while testing settings")
|
||||
showErrorDialog(R.string.account_setup_failed_dlg_server_message_fmt, e.message.orEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearCertificateErrorNotifications(direction: CheckDirection) {
|
||||
val incoming = direction == CheckDirection.INCOMING
|
||||
messagingController.clearCertificateErrorNotifications(account, incoming)
|
||||
}
|
||||
|
||||
private fun isCanceled(): Boolean {
|
||||
if (destroyed) return true
|
||||
|
||||
if (canceled) {
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkServerSettings(direction: CheckDirection) {
|
||||
when (direction) {
|
||||
CheckDirection.INCOMING -> checkIncoming()
|
||||
CheckDirection.OUTGOING -> checkOutgoing()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkOutgoing() {
|
||||
publishProgress(R.string.account_setup_check_settings_check_outgoing_msg)
|
||||
|
||||
messagingController.checkOutgoingServerSettings(account)
|
||||
}
|
||||
|
||||
private fun checkIncoming() {
|
||||
publishProgress(R.string.account_setup_check_settings_check_incoming_msg)
|
||||
|
||||
messagingController.checkIncomingServerSettings(account)
|
||||
|
||||
messagingController.refreshFolderListSynchronous(account)
|
||||
|
||||
val inboxFolderId = account.inboxFolderId
|
||||
if (inboxFolderId != null) {
|
||||
messagingController.synchronizeMailbox(account, inboxFolderId, false, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProgressUpdate(vararg values: Int?) {
|
||||
setMessage(values[0]!!)
|
||||
}
|
||||
}
|
||||
|
||||
enum class CheckDirection {
|
||||
INCOMING, OUTGOING;
|
||||
|
||||
fun toMailServerDirection(): MailServerDirection {
|
||||
return when (this) {
|
||||
INCOMING -> MailServerDirection.INCOMING
|
||||
OUTGOING -> MailServerDirection.OUTGOING
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ACTIVITY_REQUEST_CODE = 1
|
||||
|
||||
private const val EXTRA_ACCOUNT = "account"
|
||||
private const val EXTRA_CHECK_DIRECTION = "checkDirection"
|
||||
|
||||
@JvmStatic
|
||||
fun actionCheckSettings(context: Activity, account: Account, direction: CheckDirection) {
|
||||
val intent = Intent(context, AccountSetupCheckSettings::class.java).apply {
|
||||
putExtra(EXTRA_ACCOUNT, account.uuid)
|
||||
putExtra(EXTRA_CHECK_DIRECTION, direction)
|
||||
}
|
||||
|
||||
context.startActivityForResult(intent, ACTIVITY_REQUEST_CODE)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,575 +0,0 @@
|
|||
package com.fsck.k9.activity.setup;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.DigitsKeyListener;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import app.k9mail.core.common.mail.Protocols;
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.DI;
|
||||
import com.fsck.k9.LocalKeyStoreManager;
|
||||
import com.fsck.k9.Preferences;
|
||||
import com.fsck.k9.account.AccountCreatorHelper;
|
||||
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
|
||||
import com.fsck.k9.helper.Utility;
|
||||
import com.fsck.k9.mail.AuthType;
|
||||
import com.fsck.k9.mail.ConnectionSecurity;
|
||||
import com.fsck.k9.mail.MailServerDirection;
|
||||
import com.fsck.k9.mail.ServerSettings;
|
||||
import com.fsck.k9.mail.store.imap.ImapStoreSettings;
|
||||
import com.fsck.k9.ui.R;
|
||||
import com.fsck.k9.ui.base.K9Activity;
|
||||
import com.fsck.k9.ui.base.extensions.TextInputLayoutHelper;
|
||||
import com.fsck.k9.view.ClientCertificateSpinner;
|
||||
import com.fsck.k9.view.ClientCertificateSpinner.OnClientCertificateChangedListener;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
|
||||
@Deprecated(since = "Remove once the new account edit feature is the default")
|
||||
public class AccountSetupIncoming extends K9Activity implements OnClickListener {
|
||||
private static final String EXTRA_ACCOUNT = "account";
|
||||
private static final String STATE_SECURITY_TYPE_POSITION = "stateSecurityTypePosition";
|
||||
private static final String STATE_AUTH_TYPE_POSITION = "authTypePosition";
|
||||
|
||||
private final AccountCreatorHelper accountCreatorHelper = DI.get(AccountCreatorHelper.class);
|
||||
|
||||
private String mStoreType;
|
||||
private TextInputEditText mUsernameView;
|
||||
private TextInputEditText mPasswordView;
|
||||
private ClientCertificateSpinner mClientCertificateSpinner;
|
||||
private TextInputLayout mPasswordLayoutView;
|
||||
private TextInputEditText mServerView;
|
||||
private TextInputEditText mPortView;
|
||||
private String mCurrentPortViewSetting;
|
||||
private Spinner mSecurityTypeView;
|
||||
private int mCurrentSecurityTypeViewPosition;
|
||||
private Spinner mAuthTypeView;
|
||||
private int mCurrentAuthTypeViewPosition;
|
||||
private CheckBox mImapAutoDetectNamespaceView;
|
||||
private TextInputEditText mImapPathPrefixView;
|
||||
private ViewGroup mAllowClientCertificateView;
|
||||
private Button mNextButton;
|
||||
private Account mAccount;
|
||||
private CheckBox useCompressionCheckBox;
|
||||
private CheckBox isSendClientIdEnabledCheckBox;
|
||||
private AuthTypeAdapter mAuthTypeAdapter;
|
||||
private ConnectionSecurity[] mConnectionSecurityChoices = ConnectionSecurity.values();
|
||||
|
||||
public static void actionEditIncomingSettings(Context context, String accountUuid) {
|
||||
Intent intent = new Intent(context, AccountSetupIncoming.class);
|
||||
intent.setAction(Intent.ACTION_EDIT);
|
||||
intent.putExtra(EXTRA_ACCOUNT, accountUuid);
|
||||
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static Intent intentActionEditIncomingSettings(Context context, Account account) {
|
||||
Intent i = new Intent(context, AccountSetupIncoming.class);
|
||||
i.setAction(Intent.ACTION_EDIT);
|
||||
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setLayout(R.layout.account_setup_incoming);
|
||||
setTitle(R.string.account_setup_incoming_title);
|
||||
|
||||
mUsernameView = findViewById(R.id.account_username);
|
||||
mPasswordView = findViewById(R.id.account_password);
|
||||
mClientCertificateSpinner = findViewById(R.id.account_client_certificate_spinner);
|
||||
mPasswordLayoutView = findViewById(R.id.account_password_layout);
|
||||
mServerView = findViewById(R.id.account_server);
|
||||
mPortView = findViewById(R.id.account_port);
|
||||
mSecurityTypeView = findViewById(R.id.account_security_type);
|
||||
mAuthTypeView = findViewById(R.id.account_auth_type);
|
||||
mImapAutoDetectNamespaceView = findViewById(R.id.imap_autodetect_namespace);
|
||||
mImapPathPrefixView = findViewById(R.id.imap_path_prefix);
|
||||
mNextButton = findViewById(R.id.next);
|
||||
useCompressionCheckBox = findViewById(R.id.use_compression);
|
||||
isSendClientIdEnabledCheckBox = findViewById(R.id.is_send_client_id_enabled);
|
||||
mAllowClientCertificateView = findViewById(R.id.account_allow_client_certificate);
|
||||
|
||||
TextInputLayout serverLayoutView = findViewById(R.id.account_server_layout);
|
||||
|
||||
mNextButton.setOnClickListener(this);
|
||||
|
||||
mImapAutoDetectNamespaceView.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
mImapPathPrefixView.setEnabled(!isChecked);
|
||||
if (isChecked && mImapPathPrefixView.hasFocus()) {
|
||||
mImapPathPrefixView.focusSearch(View.FOCUS_UP).requestFocus();
|
||||
} else if (!isChecked) {
|
||||
mImapPathPrefixView.requestFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Only allow digits in the port field.
|
||||
*/
|
||||
mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
|
||||
|
||||
String accountUuid = getIntent().getStringExtra(EXTRA_ACCOUNT);
|
||||
mAccount = Preferences.getPreferences().getAccount(accountUuid);
|
||||
|
||||
/*
|
||||
* If we're being reloaded we override the original account with the one
|
||||
* we saved
|
||||
*/
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
|
||||
accountUuid = savedInstanceState.getString(EXTRA_ACCOUNT);
|
||||
mAccount = Preferences.getPreferences().getAccount(accountUuid);
|
||||
}
|
||||
|
||||
boolean oAuthSupported = mAccount.getIncomingServerSettings().type.equals(Protocols.IMAP);
|
||||
mAuthTypeAdapter = AuthTypeAdapter.get(this, oAuthSupported);
|
||||
mAuthTypeView.setAdapter(mAuthTypeAdapter);
|
||||
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
try {
|
||||
ServerSettings settings = mAccount.getIncomingServerSettings();
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
// The first item is selected if settings.authenticationType is null or is not in mAuthTypeAdapter
|
||||
mCurrentAuthTypeViewPosition = mAuthTypeAdapter.getAuthPosition(settings.authenticationType);
|
||||
} else {
|
||||
mCurrentAuthTypeViewPosition = savedInstanceState.getInt(STATE_AUTH_TYPE_POSITION);
|
||||
}
|
||||
mAuthTypeView.setSelection(mCurrentAuthTypeViewPosition, false);
|
||||
updateViewFromAuthType();
|
||||
|
||||
mUsernameView.setText(settings.username);
|
||||
|
||||
if (settings.password != null) {
|
||||
mPasswordView.setText(settings.password);
|
||||
}
|
||||
|
||||
if (settings.clientCertificateAlias != null) {
|
||||
mClientCertificateSpinner.setAlias(settings.clientCertificateAlias);
|
||||
}
|
||||
|
||||
mStoreType = settings.type;
|
||||
if (settings.type.equals(Protocols.POP3)) {
|
||||
serverLayoutView.setHint(getString(R.string.account_setup_incoming_pop_server_label));
|
||||
findViewById(R.id.imap_path_prefix_section).setVisibility(View.GONE);
|
||||
useCompressionCheckBox.setVisibility(View.GONE);
|
||||
isSendClientIdEnabledCheckBox.setVisibility(View.GONE);
|
||||
} else if (settings.type.equals(Protocols.IMAP)) {
|
||||
serverLayoutView.setHint(getString(R.string.account_setup_incoming_imap_server_label));
|
||||
|
||||
boolean autoDetectNamespace = ImapStoreSettings.getAutoDetectNamespace(settings);
|
||||
String pathPrefix = ImapStoreSettings.getPathPrefix(settings);
|
||||
|
||||
mImapAutoDetectNamespaceView.setChecked(autoDetectNamespace);
|
||||
if (pathPrefix != null) {
|
||||
mImapPathPrefixView.setText(pathPrefix);
|
||||
}
|
||||
} else {
|
||||
throw new Exception("Unknown account type: " + settings.type);
|
||||
}
|
||||
|
||||
// Note that mConnectionSecurityChoices is configured above based on server type
|
||||
ConnectionSecurityAdapter securityTypesAdapter =
|
||||
ConnectionSecurityAdapter.get(this, mConnectionSecurityChoices);
|
||||
mSecurityTypeView.setAdapter(securityTypesAdapter);
|
||||
|
||||
// Select currently configured security type
|
||||
if (savedInstanceState == null) {
|
||||
mCurrentSecurityTypeViewPosition = securityTypesAdapter.getConnectionSecurityPosition(settings.connectionSecurity);
|
||||
} else {
|
||||
|
||||
/*
|
||||
* Restore the spinner state now, before calling
|
||||
* setOnItemSelectedListener(), thus avoiding a call to
|
||||
* onItemSelected(). Then, when the system restores the state
|
||||
* (again) in onRestoreInstanceState(), The system will see that
|
||||
* the new state is the same as the current state (set here), so
|
||||
* once again onItemSelected() will not be called.
|
||||
*/
|
||||
mCurrentSecurityTypeViewPosition = savedInstanceState.getInt(STATE_SECURITY_TYPE_POSITION);
|
||||
}
|
||||
mSecurityTypeView.setSelection(mCurrentSecurityTypeViewPosition, false);
|
||||
|
||||
updateAuthPlainTextFromSecurityType(settings.connectionSecurity);
|
||||
updateViewFromSecurity();
|
||||
|
||||
useCompressionCheckBox.setChecked(mAccount.useCompression());
|
||||
isSendClientIdEnabledCheckBox.setChecked(mAccount.isSendClientIdEnabled());
|
||||
|
||||
if (settings.host != null) {
|
||||
mServerView.setText(settings.host);
|
||||
}
|
||||
|
||||
if (settings.port != -1) {
|
||||
mPortView.setText(String.format(Locale.ROOT, "%d", settings.port));
|
||||
} else {
|
||||
updatePortFromSecurityType();
|
||||
}
|
||||
mCurrentPortViewSetting = mPortView.getText().toString();
|
||||
} catch (Exception e) {
|
||||
failure(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end of either {@code onCreate()} or
|
||||
* {@code onRestoreInstanceState()}, after the views have been initialized,
|
||||
* so that the listeners are not triggered during the view initialization.
|
||||
* This avoids needless calls to {@code validateFields()} which is called
|
||||
* immediately after this is called.
|
||||
*/
|
||||
private void initializeViewListeners() {
|
||||
|
||||
/*
|
||||
* Updates the port when the user changes the security type. This allows
|
||||
* us to show a reasonable default which the user can change.
|
||||
*/
|
||||
mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
|
||||
/*
|
||||
* We keep our own record of the spinner state so we
|
||||
* know for sure that onItemSelected() was called
|
||||
* because of user input, not because of spinner
|
||||
* state initialization. This assures that the port
|
||||
* will not be replaced with a default value except
|
||||
* on user input.
|
||||
*/
|
||||
if (mCurrentSecurityTypeViewPosition != position) {
|
||||
updatePortFromSecurityType();
|
||||
validateFields();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) { /* unused */ }
|
||||
});
|
||||
|
||||
mAuthTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
if (mCurrentAuthTypeViewPosition == position) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateViewFromAuthType();
|
||||
updateViewFromSecurity();
|
||||
validateFields();
|
||||
AuthType selection = getSelectedAuthType();
|
||||
|
||||
// Have the user select the client certificate if not already selected
|
||||
if ((AuthType.EXTERNAL == selection) && (mClientCertificateSpinner.getAlias() == null)) {
|
||||
// This may again invoke validateFields()
|
||||
mClientCertificateSpinner.chooseCertificate();
|
||||
} else {
|
||||
mPasswordView.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) { /* unused */ }
|
||||
});
|
||||
|
||||
mClientCertificateSpinner.setOnClientCertificateChangedListener(clientCertificateChangedListener);
|
||||
mUsernameView.addTextChangedListener(validationTextWatcher);
|
||||
mPasswordView.addTextChangedListener(validationTextWatcher);
|
||||
mServerView.addTextChangedListener(validationTextWatcher);
|
||||
mPortView.addTextChangedListener(validationTextWatcher);
|
||||
|
||||
TextInputLayoutHelper.configureAuthenticatedPasswordToggle(
|
||||
mPasswordLayoutView,
|
||||
this,
|
||||
getString(R.string.account_setup_basics_show_password_biometrics_title),
|
||||
getString(R.string.account_setup_basics_show_password_biometrics_subtitle),
|
||||
getString(R.string.account_setup_basics_show_password_need_lock)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(EXTRA_ACCOUNT, mAccount.getUuid());
|
||||
outState.putInt(STATE_SECURITY_TYPE_POSITION, mCurrentSecurityTypeViewPosition);
|
||||
outState.putInt(STATE_AUTH_TYPE_POSITION, mCurrentAuthTypeViewPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
|
||||
/*
|
||||
* We didn't want the listeners active while the state was being restored
|
||||
* because they could overwrite the restored port with a default port when
|
||||
* the security type was restored.
|
||||
*/
|
||||
initializeViewListeners();
|
||||
validateFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides password field and client certificate spinner
|
||||
*/
|
||||
private void updateViewFromAuthType() {
|
||||
switch (getSelectedAuthType()) {
|
||||
case EXTERNAL:
|
||||
case XOAUTH2:
|
||||
mPasswordLayoutView.setVisibility(View.GONE);
|
||||
break;
|
||||
default:
|
||||
mPasswordLayoutView.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows/hides client certificate spinner
|
||||
*/
|
||||
private void updateViewFromSecurity() {
|
||||
ConnectionSecurity security = getSelectedSecurity();
|
||||
boolean isUsingTLS = ((ConnectionSecurity.SSL_TLS_REQUIRED == security) || (ConnectionSecurity.STARTTLS_REQUIRED == security));
|
||||
boolean isUsingOAuth = getSelectedAuthType() == AuthType.XOAUTH2;
|
||||
|
||||
if (isUsingTLS && !isUsingOAuth) {
|
||||
mAllowClientCertificateView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mAllowClientCertificateView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is invoked only when the user makes changes to a widget, not when
|
||||
* widgets are changed programmatically. (The logic is simpler when you know
|
||||
* that this is the last thing called after an input change.)
|
||||
*/
|
||||
private void validateFields() {
|
||||
AuthType authType = getSelectedAuthType();
|
||||
boolean isAuthTypeExternal = (AuthType.EXTERNAL == authType);
|
||||
|
||||
ConnectionSecurity connectionSecurity = getSelectedSecurity();
|
||||
boolean hasConnectionSecurity = (connectionSecurity != ConnectionSecurity.NONE);
|
||||
|
||||
if (isAuthTypeExternal && !hasConnectionSecurity) {
|
||||
|
||||
// Notify user of an invalid combination of AuthType.EXTERNAL & ConnectionSecurity.NONE
|
||||
String toastText = getString(R.string.account_setup_incoming_invalid_setting_combo_notice,
|
||||
getString(R.string.account_setup_incoming_auth_type_label),
|
||||
AuthType.EXTERNAL.toString(),
|
||||
getString(R.string.account_setup_incoming_security_label),
|
||||
ConnectionSecurity.NONE.toString());
|
||||
Toast.makeText(this, toastText, Toast.LENGTH_LONG).show();
|
||||
|
||||
// Reset the views back to their previous settings without recursing through here again
|
||||
OnItemSelectedListener onItemSelectedListener = mAuthTypeView.getOnItemSelectedListener();
|
||||
mAuthTypeView.setOnItemSelectedListener(null);
|
||||
mAuthTypeView.setSelection(mCurrentAuthTypeViewPosition, false);
|
||||
mAuthTypeView.setOnItemSelectedListener(onItemSelectedListener);
|
||||
updateViewFromAuthType();
|
||||
updateViewFromSecurity();
|
||||
|
||||
onItemSelectedListener = mSecurityTypeView.getOnItemSelectedListener();
|
||||
mSecurityTypeView.setOnItemSelectedListener(null);
|
||||
mSecurityTypeView.setSelection(mCurrentSecurityTypeViewPosition, false);
|
||||
mSecurityTypeView.setOnItemSelectedListener(onItemSelectedListener);
|
||||
updateAuthPlainTextFromSecurityType(getSelectedSecurity());
|
||||
updateViewFromSecurity();
|
||||
|
||||
mPortView.removeTextChangedListener(validationTextWatcher);
|
||||
mPortView.setText(mCurrentPortViewSetting);
|
||||
mPortView.addTextChangedListener(validationTextWatcher);
|
||||
|
||||
authType = getSelectedAuthType();
|
||||
isAuthTypeExternal = (AuthType.EXTERNAL == authType);
|
||||
|
||||
connectionSecurity = getSelectedSecurity();
|
||||
hasConnectionSecurity = (connectionSecurity != ConnectionSecurity.NONE);
|
||||
} else {
|
||||
mCurrentAuthTypeViewPosition = mAuthTypeView.getSelectedItemPosition();
|
||||
mCurrentSecurityTypeViewPosition = mSecurityTypeView.getSelectedItemPosition();
|
||||
mCurrentPortViewSetting = mPortView.getText().toString();
|
||||
}
|
||||
|
||||
boolean hasValidCertificateAlias = mClientCertificateSpinner.getAlias() != null;
|
||||
boolean hasValidUserName = Utility.requiredFieldValid(mUsernameView);
|
||||
|
||||
boolean hasValidPasswordSettings = hasValidUserName
|
||||
&& !isAuthTypeExternal
|
||||
&& Utility.requiredFieldValid(mPasswordView);
|
||||
|
||||
boolean hasValidExternalAuthSettings = hasValidUserName
|
||||
&& isAuthTypeExternal
|
||||
&& hasConnectionSecurity
|
||||
&& hasValidCertificateAlias;
|
||||
|
||||
boolean hasValidOAuthSettings = hasValidUserName
|
||||
&& hasConnectionSecurity
|
||||
&& authType == AuthType.XOAUTH2;
|
||||
|
||||
mNextButton.setEnabled(Utility.domainFieldValid(mServerView)
|
||||
&& Utility.requiredFieldValid(mPortView)
|
||||
&& (hasValidPasswordSettings || hasValidExternalAuthSettings || hasValidOAuthSettings));
|
||||
Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128);
|
||||
}
|
||||
|
||||
private void updatePortFromSecurityType() {
|
||||
ConnectionSecurity securityType = getSelectedSecurity();
|
||||
updateAuthPlainTextFromSecurityType(securityType);
|
||||
updateViewFromSecurity();
|
||||
|
||||
// Remove listener so as not to trigger validateFields() which is called
|
||||
// elsewhere as a result of user interaction.
|
||||
mPortView.removeTextChangedListener(validationTextWatcher);
|
||||
mPortView.setText(String.valueOf(accountCreatorHelper.getDefaultPort(securityType, mStoreType)));
|
||||
mPortView.addTextChangedListener(validationTextWatcher);
|
||||
}
|
||||
|
||||
private void updateAuthPlainTextFromSecurityType(ConnectionSecurity securityType) {
|
||||
mAuthTypeAdapter.useInsecureText(securityType == ConnectionSecurity.NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode != AccountSetupCheckSettings.ACTIVITY_REQUEST_CODE) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
Preferences.getPreferences().saveAccount(mAccount);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
protected void onNext() {
|
||||
try {
|
||||
ConnectionSecurity connectionSecurity = getSelectedSecurity();
|
||||
|
||||
String username = mUsernameView.getText().toString().trim();
|
||||
String password = null;
|
||||
String clientCertificateAlias = null;
|
||||
|
||||
AuthType authType = getSelectedAuthType();
|
||||
|
||||
if ((ConnectionSecurity.SSL_TLS_REQUIRED == connectionSecurity) ||
|
||||
(ConnectionSecurity.STARTTLS_REQUIRED == connectionSecurity) ) {
|
||||
clientCertificateAlias = mClientCertificateSpinner.getAlias();
|
||||
}
|
||||
if (authType != AuthType.EXTERNAL) {
|
||||
password = mPasswordView.getText().toString();
|
||||
}
|
||||
String host = mServerView.getText().toString();
|
||||
int port = Integer.parseInt(mPortView.getText().toString());
|
||||
|
||||
Map<String, String> extra = emptyMap();
|
||||
if (mStoreType.equals(Protocols.IMAP)) {
|
||||
boolean autoDetectNamespace = mImapAutoDetectNamespaceView.isChecked();
|
||||
String pathPrefix = mImapPathPrefixView.getText().toString();
|
||||
extra = ImapStoreSettings.createExtra(autoDetectNamespace, pathPrefix);
|
||||
}
|
||||
|
||||
DI.get(LocalKeyStoreManager.class).deleteCertificate(mAccount, host, port, MailServerDirection.INCOMING);
|
||||
ServerSettings settings = new ServerSettings(mStoreType, host, port,
|
||||
connectionSecurity, authType, username, password, clientCertificateAlias, extra);
|
||||
|
||||
mAccount.setIncomingServerSettings(settings);
|
||||
|
||||
mAccount.setUseCompression(useCompressionCheckBox.isChecked());
|
||||
mAccount.setSendClientIdEnabled(isSendClientIdEnabledCheckBox.isChecked());
|
||||
|
||||
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.INCOMING);
|
||||
} catch (Exception e) {
|
||||
failure(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
if (v.getId() == R.id.next) {
|
||||
onNext();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void failure(Exception use) {
|
||||
Timber.e(use, "Failure");
|
||||
String toastText = getString(R.string.account_setup_bad_uri, use.getMessage());
|
||||
|
||||
Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG);
|
||||
toast.show();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Calls validateFields() which enables or disables the Next button
|
||||
* based on the fields' validity.
|
||||
*/
|
||||
TextWatcher validationTextWatcher = new TextWatcher() {
|
||||
public void afterTextChanged(Editable s) {
|
||||
validateFields();
|
||||
}
|
||||
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
/* unused */
|
||||
}
|
||||
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
/* unused */
|
||||
}
|
||||
};
|
||||
|
||||
OnClientCertificateChangedListener clientCertificateChangedListener = alias -> validateFields();
|
||||
|
||||
private AuthType getSelectedAuthType() {
|
||||
AuthTypeHolder holder = (AuthTypeHolder) mAuthTypeView.getSelectedItem();
|
||||
return holder.authType;
|
||||
}
|
||||
|
||||
private ConnectionSecurity getSelectedSecurity() {
|
||||
ConnectionSecurityHolder holder = (ConnectionSecurityHolder) mSecurityTypeView.getSelectedItem();
|
||||
return holder.connectionSecurity;
|
||||
}
|
||||
}
|
|
@ -1,561 +0,0 @@
|
|||
package com.fsck.k9.activity.setup;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.DigitsKeyListener;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.DI;
|
||||
import com.fsck.k9.LocalKeyStoreManager;
|
||||
import com.fsck.k9.Preferences;
|
||||
import app.k9mail.core.common.mail.Protocols;
|
||||
import com.fsck.k9.ui.R;
|
||||
import com.fsck.k9.account.AccountCreatorHelper;
|
||||
import com.fsck.k9.ui.base.K9Activity;
|
||||
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
|
||||
import com.fsck.k9.helper.Utility;
|
||||
import com.fsck.k9.mail.AuthType;
|
||||
import com.fsck.k9.mail.ConnectionSecurity;
|
||||
import com.fsck.k9.mail.MailServerDirection;
|
||||
import com.fsck.k9.mail.ServerSettings;
|
||||
import com.fsck.k9.ui.base.extensions.TextInputLayoutHelper;
|
||||
import com.fsck.k9.view.ClientCertificateSpinner;
|
||||
import com.fsck.k9.view.ClientCertificateSpinner.OnClientCertificateChangedListener;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import timber.log.Timber;
|
||||
|
||||
@Deprecated(since = "Remove once the new account edit feature is the default")
|
||||
public class AccountSetupOutgoing extends K9Activity implements OnClickListener,
|
||||
OnCheckedChangeListener {
|
||||
private static final String EXTRA_ACCOUNT = "account";
|
||||
|
||||
private static final String STATE_SECURITY_TYPE_POSITION = "stateSecurityTypePosition";
|
||||
private static final String STATE_AUTH_TYPE_POSITION = "authTypePosition";
|
||||
|
||||
|
||||
private final AccountCreatorHelper accountCreatorHelper = DI.get(AccountCreatorHelper.class);
|
||||
|
||||
private TextInputEditText mUsernameView;
|
||||
private TextInputEditText mPasswordView;
|
||||
private TextInputLayout mPasswordLayoutView;
|
||||
private ClientCertificateSpinner mClientCertificateSpinner;
|
||||
private TextInputEditText mServerView;
|
||||
private TextInputEditText mPortView;
|
||||
private String mCurrentPortViewSetting;
|
||||
private CheckBox mRequireLoginView;
|
||||
private ViewGroup mRequireLoginSettingsView;
|
||||
private ViewGroup mAllowClientCertificateView;
|
||||
|
||||
private Spinner mSecurityTypeView;
|
||||
private int mCurrentSecurityTypeViewPosition;
|
||||
private Spinner mAuthTypeView;
|
||||
private int mCurrentAuthTypeViewPosition;
|
||||
private AuthTypeAdapter mAuthTypeAdapter;
|
||||
private Button mNextButton;
|
||||
private Account mAccount;
|
||||
|
||||
public static Intent intentActionEditOutgoingSettings(Context context, Account account) {
|
||||
Intent i = new Intent(context, AccountSetupOutgoing.class);
|
||||
i.setAction(Intent.ACTION_EDIT);
|
||||
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
return i;
|
||||
}
|
||||
|
||||
public static void actionEditOutgoingSettings(Context context, String accountUuid) {
|
||||
Intent intent = new Intent(context, AccountSetupOutgoing.class);
|
||||
intent.setAction(Intent.ACTION_EDIT);
|
||||
intent.putExtra(EXTRA_ACCOUNT, accountUuid);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setLayout(R.layout.account_setup_outgoing);
|
||||
setTitle(R.string.account_setup_outgoing_title);
|
||||
|
||||
String accountUuid = getIntent().getStringExtra(EXTRA_ACCOUNT);
|
||||
mAccount = Preferences.getPreferences().getAccount(accountUuid);
|
||||
|
||||
ServerSettings incomingServerSettings = mAccount.getIncomingServerSettings();
|
||||
|
||||
mUsernameView = findViewById(R.id.account_username);
|
||||
mPasswordView = findViewById(R.id.account_password);
|
||||
mClientCertificateSpinner = findViewById(R.id.account_client_certificate_spinner);
|
||||
mPasswordLayoutView = findViewById(R.id.account_password_layout);
|
||||
mServerView = findViewById(R.id.account_server);
|
||||
mPortView = findViewById(R.id.account_port);
|
||||
mRequireLoginView = findViewById(R.id.account_require_login);
|
||||
mRequireLoginSettingsView = findViewById(R.id.account_require_login_settings);
|
||||
mAllowClientCertificateView = findViewById(R.id.account_allow_client_certificate);
|
||||
|
||||
mSecurityTypeView = findViewById(R.id.account_security_type);
|
||||
mAuthTypeView = findViewById(R.id.account_auth_type);
|
||||
mNextButton = findViewById(R.id.next);
|
||||
|
||||
mNextButton.setOnClickListener(this);
|
||||
|
||||
mSecurityTypeView.setAdapter(ConnectionSecurityAdapter.get(this));
|
||||
|
||||
mAuthTypeAdapter = AuthTypeAdapter.get(this, true);
|
||||
mAuthTypeView.setAdapter(mAuthTypeAdapter);
|
||||
|
||||
/*
|
||||
* Only allow digits in the port field.
|
||||
*/
|
||||
mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
|
||||
|
||||
//FIXME: get Account object again?
|
||||
accountUuid = getIntent().getStringExtra(EXTRA_ACCOUNT);
|
||||
mAccount = Preferences.getPreferences().getAccount(accountUuid);
|
||||
|
||||
/*
|
||||
* If we're being reloaded we override the original account with the one
|
||||
* we saved
|
||||
*/
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
|
||||
accountUuid = savedInstanceState.getString(EXTRA_ACCOUNT);
|
||||
mAccount = Preferences.getPreferences().getAccount(accountUuid);
|
||||
}
|
||||
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
try {
|
||||
ServerSettings settings = mAccount.getOutgoingServerSettings();
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
// The first item is selected if settings.authenticationType is null or is not in mAuthTypeAdapter
|
||||
mCurrentAuthTypeViewPosition = mAuthTypeAdapter.getAuthPosition(settings.authenticationType);
|
||||
} else {
|
||||
mCurrentAuthTypeViewPosition = savedInstanceState.getInt(STATE_AUTH_TYPE_POSITION);
|
||||
}
|
||||
mAuthTypeView.setSelection(mCurrentAuthTypeViewPosition, false);
|
||||
updateViewFromAuthType();
|
||||
|
||||
// Select currently configured security type
|
||||
if (savedInstanceState == null) {
|
||||
mCurrentSecurityTypeViewPosition = settings.connectionSecurity.ordinal();
|
||||
} else {
|
||||
|
||||
/*
|
||||
* Restore the spinner state now, before calling
|
||||
* setOnItemSelectedListener(), thus avoiding a call to
|
||||
* onItemSelected(). Then, when the system restores the state
|
||||
* (again) in onRestoreInstanceState(), The system will see that
|
||||
* the new state is the same as the current state (set here), so
|
||||
* once again onItemSelected() will not be called.
|
||||
*/
|
||||
mCurrentSecurityTypeViewPosition = savedInstanceState.getInt(STATE_SECURITY_TYPE_POSITION);
|
||||
}
|
||||
mSecurityTypeView.setSelection(mCurrentSecurityTypeViewPosition, false);
|
||||
|
||||
updateAuthPlainTextFromSecurityType(getSelectedSecurity());
|
||||
updateViewFromSecurity();
|
||||
|
||||
if (!settings.username.isEmpty()) {
|
||||
mUsernameView.setText(settings.username);
|
||||
mRequireLoginView.setChecked(true);
|
||||
mRequireLoginSettingsView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (settings.password != null) {
|
||||
mPasswordView.setText(settings.password);
|
||||
}
|
||||
|
||||
if (settings.clientCertificateAlias != null) {
|
||||
mClientCertificateSpinner.setAlias(settings.clientCertificateAlias);
|
||||
}
|
||||
|
||||
if (settings.host != null) {
|
||||
mServerView.setText(settings.host);
|
||||
}
|
||||
|
||||
if (settings.port != -1) {
|
||||
mPortView.setText(String.format(Locale.ROOT, "%d", settings.port));
|
||||
} else {
|
||||
updatePortFromSecurityType();
|
||||
}
|
||||
mCurrentPortViewSetting = mPortView.getText().toString();
|
||||
} catch (Exception e) {
|
||||
/*
|
||||
* We should always be able to parse our own settings.
|
||||
*/
|
||||
failure(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end of either {@code onCreate()} or
|
||||
* {@code onRestoreInstanceState()}, after the views have been initialized,
|
||||
* so that the listeners are not triggered during the view initialization.
|
||||
* This avoids needless calls to {@code validateFields()} which is called
|
||||
* immediately after this is called.
|
||||
*/
|
||||
private void initializeViewListeners() {
|
||||
|
||||
/*
|
||||
* Updates the port when the user changes the security type. This allows
|
||||
* us to show a reasonable default which the user can change.
|
||||
*/
|
||||
mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
|
||||
/*
|
||||
* We keep our own record of the spinner state so we
|
||||
* know for sure that onItemSelected() was called
|
||||
* because of user input, not because of spinner
|
||||
* state initialization. This assures that the port
|
||||
* will not be replaced with a default value except
|
||||
* on user input.
|
||||
*/
|
||||
if (mCurrentSecurityTypeViewPosition != position) {
|
||||
updatePortFromSecurityType();
|
||||
updateViewFromSecurity();
|
||||
boolean isInsecure = (ConnectionSecurity.NONE == getSelectedSecurity());
|
||||
boolean isAuthExternal = (AuthType.EXTERNAL == getSelectedAuthType());
|
||||
boolean loginNotRequired = !mRequireLoginView.isChecked();
|
||||
|
||||
/*
|
||||
* If the user selects ConnectionSecurity.NONE, a
|
||||
* warning would normally pop up if the authentication
|
||||
* is AuthType.EXTERNAL (i.e., using client
|
||||
* certificates). But such a warning is irrelevant if
|
||||
* login is not required. So to avoid such a warning
|
||||
* (generated in validateFields()) under those
|
||||
* conditions, we change the (irrelevant) authentication
|
||||
* method to PLAIN.
|
||||
*/
|
||||
if (isInsecure && isAuthExternal && loginNotRequired) {
|
||||
OnItemSelectedListener onItemSelectedListener = mAuthTypeView.getOnItemSelectedListener();
|
||||
mAuthTypeView.setOnItemSelectedListener(null);
|
||||
mCurrentAuthTypeViewPosition = mAuthTypeAdapter.getAuthPosition(AuthType.PLAIN);
|
||||
mAuthTypeView.setSelection(mCurrentAuthTypeViewPosition, false);
|
||||
mAuthTypeView.setOnItemSelectedListener(onItemSelectedListener);
|
||||
updateViewFromAuthType();
|
||||
}
|
||||
|
||||
validateFields();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) { /* unused */ }
|
||||
});
|
||||
|
||||
mAuthTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
if (mCurrentAuthTypeViewPosition == position) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateViewFromAuthType();
|
||||
updateViewFromSecurity();
|
||||
validateFields();
|
||||
AuthType selection = getSelectedAuthType();
|
||||
|
||||
// Have the user select the client certificate if not already selected
|
||||
if ((AuthType.EXTERNAL == selection) && (mClientCertificateSpinner.getAlias() == null)) {
|
||||
// This may again invoke validateFields()
|
||||
mClientCertificateSpinner.chooseCertificate();
|
||||
} else {
|
||||
mPasswordView.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) { /* unused */ }
|
||||
});
|
||||
|
||||
mRequireLoginView.setOnCheckedChangeListener(this);
|
||||
mClientCertificateSpinner.setOnClientCertificateChangedListener(clientCertificateChangedListener);
|
||||
mUsernameView.addTextChangedListener(validationTextWatcher);
|
||||
mPasswordView.addTextChangedListener(validationTextWatcher);
|
||||
mServerView.addTextChangedListener(validationTextWatcher);
|
||||
mPortView.addTextChangedListener(validationTextWatcher);
|
||||
|
||||
TextInputLayoutHelper.configureAuthenticatedPasswordToggle(
|
||||
mPasswordLayoutView,
|
||||
this,
|
||||
getString(R.string.account_setup_basics_show_password_biometrics_title),
|
||||
getString(R.string.account_setup_basics_show_password_biometrics_subtitle),
|
||||
getString(R.string.account_setup_basics_show_password_need_lock)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(EXTRA_ACCOUNT, mAccount.getUuid());
|
||||
outState.putInt(STATE_SECURITY_TYPE_POSITION, mCurrentSecurityTypeViewPosition);
|
||||
outState.putInt(STATE_AUTH_TYPE_POSITION, mCurrentAuthTypeViewPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
|
||||
if (mRequireLoginView.isChecked()) {
|
||||
mRequireLoginSettingsView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mRequireLoginSettingsView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
|
||||
/*
|
||||
* We didn't want the listeners active while the state was being restored
|
||||
* because they could overwrite the restored port with a default port when
|
||||
* the security type was restored.
|
||||
*/
|
||||
initializeViewListeners();
|
||||
validateFields();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides password field
|
||||
*/
|
||||
private void updateViewFromAuthType() {
|
||||
switch (getSelectedAuthType()) {
|
||||
case EXTERNAL:
|
||||
case XOAUTH2:
|
||||
mPasswordLayoutView.setVisibility(View.GONE);
|
||||
break;
|
||||
default:
|
||||
mPasswordLayoutView.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides client certificate spinner
|
||||
*/
|
||||
private void updateViewFromSecurity() {
|
||||
ConnectionSecurity security = getSelectedSecurity();
|
||||
boolean isUsingTLS = ((ConnectionSecurity.SSL_TLS_REQUIRED == security) || (ConnectionSecurity.STARTTLS_REQUIRED == security));
|
||||
boolean isUsingOAuth = getSelectedAuthType() == AuthType.XOAUTH2;
|
||||
|
||||
if (isUsingTLS && !isUsingOAuth) {
|
||||
mAllowClientCertificateView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mAllowClientCertificateView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is invoked only when the user makes changes to a widget, not when
|
||||
* widgets are changed programmatically. (The logic is simpler when you know
|
||||
* that this is the last thing called after an input change.)
|
||||
*/
|
||||
private void validateFields() {
|
||||
AuthType authType = getSelectedAuthType();
|
||||
boolean isAuthTypeExternal = (AuthType.EXTERNAL == authType);
|
||||
|
||||
ConnectionSecurity connectionSecurity = getSelectedSecurity();
|
||||
boolean hasConnectionSecurity = (connectionSecurity != ConnectionSecurity.NONE);
|
||||
|
||||
if (isAuthTypeExternal && !hasConnectionSecurity) {
|
||||
|
||||
// Notify user of an invalid combination of AuthType.EXTERNAL & ConnectionSecurity.NONE
|
||||
String toastText = getString(R.string.account_setup_outgoing_invalid_setting_combo_notice,
|
||||
getString(R.string.account_setup_incoming_auth_type_label),
|
||||
AuthType.EXTERNAL.toString(),
|
||||
getString(R.string.account_setup_incoming_security_label),
|
||||
ConnectionSecurity.NONE.toString());
|
||||
Toast.makeText(this, toastText, Toast.LENGTH_LONG).show();
|
||||
|
||||
// Reset the views back to their previous settings without recursing through here again
|
||||
OnItemSelectedListener onItemSelectedListener = mAuthTypeView.getOnItemSelectedListener();
|
||||
mAuthTypeView.setOnItemSelectedListener(null);
|
||||
mAuthTypeView.setSelection(mCurrentAuthTypeViewPosition, false);
|
||||
mAuthTypeView.setOnItemSelectedListener(onItemSelectedListener);
|
||||
updateViewFromAuthType();
|
||||
updateViewFromSecurity();
|
||||
|
||||
onItemSelectedListener = mSecurityTypeView.getOnItemSelectedListener();
|
||||
mSecurityTypeView.setOnItemSelectedListener(null);
|
||||
mSecurityTypeView.setSelection(mCurrentSecurityTypeViewPosition, false);
|
||||
mSecurityTypeView.setOnItemSelectedListener(onItemSelectedListener);
|
||||
updateAuthPlainTextFromSecurityType(getSelectedSecurity());
|
||||
|
||||
mPortView.removeTextChangedListener(validationTextWatcher);
|
||||
mPortView.setText(mCurrentPortViewSetting);
|
||||
mPortView.addTextChangedListener(validationTextWatcher);
|
||||
|
||||
authType = getSelectedAuthType();
|
||||
isAuthTypeExternal = (AuthType.EXTERNAL == authType);
|
||||
|
||||
connectionSecurity = getSelectedSecurity();
|
||||
hasConnectionSecurity = (connectionSecurity != ConnectionSecurity.NONE);
|
||||
} else {
|
||||
mCurrentAuthTypeViewPosition = mAuthTypeView.getSelectedItemPosition();
|
||||
mCurrentSecurityTypeViewPosition = mSecurityTypeView.getSelectedItemPosition();
|
||||
mCurrentPortViewSetting = mPortView.getText().toString();
|
||||
}
|
||||
|
||||
boolean hasValidCertificateAlias = mClientCertificateSpinner.getAlias() != null;
|
||||
boolean hasValidUserName = Utility.requiredFieldValid(mUsernameView);
|
||||
|
||||
boolean hasValidPasswordSettings = hasValidUserName
|
||||
&& !isAuthTypeExternal
|
||||
&& Utility.requiredFieldValid(mPasswordView);
|
||||
|
||||
boolean hasValidExternalAuthSettings = hasValidUserName
|
||||
&& isAuthTypeExternal
|
||||
&& hasConnectionSecurity
|
||||
&& hasValidCertificateAlias;
|
||||
|
||||
boolean hasValidOAuthSettings = hasValidUserName
|
||||
&& hasConnectionSecurity
|
||||
&& authType == AuthType.XOAUTH2;
|
||||
|
||||
mNextButton
|
||||
.setEnabled(Utility.domainFieldValid(mServerView)
|
||||
&& Utility.requiredFieldValid(mPortView)
|
||||
&& (!mRequireLoginView.isChecked()
|
||||
|| hasValidPasswordSettings || hasValidExternalAuthSettings || hasValidOAuthSettings));
|
||||
Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128);
|
||||
}
|
||||
|
||||
private void updatePortFromSecurityType() {
|
||||
ConnectionSecurity securityType = getSelectedSecurity();
|
||||
updateAuthPlainTextFromSecurityType(securityType);
|
||||
|
||||
// Remove listener so as not to trigger validateFields() which is called
|
||||
// elsewhere as a result of user interaction.
|
||||
mPortView.removeTextChangedListener(validationTextWatcher);
|
||||
mPortView.setText(String.valueOf(accountCreatorHelper.getDefaultPort(securityType, Protocols.SMTP)));
|
||||
mPortView.addTextChangedListener(validationTextWatcher);
|
||||
}
|
||||
|
||||
private void updateAuthPlainTextFromSecurityType(ConnectionSecurity securityType) {
|
||||
mAuthTypeAdapter.useInsecureText(securityType == ConnectionSecurity.NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode != AccountSetupCheckSettings.ACTIVITY_REQUEST_CODE) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
Preferences.getPreferences().saveAccount(mAccount);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onNext() {
|
||||
ConnectionSecurity securityType = getSelectedSecurity();
|
||||
String username = "";
|
||||
String password = null;
|
||||
String clientCertificateAlias = null;
|
||||
AuthType authType = AuthType.AUTOMATIC;
|
||||
if ((ConnectionSecurity.STARTTLS_REQUIRED == securityType) ||
|
||||
(ConnectionSecurity.SSL_TLS_REQUIRED == securityType)) {
|
||||
clientCertificateAlias = mClientCertificateSpinner.getAlias();
|
||||
}
|
||||
if (mRequireLoginView.isChecked()) {
|
||||
username = mUsernameView.getText().toString().trim();
|
||||
authType = getSelectedAuthType();
|
||||
|
||||
if (AuthType.EXTERNAL != authType) {
|
||||
password = mPasswordView.getText().toString();
|
||||
}
|
||||
}
|
||||
|
||||
String newHost = mServerView.getText().toString();
|
||||
int newPort = Integer.parseInt(mPortView.getText().toString());
|
||||
ServerSettings server = new ServerSettings(Protocols.SMTP, newHost, newPort, securityType, authType, username,
|
||||
password, clientCertificateAlias);
|
||||
DI.get(LocalKeyStoreManager.class).deleteCertificate(mAccount, newHost, newPort, MailServerDirection.OUTGOING);
|
||||
mAccount.setOutgoingServerSettings(server);
|
||||
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.OUTGOING);
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.next) {
|
||||
onNext();
|
||||
}
|
||||
}
|
||||
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
mRequireLoginSettingsView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
|
||||
validateFields();
|
||||
}
|
||||
|
||||
private void failure(Exception use) {
|
||||
Timber.e(use, "Failure");
|
||||
String toastText = getString(R.string.account_setup_bad_uri, use.getMessage());
|
||||
|
||||
Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG);
|
||||
toast.show();
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls validateFields() which enables or disables the Next button
|
||||
* based on the fields' validity.
|
||||
*/
|
||||
TextWatcher validationTextWatcher = new TextWatcher() {
|
||||
public void afterTextChanged(Editable s) {
|
||||
validateFields();
|
||||
}
|
||||
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
};
|
||||
|
||||
OnClientCertificateChangedListener clientCertificateChangedListener = alias -> validateFields();
|
||||
|
||||
private AuthType getSelectedAuthType() {
|
||||
AuthTypeHolder holder = (AuthTypeHolder) mAuthTypeView.getSelectedItem();
|
||||
return holder.authType;
|
||||
}
|
||||
|
||||
private ConnectionSecurity getSelectedSecurity() {
|
||||
ConnectionSecurityHolder holder = (ConnectionSecurityHolder) mSecurityTypeView.getSelectedItem();
|
||||
return holder.connectionSecurity;
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package com.fsck.k9.activity.setup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import com.fsck.k9.mail.AuthType;
|
||||
|
||||
|
||||
class AuthTypeAdapter extends ArrayAdapter<AuthTypeHolder> {
|
||||
public AuthTypeAdapter(Context context, int resource, AuthTypeHolder[] holders) {
|
||||
super(context, resource, holders);
|
||||
}
|
||||
|
||||
public static AuthTypeAdapter get(Context context, boolean oAuthSupported) {
|
||||
AuthType[] authTypes;
|
||||
if (oAuthSupported) {
|
||||
authTypes = new AuthType[] { AuthType.PLAIN, AuthType.CRAM_MD5, AuthType.EXTERNAL, AuthType.XOAUTH2 };
|
||||
} else {
|
||||
authTypes = new AuthType[] { AuthType.PLAIN, AuthType.CRAM_MD5, AuthType.EXTERNAL };
|
||||
}
|
||||
|
||||
AuthTypeHolder[] holders = new AuthTypeHolder[authTypes.length];
|
||||
for (int i = 0; i < authTypes.length; i++) {
|
||||
holders[i] = new AuthTypeHolder(authTypes[i], context.getResources());
|
||||
}
|
||||
AuthTypeAdapter authTypesAdapter = new AuthTypeAdapter(context,
|
||||
android.R.layout.simple_spinner_item, holders);
|
||||
authTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
return authTypesAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to select an appropriate localized text label for the
|
||||
* {@code AuthType.PLAIN} option presented to users.
|
||||
*
|
||||
* @param insecure
|
||||
* <p>
|
||||
* A value of {@code true} will use "Normal password".
|
||||
* <p>
|
||||
* A value of {@code false} will use
|
||||
* "Password, transmitted insecurely"
|
||||
*/
|
||||
public void useInsecureText(boolean insecure) {
|
||||
for (int i=0; i<getCount(); i++) {
|
||||
getItem(i).setInsecure(insecure);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public int getAuthPosition(AuthType authenticationType) {
|
||||
for (int i=0; i<getCount(); i++) {
|
||||
if (getItem(i).authType == authenticationType) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package com.fsck.k9.activity.setup;
|
||||
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.fsck.k9.ui.R;
|
||||
import com.fsck.k9.mail.AuthType;
|
||||
|
||||
class AuthTypeHolder {
|
||||
final AuthType authType;
|
||||
private final Resources resources;
|
||||
private boolean insecure;
|
||||
|
||||
public AuthTypeHolder(AuthType authType, Resources resources) {
|
||||
this.authType = authType;
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
public void setInsecure(boolean insecure) {
|
||||
this.insecure = insecure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final int resourceId = resourceId();
|
||||
if (resourceId == 0) {
|
||||
return authType.name();
|
||||
} else {
|
||||
return resources.getString(resourceId);
|
||||
}
|
||||
}
|
||||
|
||||
private int resourceId() {
|
||||
switch (authType) {
|
||||
case PLAIN:
|
||||
if (insecure) {
|
||||
return R.string.account_setup_auth_type_insecure_password;
|
||||
} else {
|
||||
return R.string.account_setup_auth_type_normal_password;
|
||||
}
|
||||
case CRAM_MD5:
|
||||
return R.string.account_setup_auth_type_encrypted_password;
|
||||
case EXTERNAL:
|
||||
return R.string.account_setup_auth_type_tls_client_certificate;
|
||||
case XOAUTH2:
|
||||
return R.string.account_setup_auth_type_oauth2;
|
||||
case AUTOMATIC:
|
||||
case LOGIN:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package com.fsck.k9.activity.setup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import com.fsck.k9.mail.ConnectionSecurity;
|
||||
|
||||
|
||||
class ConnectionSecurityAdapter extends ArrayAdapter<ConnectionSecurityHolder> {
|
||||
public ConnectionSecurityAdapter(Context context, int resource, ConnectionSecurityHolder[] securityTypes) {
|
||||
super(context, resource, securityTypes);
|
||||
}
|
||||
|
||||
public static ConnectionSecurityAdapter get(Context context) {
|
||||
return get(context, ConnectionSecurity.values());
|
||||
}
|
||||
|
||||
public static ConnectionSecurityAdapter get(Context context,
|
||||
ConnectionSecurity[] items) {
|
||||
ConnectionSecurityHolder[] holders = new ConnectionSecurityHolder[items.length];
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
holders[i] = new ConnectionSecurityHolder(items[i], context.getResources());
|
||||
}
|
||||
ConnectionSecurityAdapter securityTypesAdapter = new ConnectionSecurityAdapter(context,
|
||||
android.R.layout.simple_spinner_item, holders);
|
||||
securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
return securityTypesAdapter;
|
||||
}
|
||||
|
||||
public int getConnectionSecurityPosition(ConnectionSecurity connectionSecurity) {
|
||||
for (int i=0; i<getCount(); i++) {
|
||||
if (getItem(i).connectionSecurity == connectionSecurity) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package com.fsck.k9.activity.setup;
|
||||
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.fsck.k9.ui.R;
|
||||
import com.fsck.k9.mail.ConnectionSecurity;
|
||||
|
||||
class ConnectionSecurityHolder {
|
||||
final ConnectionSecurity connectionSecurity;
|
||||
private final Resources resources;
|
||||
|
||||
public ConnectionSecurityHolder(ConnectionSecurity connectionSecurity, Resources resources) {
|
||||
this.connectionSecurity = connectionSecurity;
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
final int resourceId = resourceId();
|
||||
if (resourceId == 0) {
|
||||
return connectionSecurity.name();
|
||||
} else {
|
||||
return resources.getString(resourceId);
|
||||
}
|
||||
}
|
||||
|
||||
private int resourceId() {
|
||||
switch (connectionSecurity) {
|
||||
case NONE: return R.string.account_setup_incoming_security_none_label;
|
||||
case STARTTLS_REQUIRED: return R.string.account_setup_incoming_security_tls_label;
|
||||
case SSL_TLS_REQUIRED: return R.string.account_setup_incoming_security_ssl_label;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package com.fsck.k9.activity.setup
|
||||
|
||||
@Deprecated("Could be removed with the legacy account setup UI")
|
||||
@Deprecated("Could be removed when import uses the new oauth flow")
|
||||
object GoogleOAuthHelper {
|
||||
fun isGoogle(hostname: String): Boolean {
|
||||
return hostname.lowercase().endsWith(".gmail.com") ||
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
package com.fsck.k9.activity.setup
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.fsck.k9.mail.AuthType
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class InitialAccountSettings(
|
||||
val authenticationType: AuthType,
|
||||
val email: String,
|
||||
val password: String?,
|
||||
val clientCertificateAlias: String?,
|
||||
) : Parcelable
|
|
@ -14,6 +14,7 @@ import com.fsck.k9.ui.observe
|
|||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@Deprecated("Remove once import used the new oauth flow")
|
||||
class OAuthFlowActivity : K9Activity() {
|
||||
private val authViewModel: AuthViewModel by viewModel()
|
||||
private val accountManager: AccountManager by inject()
|
||||
|
@ -59,19 +60,24 @@ class OAuthFlowActivity : K9Activity() {
|
|||
AuthFlowState.Idle -> {
|
||||
return
|
||||
}
|
||||
|
||||
AuthFlowState.Success -> {
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
|
||||
AuthFlowState.Canceled -> {
|
||||
displayErrorText(R.string.account_setup_failed_dlg_oauth_flow_canceled)
|
||||
}
|
||||
|
||||
is AuthFlowState.Failed -> {
|
||||
displayErrorText(R.string.account_setup_failed_dlg_oauth_flow_failed, state)
|
||||
}
|
||||
|
||||
AuthFlowState.NotSupported -> {
|
||||
displayErrorText(R.string.account_setup_failed_dlg_oauth_not_supported)
|
||||
}
|
||||
|
||||
AuthFlowState.BrowserNotFound -> {
|
||||
displayErrorText(R.string.account_setup_failed_dlg_browser_not_found)
|
||||
}
|
||||
|
|
|
@ -13,15 +13,11 @@ import androidx.preference.ListPreference
|
|||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.SwitchPreference
|
||||
import app.k9mail.core.featureflag.FeatureFlagProvider
|
||||
import app.k9mail.core.featureflag.toFeatureFlagKey
|
||||
import app.k9mail.feature.launcher.FeatureLauncherActivity
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.account.BackgroundAccountRemover
|
||||
import com.fsck.k9.activity.ManageIdentities
|
||||
import com.fsck.k9.activity.setup.AccountSetupComposition
|
||||
import com.fsck.k9.activity.setup.AccountSetupIncoming
|
||||
import com.fsck.k9.activity.setup.AccountSetupOutgoing
|
||||
import com.fsck.k9.controller.MessagingController
|
||||
import com.fsck.k9.crypto.OpenPgpApiHelper
|
||||
import com.fsck.k9.fragment.ConfirmationDialogFragment
|
||||
|
@ -55,7 +51,6 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
|
|||
private val notificationChannelManager: NotificationChannelManager by inject()
|
||||
private val notificationSettingsUpdater: NotificationSettingsUpdater by inject()
|
||||
private val vibrator: Vibrator by inject()
|
||||
private val featureFlagProvider: FeatureFlagProvider by inject()
|
||||
|
||||
private lateinit var dataStore: AccountSettingsDataStore
|
||||
|
||||
|
@ -134,9 +129,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
|
|||
|
||||
private fun initializeIncomingServer() {
|
||||
findPreference<Preference>(PREFERENCE_INCOMING_SERVER)?.onClick {
|
||||
featureFlagProvider.provide("new_account_edit".toFeatureFlagKey())
|
||||
.onEnabled { FeatureLauncherActivity.launchEditIncomingSettings(requireActivity(), accountUuid) }
|
||||
.onDisabled { AccountSetupIncoming.actionEditIncomingSettings(requireActivity(), accountUuid) }
|
||||
FeatureLauncherActivity.launchEditIncomingSettings(requireActivity(), accountUuid)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,9 +155,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
|
|||
|
||||
private fun initializeOutgoingServer() {
|
||||
findPreference<Preference>(PREFERENCE_OUTGOING_SERVER)?.onClick {
|
||||
featureFlagProvider.provide("new_account_edit".toFeatureFlagKey())
|
||||
.onEnabled { FeatureLauncherActivity.launchEditOutgoingSettings(requireActivity(), accountUuid) }
|
||||
.onDisabled { AccountSetupOutgoing.actionEditOutgoingSettings(requireActivity(), accountUuid) }
|
||||
FeatureLauncherActivity.launchEditOutgoingSettings(requireActivity(), accountUuid)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
|
||||
package com.fsck.k9.view;
|
||||
|
||||
import com.fsck.k9.ui.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.security.KeyChain;
|
||||
import android.security.KeyChainAliasCallback;
|
||||
import android.util.AttributeSet;
|
||||
import timber.log.Timber;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
public class ClientCertificateSpinner extends LinearLayout {
|
||||
Activity mActivity;
|
||||
OnClientCertificateChangedListener mListener;
|
||||
|
||||
Button mSelection;
|
||||
ImageButton mDeleteButton;
|
||||
|
||||
String mAlias;
|
||||
|
||||
public interface OnClientCertificateChangedListener {
|
||||
void onClientCertificateChanged(String alias);
|
||||
}
|
||||
|
||||
public void setOnClientCertificateChangedListener(OnClientCertificateChangedListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public ClientCertificateSpinner(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
if (context instanceof Activity) {
|
||||
mActivity = (Activity) context;
|
||||
} else {
|
||||
Timber.e("ClientCertificateSpinner init failed! Please inflate with Activity!");
|
||||
}
|
||||
|
||||
setOrientation(LinearLayout.HORIZONTAL);
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
inflater.inflate(R.layout.client_certificate_spinner, this, true);
|
||||
|
||||
mSelection = findViewById(R.id.client_certificate_spinner_button);
|
||||
mSelection.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
chooseCertificate();
|
||||
}
|
||||
});
|
||||
|
||||
mDeleteButton = findViewById(R.id.client_certificate_spinner_delete);
|
||||
mDeleteButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onDelete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
// Note: KeyChainAliasCallback gives back "" on cancel
|
||||
if (alias != null && alias.equals("")) {
|
||||
alias = null;
|
||||
}
|
||||
|
||||
mAlias = alias;
|
||||
// Note: KeyChainAliasCallback is a different thread than the UI
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateView();
|
||||
mDeleteButton.setVisibility((mAlias==null)?View.GONE: View.VISIBLE);
|
||||
if (mListener != null) {
|
||||
mListener.onClientCertificateChanged(mAlias);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
String alias = mSelection.getText().toString();
|
||||
if (alias.equals(mActivity.getString(R.string.client_certificate_spinner_empty))) {
|
||||
return null;
|
||||
} else {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
|
||||
private void onDelete() {
|
||||
setAlias(null);
|
||||
mDeleteButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void chooseCertificate() {
|
||||
// NOTE: keyTypes, issuers, hosts, port are not known before we actually
|
||||
// open a connection, thus we cannot set them here!
|
||||
KeyChain.choosePrivateKeyAlias(mActivity, new KeyChainAliasCallback() {
|
||||
@Override
|
||||
public void alias(String alias) {
|
||||
Timber.d("User has selected client certificate alias: %s", alias);
|
||||
|
||||
setAlias(alias);
|
||||
}
|
||||
}, null, null, null, -1, getAlias());
|
||||
}
|
||||
|
||||
private void updateView() {
|
||||
if (mAlias != null) {
|
||||
mSelection.setText(mAlias);
|
||||
} else {
|
||||
mSelection.setText(R.string.client_certificate_spinner_empty);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="com.fsck.k9.activity.setup.AccountSetupCheckSettings">
|
||||
|
||||
<include layout="@layout/toolbar" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="6dip"
|
||||
android:fadingEdge="none"
|
||||
android:scrollbarStyle="outsideInset">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_gravity="center_horizontal|center_vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dip"
|
||||
android:layout_width="fill_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:paddingBottom="6dip"
|
||||
android:focusable="true"
|
||||
/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_horizontal|center_vertical"
|
||||
android:focusable="true"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<include layout="@layout/wizard_cancel"/>
|
||||
</LinearLayout>
|
|
@ -1,181 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
tools:context="com.fsck.k9.activity.setup.AccountSetupIncoming">
|
||||
|
||||
<include layout="@layout/toolbar"/>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="6dip"
|
||||
android:fadingEdge="none"
|
||||
android:scrollbarStyle="outsideInset">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- The hint text may be changed in code if the server is IMAP, etc. -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/account_server_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/account_setup_margin_between_items_incoming_and_outgoing">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/account_server"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri"
|
||||
android:hint="@string/account_setup_incoming_pop_server_label"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:text="@string/account_setup_incoming_security_label"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
style="@style/InputLabel"
|
||||
android:layout_marginTop="6dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/account_security_type"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:contentDescription="@string/account_setup_incoming_security_label"/>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/account_setup_margin_between_items_incoming_and_outgoing">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/account_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"
|
||||
android:hint="@string/account_setup_incoming_port_label"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/account_setup_margin_between_items_incoming_and_outgoing">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/account_username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textEmailAddress"
|
||||
android:hint="@string/account_setup_incoming_username_label"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_auth_type_label"
|
||||
android:text="@string/account_setup_incoming_auth_type_label"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="6dp"
|
||||
style="@style/InputLabel"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/account_auth_type"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:contentDescription="@string/account_setup_incoming_auth_type_label"/>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/account_password_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/account_setup_margin_between_items_incoming_and_outgoing"
|
||||
app:endIconMode="password_toggle">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/account_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/account_setup_incoming_password_label"
|
||||
android:singleLine="true"
|
||||
android:inputType="textPassword"
|
||||
android:nextFocusDown="@+id/next"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/account_allow_client_certificate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
<TextView
|
||||
android:text="@string/account_setup_incoming_client_certificate_label"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="6dp"
|
||||
style="@style/InputLabel"/>
|
||||
|
||||
<com.fsck.k9.view.ClientCertificateSpinner
|
||||
android:id="@+id/account_client_certificate_spinner"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/imap_path_prefix_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/imap_autodetect_namespace"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/account_setup_incoming_autodetect_namespace_label"/>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/imap_path_prefix"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/account_setup_incoming_imap_path_prefix_label"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/use_compression"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/account_setup_incoming_use_compression" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/is_send_client_id_enabled"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/account_setup_incoming_send_client_id" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dip"
|
||||
android:layout_weight="1"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<include layout="@layout/wizard_next"/>
|
||||
</LinearLayout>
|
|
@ -1,161 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
tools:context="com.fsck.k9.activity.setup.AccountSetupOutgoing">
|
||||
|
||||
<include layout="@layout/toolbar"/>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="6dip"
|
||||
android:fadingEdge="none"
|
||||
android:scrollbarStyle="outsideInset">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/account_setup_margin_between_items_incoming_and_outgoing">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/account_server"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri"
|
||||
android:hint="@string/account_setup_outgoing_smtp_server_label"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:text="@string/account_setup_outgoing_security_label"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="6dp"
|
||||
style="@style/InputLabel" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/account_security_type"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:contentDescription="@string/account_setup_outgoing_security_label" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/account_setup_margin_between_items_incoming_and_outgoing">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/account_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"
|
||||
android:hint="@string/account_setup_outgoing_port_label"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/account_require_login"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/account_setup_outgoing_require_login_label"
|
||||
tools:checked="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/account_require_login_settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/account_setup_margin_between_items_incoming_and_outgoing">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/account_username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textEmailAddress"
|
||||
android:hint="@string/account_setup_outgoing_username_label"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:text="@string/account_setup_outgoing_authentication_label"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_marginTop="6dp"
|
||||
style="@style/InputLabel" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/account_auth_type"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:contentDescription="@string/account_setup_outgoing_authentication_label" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/account_password_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/account_setup_margin_between_items_incoming_and_outgoing"
|
||||
app:endIconMode="password_toggle">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/account_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/account_setup_outgoing_password_label"
|
||||
android:singleLine="true"
|
||||
android:inputType="textPassword"
|
||||
android:nextFocusDown="@+id/next"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/account_allow_client_certificate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:text="@string/account_setup_incoming_client_certificate_label"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
style="@style/InputLabel"
|
||||
android:layout_marginTop="6dp"/>
|
||||
|
||||
<com.fsck.k9.view.ClientCertificateSpinner
|
||||
android:id="@+id/account_client_certificate_spinner"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dip"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<include layout="@layout/wizard_next" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1,32 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="LinearLayout"
|
||||
tools:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/client_certificate_spinner_button"
|
||||
style="?android:attr/spinnerStyle"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:padding="6dp"
|
||||
android:text="@string/client_certificate_spinner_empty"
|
||||
android:freezesText="true" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/client_certificate_spinner_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?selectableItemBackground"
|
||||
android:contentDescription="@string/client_certificate_spinner_delete"
|
||||
android:padding="8dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
app:srcCompat="?attr/iconActionCancel" />
|
||||
|
||||
</merge>
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/dividerHorizontal" />
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancel"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?selectableItemBackground"
|
||||
android:text="@string/cancel_action" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/dividerHorizontal" />
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/done"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?selectableItemBackground"
|
||||
android:text="@string/done_action" />
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/dividerHorizontal" />
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/next"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?selectableItemBackground"
|
||||
android:text="@string/next_action" />
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
<dimen name="input_label_vertical_spacing">8dp</dimen>
|
||||
<dimen name="input_label_horizontal_spacing">4dp</dimen>
|
||||
<dimen name="account_setup_margin_between_items_incoming_and_outgoing">12dp</dimen>
|
||||
|
||||
<dimen name="message_view_pager_page_margin">16dp</dimen>
|
||||
<dimen name="messageListSwipeThreshold">72dp</dimen>
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
<item type="id" name="dialog_confirm_delete"/>
|
||||
<item type="id" name="dialog_confirm_spam"/>
|
||||
<item type="id" name="dialog_attachment_progress"/>
|
||||
<item type="id" name="dialog_account_setup_error"/>
|
||||
|
||||
<item type="id" name="settings_export_list_general_item"/>
|
||||
<item type="id" name="settings_export_list_account_item"/>
|
||||
|
|
|
@ -62,7 +62,6 @@
|
|||
<string name="choose_folder_copy_title">Copy to…</string>
|
||||
|
||||
<string name="actionbar_selected"><xliff:g id="selection_count">%d</xliff:g> selected</string>
|
||||
<string name="next_action">Next</string>
|
||||
<!-- Used to confirm acceptance of dialog boxes, warnings, errors, etc. -->
|
||||
<string name="okay_action">OK</string>
|
||||
<string name="cancel_action">Cancel</string>
|
||||
|
@ -351,55 +350,16 @@
|
|||
<!-- Displayed below the 'account_setup_oauth_description' text -->
|
||||
<string name="account_setup_oauth_sign_in_with_google">Sign in with Google</string>
|
||||
|
||||
<!-- Please use the same translation for "screen lock" as is used in the 'Security' section in Android's settings app -->
|
||||
<string name="account_setup_basics_show_password_need_lock">To view your password here, enable screen lock on this device.</string>
|
||||
<string name="account_setup_basics_show_password_biometrics_title">Verify your identity</string>
|
||||
<string name="account_setup_basics_show_password_biometrics_subtitle">Unlock to view your password</string>
|
||||
|
||||
<string name="account_setup_check_settings_title" />
|
||||
<string name="account_setup_check_settings_retr_info_msg">Retrieving account information\u2026</string>
|
||||
<string name="account_setup_check_settings_check_incoming_msg">Checking incoming server settings\u2026</string>
|
||||
<string name="account_setup_check_settings_check_outgoing_msg">Checking outgoing server settings\u2026</string>
|
||||
<string name="account_setup_check_settings_authenticate">Authenticating\u2026</string>
|
||||
<string name="account_setup_check_settings_fetch">Fetching account settings\u2026</string>
|
||||
<string name="account_setup_check_settings_canceling_msg">Canceling\u2026</string>
|
||||
|
||||
<string name="account_setup_auth_type_normal_password">Normal password</string>
|
||||
<string name="account_setup_auth_type_insecure_password">Password, transmitted insecurely</string>
|
||||
<string name="account_setup_auth_type_encrypted_password">Encrypted password</string>
|
||||
<string name="account_setup_auth_type_tls_client_certificate">Client certificate</string>
|
||||
<string name="account_setup_auth_type_oauth2">OAuth 2.0</string>
|
||||
|
||||
<string name="account_setup_incoming_title">Incoming server settings</string>
|
||||
<string name="account_setup_incoming_username_label">Username</string>
|
||||
<string name="account_setup_incoming_password_label">Password</string>
|
||||
<string name="account_setup_incoming_client_certificate_label">Client certificate</string>
|
||||
<string name="account_setup_incoming_pop_server_label">POP3 server</string>
|
||||
<string name="account_setup_incoming_imap_server_label">IMAP server</string>
|
||||
<string name="account_setup_incoming_port_label">Port</string>
|
||||
<string name="account_setup_incoming_security_label">Security</string>
|
||||
<string name="account_setup_incoming_auth_type_label">Authentication</string>
|
||||
<string name="account_setup_incoming_security_none_label">None</string>
|
||||
<string name="account_setup_incoming_security_ssl_label">SSL/TLS</string>
|
||||
<string name="account_setup_incoming_security_tls_label">STARTTLS</string>
|
||||
<string name="account_setup_incoming_invalid_setting_combo_notice">\"<xliff:g id="setting_1_label">%1$s</xliff:g> = <xliff:g id="setting_1_value">%2$s</xliff:g>\" is not valid with \"<xliff:g id="setting_2_label">%3$s</xliff:g> = <xliff:g id="setting_2_value">%4$s</xliff:g>\"</string>
|
||||
|
||||
<string name="account_setup_incoming_delete_policy_label">When I delete a message</string>
|
||||
<string name="account_setup_incoming_delete_policy_never_label">Do not delete on server</string>
|
||||
<string name="account_setup_incoming_delete_policy_delete_label">Delete from server</string>
|
||||
<string name="account_setup_incoming_delete_policy_markread_label">Mark as read on server</string>
|
||||
|
||||
<string name="account_setup_incoming_use_compression">Use compression</string>
|
||||
<string name="account_setup_incoming_send_client_id">Send client ID</string>
|
||||
|
||||
<string name="account_setup_expunge_policy_label">Erase deleted messages on server</string>
|
||||
<string name="account_setup_expunge_policy_immediately">Immediately</string>
|
||||
<string name="account_setup_expunge_policy_on_poll">When polling</string>
|
||||
<string name="account_setup_expunge_policy_manual">Manually</string>
|
||||
|
||||
<string name="account_setup_incoming_autodetect_namespace_label">Auto-detect IMAP namespace</string>
|
||||
<string name="account_setup_incoming_imap_path_prefix_label">IMAP path prefix</string>
|
||||
|
||||
<string name="drafts_folder_label">Drafts folder</string>
|
||||
<string name="sent_folder_label">Sent folder</string>
|
||||
<string name="trash_folder_label">Trash folder</string>
|
||||
|
@ -409,19 +369,6 @@
|
|||
<string name="account_setup_incoming_subscribed_folders_only_label">Show only subscribed folders</string>
|
||||
<string name="account_setup_auto_expand_folder">Auto-expand folder</string>
|
||||
|
||||
<string name="account_setup_outgoing_title">Outgoing server settings</string>
|
||||
<string name="account_setup_outgoing_smtp_server_label">SMTP server</string>
|
||||
<string name="account_setup_outgoing_port_label">Port</string>
|
||||
<string name="account_setup_outgoing_security_label">Security</string>
|
||||
<string name="account_setup_outgoing_require_login_label">Require sign-in.</string>
|
||||
<string name="account_setup_outgoing_username_label">Username</string>
|
||||
<string name="account_setup_outgoing_password_label">Password</string>
|
||||
<string name="account_setup_outgoing_authentication_label">Authentication</string>
|
||||
<string name="account_setup_outgoing_invalid_setting_combo_notice">\"<xliff:g id="setting_1_label">%1$s</xliff:g> = <xliff:g id="setting_1_value">%2$s</xliff:g>\" is not valid with \"<xliff:g id="setting_2_label">%3$s</xliff:g> = <xliff:g id="setting_2_value">%4$s</xliff:g>\"</string>
|
||||
|
||||
<string name="account_setup_bad_uri">Invalid setup: <xliff:g id="err_mess">%s</xliff:g></string>
|
||||
|
||||
|
||||
<string name="account_setup_options_mail_check_frequency_label">Folder poll frequency</string>
|
||||
<string name="account_setup_options_mail_check_frequency_never">Never</string>
|
||||
<string name="account_setup_options_mail_check_frequency_15min">Every 15 minutes</string>
|
||||
|
@ -459,16 +406,10 @@
|
|||
|
||||
<string name="move_copy_cannot_copy_unsynced_message">Cannot copy or move a message that is not synchronized with the server</string>
|
||||
|
||||
<string name="account_setup_failed_dlg_title">Setup could not finish</string>
|
||||
<string name="account_setup_failed_dlg_auth_message_fmt">Username or password incorrect.\n(<xliff:g id="error">%s</xliff:g>)</string>
|
||||
<string name="account_setup_failed_dlg_certificate_message_fmt">The server presented an invalid SSL certificate. Sometimes, this is because of a server misconfiguration. Sometimes it is because someone is trying to attack you or your mail server. If you\'re not sure what\'s up, click Reject and contact the folks who manage your mail server.\n\n(<xliff:g id="error">%s</xliff:g>)</string>
|
||||
<string name="account_setup_failed_dlg_server_message_fmt">Cannot connect to server.\n(<xliff:g id="error">%s</xliff:g>)</string>
|
||||
<string name="account_setup_failed_dlg_oauth_flow_canceled">Authorization canceled</string>
|
||||
<string name="account_setup_failed_dlg_oauth_flow_failed">Authorization failed with the following error: <xliff:g id="error">%s</xliff:g></string>
|
||||
<string name="account_setup_failed_dlg_oauth_not_supported">OAuth 2.0 is currently not supported with this provider.</string>
|
||||
<string name="account_setup_failed_dlg_browser_not_found">The app couldn\'t find a browser to use for granting access to your account.</string>
|
||||
<string name="account_setup_failed_dlg_edit_details_action">Edit details</string>
|
||||
<string name="account_setup_failed_dlg_continue_action">Continue</string>
|
||||
|
||||
<string name="account_settings_push_advanced_title">Advanced</string>
|
||||
<string name="account_settings_title_fmt">Account settings</string>
|
||||
|
@ -745,10 +686,6 @@
|
|||
|
||||
<string name="account_delete_dlg_title">Remove Account</string>
|
||||
|
||||
<string name="account_setup_failed_dlg_invalid_certificate_title">Unrecognized Certificate</string>
|
||||
<string name="account_setup_failed_dlg_invalid_certificate_accept">Accept Key</string>
|
||||
<string name="account_setup_failed_dlg_invalid_certificate_reject">Reject Key</string>
|
||||
|
||||
<string name="folder_list_filter_hint">Folder name contains</string>
|
||||
|
||||
<string name="folder_list_display_mode_label">Show folders…</string>
|
||||
|
@ -1040,14 +977,6 @@
|
|||
<string name="fetching_attachment_dialog_title_save">Saving draft</string>
|
||||
<string name="fetching_attachment_dialog_message">Fetching attachment…</string>
|
||||
|
||||
<string name="auth_external_error">Unable to authenticate. The server does not advertise the SASL EXTERNAL capability. This could be due to a problem with the client certificate (expired, unknown certificate authority) or some other configuration problem.</string>
|
||||
|
||||
<!-- === Client certificates specific ================================================================== -->
|
||||
<string name="client_certificate_spinner_empty">No client certificate</string>
|
||||
<string name="client_certificate_spinner_delete">Remove client certificate selection</string>
|
||||
<string name="client_certificate_retrieval_failure">"Failed to retrieve client certificate for alias \"<xliff:g id="alias">%s</xliff:g>\""</string>
|
||||
<string name="client_certificate_expired">"Client certificate \"<xliff:g id="certificate_alias">%1$s</xliff:g>\" has expired or is not yet valid (<xliff:g id="exception_message">%2$s</xliff:g>)"</string>
|
||||
|
||||
<!-- Note: This references message_view_download_remainder -->
|
||||
<string name="preview_encrypted">*Encrypted*</string>
|
||||
<string name="add_from_contacts">Add from Contacts</string>
|
||||
|
|
|
@ -45,20 +45,32 @@ class FeatureLauncherActivity : K9Activity() {
|
|||
}
|
||||
|
||||
@JvmStatic
|
||||
fun launchEditIncomingSettings(context: Context, accountUuid: String) {
|
||||
fun getEditIncomingSettingsIntent(context: Context, accountUuid: String): Intent {
|
||||
val intent = Intent(context, FeatureLauncherActivity::class.java).apply {
|
||||
data = NAVIGATION_ROUTE_ACCOUNT_EDIT_SERVER_SETTINGS_INCOMING
|
||||
.withAccountUuid(accountUuid).toDeepLinkUri()
|
||||
}
|
||||
return intent
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun launchEditIncomingSettings(context: Context, accountUuid: String) {
|
||||
val intent = getEditIncomingSettingsIntent(context, accountUuid)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun launchEditOutgoingSettings(context: Context, accountUuid: String) {
|
||||
fun getEditOutgoingSettingsIntent(context: Context, accountUuid: String): Intent {
|
||||
val intent = Intent(context, FeatureLauncherActivity::class.java).apply {
|
||||
data = NAVIGATION_ROUTE_ACCOUNT_EDIT_SERVER_SETTINGS_OUTGOING
|
||||
.withAccountUuid(accountUuid).toDeepLinkUri()
|
||||
}
|
||||
return intent
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun launchEditOutgoingSettings(context: Context, accountUuid: String) {
|
||||
val intent = getEditOutgoingSettingsIntent(context, accountUuid)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue