Skip to content

Commit eaa9e9b

Browse files
committed
Provides native prompt to authenticate; brings Android to parity with iOS
- isSensorAvailable uses android BiometricManager - provides 'Biometrics' biometryType, as opposed to 'Fingerprint' - on Android Q (v29)+, uses device's preferred biometry type (user-decided) - on Android P (v28) and below, defaults to Fingerprint, supported by FingerprintCompat lib - on Android M-P (v23), checks for native fingerprint support on device (Samsung & MeiZu), and defaults to it if available - prefers Android native > other device native - authenticate uses android BiometricPrompt - now provides native prompt, as opposed to in addition to a lower-level key-signing API - now accepts description parameter for title text - in v23+, removes onAttempt, as event emitting can be removed from app in favor of being handled by promise resolve/rejection - update README and examples doc to reflect
1 parent ef1a22b commit eaa9e9b

File tree

14 files changed

+532
-136
lines changed

14 files changed

+532
-136
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ matrix:
2323
components:
2424
- tools
2525
- platform-tools
26-
- build-tools-28.0.3
27-
- android-28
26+
- build-tools-29.0.2
27+
- android-29
2828
sudo: true
2929
before_install:
3030
- nvm install --lts

README.md

Lines changed: 136 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ It provides a **Default View** that prompts the user to place a finger to the iP
2525
</div>
2626

2727
### Android Version
28+
4.0.0 Prefers the new native Android BiometricPrompt lib on any Android >= v23 (M)
29+
4.0.0 also DEPRECATES support for the legacy library that provides support for Samsung & MeiZu phones
30+
31+
3.0.2 and below:
2832
Using an expandable Android Fingerprint API library, which combines [Samsung](http://developer.samsung.com/galaxy/pass#) and [MeiZu](http://open-wiki.flyme.cn/index.php?title=%E6%8C%87%E7%BA%B9%E8%AF%86%E5%88%ABAPI)'s official Fingerprint API.
2933

3034
Samsung and MeiZu's Fingerprint SDK supports most devices which system versions less than Android 6.0.
@@ -76,7 +80,7 @@ $ react-native link react-native-fingerprint-scanner
7680
```
7781
3. Insert the following lines inside the dependencies block in `android/app/build.gradle`:
7882
```
79-
compile project(':react-native-fingerprint-scanner')
83+
implementation project(':react-native-fingerprint-scanner')
8084
```
8185

8286
### App Permissions
@@ -86,12 +90,18 @@ Add the following permissions to their respective files:
8690
#### Android
8791
In your `AndroidManifest.xml`:
8892

89-
API level 28+ ([Reference](https://developer.android.com/reference/android/Manifest.permission#USE_BIOMETRIC))
93+
API level 28+ (Uses Android native BiometricPrompt) ([Reference](https://developer.android.com/reference/android/Manifest.permission#USE_BIOMETRIC))
9094
```xml
9195
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
9296
```
9397

94-
API level <28 ([Reference](https://developer.android.com/reference/android/Manifest.permission#USE_FINGERPRINT))
98+
API level 23-28 (Uses Android native FingerprintCompat) [Reference](https://developer.android.com/reference/android/Manifest.permission#USE_FINGERPRINT))
99+
```xml
100+
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
101+
```
102+
103+
// DEPRECATED in 4.0.0
104+
API level <23 (Uses device-specific native fingerprinting, if available - Samsung & MeiZu only) [Reference](https://developer.android.com/reference/android/Manifest.permission#USE_FINGERPRINT))
95105
```xml
96106
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
97107
```
@@ -107,23 +117,26 @@ In your `Info.plist`:
107117

108118
1. Make sure the following versions are all correct in `android/app/build.gradle`
109119
```
120+
// API v29 enables FaceId
110121
android {
111-
compileSdkVersion 25
112-
buildToolsVersion "25.0.3"
122+
compileSdkVersion 29
123+
buildToolsVersion "29.0.2"
113124
...
114125
defaultConfig {
115-
targetSdkVersion 25
126+
targetSdkVersion 29
116127
```
117128
118129
2. Add necessary rules to `android/app/proguard-rules.pro` if you are using proguard:
119130
```
120131
# MeiZu Fingerprint
121132
133+
// DEPRECATED in 4.0.0
122134
-keep class com.fingerprints.service.** { *; }
123135
-dontwarn com.fingerprints.service.**
124136
125137
# Samsung Fingerprint
126138
139+
// DEPRECATED in 4.0.0
127140
-keep class com.samsung.android.sdk.** { *; }
128141
-dontwarn com.samsung.android.sdk.**
129142
```
@@ -133,6 +146,7 @@ In your `Info.plist`:
133146
* For Gradle < 3 you MUST install react-native-fingerprint-scanner at version <= 2.5.0
134147
* For RN >= 0.57 and/or Gradle >= 3 you MUST install react-native-fingerprint-scanner at version >= 2.6.0
135148
* For RN >= 0.60 you MUST install react-native-fingerprint-scanner at version >= 3.0.0
149+
* For Android native Face Unlock, MUST use >= 4.0.0
136150
137151
## Example
138152
@@ -174,54 +188,84 @@ export default FingerprintPopup;
174188

175189
**Android Implementation**
176190
```javascript
191+
177192
import React, { Component } from 'react';
178193
import PropTypes from 'prop-types';
179-
180194
import {
181195
Alert,
182196
Image,
183197
Text,
184198
TouchableOpacity,
185199
View,
186-
ViewPropTypes
200+
ViewPropTypes,
201+
Platform,
187202
} from 'react-native';
188-
import FingerprintScanner from 'react-native-fingerprint-scanner';
189203

190-
import ShakingText from './ShakingText.component';
204+
import FingerprintScanner from 'react-native-fingerprint-scanner';
191205
import styles from './FingerprintPopup.component.styles';
206+
import ShakingText from './ShakingText.component';
192207

193-
class FingerprintPopup extends Component {
194208

209+
// - this example component supports both the
210+
// legacy device-specific (Android < v23) and
211+
// current (Android >= 23) biometric APIs
212+
// - your lib and implementation may not need both
213+
class BiometricPopup extends Component {
195214
constructor(props) {
196215
super(props);
197-
this.state = { errorMessage: undefined };
216+
this.state = {
217+
errorMessageLegacy: undefined,
218+
biometricLegacy: undefined
219+
};
220+
221+
this.description = null;
198222
}
199223

200224
componentDidMount() {
225+
if (requiresLegacyAuthentication()) {
226+
authLegacy();
227+
} else {
228+
authCurrent();
229+
}
230+
}
231+
232+
componentWillUnmount = () => {
233+
FingerprintScanner.release();
234+
}
235+
236+
requiresLegacyAuthentication() {
237+
return Platform.Version < 23;
238+
}
239+
240+
authCurrent() {
201241
FingerprintScanner
202-
.authenticate({ onAttempt: this.handleAuthenticationAttempted })
242+
.authenticate({ description: 'Log in with Biometrics' })
203243
.then(() => {
204-
this.props.handlePopupDismissed();
244+
this.props.onAuthenticate();
245+
});
246+
}
247+
248+
authLegacy() {
249+
FingerprintScanner
250+
.authenticate({ onAttempt: this.handleAuthenticationAttemptedLegacy })
251+
.then(() => {
252+
this.props.handlePopupDismissedLegacy();
205253
Alert.alert('Fingerprint Authentication', 'Authenticated successfully');
206254
})
207255
.catch((error) => {
208-
this.setState({ errorMessage: error.message });
256+
this.setState({ errorMessageLegacy: error.message, biometricLegacy: error.biometric });
209257
this.description.shake();
210258
});
211259
}
212260

213-
componentWillUnmount() {
214-
FingerprintScanner.release();
215-
}
216-
217-
handleAuthenticationAttempted = (error) => {
218-
this.setState({ errorMessage: error.message });
261+
handleAuthenticationAttemptedLegacy = (error) => {
262+
this.setState({ errorMessageLegacy: error.message });
219263
this.description.shake();
220264
};
221265

222-
render() {
223-
const { errorMessage } = this.state;
224-
const { style, handlePopupDismissed } = this.props;
266+
renderLegacy() {
267+
const { errorMessageLegacy, biometricLegacy } = this.state;
268+
const { style, handlePopupDismissedLegacy } = this.props;
225269

226270
return (
227271
<View style={styles.container}>
@@ -233,17 +277,17 @@ class FingerprintPopup extends Component {
233277
/>
234278

235279
<Text style={styles.heading}>
236-
Fingerprint{'\n'}Authentication
280+
Biometric{'\n'}Authentication
237281
</Text>
238282
<ShakingText
239283
ref={(instance) => { this.description = instance; }}
240-
style={styles.description(!!errorMessage)}>
241-
{errorMessage || 'Scan your fingerprint on the\ndevice scanner to continue'}
284+
style={styles.description(!!errorMessageLegacy)}>
285+
{errorMessageLegacy || `Scan your ${biometricLegacy} on the\ndevice scanner to continue`}
242286
</ShakingText>
243287

244288
<TouchableOpacity
245289
style={styles.buttonContainer}
246-
onPress={handlePopupDismissed}
290+
onPress={handlePopupDismissedLegacy}
247291
>
248292
<Text style={styles.buttonText}>
249293
BACK TO MAIN
@@ -254,14 +298,25 @@ class FingerprintPopup extends Component {
254298
</View>
255299
);
256300
}
301+
302+
303+
render = () => {
304+
if (this.requiresLegacyAuthentication()) {
305+
return this.renderLegacy();
306+
}
307+
308+
// current API UI provided by native BiometricPrompt
309+
return null;
310+
}
257311
}
258312

259-
FingerprintPopup.propTypes = {
313+
BiometricPopup.propTypes = {
314+
onAuthenticate: PropTypes.func.isRequired,
315+
handlePopupDismissedLegacy: PropTypes.func,
260316
style: ViewPropTypes.style,
261-
handlePopupDismissed: PropTypes.func.isRequired,
262317
};
263318

264-
export default FingerprintPopup;
319+
export default BiometricPopup;
265320
```
266321

267322
## API
@@ -271,6 +326,8 @@ Checks if Fingerprint Scanner is able to be used by now.
271326

272327
- Returns a `Promise<string>`
273328
- `biometryType: String` - The type of biometric authentication supported by the device.
329+
- iOS: biometryType = 'Touch ID', 'Face ID'
330+
- Android: biometryType = 'Biometrics'
274331
- `error: FingerprintScannerError { name, message, biometric }` - The name and message of failure and the biometric type in use.
275332

276333
```javascript
@@ -304,29 +361,59 @@ componentDidMount() {
304361
}
305362
```
306363

307-
### `authenticate({ onAttempt })`: (Android)
364+
### `authenticate({ description="Log In", onAttempt=() => (null) })`: (Android)
308365
Starts Fingerprint authentication on Android.
309366

310367
- Returns a `Promise`
368+
- `description: String` the title text to display in the native Android popup
311369
- `onAttempt: Function` - a callback function when users are trying to scan their fingerprint but failed.
312370

313371
```javascript
314372
componentDidMount() {
373+
if (requiresLegacyAuthentication()) {
374+
authLegacy();
375+
} else {
376+
authCurrent();
377+
}
378+
}
379+
380+
componentWillUnmount = () => {
381+
FingerprintScanner.release();
382+
}
383+
384+
requiresLegacyAuthentication() {
385+
return Platform.Version < 23;
386+
}
387+
388+
authCurrent() {
315389
FingerprintScanner
316-
.authenticate({ onAttempt: this.handleAuthenticationAttempted })
390+
.authenticate({ description: 'Log in with Biometrics' })
317391
.then(() => {
318-
this.props.handlePopupDismissed();
392+
this.props.onAuthenticate();
393+
});
394+
}
395+
396+
authLegacy() {
397+
FingerprintScanner
398+
.authenticate({ onAttempt: this.handleAuthenticationAttemptedLegacy })
399+
.then(() => {
400+
this.props.handlePopupDismissedLegacy();
319401
Alert.alert('Fingerprint Authentication', 'Authenticated successfully');
320402
})
321403
.catch((error) => {
322-
this.setState({ errorMessage: error.message });
404+
this.setState({ errorMessageLegacy: error.message, biometricLegacy: error.biometric });
323405
this.description.shake();
324406
});
325407
}
408+
409+
handleAuthenticationAttemptedLegacy = (error) => {
410+
this.setState({ errorMessageLegacy: error.message });
411+
this.description.shake();
412+
};
326413
```
327414

328415
### `release()`: (Android)
329-
Stops fingerprint scanner listener, releases cache of internal state in native code.
416+
Stops fingerprint scanner listener, releases cache of internal state in native code, and cancels native prompt if visible.
330417

331418
- Returns a `Void`
332419

@@ -338,27 +425,32 @@ componentWillUnmount() {
338425

339426
### `Types of Biometrics`
340427

341-
| Value | OS |
342-
|---|---|
343-
| Touch ID | iOS |
344-
| Face ID | iOS |
345-
| Fingerprint | Android |
428+
| Value | OS | Description|
429+
|---|---|---|
430+
| Touch ID | iOS | |
431+
| Face ID | iOS | |
432+
| Biometrics | Android | Refers to the biometric set as preferred on the device |
346433

347434
### `Errors`
348435

349436
| Name | Message |
350437
|---|---|
351438
| AuthenticationNotMatch | No match |
352439
| AuthenticationFailed | Authentication was not successful because the user failed to provide valid credentials |
440+
| AuthenticationTimeout | Authentication was not successful because the operation timed out |
441+
| AuthenticationProcessFailed | 'Sensor was unable to process the image. Please try again |
353442
| UserCancel | Authentication was canceled by the user - e.g. the user tapped Cancel in the dialog |
354443
| UserFallback | Authentication was canceled because the user tapped the fallback button (Enter Password) |
355444
| SystemCancel | Authentication was canceled by system - e.g. if another application came to foreground while the authentication dialog was up |
356445
| PasscodeNotSet | Authentication could not start because the passcode is not set on the device |
357-
| FingerprintScannerNotAvailable | Authentication could not start because Fingerprint Scanner is not available on the device |
358-
| FingerprintScannerNotEnrolled | Authentication could not start because Fingerprint Scanner has no enrolled fingers |
446+
| DeviceLocked | Authentication was not successful, the device currently in a lockout of 30 seconds |
447+
| DeviceLockedPermanent | Authentication was not successful, device must be unlocked via password |
448+
| DeviceOutOfMemory | Authentication could not proceed because there is not enough free memory on the device |
449+
| HardwareError | A hardware error occurred |
359450
| FingerprintScannerUnknownError | Could not authenticate for an unknown reason |
360451
| FingerprintScannerNotSupported | Device does not support Fingerprint Scanner |
361-
| DeviceLocked | Authentication was not successful, the device currently in a lockout of 30 seconds |
452+
| FingerprintScannerNotEnrolled | Authentication could not start because Fingerprint Scanner has no enrolled fingers |
453+
| FingerprintScannerNotAvailable | Authentication could not start because Fingerprint Scanner is not available on the device |
362454

363455
## License
364456

android/build.gradle

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ buildscript {
2121
apply plugin: 'com.android.library'
2222

2323
android {
24-
compileSdkVersion safeExtGet('compileSdkVersion', 25)
25-
buildToolsVersion safeExtGet('buildToolsVersion', '25.0.3')
24+
compileSdkVersion safeExtGet('compileSdkVersion', 29)
25+
buildToolsVersion safeExtGet('buildToolsVersion', '29.0.2')
2626

2727
defaultConfig {
2828
minSdkVersion safeExtGet('minSdkVersion', 16)
29-
targetSdkVersion safeExtGet('targetSdkVersion', 25)
29+
targetSdkVersion safeExtGet('targetSdkVersion', 29)
3030
versionCode 1
3131
versionName "1.0"
3232
}
@@ -42,6 +42,10 @@ repositories {
4242

4343
dependencies {
4444
compile 'com.facebook.react:react-native:+'
45+
// androidx:biometric now supports fingerprint back to Android v23
46+
implementation "androidx.biometric:biometric:1.0.1"
47+
48+
// retain fingerprintScanner lib for compat with Android v16-23 device-specific drivers (Samsung & MeiZu)
4549
// 1.2.3 is the minimum version compatible with androidx.
4650
// See https://github.com/uccmawei/FingerprintIdentify/issues/74
4751
// (translation https://translate.google.com/translate?sl=zh-CN&tl=en&u=https://github.com/uccmawei/FingerprintIdentify/issues/74)

android/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
22
package="com.hieuvp.fingerprint">
33
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
4+
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
45
<uses-permission android:name="com.fingerprints.service.ACCESS_FINGERPRINT_MANAGER" />
56
<uses-permission
67
android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />

0 commit comments

Comments
 (0)