Skip to content

Commit 38c09eb

Browse files
Android Custom UI Fingerprint detection stops working after suspending and resuming application while screen is showing #62
1 parent 957e20c commit 38c09eb

File tree

5 files changed

+129
-108
lines changed

5 files changed

+129
-108
lines changed

demo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"version": "6.1.1"
66
},
77
"tns-android": {
8-
"version": "6.1.1"
8+
"version": "6.1.2"
99
}
1010
},
1111
"dependencies": {

src/fingerprint-auth.android.ts

Lines changed: 113 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export class FingerprintAuth implements FingerprintAuthApi {
1515

1616
constructor() {
1717
this.keyguardManager = androidUtils
18-
.getApplicationContext()
19-
.getSystemService("keyguard");
18+
.getApplicationContext()
19+
.getSystemService("keyguard");
2020
}
2121

2222
// TODO can we detect face on the Samsung S8?
@@ -37,10 +37,10 @@ export class FingerprintAuth implements FingerprintAuthApi {
3737
}
3838

3939
const fingerprintManager = androidUtils
40-
.getApplicationContext()
41-
.getSystemService(
42-
"fingerprint"
43-
) as android.hardware.fingerprint.FingerprintManager;
40+
.getApplicationContext()
41+
.getSystemService(
42+
"fingerprint"
43+
) as android.hardware.fingerprint.FingerprintManager;
4444

4545
if (!fingerprintManager.isHardwareDetected()) {
4646
// Device doesn't support fingerprint authentication
@@ -59,7 +59,7 @@ export class FingerprintAuth implements FingerprintAuthApi {
5959
} else {
6060
// User hasn't enrolled any fingerprints to authenticate with
6161
reject(
62-
`User hasn't enrolled any fingerprints to authenticate with`
62+
`User hasn't enrolled any fingerprints to authenticate with`
6363
);
6464
}
6565
} else {
@@ -83,12 +83,11 @@ export class FingerprintAuth implements FingerprintAuthApi {
8383
});
8484
}
8585

86-
8786
private verifyWithCustomAndroidUI(resolve, reject, authenticationCallback) {
8887
// this instance is com.jesusm.kfingerprintmanager.KFingerprintManager, not the android OS fingerprint manager
8988
this.fingerPrintManager.authenticate(
90-
authenticationCallback,
91-
this.getActivity().getSupportFragmentManager()
89+
authenticationCallback,
90+
this.getActivity().getSupportFragmentManager()
9291
);
9392
}
9493

@@ -98,73 +97,73 @@ export class FingerprintAuth implements FingerprintAuthApi {
9897
// in case 'activity.getSupportFragmentManager' is available ({N} started supporting it,
9998
// or the user added our Activity to their Android manifest), use the 3rd party FP library
10099
const hasSupportFragment =
101-
this.getActivity().getSupportFragmentManager !== undefined;
100+
this.getActivity().getSupportFragmentManager !== undefined;
102101

103102
if (options.useCustomAndroidUI && !hasSupportFragment) {
104103
reject({
105104
code: ERROR_CODES.DEVELOPER_ERROR,
106105
message:
107-
"Custom Fingerprint UI requires changes to AndroidManifest.xml. See the nativescript-fingerprint-auth documentation."
106+
"Custom Fingerprint UI requires changes to AndroidManifest.xml. See the nativescript-fingerprint-auth documentation."
108107
});
109108
} else if (options.useCustomAndroidUI && hasSupportFragment) {
110109
if (!this.fingerPrintManager) {
111110
this.fingerPrintManager = new com.jesusm.kfingerprintmanager.KFingerprintManager(
112-
androidUtils.getApplicationContext(),
113-
KEY_NAME
111+
androidUtils.getApplicationContext(),
112+
KEY_NAME
114113
);
115114
}
116115
const that = this;
117116
const callback = new com.jesusm.kfingerprintmanager.KFingerprintManager.AuthenticationCallback(
118-
{
119-
attempts: 0,
120-
onAuthenticationFailedWithHelp(help): void {
121-
if (++this.attempts < 3) {
122-
// just invoke the UI again as it's very sensitive (need a timeout to prevent an infinite loop)
123-
setTimeout(
124-
() => that.verifyWithCustomAndroidUI(resolve, reject, this),
125-
50
126-
);
127-
} else {
117+
{
118+
attempts: 0,
119+
onAuthenticationFailedWithHelp(help): void {
120+
if (++this.attempts < 3) {
121+
// just invoke the UI again as it's very sensitive (need a timeout to prevent an infinite loop)
122+
setTimeout(
123+
() => that.verifyWithCustomAndroidUI(resolve, reject, this),
124+
50
125+
);
126+
} else {
127+
reject({
128+
code: ERROR_CODES.RECOVERABLE_ERROR,
129+
message: help
130+
});
131+
}
132+
},
133+
onAuthenticationSuccess(): void {
134+
resolve();
135+
},
136+
onSuccessWithManualPassword(password): void {
137+
resolve(password);
138+
},
139+
onFingerprintNotRecognized(): void {
140+
if (++this.attempts < 3) {
141+
// just invoke the UI again as it's very sensitive (need a timeout to prevent an infinite loop)
142+
setTimeout(
143+
() => that.verifyWithCustomAndroidUI(resolve, reject, this),
144+
50
145+
);
146+
} else {
147+
reject({
148+
code: ERROR_CODES.NOT_RECOGNIZED,
149+
message: "Fingerprint not recognized."
150+
});
151+
}
152+
},
153+
onFingerprintNotAvailable(): void {
128154
reject({
129-
code: ERROR_CODES.RECOVERABLE_ERROR,
130-
message: help
155+
code: ERROR_CODES.NOT_CONFIGURED,
156+
message:
157+
'Secure lock screen hasn\'t been set up.\n Go to "Settings -> Security -> Screenlock" to set up a lock screen.'
131158
});
132-
}
133-
},
134-
onAuthenticationSuccess(): void {
135-
resolve();
136-
},
137-
onSuccessWithManualPassword(password): void {
138-
resolve(password);
139-
},
140-
onFingerprintNotRecognized(): void {
141-
if (++this.attempts < 3) {
142-
// just invoke the UI again as it's very sensitive (need a timeout to prevent an infinite loop)
143-
setTimeout(
144-
() => that.verifyWithCustomAndroidUI(resolve, reject, this),
145-
50
146-
);
147-
} else {
159+
},
160+
onCancelled(): void {
148161
reject({
149-
code: ERROR_CODES.NOT_RECOGNIZED,
150-
message: "Fingerprint not recognized."
162+
code: ERROR_CODES.PASSWORD_FALLBACK_SELECTED,
163+
message: "Cancelled by user"
151164
});
152165
}
153-
},
154-
onFingerprintNotAvailable(): void {
155-
reject({
156-
code: ERROR_CODES.NOT_CONFIGURED,
157-
message:
158-
'Secure lock screen hasn\'t been set up.\n Go to "Settings -> Security -> Screenlock" to set up a lock screen.'
159-
});
160-
},
161-
onCancelled(): void {
162-
reject({
163-
code: ERROR_CODES.PASSWORD_FALLBACK_SELECTED,
164-
message: "Cancelled by user"
165-
});
166166
}
167-
}
168167
);
169168
this.verifyWithCustomAndroidUI(resolve, reject, callback);
170169
} else {
@@ -183,14 +182,14 @@ export class FingerprintAuth implements FingerprintAuthApi {
183182
}
184183
}
185184
app.android.off(
186-
app.AndroidApplication.activityResultEvent,
187-
onActivityResult
185+
app.AndroidApplication.activityResultEvent,
186+
onActivityResult
188187
);
189188
};
190189

191190
app.android.on(
192-
app.AndroidApplication.activityResultEvent,
193-
onActivityResult
191+
app.AndroidApplication.activityResultEvent,
192+
onActivityResult
194193
);
195194

196195
if (!this.keyguardManager) {
@@ -200,13 +199,13 @@ export class FingerprintAuth implements FingerprintAuthApi {
200199
});
201200
}
202201
if (
203-
this.keyguardManager &&
204-
!this.keyguardManager.isKeyguardSecure()
202+
this.keyguardManager &&
203+
!this.keyguardManager.isKeyguardSecure()
205204
) {
206205
reject({
207206
code: ERROR_CODES.NOT_CONFIGURED,
208207
message:
209-
'Secure lock screen hasn\'t been set up.\n Go to "Settings -> Security -> Screenlock" to set up a lock screen.'
208+
'Secure lock screen hasn\'t been set up.\n Go to "Settings -> Security -> Screenlock" to set up a lock screen.'
210209
});
211210
}
212211

@@ -235,11 +234,23 @@ export class FingerprintAuth implements FingerprintAuthApi {
235234
}
236235

237236
verifyFingerprintWithCustomFallback(
238-
options: VerifyFingerprintWithCustomFallbackOptions
237+
options: VerifyFingerprintWithCustomFallbackOptions
239238
): Promise<any> {
240239
return this.verifyFingerprint(options);
241240
}
242241

242+
close(): void {
243+
const fragmentManager = this.getActivity().getSupportFragmentManager();
244+
// this is for custom ui
245+
const fragmentTag = "KFingerprintManager:fingerprintDialog";
246+
const fragment = fragmentManager.findFragmentByTag(fragmentTag);
247+
if (fragment) {
248+
fragmentManager.beginTransaction().remove(fragment).commit();
249+
} else {
250+
// AFAIK it's not possible to programmatically close the standard one
251+
}
252+
}
253+
243254
/**
244255
* Creates a symmetric key in the Android Key Store which can only be used after the user has
245256
* authenticated with device credentials within the last X seconds.
@@ -249,37 +260,37 @@ export class FingerprintAuth implements FingerprintAuthApi {
249260
const keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
250261
keyStore.load(null);
251262
const keyGenerator = javax.crypto.KeyGenerator.getInstance(
252-
android.security.keystore.KeyProperties.KEY_ALGORITHM_AES,
253-
"AndroidKeyStore"
263+
android.security.keystore.KeyProperties.KEY_ALGORITHM_AES,
264+
"AndroidKeyStore"
254265
);
255266

256267
keyGenerator.init(
257-
new android.security.keystore.KeyGenParameterSpec.Builder(
258-
KEY_NAME,
259-
android.security.keystore.KeyProperties.PURPOSE_ENCRYPT |
260-
android.security.keystore.KeyProperties.PURPOSE_DECRYPT
261-
)
262-
.setBlockModes([
263-
android.security.keystore.KeyProperties.BLOCK_MODE_CBC
264-
])
265-
.setUserAuthenticationRequired(true)
266-
.setUserAuthenticationValidityDurationSeconds(
267-
options && options.authenticationValidityDuration
268-
? options.authenticationValidityDuration
269-
: 5
268+
new android.security.keystore.KeyGenParameterSpec.Builder(
269+
KEY_NAME,
270+
android.security.keystore.KeyProperties.PURPOSE_ENCRYPT |
271+
android.security.keystore.KeyProperties.PURPOSE_DECRYPT
270272
)
271-
.setEncryptionPaddings([
272-
android.security.keystore.KeyProperties.ENCRYPTION_PADDING_PKCS7
273-
])
274-
.build()
273+
.setBlockModes([
274+
android.security.keystore.KeyProperties.BLOCK_MODE_CBC
275+
])
276+
.setUserAuthenticationRequired(true)
277+
.setUserAuthenticationValidityDurationSeconds(
278+
options && options.authenticationValidityDuration
279+
? options.authenticationValidityDuration
280+
: 5
281+
)
282+
.setEncryptionPaddings([
283+
android.security.keystore.KeyProperties.ENCRYPTION_PADDING_PKCS7
284+
])
285+
.build()
275286
);
276287
keyGenerator.generateKey();
277288
} catch (error) {
278289
// checks if the AES algorithm is implemented by the AndroidKeyStore
279290
if (
280-
`${error.nativeException}`.indexOf(
281-
"java.security.NoSuchAlgorithmException:"
282-
) > -1
291+
`${error.nativeException}`.indexOf(
292+
"java.security.NoSuchAlgorithmException:"
293+
) > -1
283294
) {
284295
// You need a device with API level >= 23 in order to detect if the user has already been authenticated in the last x seconds.
285296
}
@@ -293,9 +304,9 @@ export class FingerprintAuth implements FingerprintAuthApi {
293304
const secretKey = keyStore.getKey(KEY_NAME, null);
294305

295306
const cipher = javax.crypto.Cipher.getInstance(
296-
`${android.security.keystore.KeyProperties.KEY_ALGORITHM_AES}/${
297-
android.security.keystore.KeyProperties.BLOCK_MODE_CBC
298-
}/${android.security.keystore.KeyProperties.ENCRYPTION_PADDING_PKCS7}`
307+
`${android.security.keystore.KeyProperties.KEY_ALGORITHM_AES}/${
308+
android.security.keystore.KeyProperties.BLOCK_MODE_CBC
309+
}/${android.security.keystore.KeyProperties.ENCRYPTION_PADDING_PKCS7}`
299310
);
300311

301312
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey);
@@ -304,17 +315,17 @@ export class FingerprintAuth implements FingerprintAuthApi {
304315
return true;
305316
} catch (error) {
306317
if (
307-
`${error.nativeException}`.indexOf(
308-
"android.security.keystore.UserNotAuthenticatedException"
309-
) > -1
318+
`${error.nativeException}`.indexOf(
319+
"android.security.keystore.UserNotAuthenticatedException"
320+
) > -1
310321
) {
311322
// the user must provide their credentials in order to proceed
312323
this.showAuthenticationScreen(options);
313324
return undefined;
314325
} else if (
315-
`${error.nativeException}`.indexOf(
316-
"android.security.keystore.KeyPermanentlyInvalidatedException"
317-
) > -1
326+
`${error.nativeException}`.indexOf(
327+
"android.security.keystore.KeyPermanentlyInvalidatedException"
328+
) > -1
318329
) {
319330
// Invalid fingerprint
320331
console.log(error);
@@ -331,14 +342,14 @@ export class FingerprintAuth implements FingerprintAuthApi {
331342
private showAuthenticationScreen(options): void {
332343
// https://developer.android.com/reference/android/app/KeyguardManager#createConfirmDeviceCredentialIntent(java.lang.CharSequence,%2520java.lang.CharSequence)
333344
const intent = (this
334-
.keyguardManager as any).createConfirmDeviceCredentialIntent(
335-
options && options.title ? options.title : null,
336-
options && options.message ? options.message : null
345+
.keyguardManager as any).createConfirmDeviceCredentialIntent(
346+
options && options.title ? options.title : null,
347+
options && options.message ? options.message : null
337348
);
338349
if (intent !== null) {
339350
this.getActivity().startActivityForResult(
340-
intent,
341-
REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS
351+
intent,
352+
REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS
342353
);
343354
}
344355
}

src/fingerprint-auth.common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,6 @@ export interface FingerprintAuthApi {
8181
verifyFingerprintWithCustomFallback(
8282
options: VerifyFingerprintWithCustomFallbackOptions
8383
): Promise<void>;
84+
85+
close(): void;
8486
}

0 commit comments

Comments
 (0)