code: package co.infinum.goldfinger;
  
  import android.content.Context;
  import android.os.Build;
  import android.os.Handler;
  import android.os.Looper;
  
  import java.util.List;
  import java.util.concurrent.Executor;
  import java.util.concurrent.Executors;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.annotation.RequiresApi;
  import androidx.biometric.BiometricManager;
  import androidx.biometric.BiometricPrompt;
  import androidx.fragment.app.Fragment;
  import androidx.fragment.app.FragmentActivity;
  
  import static co.infinum.goldfinger.LogUtils.log;
  
  /**
   * Goldfinger implementation for Android Marshmallow and newer.
   * Older versions use {@link GoldfingerMock}.
   */
  @RequiresApi(Build.VERSION_CODES.M)
  class GoldfingerImpl implements Goldfinger {
  
      private static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
  
      @NonNull private final AsyncCryptoObjectFactory asyncCryptoFactory;
      @Nullable private AsyncCryptoObjectFactory.Callback asyncCryptoFactoryCallback;
      @Nullable private BiometricPrompt biometricPrompt;
      @NonNull private final CrypterProxy cryptoProxy;
      @NonNull private final BiometricManager biometricManager;
      @NonNull private final Executor executor = Executors.newSingleThreadExecutor();
      @Nullable private BiometricCallback biometricCallback;
      private boolean creatingCryptoObject = false;
  
      GoldfingerImpl(
          @NonNull Context context,
          @NonNull AsyncCryptoObjectFactory asyncCryptoFactory,
          @NonNull CrypterProxy cryptoProxy
      ) {
          this.biometricManager = BiometricManager.from(context);
          this.asyncCryptoFactory = asyncCryptoFactory;
          this.cryptoProxy = cryptoProxy;
      }
  
      /**
       * @see Goldfinger#authenticate
       */
      @Override
      public void authenticate(
          @NonNull PromptParams params,
          @NonNull Callback callback
      ) {
          if (preconditionsInvalid(params, Mode.AUTHENTICATION, null, null, callback)) {
              return;
          }
  
          log("Starting authentication");
          startNativeFingerprintAuthentication(params, Mode.AUTHENTICATION, null, null, callback, null);
      }
  
      @Override
      public boolean canAuthenticate() {
          return biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS;
      }
  
      @Override
      public boolean canAuthenticate(int authenticators) {
          return biometricManager.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS;
      }
  
      /**
       * @see Goldfinger#cancel
       */
      @Override
      public void cancel() {
          if (biometricPrompt != null) {
              biometricPrompt.cancelAuthentication();
              biometricPrompt = null;
          }
  
          if (biometricCallback != null) {
              biometricCallback.cancel();
              biometricCallback = null;
          }
  
          if (asyncCryptoFactoryCallback != null) {
              asyncCryptoFactoryCallback.cancel();
              asyncCryptoFactoryCallback = null;
          }
      }
  
      @Override
      public void decrypt(@NonNull PromptParams params, @NonNull String key, @NonNull String value, @NonNull Callback callback) {
          if (preconditionsInvalid(params, Mode.DECRYPTION, key, value, callback)) {
              return;
          }
  
          initializeCryptoObject(params, Mode.DECRYPTION, key, value, callback);
      }
  
      @Override
      public void encrypt(@NonNull PromptParams params, @NonNull String key, @NonNull String value, @NonNull Callback callback) {
          if (preconditionsInvalid(params, Mode.ENCRYPTION, key, value, callback)) {
              return;
          }
  
          initializeCryptoObject(params, Mode.ENCRYPTION, key, value, callback);
      }
  
      @Override
      public boolean hasEnrolledFingerprint() {
          int authenticationStatus = biometricManager.canAuthenticate();
          return authenticationStatus != BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
              && authenticationStatus != BiometricManager.BIOMETRIC_STATUS_UNKNOWN;
      }
  
      @Override
      public boolean hasEnrolledFingerprint(int authenticators) {
          int authenticationStatus = biometricManager.canAuthenticate(authenticators);
          return authenticationStatus != BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
              && authenticationStatus != BiometricManager.BIOMETRIC_STATUS_UNKNOWN;
      }
  
      @Override
      public boolean hasFingerprintHardware() {
          int authenticationStatus = biometricManager.canAuthenticate();
          return authenticationStatus != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
              && authenticationStatus != BiometricManager.BIOMETRIC_STATUS_UNKNOWN;
      }
  
      @Override
      public boolean hasFingerprintHardware(int authenticators) {
          int authenticationStatus = biometricManager.canAuthenticate(authenticators);
          return authenticationStatus != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
              && authenticationStatus != BiometricManager.BIOMETRIC_STATUS_UNKNOWN;
      }
  
      private void initializeCryptoObject(
          @NonNull final PromptParams params,
          @NonNull final Mode mode,
          @NonNull final String key,
          @NonNull final String value,
          @NonNull final Callback callback
      ) {
          log("Creating CryptoObject");
          asyncCryptoFactoryCallback = new AsyncCryptoObjectFactory.Callback() {
              @Override
              void onCryptoObjectCreated(@Nullable BiometricPrompt.CryptoObject cryptoObject) {
                  creatingCryptoObject = false;
                  if (cryptoObject != null) {
                      startNativeFingerprintAuthentication(params, mode, key, value, callback, cryptoObject);
                  } else {
                      log("Failed to create CryptoObject");
                      callback.onError(new CryptoObjectInitException());
                  }
              }
          };
          creatingCryptoObject = true;
          asyncCryptoFactory.createCryptoObject(mode, key, asyncCryptoFactoryCallback);
      }
  
      private boolean preconditionsInvalid(PromptParams params, Mode mode, String key, String value, Callback callback) {
          if ((biometricCallback != null && biometricCallback.isAuthenticationActive) || creatingCryptoObject) {
              log("Authentication is already active. Ignoring authenticate call.");
              return true;
          }
  
          if (!hasFingerprintHardware(params.allowedAuthenticators())) {
              callback.onError(new MissingHardwareException());
              return true;
          }
  
          if (!hasEnrolledFingerprint(params.allowedAuthenticators())) {
              callback.onError(new NoEnrolledFingerprintException());
              return true;
          }
  
          List<String> promptParams = ValidateUtils.validatePromptParams(mode, params);
          if (!promptParams.isEmpty()) {
              callback.onError(new InvalidParametersException(promptParams));
              return true;
          }
  
          List<String> cipherErrors = ValidateUtils.validateCipherParams(mode, key, value);
          if (!cipherErrors.isEmpty()) {
              callback.onError(new InvalidParametersException(cipherErrors));
              return true;
          }
  
          return false;
      }
  
      @SuppressWarnings("ConstantConditions")
      private void startNativeFingerprintAuthentication(
          @NonNull final PromptParams params,
          @NonNull final Mode mode,
          @Nullable final String key,
          @Nullable final String value,
          @NonNull final Callback callback,
          @Nullable final BiometricPrompt.CryptoObject cryptoObject
      ) {
          /*
           * Use proxy callback because some devices do not cancel authentication when error is received.
           * Cancel authentication manually and proxy the result to real callback.
           */
          this.biometricCallback = new BiometricCallback(cryptoProxy, mode, value, new Callback() {
              @Override
              public void onError(@NonNull Exception e) {
                  cancel();
                  callback.onError(e);
              }
  
              @Override
              public void onResult(@NonNull Result result) {
                  if (result.type() == Type.ERROR || result.type() == Type.SUCCESS) {
                      cancel();
                  }
                  callback.onResult(result);
              }
          });
  
          if (params.dialogOwner() instanceof FragmentActivity) {
              this.biometricPrompt = new BiometricPrompt((FragmentActivity) params.dialogOwner(), executor, biometricCallback);
          }
          if (params.dialogOwner() instanceof Fragment) {
              this.biometricPrompt = new BiometricPrompt((Fragment) params.dialogOwner(), executor, biometricCallback);
          }
  
          /* Delay with post because Navigation and Prompt both work with Fragment transactions */
          MAIN_HANDLER.post(new Runnable() {
              @Override
              public void run() {
                  if (GoldfingerImpl.this.biometricPrompt == null) {
                      return;
                  }
  
                  if (mode == Mode.AUTHENTICATION) {
                      /* Simple Authentication call */
                      log("Starting authentication");
                      callback.onResult(new Result(Type.INFO, Reason.AUTHENTICATION_START));
                      GoldfingerImpl.this.biometricPrompt.authenticate(params.buildPromptInfo());
                  } else {
                      /* Encryption/Decryption call with initialized CryptoObject */
                      log("Starting authentication [keyName=%s; value=%s]", key, value);
                      callback.onResult(new Result(Type.INFO, Reason.AUTHENTICATION_START));
                      GoldfingerImpl.this.biometricPrompt.authenticate(params.buildPromptInfo(), cryptoObject);
                  }
              }
          });
      }
  }
  