11package br .com .classapp .RNSensitiveInfo ;
22
33import android .app .Activity ;
4- import android .app .Fragment ;
5- import android .app .FragmentTransaction ;
64import android .content .Context ;
75import android .content .SharedPreferences ;
86import android .hardware .fingerprint .FingerprintManager ;
97import android .os .Build ;
108import android .os .CancellationSignal ;
119import android .security .keystore .KeyGenParameterSpec ;
1210import android .security .keystore .KeyInfo ;
11+
1312import java .security .InvalidKeyException ;
13+
1414import android .security .keystore .KeyProperties ;
1515import android .util .Base64 ;
1616import android .util .Log ;
1717
1818import androidx .annotation .NonNull ;
19+ import androidx .biometric .BiometricConstants ;
20+ import androidx .biometric .BiometricManager ;
21+ import androidx .biometric .BiometricPrompt ;
1922
2023import com .facebook .react .bridge .Promise ;
2124import com .facebook .react .bridge .ReactApplicationContext ;
2225import com .facebook .react .bridge .ReactContextBaseJavaModule ;
2326import com .facebook .react .bridge .ReactMethod ;
2427import com .facebook .react .bridge .ReadableMap ;
25- import com .facebook .react .bridge .WritableArray ;
2628import com .facebook .react .bridge .WritableMap ;
27- import com .facebook .react .bridge .WritableNativeArray ;
2829import com .facebook .react .bridge .WritableNativeMap ;
30+ import com .facebook .react .bridge .UiThreadUtil ;
2931import com .facebook .react .modules .core .DeviceEventManagerModule ;
3032
3133import java .security .KeyStore ;
3234import java .util .HashMap ;
3335import java .util .Map ;
36+ import java .util .concurrent .Executor ;
37+ import java .util .concurrent .Executors ;
3438
3539import javax .crypto .Cipher ;
3640import javax .crypto .KeyGenerator ;
3741import javax .crypto .SecretKey ;
3842import javax .crypto .SecretKeyFactory ;
3943import javax .crypto .spec .IvParameterSpec ;
4044
45+ import androidx .fragment .app .FragmentActivity ;
4146import br .com .classapp .RNSensitiveInfo .utils .AppConstants ;
42- import br .com .classapp .RNSensitiveInfo .view .Fragments .FingerprintAuthenticationDialogFragment ;
43- import br .com .classapp .RNSensitiveInfo .view .Fragments .FingerprintUiHelper ;
4447
4548public class RNSensitiveInfoModule extends ReactContextBaseJavaModule {
4649
@@ -81,27 +84,24 @@ public String getName() {
8184 }
8285
8386 /**
84- * Checks whether the device supports fingerprint authentication and if the user has
85- * enrolled at least one fingerprint .
87+ * Checks whether the device supports Biometric authentication and if the user has
88+ * enrolled at least one credential .
8689 *
87- * @return true if the user has a fingerprint capable device and has enrolled
88- * one or more fingerprints
90+ * @return true if the user has a biometric capable device and has enrolled
91+ * one or more credentials
8992 */
90- private boolean hasSetupFingerprint () {
93+ private boolean hasSetupBiometricCredential () {
9194 try {
92- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M && mFingerprintManager != null ) {
93- if (!mFingerprintManager .isHardwareDetected ()) {
94- return false ;
95- } else if (!mFingerprintManager .hasEnrolledFingerprints ()) {
96- return false ;
97- }
98- return true ;
95+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
96+ ReactApplicationContext reactApplicationContext = getReactApplicationContext ();
97+ BiometricManager biometricManager = BiometricManager .from (reactApplicationContext );
98+ int canAuthenticate = biometricManager .canAuthenticate ();
99+
100+ return canAuthenticate == BiometricManager .BIOMETRIC_SUCCESS ;
99101 } else {
100102 return false ;
101103 }
102- } catch (SecurityException e ) {
103- // Should never be thrown since we have declared the USE_FINGERPRINT permission
104- // in the manifest file
104+ } catch (Exception e ) {
105105 return false ;
106106 }
107107 }
@@ -118,8 +118,12 @@ public void setInvalidatedByBiometricEnrollment(final boolean invalidatedByBiome
118118
119119 @ ReactMethod
120120 public void isHardwareDetected (final Promise pm ) {
121- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M && mFingerprintManager != null ) {
122- pm .resolve (mFingerprintManager .isHardwareDetected ());
121+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
122+ ReactApplicationContext reactApplicationContext = getReactApplicationContext ();
123+ BiometricManager biometricManager = BiometricManager .from (reactApplicationContext );
124+ int canAuthenticate = biometricManager .canAuthenticate ();
125+
126+ pm .resolve (canAuthenticate != BiometricManager .BIOMETRIC_ERROR_NO_HARDWARE );
123127 } else {
124128 pm .resolve (false );
125129 }
@@ -136,7 +140,7 @@ public void hasEnrolledFingerprints(final Promise pm) {
136140
137141 @ ReactMethod
138142 public void isSensorAvailable (final Promise promise ) {
139- promise .resolve (hasSetupFingerprint ());
143+ promise .resolve (hasSetupBiometricCredential ());
140144 }
141145
142146 @ ReactMethod
@@ -190,31 +194,25 @@ public void deleteItem(String key, ReadableMap options, Promise pm) {
190194 pm .resolve (null );
191195 }
192196
197+
193198 @ ReactMethod
194199 public void getAllItems (ReadableMap options , Promise pm ) {
195200
196201 String name = sharedPreferences (options );
197202
198203 Map <String , ?> allEntries = prefs (name ).getAll ();
199- WritableArray resultData = new WritableNativeArray ();
204+ WritableMap resultData = new WritableNativeMap ();
200205
201206 for (Map .Entry <String , ?> entry : allEntries .entrySet ()) {
202- WritableMap entryMap = new WritableNativeMap ();
203-
204- entryMap .putString ("key" , entry .getKey ());
205- entryMap .putString ("value" , entry .getValue ().toString ());
206- entryMap .putString ("service" , name );
207- resultData .pushMap (entryMap );
207+ String value = entry .getValue ().toString ();
208+ resultData .putString (entry .getKey (), value );
208209 }
209-
210- WritableArray resultWrapper = new WritableNativeArray ();
211- resultWrapper .pushArray (resultData );
212- pm .resolve (resultWrapper );
210+ pm .resolve (resultData );
213211 }
214212
215213 @ ReactMethod
216214 public void cancelFingerprintAuth () {
217- if (mCancellationSignal != null && !mCancellationSignal .isCanceled ()) {
215+ if (mCancellationSignal != null && !mCancellationSignal .isCanceled ()) {
218216 mCancellationSignal .cancel ();
219217 }
220218 }
@@ -248,31 +246,38 @@ private void putExtra(String key, Object value, SharedPreferences mSharedPrefere
248246 }
249247 }
250248
251- private void showDialog (final HashMap strings , Object cryptoObject , FingerprintUiHelper . Callback callback ) {
249+ private void showDialog (final HashMap strings , final BiometricPrompt . CryptoObject cryptoObject , final BiometricPrompt . AuthenticationCallback callback ) {
252250 if (android .os .Build .VERSION .SDK_INT >= android .os .Build .VERSION_CODES .M ) {
253- // DialogFragment.show() will take care of adding the fragment
254- // in a transaction. We also want to remove any currently showing
255- // dialog, so make our own transaction and take care of that here.
256-
257- Activity activity = getCurrentActivity ();
258- if (activity == null ) {
259- callback .onError (AppConstants .E_INIT_FAILURE ,
260- strings .containsKey ("cancelled" ) ? strings .get ("cancelled" ).toString () : "Authentication was cancelled" );
261- return ;
262- }
263251
264- FragmentTransaction ft = activity .getFragmentManager ().beginTransaction ();
265- Fragment prev = getCurrentActivity ().getFragmentManager ().findFragmentByTag (AppConstants .DIALOG_FRAGMENT_TAG );
266- if (prev != null ) {
267- ft .remove (prev );
268- }
269- ft .addToBackStack (null );
252+ UiThreadUtil .runOnUiThread (
253+ new Runnable () {
254+ @ Override
255+ public void run () {
256+ try {
257+ Activity activity = getCurrentActivity ();
258+ if (activity == null ) {
259+ callback .onAuthenticationError (BiometricConstants .ERROR_CANCELED ,
260+ strings .containsKey ("cancelled" ) ? strings .get ("cancelled" ).toString () : "Authentication was cancelled" );
261+ return ;
262+ }
270263
271- // Create and show the dialog.
272- FingerprintAuthenticationDialogFragment newFragment = FingerprintAuthenticationDialogFragment .newInstance (strings );
273- newFragment .setCryptoObject ((FingerprintManager .CryptoObject ) cryptoObject );
274- newFragment .setCallback (callback );
275- newFragment .show (ft , AppConstants .DIALOG_FRAGMENT_TAG );
264+ FragmentActivity fragmentActivity = (FragmentActivity ) getCurrentActivity ();
265+ Executor executor = Executors .newSingleThreadExecutor ();
266+ BiometricPrompt biometricPrompt = new BiometricPrompt (fragmentActivity , executor , callback );
267+
268+ BiometricPrompt .PromptInfo promptInfo = new BiometricPrompt .PromptInfo .Builder ()
269+ .setDeviceCredentialAllowed (false )
270+ .setNegativeButtonText (strings .containsKey ("cancel" ) ? strings .get ("cancel" ).toString () : "Cancel" )
271+ .setDescription (strings .containsKey ("description" ) ? strings .get ("description" ).toString () : null )
272+ .setTitle (strings .containsKey ("header" ) ? strings .get ("header" ).toString () : null )
273+ .build ();
274+ biometricPrompt .authenticate (promptInfo , cryptoObject );
275+ } catch (Exception e ) {
276+ throw e ;
277+ }
278+ }
279+ }
280+ );
276281 }
277282 }
278283
@@ -325,7 +330,7 @@ private void prepareKey() throws Exception {
325330
326331 private void putExtraWithAES (final String key , final String value , final SharedPreferences mSharedPreferences , final boolean showModal , final HashMap strings , final Promise pm , Cipher cipher ) {
327332
328- if (android .os .Build .VERSION .SDK_INT >= android .os .Build .VERSION_CODES .M && hasSetupFingerprint ()) {
333+ if (android .os .Build .VERSION .SDK_INT >= android .os .Build .VERSION_CODES .M && hasSetupBiometricCredential ()) {
329334 try {
330335 if (cipher == null ) {
331336 SecretKey secretKey = (SecretKey ) mKeyStore .getKey (KEY_ALIAS_AES , null );
@@ -342,25 +347,26 @@ private void putExtraWithAES(final String key, final String value, final SharedP
342347 info .getUserAuthenticationValidityDurationSeconds () == -1 ) {
343348
344349 if (showModal ) {
345-
346- // define class as a callback
347- class PutExtraWithAESCallback implements FingerprintUiHelper .Callback {
350+ class PutExtraWithAESCallback extends BiometricPrompt .AuthenticationCallback {
348351 @ Override
349- public void onAuthenticated ( FingerprintManager .AuthenticationResult result ) {
352+ public void onAuthenticationSucceeded ( @ NonNull BiometricPrompt .AuthenticationResult result ) {
350353 if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
351354 putExtraWithAES (key , value , mSharedPreferences , true , strings , pm , result .getCryptoObject ().getCipher ());
352355 }
353356 }
354357
355358 @ Override
356- public void onError ( String errorCode , CharSequence errString ) {
359+ public void onAuthenticationError ( int errorCode , @ NonNull CharSequence errString ) {
357360 pm .reject (String .valueOf (errorCode ), errString .toString ());
358361 }
359- }
360362
361- // Show the fingerprint dialog
362- showDialog (strings , new FingerprintManager .CryptoObject (cipher ), new PutExtraWithAESCallback ());
363+ @ Override
364+ public void onAuthenticationFailed () {
365+ pm .reject (AppConstants .E_AUTHENTICATION_NOT_RECOGNIZED , strings .containsKey ("notRecognized" ) ? strings .get ("notRecognized" ).toString () : "Fingerprint not recognized, try again" );
366+ }
367+ }
363368
369+ showDialog (strings , new BiometricPrompt .CryptoObject (cipher ), new PutExtraWithAESCallback ());
364370 } else {
365371 mCancellationSignal = new CancellationSignal ();
366372 mFingerprintManager .authenticate (new FingerprintManager .CryptoObject (cipher ), mCancellationSignal ,
@@ -429,7 +435,7 @@ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult re
429435 private void decryptWithAes (final String encrypted , final boolean showModal , final HashMap strings , final Promise pm , Cipher cipher ) {
430436
431437 if (android .os .Build .VERSION .SDK_INT >= android .os .Build .VERSION_CODES .M
432- && hasSetupFingerprint ()) {
438+ && hasSetupBiometricCredential ()) {
433439
434440 String [] inputs = encrypted .split (DELIMITER );
435441 if (inputs .length < 2 ) {
@@ -440,7 +446,7 @@ && hasSetupFingerprint()) {
440446 byte [] iv = Base64 .decode (inputs [0 ], Base64 .DEFAULT );
441447 byte [] cipherBytes = Base64 .decode (inputs [1 ], Base64 .DEFAULT );
442448
443- if (cipher == null ){
449+ if (cipher == null ) {
444450 SecretKey secretKey = (SecretKey ) mKeyStore .getKey (KEY_ALIAS_AES , null );
445451 cipher = Cipher .getInstance (AES_DEFAULT_TRANSFORMATION );
446452 cipher .init (Cipher .DECRYPT_MODE , secretKey , new IvParameterSpec (iv ));
@@ -453,27 +459,32 @@ && hasSetupFingerprint()) {
453459 info .getUserAuthenticationValidityDurationSeconds () == -1 ) {
454460
455461 if (showModal ) {
456-
457- // define class as a callback
458- class DecryptWithAesCallback implements FingerprintUiHelper .Callback {
462+ class DecryptWithAesCallback extends BiometricPrompt .AuthenticationCallback {
459463 @ Override
460- public void onAuthenticated ( FingerprintManager .AuthenticationResult result ) {
464+ public void onAuthenticationSucceeded ( @ NonNull BiometricPrompt .AuthenticationResult result ) {
461465 if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
462466 decryptWithAes (encrypted , true , strings , pm , result .getCryptoObject ().getCipher ());
463467 }
464468 }
465469
466470 @ Override
467- public void onError ( String errorCode , CharSequence errString ) {
471+ public void onAuthenticationError ( int errorCode , @ NonNull CharSequence errString ) {
468472 pm .reject (String .valueOf (errorCode ), errString .toString ());
469473 }
470- }
471474
472- // Show the fingerprint dialog
473- showDialog (strings , new FingerprintManager .CryptoObject (cipher ), new DecryptWithAesCallback ());
475+ @ Override
476+ public void onAuthenticationFailed () {
477+ pm .reject (AppConstants .E_AUTHENTICATION_NOT_RECOGNIZED , strings .containsKey ("notRecognized" ) ? strings .get ("notRecognized" ).toString () : "Fingerprint not recognized, try again" );
478+ }
479+ }
474480
481+ showDialog (strings , new BiometricPrompt .CryptoObject (cipher ), new DecryptWithAesCallback ());
475482 } else {
476483 mCancellationSignal = new CancellationSignal ();
484+ ReactApplicationContext reactApplicationContext = getReactApplicationContext ();
485+ BiometricManager biometricManager = BiometricManager .from (reactApplicationContext );
486+
487+
477488 mFingerprintManager .authenticate (new FingerprintManager .CryptoObject (cipher ), mCancellationSignal ,
478489 0 , new FingerprintManager .AuthenticationCallback () {
479490
0 commit comments