|  | @@ -1,43 +1,108 @@
 | 
	
		
			
				|  |  |  package eu.kanade.tachiyomi.util.system
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import android.content.Context
 | 
	
		
			
				|  |  | -import android.os.Build
 | 
	
		
			
				|  |  | +import androidx.annotation.CallSuper
 | 
	
		
			
				|  |  |  import androidx.biometric.BiometricManager
 | 
	
		
			
				|  |  |  import androidx.biometric.BiometricManager.Authenticators
 | 
	
		
			
				|  |  | +import androidx.biometric.BiometricPrompt
 | 
	
		
			
				|  |  | +import androidx.biometric.BiometricPrompt.AuthenticationError
 | 
	
		
			
				|  |  | +import androidx.biometric.auth.AuthPromptCallback
 | 
	
		
			
				|  |  | +import androidx.biometric.auth.startClass2BiometricOrCredentialAuthentication
 | 
	
		
			
				|  |  | +import androidx.core.content.ContextCompat
 | 
	
		
			
				|  |  | +import androidx.fragment.app.FragmentActivity
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  object AuthenticatorUtil {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    fun getSupportedAuthenticators(context: Context): Int {
 | 
	
		
			
				|  |  | -        if (isLegacySecured(context)) {
 | 
	
		
			
				|  |  | -            return Authenticators.BIOMETRIC_WEAK or Authenticators.DEVICE_CREDENTIAL
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * A check to avoid double authentication on older APIs when confirming settings changes since
 | 
	
		
			
				|  |  | +     * the biometric prompt is launched in a separate activity outside of the app.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    var isAuthenticating = false
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        return listOf(
 | 
	
		
			
				|  |  | -            Authenticators.BIOMETRIC_STRONG,
 | 
	
		
			
				|  |  | -            Authenticators.BIOMETRIC_WEAK,
 | 
	
		
			
				|  |  | -            Authenticators.DEVICE_CREDENTIAL,
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Launches biometric prompt.
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param title String title that will be shown on the prompt
 | 
	
		
			
				|  |  | +     * @param subtitle Optional string subtitle that will be shown on the prompt
 | 
	
		
			
				|  |  | +     * @param confirmationRequired Whether require explicit user confirmation after passive biometric is recognized
 | 
	
		
			
				|  |  | +     * @param callback Callback object to handle the authentication events
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    fun FragmentActivity.startAuthentication(
 | 
	
		
			
				|  |  | +        title: String,
 | 
	
		
			
				|  |  | +        subtitle: String? = null,
 | 
	
		
			
				|  |  | +        confirmationRequired: Boolean = true,
 | 
	
		
			
				|  |  | +        callback: AuthenticationCallback
 | 
	
		
			
				|  |  | +    ) {
 | 
	
		
			
				|  |  | +        isAuthenticating = true
 | 
	
		
			
				|  |  | +        startClass2BiometricOrCredentialAuthentication(
 | 
	
		
			
				|  |  | +            title = title,
 | 
	
		
			
				|  |  | +            subtitle = subtitle,
 | 
	
		
			
				|  |  | +            confirmationRequired = confirmationRequired,
 | 
	
		
			
				|  |  | +            executor = ContextCompat.getMainExecutor(this),
 | 
	
		
			
				|  |  | +            callback = callback
 | 
	
		
			
				|  |  |          )
 | 
	
		
			
				|  |  | -            .filter { BiometricManager.from(context).canAuthenticate(it) == BiometricManager.BIOMETRIC_SUCCESS }
 | 
	
		
			
				|  |  | -            .fold(0) { acc, auth -> acc or auth }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    fun isSupported(context: Context): Boolean {
 | 
	
		
			
				|  |  | -        return isLegacySecured(context) || getSupportedAuthenticators(context) != 0
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    fun isDeviceCredentialAllowed(context: Context): Boolean {
 | 
	
		
			
				|  |  | -        return isLegacySecured(context) || (getSupportedAuthenticators(context) and Authenticators.DEVICE_CREDENTIAL != 0)
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Returns true if Class 2 biometric or credential lock is set and available to use
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    fun Context.isAuthenticationSupported(): Boolean {
 | 
	
		
			
				|  |  | +        val authenticators = Authenticators.BIOMETRIC_WEAK or Authenticators.DEVICE_CREDENTIAL
 | 
	
		
			
				|  |  | +        return BiometricManager.from(this).canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  | -     * Returns whether the device is secured with a PIN, pattern or password.
 | 
	
		
			
				|  |  | +     * [AuthPromptCallback] with extra check
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @see isAuthenticating
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  | -    private fun isLegacySecured(context: Context): Boolean {
 | 
	
		
			
				|  |  | -        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
 | 
	
		
			
				|  |  | -            if (context.keyguardManager.isDeviceSecure) {
 | 
	
		
			
				|  |  | -                return true
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +    abstract class AuthenticationCallback : AuthPromptCallback() {
 | 
	
		
			
				|  |  | +        /**
 | 
	
		
			
				|  |  | +         * Called when an unrecoverable error has been encountered and authentication has stopped.
 | 
	
		
			
				|  |  | +         *
 | 
	
		
			
				|  |  | +         *
 | 
	
		
			
				|  |  | +         * After this method is called, no further events will be sent for the current
 | 
	
		
			
				|  |  | +         * authentication session.
 | 
	
		
			
				|  |  | +         *
 | 
	
		
			
				|  |  | +         * @param activity  The activity that is currently hosting the prompt.
 | 
	
		
			
				|  |  | +         * @param errorCode An integer ID associated with the error.
 | 
	
		
			
				|  |  | +         * @param errString A human-readable string that describes the error.
 | 
	
		
			
				|  |  | +         */
 | 
	
		
			
				|  |  | +        @CallSuper
 | 
	
		
			
				|  |  | +        override fun onAuthenticationError(
 | 
	
		
			
				|  |  | +            activity: FragmentActivity?,
 | 
	
		
			
				|  |  | +            @AuthenticationError errorCode: Int,
 | 
	
		
			
				|  |  | +            errString: CharSequence
 | 
	
		
			
				|  |  | +        ) {
 | 
	
		
			
				|  |  | +            isAuthenticating = false
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /**
 | 
	
		
			
				|  |  | +         * Called when the user has successfully authenticated.
 | 
	
		
			
				|  |  | +         *
 | 
	
		
			
				|  |  | +         *
 | 
	
		
			
				|  |  | +         * After this method is called, no further events will be sent for the current
 | 
	
		
			
				|  |  | +         * authentication session.
 | 
	
		
			
				|  |  | +         *
 | 
	
		
			
				|  |  | +         * @param activity The activity that is currently hosting the prompt.
 | 
	
		
			
				|  |  | +         * @param result   An object containing authentication-related data.
 | 
	
		
			
				|  |  | +         */
 | 
	
		
			
				|  |  | +        @CallSuper
 | 
	
		
			
				|  |  | +        override fun onAuthenticationSucceeded(
 | 
	
		
			
				|  |  | +            activity: FragmentActivity?,
 | 
	
		
			
				|  |  | +            result: BiometricPrompt.AuthenticationResult
 | 
	
		
			
				|  |  | +        ) {
 | 
	
		
			
				|  |  | +            isAuthenticating = false
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /**
 | 
	
		
			
				|  |  | +         * Called when an authentication attempt by the user has been rejected.
 | 
	
		
			
				|  |  | +         *
 | 
	
		
			
				|  |  | +         * @param activity The activity that is currently hosting the prompt.
 | 
	
		
			
				|  |  | +         */
 | 
	
		
			
				|  |  | +        @CallSuper
 | 
	
		
			
				|  |  | +        override fun onAuthenticationFailed(activity: FragmentActivity?) {
 | 
	
		
			
				|  |  | +            isAuthenticating = false
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        return false
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 |