I am implementing the android native fingerprint API for one of our applications.
Below is the code of the handler:
public class FingerprintHandler extends
FingerprintManager.AuthenticationCallback {
private CancellationSignal cancellationSignal;
private Context appContext;
private boolean sendCancellation=false;
public FingerprintHandler(Context context) {
appContext = context;
}
public void startAuth(FingerprintManager manager,
FingerprintManager.CryptoObject cryptoObject) {
cancellationSignal = new CancellationSignal();
if (ActivityCompat.checkSelfPermission(appContext,
Manifest.permission.USE_FINGERPRINT) !=
PackageManager.PERMISSION_GRANTED) {
return;
}
manager.authenticate(cryptoObject, cancellationSignal, 0, this, null);
}
#Override
public void onAuthenticationError(int errMsgId,
CharSequence errString) {
Log.d(TAG,"Authentication error callback");
Log.d(TAG,"Error Value: "+errMsgId);
switch(errMsgId){
case FINGERPRINT_ERROR_LOCKOUT:
Log.d(TAG,"Fingerprint error lockout");
nativeLocked = true;
mPreferences.edit().putLong(LAST_FAILURE, System.currentTimeMillis()).apply();
showError(getString(R.string.test_bio_fingerprint_fingerprint_authentication_failed));
mCancelButton.setEnabled(true);
dialogHandler.postDelayed(new Runnable() {
#Override
public void run() {
mCancelButton.setEnabled(true);
dismissDialog();
sendFailure();
}
}, SUCCESS_OR_FAIL_DELAY_MILLIS);
break;
case FINGERPRINT_ERROR_CANCELED:
Log.d(TAG,"Fingerprint has been cancelled");
if(sendCancellation) {
dismissDialog();
if (useEye)
sendCancelForEye();
else
sendCancel();
}
break;
}
}
#Override
public void onAuthenticationHelp(int helpMsgId,
CharSequence helpString) {
Log.d(TAG,"Authentication helper callback");
retryWithError(R.string.test_bio_fingerprint_fingerprint_too_fast);
}
#Override
public void onAuthenticationFailed() {
Log.d(TAG,"Authentication failed callback");
retryWithError(R.string.test_bio_fingerprint_fingerprint_not_recognized);
}
#Override
public void onAuthenticationSucceeded(
FingerprintManager.AuthenticationResult result) {
Log.d(TAG,"Authentication successfull callback");
onAuthenticationSuccess();
}
public void stopListening(boolean sendCancellation) {
this.sendCancellation=sendCancellation;
Log.d(TAG,"stopListening called");
if (cancellationSignal != null) {
cancellationSignal.cancel();
cancellationSignal = null;
}
}
}
The problem is that when there are 5 incorrect authentication attempts I can see the below in my logs:
Authentication error callback
Error Value: 7
Fingerprint error lockout
Authentication error callback
Error Value: 7
Fingerprint error lockout
The lockout call back gets twice. I am unable to figure out why this is happening. Can someone please help.
You need to check errMsgId because first is triggered for your action callback and second time is triggered for "Fingerprint operation canceled."
Also please notice that FingerPrintManager is deprecated and for the new api for BiometricPrompt the implementation should look like example below (you need to replace BiometricConstants.ERROR_NEGATIVE_BUTTON with your action id code):
#Override
public void onAuthenticationError(int errorCode,
#NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
if (BiometricConstants.ERROR_NEGATIVE_BUTTON == errorCode) {
//do your job only once
}
}
Related
I am new to ReactiveX and I have a case where I want my observable to emit data to a late subscriber(whenever the observer subscribes, observable should emit the same data that it emitted previously). I made this Observable class that provide ReplaySubject's same instance to all observers (it is singleton class).
public class AccountsObservable {
private static ConnectableObservable<String> hotObservable;
private static AccountsObservable accountsObservable;
public static AccountsObservable getObject() {
if (accountsObservable == null) {
accountsObservable = new AccountsObservable();
}
return accountsObservable;
}
public ConnectableObservable<String> getObservable() {
if (hotObservable == null) {
Observable<String> observable = ReplaySubject.create(new ObservableOnSubscribe<String>() {
#Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
emitter.onNext("XYZ");
emitter.onComplete();
}
});
hotObservable = observable.replay();//publish
}
return hotObservable;
}
}
Similarly, this is the observer class that creates new observer instance.
public class AccountsObserver {
AccountsFetchListener listener;
public AccountsObserver(AccountsFetchListener listener) {
this.listener = listener;
}
public Observer<String> getObserver() {
return new Observer<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String accounts) {
listener.onSuccess(accounts);
}
#Override
public void onError(Throwable e) {
listener.onFailure();
}
#Override
public void onComplete() {
}
};
}
public interface AccountsFetchListener {
void onSuccess(String accounts);
void onFailure();
}
}
Here is the function where I test these observables
private void testObs() {
ConnectableObservable<String> observable = AccountsObservable.getObject().getObservable();
Observer<String> observer = new AccountsObserver(new AccountsObserver.AccountsFetchListener() {
#Override
public void onSuccess(String accounts) {
Log.e("DATA -> ", accounts);
}
#Override
public void onFailure() {
}
}).getObserver();
observable.subscribe(observer);
observable.connect();
}
I called this function "testObs()" 5 times but it emitted data only 2 times. The problem seems to be in AccountsObservable class where I provide ReplaySUbject's instance. Thanks
Your code runs fine as it is, your logs are being suppressed in logcat as per this:
We declared an application as too chatty once it logs more than 5 lines a second. Please file a bug against the application's owner that is producing this developer-verbose-debug-level class logging spam. The logs are 256KB, that means the application is creating a DOS attack and shortening the logs timepan to 6 seconds(!) making it useless for all others.
You can avoid this behaviour by whitelisting your app for logcat:
adb logcat -P '<pid or uid of your app>'
I'm trying to learn how to implement a fingerprint API.
In one of fingerprint guides, it gave me a code
#RequiresApi(api = Build.VERSION_CODES.P)
public class BiometricCallbackV28 extends BiometricPrompt.AuthenticationCallback {
private BiometricCallback biometricCallback;
public BiometricCallbackV28(BiometricCallback biometricCallback) {
this.biometricCallback = biometricCallback;
}
#Override
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
biometricCallback.onAuthenticationSuccessful();
}
#Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
super.onAuthenticationHelp(helpCode, helpString);
biometricCallback.onAuthenticationHelp(helpCode, helpString);
}
#Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
biometricCallback.onAuthenticationError(errorCode, errString);
}
#Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
biometricCallback.onAuthenticationFailed();
}
}
But shouldn't there be a test whether the authentication passed THEN call onAuthenticationSucceeded? I don't see anywhere that calls public void onAuthenticationSucceeded. How does it know that the fingerprint matches? Who calls the method?
But shouldn't there be a test whether the authentication passed
The system knows whether the authentication passed, by comparing the scanned fingerprint against fingerprints registered by the user
I don't see anywhere that calls public void onAuthenticationSucceeded
The framework calls all of those callback methods, based on the results from the biometric hardware.
I am testing real API Calls with Retrofit as following:
#Test
public void getList(){
TestObserver<MyResponse> testObserver = api
.getResults(params)
.lift(apiErrorOperator)
.lift(emptyResponseOperator)
.test();
testObserver.awaitTerminalEvent();
testObserver.assertError(ApiException.class);
}
The test fails with these 2 errors:
Caused by: java.lang.IllegalStateException: onSubscribe not called in proper order
and
Caused by: com.example.myapplication.repository.ApiException: Search found 0 results
The second makes sense, since this is the behaviour I am expecting. However, I do not understand why testObserver.assertError(ApiException.class) is not returning true, and why I get the first error too.
For the first error, this line java.lang.IllegalStateException: onSubscribe not called in proper order is thrown at this line observer.onError(new ApiException("Search found 0 results")) from emptyResponseOperator. Below is code for full class:
public class EmptyResponseOperator implements ObservableOperator<MyResponse, MyResponse> {
#Override
public Observer<? super MyResponse> apply(Observer<? super MyResponse> observer) throws Exception {
return new DisposableObserver<MyResponse>() {
#Override
public void onNext(MyResponse myResponse) {
if(myResponse.getTotalResultsCount() == 0)
observer.onError(new ApiException("Search found 0 results"));
else{
observer.onNext(myResponse);
observer.onComplete();
}
}
#Override
public void onError(Throwable e) {
observer.onError(e);
}
#Override
public void onComplete() {
observer.onComplete();
}
};
}
}
And also here is the code for ApiErrorOperator class:
public class ApiErrorOperator<T> implements ObservableOperator<T, Response<T>> {
#Override
public Observer<? super Response<T>> apply(Observer<? super T> observer) throws Exception {
return new DisposableObserver<Response<T>>() {
#Override
public void onNext(Response<T> tResponse) {
if(!tResponse.isSuccessful()){
try {
if (tResponse.errorBody() != null) {
observer.onError(new ApiException(tResponse.errorBody().string()));
}else{
observer.onError(new ApiException(C.ERROR_UNKNOWN));
}
} catch (IOException e) {
observer.onError(new ApiException(C.ERROR_IO));
}
}
else if (tResponse.body() == null) {
observer.onError(new ApiException(C.ERROR_NOT_FOUND));
}else{
observer.onNext(tResponse.body());
observer.onComplete();
}
}
#Override
public void onError(Throwable e) {
observer.onError(e);
}
#Override
public void onComplete() {
observer.onComplete();
}
};
}
}
We don't recommend writing custom behavior this way. You have to follow the Observable protocol, like this:
public class EmptyResponseOperator implements ObservableOperator<MyResponse, MyResponse> {
#Override
public Observer<? super MyResponse> apply(Observer<? super MyResponse> observer)
throws Exception {
return new DisposableObserver<MyResponse>() {
// -------------------------------------
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
#Override
public void onStart() {
observer.onSubscribe(this);
}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// -------------------------------------
#Override
public void onNext(MyResponse myResponse) {
dispose(); // <-------------------------------------------------------
if (myResponse.getTotalResultsCount() == 0) {
observer.onError(new ApiException("Search found 0 results"));
} else {
observer.onNext(myResponse);
observer.onComplete();
}
}
#Override
public void onError(Throwable e) {
if (!isDisposed()) { // <---------------------------------------
observer.onError(e);
}
}
#Override
public void onComplete() {
if (!isDisposed()) { // <---------------------------------------
observer.onComplete();
}
}
};
}
}
Your implementation is wrong, and try to avoid chain travels downstream in your code.
check below sample and go through documentation here.
Single.just(1)
.delaySubscription(Completable.create(new CompletableOnSubscribe() {
#Override
public void subscribe(CompletableEmitter e) throws Exception {
if (!e.isDisposed()) {
e.onError(new TestException());
}
}
}))
.test()
.assertFailure(TestException.class);
-- onSubscribe wires them up and there you go.
Another solution
create custom operator. How?
I am implementing an android fingerprint authentication. I want to know which user, who has registered in device before, is authenticating. Is there any information about the user, who has registered and is valid for the device, in the FingerprintManager.AuthenticationResult argument in onAuthenticationSucceeded method?!
I am using this sample.
this is my class, which is implementing FingerprintManager.AuthenticationCallback:
public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
private static final long ERROR_TIMEOUT_MILLIS = 1600;
private static final long SUCCESS_DELAY_MILLIS = 1300;
private final FingerprintManager mFingerprintManager;
private final ImageView mIcon;
private final TextView mErrorTextView;
private final Callback mCallback;
private CancellationSignal mCancellationSignal;
private boolean mSelfCancelled;
/**
* Constructor for {#link FingerprintUiHelper}.
*/
FingerprintUiHelper(FingerprintManager fingerprintManager,
ImageView icon, TextView errorTextView, Callback callback) {
mFingerprintManager = fingerprintManager;
mIcon = icon;
mErrorTextView = errorTextView;
mCallback = callback;
}
public boolean isFingerprintAuthAvailable() {
// The line below prevents the false positive inspection from Android Studio
// noinspection ResourceType
return mFingerprintManager.isHardwareDetected()
&& mFingerprintManager.hasEnrolledFingerprints();
}
public void startListening(FingerprintManager.CryptoObject cryptoObject) {
if (!isFingerprintAuthAvailable()) {
return;
}
mCancellationSignal = new CancellationSignal();
mSelfCancelled = false;
// The line below prevents the false positive inspection from Android Studio
// noinspection ResourceType
mFingerprintManager
.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
mIcon.setImageResource(R.drawable.ic_fp_40px);
}
public void stopListening() {
if (mCancellationSignal != null) {
mSelfCancelled = true;
mCancellationSignal.cancel();
mCancellationSignal = null;
}
}
#Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
if (!mSelfCancelled) {
showError(errString);
mIcon.postDelayed(new Runnable() {
#Override
public void run() {
mCallback.onError();
}
}, ERROR_TIMEOUT_MILLIS);
}
}
#Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
showError(helpString);
}
#Override
public void onAuthenticationFailed() {
showError(mIcon.getResources().getString(
R.string.fingerprint_not_recognized));
}
#Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
mIcon.setImageResource(R.drawable.ic_fingerprint_success);
mErrorTextView.setTextColor(
mErrorTextView.getResources().getColor(R.color.success_color, null));
mErrorTextView.setText(
mErrorTextView.getResources().getString(R.string.fingerprint_success));
mIcon.postDelayed(new Runnable() {
#Override
public void run() {
mCallback.onAuthenticated();
}
}, SUCCESS_DELAY_MILLIS);
}
private void showError(CharSequence error) {
mIcon.setImageResource(R.drawable.ic_fingerprint_error);
mErrorTextView.setText(error);
mErrorTextView.setTextColor(
mErrorTextView.getResources().getColor(R.color.warning_color, null));
mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
}
private Runnable mResetErrorTextRunnable = new Runnable() {
#Override
public void run() {
mErrorTextView.setTextColor(
mErrorTextView.getResources().getColor(R.color.hint_color, null));
mErrorTextView.setText(
mErrorTextView.getResources().getString(R.string.fingerprint_hint));
mIcon.setImageResource(R.drawable.ic_fp_40px);
}
};
public interface Callback {
void onAuthenticated();
void onError();
}
}
I want to display a new activity on success callback of my WebService called by Retrofit.
And I have difficulties to find examples on how to use Retrofit callback result to launch new activity.
Is that a good way to do it ? Do I have to clean up some stuff before?
public void validate(View view) {
RetrofitWebServices.getInstance().webserviceMethod(params,new Callback<MyObject>() {
#Override
public void success(MyObject object, Response response) {
Intent barIntent = new Intent(FooActivity.this, BarActivity.class);
startActivity(barIntent);
}
#Override
public void failure(RetrofitError error) {
...
}
});
}
You can implement Callback with weak reference to Context
public class MyCallback implements Callback<MyObject> {
WeakReference<Context> mContextReference;
public MyCallback(Context context) {
mContextReference = new WeakReference<Context>(context);
}
#Override
public void success(MyObject arg0, Response arg1) {
Context context = mContextReference.get();
if(context != null){
Intent barIntent = new Intent(FooActivity.this, BarActivity.class);
context.startActivity(barIntent);
} else {
// TODO process context lost
}
}
#Override
public void failure(RetrofitError arg0) {
// TODO process error
}
}
Just remember - this solution will not work if Context lost occurred while request in progress but you may don't worry about potential memory leak which may be if you keep strong reference to Context object.
Hi have a solution that seems easier: use the getApplicationContext() function.
I am not 100% sure it is OK, but in my case it works as expected.
Your code would be:
public void validate(View view) {
RetrofitWebServices.getInstance().webserviceMethod(params,new Callback<MyObject>() {
#Override
public void success(MyObject object, Response response) {
Intent barIntent = new Intent(getApplicationContext(), BarActivity.class);
startActivity(barIntent);
}
#Override
public void failure(RetrofitError error) {
...
}
});
}