Loading app/ui/base/build.gradle +2 −0 Original line number Diff line number Diff line Loading @@ -5,11 +5,13 @@ dependencies { implementation project(":app:core") api "androidx.appcompat:appcompat:${versions.androidxAppCompat}" api "com.google.android.material:material:${versions.materialComponents}" api "androidx.navigation:navigation-fragment-ktx:${versions.androidxNavigation}" api "androidx.navigation:navigation-ui-ktx:${versions.androidxNavigation}" api "androidx.lifecycle:lifecycle-livedata-ktx:${versions.androidxLifecycle}" implementation "androidx.core:core-ktx:${versions.androidxCore}" implementation "androidx.biometric:biometric:${versions.androidxBiometric}" implementation "com.jakewharton.timber:timber:${versions.timber}" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.kotlinCoroutines}" } Loading app/ui/base/src/main/java/com/fsck/k9/ui/base/extensions/TextInputLayoutExtensions.kt 0 → 100644 +119 −0 Original line number Diff line number Diff line @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.fragment.app.FragmentActivity import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.google.android.material.textfield.TextInputLayout /** * Configures a [TextInputLayout] so the password can only be revealed after authentication. */ fun TextInputLayout.configureAuthenticatedPasswordToggle( activity: FragmentActivity, title: String, subtitle: String, needScreenLockMessage: String, ) { val viewModel = ViewModelProvider(activity).get(AuthenticatedPasswordToggleViewModel::class.java) 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") setEndIconOnClickListener { if (editText.isPasswordHidden) { if (viewModel.isAuthenticated) { 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() { var isAuthenticated = false var textInputLayout: TextInputLayout? = null var activity: FragmentActivity? = null set(value) { field = value value?.lifecycle?.addObserver(object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun removeReferences() { textInputLayout = null field = null } }) } } app/ui/legacy/build.gradle +0 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ dependencies { implementation "com.takisoft.preferencex:preferencex-datetimepicker:${versions.preferencesFix}" implementation "com.takisoft.preferencex:preferencex-colorpicker:${versions.preferencesFix}" implementation "com.takisoft.preferencex:preferencex-ringtone:${versions.preferencesFix}" implementation "androidx.biometric:biometric:${versions.androidxBiometric}" implementation "androidx.recyclerview:recyclerview:${versions.androidxRecyclerView}" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.androidxLifecycle}" implementation "androidx.lifecycle:lifecycle-livedata-ktx:${versions.androidxLifecycle}" Loading @@ -30,7 +29,6 @@ dependencies { implementation "androidx.cardview:cardview:${versions.androidxCardView}" implementation "androidx.localbroadcastmanager:localbroadcastmanager:${versions.androidxLocalBroadcastManager}" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "com.google.android.material:material:${versions.materialComponents}" implementation "de.cketti.library.changelog:ckchangelog-core:2.0.0-beta02" implementation "com.splitwise:tokenautocomplete:4.0.0-beta01" implementation "de.cketti.safecontentresolver:safe-content-resolver-v21:1.0.0" Loading app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupIncoming.java +10 −42 Original line number Diff line number Diff line Loading @@ -9,7 +9,6 @@ import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.text.method.DigitsKeyListener; import android.text.method.PasswordTransformationMethod; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; Loading @@ -22,10 +21,6 @@ import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.Spinner; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.biometric.BiometricManager.Authenticators; import androidx.biometric.BiometricPrompt; import androidx.core.content.ContextCompat; import com.fsck.k9.Account; import com.fsck.k9.DI; import com.fsck.k9.LocalKeyStoreManager; Loading @@ -47,6 +42,7 @@ import com.fsck.k9.mail.store.imap.ImapStoreSettings; import com.fsck.k9.mail.store.webdav.WebDavStoreSettings; import com.fsck.k9.preferences.Protocols; import com.fsck.k9.ui.R; import com.fsck.k9.ui.base.extensions.TextInputLayoutHelper; import com.fsck.k9.view.ClientCertificateSpinner; import com.fsck.k9.view.ClientCertificateSpinner.OnClientCertificateChangedListener; Loading Loading @@ -184,18 +180,15 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener } boolean editSettings = Intent.ACTION_EDIT.equals(getIntent().getAction()); mPasswordLayoutView.setEndIconOnClickListener(v -> { if (mPasswordView.getTransformationMethod() instanceof PasswordTransformationMethod) { if (editSettings) { authenticateUserAndShowPassword(); } else { mPasswordView.setTransformationMethod(null); } } else { mPasswordView.setTransformationMethod(PasswordTransformationMethod.getInstance()); 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) ); } }); try { ServerSettings settings = mAccount.getIncomingServerSettings(); Loading Loading @@ -631,31 +624,6 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener } private void authenticateUserAndShowPassword() { new BiometricPrompt(this, ContextCompat.getMainExecutor(this), new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { mPasswordView.setTransformationMethod(null); } @Override public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { if (errorCode == BiometricPrompt.ERROR_HW_NOT_PRESENT || errorCode == BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL) { Toast.makeText(AccountSetupIncoming.this, R.string.account_setup_basics_show_password_need_lock, Toast.LENGTH_SHORT).show(); } else if (errString.length() != 0) { Toast.makeText(AccountSetupIncoming.this, errString, Toast.LENGTH_SHORT).show(); } } }).authenticate(new BiometricPrompt.PromptInfo.Builder() .setAllowedAuthenticators(Authenticators.BIOMETRIC_STRONG | Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL) .setTitle(getString(R.string.account_setup_basics_show_password_biometrics_title)) .setSubtitle(getString(R.string.account_setup_basics_show_password_biometrics_subtitle)) .build()); } public void onClick(View v) { try { if (v.getId() == R.id.next) { Loading app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupOutgoing.java +10 −42 Original line number Diff line number Diff line Loading @@ -8,7 +8,6 @@ import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.text.method.DigitsKeyListener; import android.text.method.PasswordTransformationMethod; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; Loading @@ -21,10 +20,6 @@ import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.Spinner; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.biometric.BiometricManager.Authenticators; import androidx.biometric.BiometricPrompt; import androidx.core.content.ContextCompat; import com.fsck.k9.Account; import com.fsck.k9.DI; import com.fsck.k9.LocalKeyStoreManager; Loading @@ -39,6 +34,7 @@ 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; Loading Loading @@ -154,18 +150,15 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, } boolean editSettings = Intent.ACTION_EDIT.equals(getIntent().getAction()); mPasswordLayoutView.setEndIconOnClickListener(v -> { if (mPasswordView.getTransformationMethod() instanceof PasswordTransformationMethod) { if (editSettings) { authenticateUserAndShowPassword(); } else { mPasswordView.setTransformationMethod(null); } } else { mPasswordView.setTransformationMethod(PasswordTransformationMethod.getInstance()); 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) ); } }); try { ServerSettings settings = mAccount.getOutgoingServerSettings(); Loading Loading @@ -517,31 +510,6 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.OUTGOING); } private void authenticateUserAndShowPassword() { new BiometricPrompt(this, ContextCompat.getMainExecutor(this), new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { mPasswordView.setTransformationMethod(null); } @Override public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { if (errorCode == BiometricPrompt.ERROR_HW_NOT_PRESENT || errorCode == BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL) { Toast.makeText(AccountSetupOutgoing.this, R.string.account_setup_basics_show_password_need_lock, Toast.LENGTH_SHORT).show(); } else if (errString.length() != 0) { Toast.makeText(AccountSetupOutgoing.this, errString, Toast.LENGTH_SHORT).show(); } } }).authenticate(new BiometricPrompt.PromptInfo.Builder() .setAllowedAuthenticators(Authenticators.BIOMETRIC_STRONG | Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL) .setTitle(getString(R.string.account_setup_basics_show_password_biometrics_title)) .setSubtitle(getString(R.string.account_setup_basics_show_password_biometrics_subtitle)) .build()); } public void onClick(View v) { if (v.getId() == R.id.next) { onNext(); Loading Loading
app/ui/base/build.gradle +2 −0 Original line number Diff line number Diff line Loading @@ -5,11 +5,13 @@ dependencies { implementation project(":app:core") api "androidx.appcompat:appcompat:${versions.androidxAppCompat}" api "com.google.android.material:material:${versions.materialComponents}" api "androidx.navigation:navigation-fragment-ktx:${versions.androidxNavigation}" api "androidx.navigation:navigation-ui-ktx:${versions.androidxNavigation}" api "androidx.lifecycle:lifecycle-livedata-ktx:${versions.androidxLifecycle}" implementation "androidx.core:core-ktx:${versions.androidxCore}" implementation "androidx.biometric:biometric:${versions.androidxBiometric}" implementation "com.jakewharton.timber:timber:${versions.timber}" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.kotlinCoroutines}" } Loading
app/ui/base/src/main/java/com/fsck/k9/ui/base/extensions/TextInputLayoutExtensions.kt 0 → 100644 +119 −0 Original line number Diff line number Diff line @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.fragment.app.FragmentActivity import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.google.android.material.textfield.TextInputLayout /** * Configures a [TextInputLayout] so the password can only be revealed after authentication. */ fun TextInputLayout.configureAuthenticatedPasswordToggle( activity: FragmentActivity, title: String, subtitle: String, needScreenLockMessage: String, ) { val viewModel = ViewModelProvider(activity).get(AuthenticatedPasswordToggleViewModel::class.java) 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") setEndIconOnClickListener { if (editText.isPasswordHidden) { if (viewModel.isAuthenticated) { 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() { var isAuthenticated = false var textInputLayout: TextInputLayout? = null var activity: FragmentActivity? = null set(value) { field = value value?.lifecycle?.addObserver(object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun removeReferences() { textInputLayout = null field = null } }) } }
app/ui/legacy/build.gradle +0 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ dependencies { implementation "com.takisoft.preferencex:preferencex-datetimepicker:${versions.preferencesFix}" implementation "com.takisoft.preferencex:preferencex-colorpicker:${versions.preferencesFix}" implementation "com.takisoft.preferencex:preferencex-ringtone:${versions.preferencesFix}" implementation "androidx.biometric:biometric:${versions.androidxBiometric}" implementation "androidx.recyclerview:recyclerview:${versions.androidxRecyclerView}" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.androidxLifecycle}" implementation "androidx.lifecycle:lifecycle-livedata-ktx:${versions.androidxLifecycle}" Loading @@ -30,7 +29,6 @@ dependencies { implementation "androidx.cardview:cardview:${versions.androidxCardView}" implementation "androidx.localbroadcastmanager:localbroadcastmanager:${versions.androidxLocalBroadcastManager}" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "com.google.android.material:material:${versions.materialComponents}" implementation "de.cketti.library.changelog:ckchangelog-core:2.0.0-beta02" implementation "com.splitwise:tokenautocomplete:4.0.0-beta01" implementation "de.cketti.safecontentresolver:safe-content-resolver-v21:1.0.0" Loading
app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupIncoming.java +10 −42 Original line number Diff line number Diff line Loading @@ -9,7 +9,6 @@ import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.text.method.DigitsKeyListener; import android.text.method.PasswordTransformationMethod; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; Loading @@ -22,10 +21,6 @@ import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.Spinner; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.biometric.BiometricManager.Authenticators; import androidx.biometric.BiometricPrompt; import androidx.core.content.ContextCompat; import com.fsck.k9.Account; import com.fsck.k9.DI; import com.fsck.k9.LocalKeyStoreManager; Loading @@ -47,6 +42,7 @@ import com.fsck.k9.mail.store.imap.ImapStoreSettings; import com.fsck.k9.mail.store.webdav.WebDavStoreSettings; import com.fsck.k9.preferences.Protocols; import com.fsck.k9.ui.R; import com.fsck.k9.ui.base.extensions.TextInputLayoutHelper; import com.fsck.k9.view.ClientCertificateSpinner; import com.fsck.k9.view.ClientCertificateSpinner.OnClientCertificateChangedListener; Loading Loading @@ -184,18 +180,15 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener } boolean editSettings = Intent.ACTION_EDIT.equals(getIntent().getAction()); mPasswordLayoutView.setEndIconOnClickListener(v -> { if (mPasswordView.getTransformationMethod() instanceof PasswordTransformationMethod) { if (editSettings) { authenticateUserAndShowPassword(); } else { mPasswordView.setTransformationMethod(null); } } else { mPasswordView.setTransformationMethod(PasswordTransformationMethod.getInstance()); 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) ); } }); try { ServerSettings settings = mAccount.getIncomingServerSettings(); Loading Loading @@ -631,31 +624,6 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener } private void authenticateUserAndShowPassword() { new BiometricPrompt(this, ContextCompat.getMainExecutor(this), new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { mPasswordView.setTransformationMethod(null); } @Override public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { if (errorCode == BiometricPrompt.ERROR_HW_NOT_PRESENT || errorCode == BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL) { Toast.makeText(AccountSetupIncoming.this, R.string.account_setup_basics_show_password_need_lock, Toast.LENGTH_SHORT).show(); } else if (errString.length() != 0) { Toast.makeText(AccountSetupIncoming.this, errString, Toast.LENGTH_SHORT).show(); } } }).authenticate(new BiometricPrompt.PromptInfo.Builder() .setAllowedAuthenticators(Authenticators.BIOMETRIC_STRONG | Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL) .setTitle(getString(R.string.account_setup_basics_show_password_biometrics_title)) .setSubtitle(getString(R.string.account_setup_basics_show_password_biometrics_subtitle)) .build()); } public void onClick(View v) { try { if (v.getId() == R.id.next) { Loading
app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupOutgoing.java +10 −42 Original line number Diff line number Diff line Loading @@ -8,7 +8,6 @@ import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.text.method.DigitsKeyListener; import android.text.method.PasswordTransformationMethod; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; Loading @@ -21,10 +20,6 @@ import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.Spinner; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.biometric.BiometricManager.Authenticators; import androidx.biometric.BiometricPrompt; import androidx.core.content.ContextCompat; import com.fsck.k9.Account; import com.fsck.k9.DI; import com.fsck.k9.LocalKeyStoreManager; Loading @@ -39,6 +34,7 @@ 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; Loading Loading @@ -154,18 +150,15 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, } boolean editSettings = Intent.ACTION_EDIT.equals(getIntent().getAction()); mPasswordLayoutView.setEndIconOnClickListener(v -> { if (mPasswordView.getTransformationMethod() instanceof PasswordTransformationMethod) { if (editSettings) { authenticateUserAndShowPassword(); } else { mPasswordView.setTransformationMethod(null); } } else { mPasswordView.setTransformationMethod(PasswordTransformationMethod.getInstance()); 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) ); } }); try { ServerSettings settings = mAccount.getOutgoingServerSettings(); Loading Loading @@ -517,31 +510,6 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.OUTGOING); } private void authenticateUserAndShowPassword() { new BiometricPrompt(this, ContextCompat.getMainExecutor(this), new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { mPasswordView.setTransformationMethod(null); } @Override public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { if (errorCode == BiometricPrompt.ERROR_HW_NOT_PRESENT || errorCode == BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL) { Toast.makeText(AccountSetupOutgoing.this, R.string.account_setup_basics_show_password_need_lock, Toast.LENGTH_SHORT).show(); } else if (errString.length() != 0) { Toast.makeText(AccountSetupOutgoing.this, errString, Toast.LENGTH_SHORT).show(); } } }).authenticate(new BiometricPrompt.PromptInfo.Builder() .setAllowedAuthenticators(Authenticators.BIOMETRIC_STRONG | Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL) .setTitle(getString(R.string.account_setup_basics_show_password_biometrics_title)) .setSubtitle(getString(R.string.account_setup_basics_show_password_biometrics_subtitle)) .build()); } public void onClick(View v) { if (v.getId() == R.id.next) { onNext(); Loading