Merge pull request #7390 from thunderbird/remove-account-edit-legacy-code

Remove account edit legacy code
This commit is contained in:
Wolf-Martell Montwé 2023-11-30 10:29:00 +00:00 committed by GitHub
commit 0fcc98f5c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 31 additions and 2749 deletions

View file

@ -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);
}

View file

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

View file

@ -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"

View file

@ -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()
}
}

View file

@ -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)
}

View file

@ -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
}
},
)
}
}

View file

@ -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)
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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") ||

View file

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

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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);
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"/>

View file

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

View file

@ -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)
}
}