Skip to content

Commit fd78b9e

Browse files
committed
Made it easier to detect the case where the user cancelled a fingerprint request. Added a cancel button to the fingerprint dialog on Android <= 9. Removed the auto fail behaviour after 5 failed fingerprints on Android. It is no longer necessary because there is a cancel button for the user to cancel the fingerprint request.
You can test to see if the fingerprint request was cancelled by the user by via either the isCancelled() method on the AsyncResource directly, or by calling AsyncResource.isCancelled(error) on the error parameter of the onResult() callback. codenameone/CodenameOne#3045
1 parent 9590f85 commit fd78b9e

File tree

11 files changed

+152
-52
lines changed

11 files changed

+152
-52
lines changed

native/android/com/codename1/fingerprint/impl/InternalFingerprintImpl.java

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -372,10 +372,10 @@ public void onAuthenticationFailed() {
372372
// either the user cancels the action, it succeeds, or until
373373
// we cancel the action with cs.cancel.
374374
// Here, we'll try 5 times then quit.
375-
if (failures++ > 5) {
376-
cs.cancel();
377-
InternalCallback.requestComplete(requestId, false);
378-
}
375+
//if (failures++ > 5) {
376+
// cs.cancel();
377+
// InternalCallback.requestComplete(requestId, false);
378+
//}
379379
}
380380

381381
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
@@ -431,7 +431,7 @@ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult resul
431431
.setNegativeButton("Cancel", AndroidNativeUtil.getActivity().getMainExecutor(), new DialogInterface.OnClickListener() {
432432
@Override
433433
public void onClick(DialogInterface dialogInterface, int i) {
434-
InternalCallback.requestError(requestId, "Cancelled by the user");
434+
InternalCallback.requestError(requestId, "__CANCELLED__");
435435
}
436436
})
437437
.build().authenticate(crypto, cs, AndroidNativeUtil.getActivity().getMainExecutor(), callback);
@@ -507,10 +507,10 @@ public void onAuthenticationFailed() {
507507
// either the user cancels the action, it succeeds, or until
508508
// we cancel the action with cs.cancel.
509509
// Here, we'll try 5 times then quit.
510-
if (failures++ > 5) {
511-
cs.cancel();
512-
InternalCallback.requestComplete(requestId, false);
513-
}
510+
//if (failures++ > 5) {
511+
// cs.cancel();
512+
// InternalCallback.requestComplete(requestId, false);
513+
//}
514514
}
515515

516516
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
@@ -644,10 +644,10 @@ public void onAuthenticationFailed() {
644644
// either the user cancels the action, it succeeds, or until
645645
// we cancel the action with cs.cancel.
646646
// Here, we'll try 5 times then quit.
647-
if (failures++ > 5) {
648-
cs.cancel();
649-
InternalCallback.requestError(requestId, "Authentication failed");
650-
}
647+
//if (failures++ > 5) {
648+
// cs.cancel();
649+
// InternalCallback.requestError(requestId, "Authentication failed");
650+
//}
651651

652652
}
653653

@@ -696,7 +696,7 @@ public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult resul
696696
.setNegativeButton("Cancel", AndroidNativeUtil.getActivity().getMainExecutor(), new DialogInterface.OnClickListener() {
697697
@Override
698698
public void onClick(DialogInterface dialogInterface, int i) {
699-
InternalCallback.requestError(requestId, "Cancelled by the user");
699+
InternalCallback.requestError(requestId, "__CANCELLED__");
700700
}
701701
})
702702
.build().authenticate(crypto, cs, AndroidNativeUtil.getActivity().getMainExecutor(), callback);
@@ -776,10 +776,10 @@ public void onAuthenticationFailed() {
776776
// either the user cancels the action, it succeeds, or until
777777
// we cancel the action with cs.cancel.
778778
// Here, we'll try 5 times then quit.
779-
if (failures++ > 5) {
780-
cs.cancel();
781-
InternalCallback.requestError(requestId, "Authentication failed");
782-
}
779+
//if (failures++ > 5) {
780+
// cs.cancel();
781+
// InternalCallback.requestError(requestId, "Authentication failed");
782+
//}
783783

784784
}
785785

@@ -1002,4 +1002,17 @@ private void removePermanentlyInvalidatedKey() {
10021002
Log.e(e);
10031003
}
10041004
}
1005+
1006+
public void cancelRequest(final int requestId) {
1007+
AndroidNativeUtil.getActivity().runOnUiThread(new Runnable() {
1008+
public void run() {
1009+
if (cancellationSignal != null) {
1010+
cancellationSignal.cancel();
1011+
cancellationSignal = null;
1012+
InternalCallback.requestError(requestId, "__CANCELLED__");
1013+
}
1014+
1015+
}
1016+
});
1017+
}
10051018
}

native/ios/com_codename1_fingerprint_impl_InternalFingerprintImpl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
-(void)addPassword:(int)requestId param1:(NSString*)reason param2:(NSString*)account param3:(NSString*)password;
1010
-(void)deletePassword:(int)requestId param1:(NSString*)reason param2:(NSString*)account;
1111
-(void)getPassword:(int)requestId param1:(NSString*)reason param2:(NSString*)account;
12-
12+
-(void)cancelRequest:(int)requestId;
1313
@end

native/ios/com_codename1_fingerprint_impl_InternalFingerprintImpl.m

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ -(void)updatePassword:(int)requestId reason:(NSString*)reason account:(NSString*
134134
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)changes);
135135
if (status == errSecSuccess) {
136136
com_codename1_fingerprint_impl_InternalCallback_requestComplete___int_boolean(getThreadLocalData(), requestId, JAVA_TRUE);
137+
} else if (status == errSecUserCanceled) {
138+
JAVA_OBJECT jErrorMessage = fromNSString(getThreadLocalData(), @"__CANCELLED__");
139+
com_codename1_fingerprint_impl_InternalCallback_requestError___int_java_lang_String(getThreadLocalData(), requestId, jErrorMessage);
137140
} else {
138141
NSString* errorMessage = [self errorString:status];
139142
JAVA_OBJECT jErrorMessage = fromNSString(getThreadLocalData(), errorMessage);
@@ -164,6 +167,9 @@ -(void)addPassword:(int)requestId param1:(NSString*)reason param2:(NSString*)acc
164167
}
165168
if (status == errSecSuccess) {
166169
com_codename1_fingerprint_impl_InternalCallback_requestComplete___int_boolean(getThreadLocalData(), requestId, JAVA_TRUE);
170+
} else if (status == errSecUserCanceled) {
171+
JAVA_OBJECT jErrorMessage = fromNSString(getThreadLocalData(), @"__CANCELLED__");
172+
com_codename1_fingerprint_impl_InternalCallback_requestError___int_java_lang_String(getThreadLocalData(), requestId, jErrorMessage);
167173
} else {
168174
NSString* errorMessage = [self errorString:status];
169175
JAVA_OBJECT jErrorMessage = fromNSString(getThreadLocalData(), errorMessage);
@@ -194,6 +200,9 @@ -(void)deletePassword:(int)requestId param1:(NSString*)reason param2:(NSString*)
194200
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)dict);
195201
if (status == errSecSuccess) {
196202
com_codename1_fingerprint_impl_InternalCallback_requestComplete___int_boolean(getThreadLocalData(), requestId, JAVA_TRUE);
203+
} else if (status == errSecUserCanceled) {
204+
JAVA_OBJECT jErrorMessage = fromNSString(getThreadLocalData(), @"__CANCELLED__");
205+
com_codename1_fingerprint_impl_InternalCallback_requestError___int_java_lang_String(getThreadLocalData(), requestId, jErrorMessage);
197206
} else {
198207
NSString* errorMessage = [self errorString:status];
199208
JAVA_OBJECT jErrorMessage = fromNSString(getThreadLocalData(), errorMessage);
@@ -219,6 +228,9 @@ -(void)getPassword:(int)requestId param1:(NSString*)reason param2:(NSString*)acc
219228
NSString* dataStr = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
220229
JAVA_OBJECT jDataStr = fromNSString(getThreadLocalData(), dataStr);
221230
com_codename1_fingerprint_impl_InternalCallback_requestSuccess___int_java_lang_String(getThreadLocalData(), requestId, jDataStr);
231+
} else if (status == errSecUserCanceled) {
232+
JAVA_OBJECT jErrorMessage = fromNSString(getThreadLocalData(), @"__CANCELLED__");
233+
com_codename1_fingerprint_impl_InternalCallback_requestError___int_java_lang_String(getThreadLocalData(), requestId, jErrorMessage);
222234
} else {
223235
NSString* errorMessage = [self errorString:status];
224236
JAVA_OBJECT jErrorMessage = fromNSString(getThreadLocalData(), errorMessage);
@@ -227,5 +239,8 @@ -(void)getPassword:(int)requestId param1:(NSString*)reason param2:(NSString*)acc
227239
});
228240
}
229241

242+
-(void)cancelRequest:(int)requestId {
243+
NSLog(@"User requested cancelling fingerprint/faceID request. Not implemented on iOS.");
244+
}
230245

231246
@end

native/j2me/com/codename1/fingerprint/impl/InternalFingerprintImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@ public void deletePassword(int requestId, String reason, String key) {
2222
public void getPassword(int requestId, String reason, String key) {
2323

2424
}
25+
26+
public void cancelRequest(int requestId){}
2527

2628
}

native/javascript/com_codename1_fingerprint_impl_InternalFingerprint.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ var o = {};
2121
o.getPassword__int_java_lang_String_java_lang_String = function(param1, param2, param3, callback) {
2222
callback.error(new Error("Not implemented yet"));
2323
};
24+
25+
o.cancelRequest__int = function(requestId){};
2426

2527
o.isSupported_ = function(callback) {
2628
callback.complete(false);

native/javase/com/codename1/fingerprint/impl/InternalFingerprintImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,7 @@ public void getPassword(int requestId, String reason, String key) {
5252
installBuildHints();
5353
InternalCallback.requestError(requestId, "getSecureItem not supported on this platform");
5454
}
55+
56+
public void cancelRequest(int requestId) {}
5557

5658
}

native/rim/com/codename1/fingerprint/impl/InternalFingerprintImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@ public void deletePassword(int requestId, String reason, String key) {
2222
public void getPassword(int requestId, String reason, String key) {
2323

2424
}
25+
26+
public void cancelRequest(int requestId) {}
2527

2628
}

native/win/com/codename1/fingerprint/impl/InternalFingerprintImpl.cs

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,27 @@ namespace com.codename1.fingerprint.impl{
22

33

44
public class InternalFingerprintImpl : IInternalFingerprintImpl {
5-
public void addPassword(int param, String param1, String param2, String param3) {
6-
}
7-
8-
public void deletePassword(int param, String param1, String param2) {
9-
}
10-
11-
public void scan(String param) {
12-
}
13-
14-
public bool isAvailable() {
15-
return false;
16-
}
17-
18-
public void getPassword(int param, String param1, String param2) {
19-
}
20-
21-
public bool isSupported() {
22-
return false;
23-
}
24-
5+
public void addPassword(int param, String param1, String param2, String param3) {
6+
}
7+
8+
public void deletePassword(int param, String param1, String param2) {
9+
}
10+
11+
public void scan(String param) {
12+
}
13+
14+
public bool isAvailable() {
15+
return false;
16+
}
17+
18+
public void getPassword(int param, String param1, String param2) {
19+
}
20+
21+
public bool isSupported() {
22+
return false;
23+
}
24+
25+
public void cancelRequest(int requestId){}
26+
2527
}
2628
}

src/com/codename1/fingerprint/Fingerprint.java

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.codename1.fingerprint.impl.InternalCallback;
2727
import com.codename1.fingerprint.impl.InternalFingerprint;
2828
import com.codename1.system.NativeLookup;
29+
import com.codename1.ui.Button;
2930
import com.codename1.ui.CN;
3031
import com.codename1.ui.Container;
3132
import com.codename1.ui.Dialog;
@@ -57,9 +58,45 @@ public class Fingerprint {
5758
InternalCallback.requestSuccess(0, null);
5859
}
5960

60-
private static class BooleanPasswordRequest extends AsyncResource<Boolean> {
61+
private static interface PasswordRequest {
62+
int getRequestId();
63+
}
64+
65+
private static class BooleanPasswordRequest extends AsyncResource<Boolean> implements PasswordRequest {
66+
private int requestId;
6167
private boolean shouldPrompt;
6268
private boolean didPrompt;
69+
70+
@Override
71+
public int getRequestId() {
72+
return requestId;
73+
}
74+
75+
@Override
76+
public boolean cancel(boolean mayInterruptIfRunning) {
77+
impl.cancelRequest(requestId);
78+
return super.cancel(mayInterruptIfRunning);
79+
}
80+
81+
82+
83+
}
84+
85+
private static class StringPasswordRequest extends AsyncResource<String> implements PasswordRequest {
86+
private int requestId;
87+
88+
@Override
89+
public int getRequestId() {
90+
return requestId;
91+
}
92+
93+
@Override
94+
public boolean cancel(boolean mayInterruptIfRunning) {
95+
impl.cancelRequest(requestId);
96+
return super.cancel(mayInterruptIfRunning);
97+
}
98+
99+
63100
}
64101

65102
/**
@@ -219,16 +256,16 @@ private static boolean showDialogOnAndroid() {
219256
* the keychain, the result will be the password as a string. If the password doesn't exist, then it will return an error.
220257
*/
221258
public static AsyncResource<String> getPassword(String reason, String account, boolean showDialogOnAndroid) {
222-
AsyncResource<String> out = new AsyncResource<>();
259+
StringPasswordRequest out = new StringPasswordRequest();
223260
if (!isAvailable()) {
224261
out.error(new IllegalStateException("Fingerprint scanning not available"));
225262
return out;
226263
}
227-
int requestId = InternalCallback.addRequest(out);
264+
out.requestId = InternalCallback.addRequest(out);
228265
if (showDialogOnAndroid) {
229266
showDialogOnAndroid(reason, out);
230267
}
231-
impl.getPassword(requestId, reason, account);
268+
impl.getPassword(out.requestId, reason, account);
232269
return out;
233270
}
234271

@@ -285,7 +322,7 @@ private static AsyncResource<Boolean> addPassword(BooleanPasswordRequest out, St
285322
out.error(new IllegalStateException("Fingerprint scanning not available"));
286323
return out;
287324
}
288-
int requestId = InternalCallback.addRequest(out);
325+
out.requestId = InternalCallback.addRequest(out);
289326
if (showDialogOnAndroid) {
290327
showDialogOnAndroid(reason, out);
291328
}
@@ -309,7 +346,7 @@ public void onError(Object arg0, Throwable arg1, int arg2, String arg3) {
309346
});
310347
return out;
311348
}
312-
impl.addPassword(requestId, reason, account, password);
349+
impl.addPassword(out.requestId, reason, account, password);
313350
return out;
314351
}
315352

@@ -320,13 +357,13 @@ public void onError(Object arg0, Throwable arg1, int arg2, String arg3) {
320357
* @return
321358
*/
322359
public static AsyncResource<Boolean> deletePassword(String reason, String account) {
323-
AsyncResource<Boolean> out = new AsyncResource<>();
360+
BooleanPasswordRequest out = new BooleanPasswordRequest();
324361
if (!isAvailable()) {
325362
out.error(new IllegalStateException("Fingerprint scanning not available"));
326363
return out;
327364
}
328-
int requestId = InternalCallback.addRequest(out);
329-
impl.deletePassword(requestId, reason, account);
365+
out.requestId = InternalCallback.addRequest(out);
366+
impl.deletePassword(out.requestId, reason, account);
330367
return out;
331368
}
332369

@@ -343,6 +380,14 @@ private static void showDialogOnAndroid(String reason, AsyncResource<?> request)
343380
SpanLabel lblReason = new SpanLabel(reason, "DialogBody");
344381
FontImage.setMaterialIcon(fingerprintIcon, FontImage.MATERIAL_FINGERPRINT, 7);
345382
d.add(BorderLayout.CENTER, BoxLayout.encloseY(iconWrapper, lblReason));
383+
Button cancel = new Button("Cancel");
384+
cancel.addActionListener(e->{
385+
if (!request.isDone()) {
386+
request.cancel(true);
387+
}
388+
d.dispose();
389+
});
390+
d.add(BorderLayout.SOUTH, cancel);
346391
d.showPacked(BorderLayout.CENTER, false);
347392
request.onResult((res, err)->{
348393
d.dispose();

0 commit comments

Comments
 (0)