@ -4,11 +4,25 @@
package org.mozilla.fenix.settings
package org.mozilla.fenix.settings
import android.annotation.TargetApi
import android.app.Activity.RESULT_OK
import android.app.KeyguardManager
import android.content.Context.KEYGUARD_SERVICE
import android.content.DialogInterface
import android.content.Intent
import android.os.Build
import android.os.Build.VERSION_CODES.M
import android.os.Bundle
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceFragmentCompat
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.OAuthAccount
@ -17,6 +31,8 @@ import mozilla.components.service.fxa.manager.SyncEnginesStorage
import org.mozilla.fenix.R
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import java.util.concurrent.Executors
@Suppress ( " TooManyFunctions " )
@Suppress ( " TooManyFunctions " )
class LoginsFragment : PreferenceFragmentCompat ( ) , AccountObserver {
class LoginsFragment : PreferenceFragmentCompat ( ) , AccountObserver {
@ -31,7 +47,11 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
val savedLoginsKey = getPreferenceKey ( R . string . pref _key _saved _logins )
val savedLoginsKey = getPreferenceKey ( R . string . pref _key _saved _logins )
findPreference < Preference > ( savedLoginsKey ) ?. setOnPreferenceClickListener {
findPreference < Preference > ( savedLoginsKey ) ?. setOnPreferenceClickListener {
navigateToLoginsSettingsFragment ( )
if ( Build . VERSION . SDK _INT >= M && isHardwareAvailable && hasBiometricEnrolled ) {
showBiometricPrompt ( )
} else {
verifyPinOrShowSetupWarning ( )
}
true
true
}
}
@ -54,6 +74,31 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
override fun onAuthenticationProblems ( ) = updateSyncPreferenceNeedsReauth ( )
override fun onAuthenticationProblems ( ) = updateSyncPreferenceNeedsReauth ( )
val isHardwareAvailable : Boolean by lazy {
if ( Build . VERSION . SDK _INT >= M ) {
context ?. let {
val bm = BiometricManager . from ( it )
val canAuthenticate = bm . canAuthenticate ( )
! ( canAuthenticate == BiometricManager . BIOMETRIC _ERROR _NO _HARDWARE ||
canAuthenticate == BiometricManager . BIOMETRIC _ERROR _HW _UNAVAILABLE )
} ?: false
} else {
false
}
}
val hasBiometricEnrolled : Boolean by lazy {
if ( Build . VERSION . SDK _INT >= M ) {
context ?. let {
val bm = BiometricManager . from ( it )
val canAuthenticate = bm . canAuthenticate ( )
( canAuthenticate == BiometricManager . BIOMETRIC _SUCCESS )
} ?: false
} else {
false
}
}
private fun updateSyncPreferenceStatus ( ) {
private fun updateSyncPreferenceStatus ( ) {
val syncLogins = getPreferenceKey ( R . string . pref _key _password _sync _logins )
val syncLogins = getPreferenceKey ( R . string . pref _key _password _sync _logins )
findPreference < Preference > ( syncLogins ) ?. apply {
findPreference < Preference > ( syncLogins ) ?. apply {
@ -92,7 +137,93 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
}
}
}
}
private fun navigateToLoginsSettingsFragment ( ) {
@TargetApi ( M )
private fun showBiometricPrompt ( ) {
val biometricPromptCallback = object : BiometricPrompt . AuthenticationCallback ( ) {
override fun onAuthenticationError ( errorCode : Int , errString : CharSequence ) {
// Authentication Error
}
override fun onAuthenticationSucceeded ( result : BiometricPrompt . AuthenticationResult ) {
lifecycleScope . launch ( Main ) {
navigateToSavedLoginsFragment ( )
}
}
override fun onAuthenticationFailed ( ) {
// Authenticated Failed
}
}
val executor = Executors . newSingleThreadExecutor ( )
val biometricPrompt = BiometricPrompt ( this , executor , biometricPromptCallback )
val promptInfo = BiometricPrompt . PromptInfo . Builder ( )
. setTitle ( getString ( R . string . logins _biometric _prompt _message ) )
. setDeviceCredentialAllowed ( true )
. build ( )
biometricPrompt . authenticate ( promptInfo )
}
private fun verifyPinOrShowSetupWarning ( ) {
val manager = activity ?. getSystemService ( KEYGUARD _SERVICE ) as KeyguardManager
if ( manager . isKeyguardSecure ) {
showPinVerification ( )
} else {
if ( context ?. settings ( ) ?. shouldShowSecurityPinWarning == true ) {
showPinDialogWarning ( )
} else {
navigateToSavedLoginsFragment ( )
}
}
}
private fun showPinDialogWarning ( ) {
context ?. let {
AlertDialog . Builder ( it ) . apply {
setTitle ( getString ( R . string . logins _warning _dialog _title ) )
setMessage (
getString ( R . string . logins _warning _dialog _message )
)
setNegativeButton ( getString ( R . string . logins _warning _dialog _later ) ) { _ : DialogInterface , _ ->
navigateToSavedLoginsFragment ( )
}
setPositiveButton ( getString ( R . string . logins _warning _dialog _set _up _now ) ) { it : DialogInterface , _ ->
it . dismiss ( )
val intent = Intent (
android . provider . Settings . ACTION _SECURITY _SETTINGS
)
startActivity ( intent )
}
create ( )
} . show ( )
it . settings ( ) . incrementShowLoginsSecureWarningCount ( )
}
}
private fun showPinVerification ( ) {
val manager = activity ?. getSystemService ( KEYGUARD _SERVICE ) as KeyguardManager
val intent = manager . createConfirmDeviceCredentialIntent (
getString ( R . string . logins _biometric _prompt _message _pin ) ,
getString ( R . string . logins _biometric _prompt _message )
)
startActivityForResult ( intent , PIN _REQUEST )
}
override fun onActivityResult ( requestCode : Int , resultCode : Int , data : Intent ? ) {
if ( requestCode == PIN _REQUEST && resultCode == RESULT _OK ) {
navigateToSavedLoginsFragment ( )
} else {
super . onActivityResult ( requestCode , resultCode , data )
}
}
private fun navigateToSavedLoginsFragment ( ) {
val directions = LoginsFragmentDirections . actionLoginsFragmentToSavedLoginsFragment ( )
val directions = LoginsFragmentDirections . actionLoginsFragmentToSavedLoginsFragment ( )
findNavController ( ) . navigate ( directions )
findNavController ( ) . navigate ( directions )
}
}
@ -111,4 +242,8 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
val directions = LoginsFragmentDirections . actionLoginsFragmentToTurnOnSyncFragment ( )
val directions = LoginsFragmentDirections . actionLoginsFragmentToTurnOnSyncFragment ( )
findNavController ( ) . navigate ( directions )
findNavController ( ) . navigate ( directions )
}
}
companion object {
const val PIN _REQUEST = 303
}
}
}