Android fingerprint api - FingerprintManager.AuthenticationCallback not called after SCREEN_ON intent - android

I'm writing an app that authenticates the user using the native Android Fingerprint API (on Android 6.0 and up).
In one scenario - the device receives a Gcm notification and if the screen is off but the phone is not locked - the app "wakes" the device by launching an activity with the following flags:
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
The app then displays a dialogs that asks the user to authenticate using his finger. In this case - no callback function (from FingerprintManager.AuthenticationCallback - ) is called
here is the code :
fingerprintManager.authenticate(null, cancellationSignal, 0, new FingerprintManager.AuthenticationCallback() {
#Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
logger.info("Authentication error " + errorCode + " " + errString);
...
}
#Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
super.onAuthenticationHelp(helpCode, helpString);
logger.info("Authentication help message thrown " + helpCode + " " + helpString);
...
}
#Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
logger.info("Authentication succeeded");
...
}
/*
* Called when authentication failed but the user can try again
* When called four times - on the next fail onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT)
* will be called
*/
#Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
logger.info("Authentication failed");
...
}
}, null);
The same code runs when the screen is on and when it's off but when it's off and turned on by the activity - the callbacks don't get called.
Any ideas?
Thanks in advance!

I've noticed the same issue and in the adb logcat I've seen the next line:
W/FingerprintManager: authentication already canceled
I've searched in depth into source code and I've found the following function in FingerprintManager:
if (cancel != null) {
if (cancel.isCanceled()) {
Log.w(TAG, "authentication already canceled");
return;
} else {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
}
}
This means, that you are entering your authenticate() function with already cancelled cancellationSignal. Just add the following before your authenticate():
if(cancellationSignal.isCanceled()){
cancellationSignal = new CancellationSignal();
}
This way you will always pass the non-cancelled cancellationSignal and your flow will be correct.

Related

Open Android credentials Setup programmatically

To display sensible data users can enable authentication in my app. I am using the android in-build authentication.
However, if the user did not secure his device using any pattern, pin, password or biometric authentication, I would like to open the android settings, where he can setup his authentication. Is there any Intent/ way to go there? I did not find it.
Some code so far:
To determine, if the user did not setup any authentication method:
androidx.biometric.BiometricPrompt biometricPrompt = new BiometricPrompt((FragmentActivity) activity, executor, new BiometricPrompt.AuthenticationCallback() {
#Override
public void onAuthenticationError(int errorCode, #NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
// Determine, if the user has no device password set.
boolean errorCodeIsBeingHandledSeparately = false;
// HERE WE DETERMINE THAT CREDENTIALS HAVE NOT BEEN SETUP
if (errorCode == BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL) {
if (authenticationInterface != null) {
errorCodeIsBeingHandledSeparately = true;
authenticationInterface.onUserHasNoDevicePassWordSet();
}
}
// Display error message, only if the error code is not being handled seperately.
if (!errorCodeIsBeingHandledSeparately) {
Toast.makeText(activity, "Authentication error\n" + errString, Toast.LENGTH_LONG).show();
}
}
#Override
public void onAuthenticationSucceeded(#NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
if (authenticationInterface == null) {
Toast.makeText(activity, "Success", Toast.LENGTH_LONG).show();
}
else {
authenticationInterface.onUserSuccessfullyAuthenticated();
}
}
#Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
Toast.makeText(activity, "Authentication failed", Toast.LENGTH_LONG).show();
}
});
The interface to receive authentication return.
/**
* Interface to receive authentication return.
*/
private AuthenticationUtils.AuthenticationInterface authenticationInterface;
public interface AuthenticationInterface {
public void onUserSuccessfullyAuthenticated();
public void onUserHasNoDevicePassWordSet();
}
The dialog where I want to lead the user to go to the device setup credentials.
public void displayNoDeviceCredentialsSetDialog() {
MaterialAlertDialogBuilder noDeviceCredentialsDialog = new MaterialAlertDialogBuilder(activity, R.style.AlertDialogTheme);
String noDeviceCredentials_goToSettings_dialogMessage = activity.getString(R.string.authentication_noDeviceCredentials_goToSettings_dialogMessage);
noDeviceCredentialsDialog.setMessage(noDeviceCredentials_goToSettings_dialogMessage);
noDeviceCredentialsDialog.setPositiveButton(
R.string.DialogConfirmationOK,
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// HERE I WOULD LIKE TO OPEN THE ANDROID SETTING WHERE HE CAN SETUP HIS CREDENTIALS
}
}
);
noDeviceCredentialsDialog.setNegativeButton(
R.string.DialogConfirmationNegativeAnswerText,
null
);
noDeviceCredentialsDialog.show();
}
I would like to go here:
You can get there from settings here:
What i am looking for is something like this: Here we navigate the user to some other android settings.
Intent intent2 = new Intent();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent2.setAction(android.provider.Settings.ACTION_APPLICATION_SETTINGS);
intent2.putExtra(android.provider.Settings.EXTRA_APP_PACKAGE, getPackageName());
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
intent2.setAction(android.provider.Settings.ACTION_APPLICATION_SETTINGS);
intent2.putExtra("app_package", getPackageName());
intent2.putExtra("app_uid", getApplicationInfo().uid);
} else {
intent2.setAction(android.provider.Settings.ACTION_APPLICATION_SETTINGS);
intent2.addCategory(Intent.CATEGORY_DEFAULT);
intent2.setData(Uri.parse("package:" + getPackageName()));
}
startActivity(intent2);
Just figured it out.
There are 3 viable options to use: Settings.ACTION_BIOMETRIC_ENROLL, Settings.ACTION_FINGERPRINT_ENROLL and Settings.ACTION_SECURITY_SETTINGS.
Final implementation I use is:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
activity.startActivity(new Intent(Settings.ACTION_BIOMETRIC_ENROLL));
}
else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
activity.startActivity(new Intent(Settings.ACTION_FINGERPRINT_ENROLL));
}
else {
activity.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
}
}
Settings.ACTION_FINGERPRINT_ENROLL opens this: After chosing backup lock screen method and setup the chosen method, the device will ask you to register a fingerprint.
Settings.ACTION_SECURITY_SETTINGS opens this:
In lack of a device higher than Android Build "R" I could not test ACTION_BIOMETRIC_ENROLL, but I presume it will be similar to ACTION_FINGERPRINT_ENROLL.
If you want to see what options there are to open android settings. You can just use "CTRL" + "mouse click" on any Settings.XXX (ACTION_SECURITY_SETTINGS, ACTION_FINGERPRINT_ENROLL, ...) in Android Studio.
You will then see "..\android\platforms\android-31\android.jar!\android\provider\Settings.class"
In case you struggle to figure out which API version is described with "Build.VERSION_CODES.P" you can also click "CTRL" + "Mose Click" on the Build version (P, O, ...).
You will then see this:

Biometric Prompt crashing on Android 9 and 10 on some devices

I am using BiometricPrompt to let the user use fingerprint authentication to log into the app
I have done the following in my PasswordActivity class:
Executor executor = Executors.newSingleThreadExecutor();
FragmentActivity activity = this;
final BiometricPrompt biometricPrompt = new BiometricPrompt(activity, executor, new BiometricPrompt.AuthenticationCallback() {
#Override
public void onAuthenticationError(int errorCode, #NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
// user clicked negative button
} else {
//TODO: Called when an unrecoverable error has been encountered and the operation is complete.
}
}
#Override
public void onAuthenticationSucceeded(#NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
//TODO: Called when a biometric is recognized.
final String decryptedText = decryptText();
runOnUiThread(new Runnable() {
#Override
public void run() {
if (decryptedText != null && !decryptedText.isEmpty()) {
editPassword.setText(decryptedText);
buttonNext();
}
}
});
}
#Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
//TODO: Called when a biometric is valid but not recognized.
}
});
final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("My App"))
.setSubtitle("Log on into the app"))
.setNegativeButtonText("Cancel").toUpperCase())
.build();
if (sharedPreferenceManager.isFingerprintEnabled(this))
biometricPrompt.authenticate(promptInfo);
This is the exception that I am getting. Do I have to set?
setNegativeButton (CharSequence text,
Executor executor,
DialogInterface.OnClickListener listener) as well?
I am using implementation 'androidx.biometric:biometric:1.0.0-alpha03' this version.
Caused by java.lang.IllegalArgumentException: Executor must not be null
at android.hardware.biometrics.BiometricPrompt$Builder.setNegativeButton + 182(BiometricPrompt.java:182)
at androidx.biometric.BiometricFragment.onCreate + 201(BiometricFragment.java:201)
at androidx.fragment.app.Fragment.performCreate + 2414(Fragment.java:2414)
at androidx.fragment.app.FragmentManagerImpl.moveToState + 1418(FragmentManagerImpl.java:1418)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState + 1784(FragmentManagerImpl.java:1784)
at androidx.fragment.app.FragmentManagerImpl.moveToState + 1861(FragmentManagerImpl.java:1861)
at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange + 3269(FragmentManagerImpl.java:3269)
at androidx.fragment.app.FragmentManagerImpl.dispatchCreate + 3223(FragmentManagerImpl.java:3223)
at androidx.fragment.app.FragmentController.dispatchCreate + 190(FragmentController.java:190)
at androidx.fragment.app.FragmentActivity.onCreate + 369(FragmentActivity.java:369)
at androidx.appcompat.app.AppCompatActivity.onCreate + 85(AppCompatActivity.java:85)
Can you try replacing Executor executor = Executors.newSingleThreadExecutor(); with:
private Handler handler = new Handler();
private Executor executor = new Executor() {
#Override
public void execute(Runnable command) {
handler.post(command);
}
};
This is according to the code given in this developer.android.com tutorial.
Try to update the dependency, the currently latest version is already a release candidate:
implementation "androidx.biometric:biometric:1.0.0-rc01"
Caused by java.lang.IllegalArgumentException: Executor must not be null
at android.hardware.biometrics.BiometricPrompt$Builder.setNegativeButton + 182(BiometricPrompt.java:182)
This indicates that the framework on the device you're testing is either not receiving the executor from the support library (bug in support library), or the framework itself has a bug.
Could you try on a later version of the androidx.biometric library? Beta02 was recently released, a lot of things have been fixed since alpha03.
Also, what device are you testing, if it's reproducible on Beta02 could you grab a bugreport via adb bugreport foo.zip and attach your sample app with the bug to the public issue tracker?

Snapshot API - ResultCallBack not triggered

I am using the Google Snapshot API in Android.
I use this code to get the user's activity and store it to Firebase.
//Get user's current activity
private void myCurrentActivity(final String timestamp) {
if (checkPermission()) {
Awareness.SnapshotApi.getDetectedActivity(mGoogleApiClient)
.setResultCallback(new ResultCallback<DetectedActivityResult>() {
#Override
public void onResult(#NonNull DetectedActivityResult detectedActivityResult) {
if (detectedActivityResult.getStatus().isSuccess()) {
Log.d(TAG, "myCurrentActivity - SUCCESS");
ActivityRecognitionResult activityRecognitionResult = detectedActivityResult.getActivityRecognitionResult();
databaseReference.child(getUID(getApplicationContext(), "myCurrentActivity")).child(timestamp).child("activity").setValue(getActivityString(activityRecognitionResult.getMostProbableActivity().getType()));
Log.d(TAG, "Most Propable Activity : " + getActivityString(activityRecognitionResult.getMostProbableActivity().getType()));
} else {
Log.d(TAG, "myCurrentActivity - FAILURE");
databaseReference.child(getUID(getApplicationContext(), "myCurrentActivity")).child(timestamp).child("activity").setValue("null");
}
}
});
}
}
The problem is that the onResult function is never executed when i run it.
Do you have any ideas what may cause this ?
Thank you.
EDIT: I just ran it in the emulator and it's working without problems. Is it possible that this has something to do with my device ?

Codenameone when I read the qrCode uzing Zxing on Android the application goes back to 'init' and 'start'

I am using Codenameone and ZXing to read a QRCode. When I call the Scanner, my mobile opens the QRCode reader application and I get to read the QRCode except that when android takes me back to my app it goes through init then start statuses. Which moves me back to the login form of my application instead of continuing filling the form that I was in.
Any help on what to do to stay in the same form? Is there something I'm doing wrong? Thanks in advance.
EverproX.addMessage("Before Scan\n");
CodeScanner.getInstance().scanQRCode(new ScanResult() {
public void scanCompleted(String contents, String formatName, byte[] rawBytes) {
EverproX.addMessage("Scan Completed "+contents);
}
public void scanCanceled() {
EverproX.addMessage("Scan Cancelled");
}
public void scanError(int errorCode, String message) {
EverproX.addMessage("Scan Error "+errorCode+" "+message);
}
});
EverproX can be seen as a log class.
By analyzing our log we can say that as soon as we call the CodeScanner.getInstance().scanQRCode() the application is called for 'Destroy'. Then after the scanning is done it goes again through the init and start. It never goes into the scanComplete scanCanceled or scanError Callbacks.
Is it normal that the App is destroyed upon call of CodeScanner? Many thanks.
Inside your codenameone project, you should find a class named (for example MyApp.java) based on your app's name, modify the code to read something like similar to this:
public class MyApp {
private Form current;
public void init(Object context) {
// Pro users - uncomment this code to get crash reports sent to you automatically
Display.getInstance().addEdtErrorHandler(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
evt.consume();
Log.p("Exception in AppName version " + Display.getInstance().getProperty("AppVersion", "Unknown"));
Log.p("OS " + Display.getInstance().getPlatformName());
Log.p("Error " + evt.getSource());
Log.p("Current Form " + Display.getInstance().getCurrent().getName());
Log.e((Throwable) evt.getSource());
Log.sendLog();
}
});
}
public void start() {
if (current != null) {
current.show();
return;
}
new StateMachine("/theme");
}
public void stop() {
current = Display.getInstance().getCurrent();
}
public void destroy() {
current = null;
}
}

Open Android Location Settings using phonegap

I'm using user's geolocation via phonegap.The exapmle is shown below. (ANDROID)
// onSuccess Callback
// This method accepts a Position object, which contains the
// current GPS coordinates
//
var onSuccess = function(position) {
var element = document.getElementById('geolocation');
element.innerHTML = 'Latitude: '+ position.coords.latitude +'<br/>' +
'Longitude: ' + position.coords.longitude +<br/>' +
'Altitude: ' + position.coords.altitude +'<br/>' +
'Accuracy: ' + position.coords.accuracy +<br/>' +
'Altitude Accuracy: ' + position.coords.altitudeAccuracy +'<br/>' +
'Heading: ' + position.coords.heading +<br/>' +
'timestamp: ' + position.timestamp +<br/>';
};
// onError Callback receives a PositionError object
//
function onError(error) {
alert('code: ' + error.code + '\n' +
'message: ' + error.message + '\n');
}
navigator.geolocation.getCurrentPosition(onSuccess, onError);
Is it possible (using phonegap) on error function to open dialog which will lead us to location settings (where user will be able to give me access to his location) instead of alert, as it's done in google maps android application (screenshot below) ?
I'd do just what you are saying, create the dialog, and send them to settings; I'm assuming you have managed to get the onError fcn called at the apropos time, so then
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(getString(R.string.loc_man));
builder.setMessage(getString(R.string.ask_for_gps));
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which)
{
Intent i = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(i);
}
});
builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which)
{
Toast.makeText(getBaseContext(), getString(R.string.gps_no), Toast.LENGTH_SHORT).show();
finish();
}
});
builder.create().show();
what you also need to do is call this function from your javascript... Oh, this is fun, b/c you'll be using a lot of pretty cool java concepts all at once and I've only worked with Cordova, but this should all work the same =] Keep in mind that if you see some variable undefined in my code, you should probably assume it's a feild.
so let's say you are sure this code hits on error, let's make up an interface name; 'Android' is a pretty logical one
function onError(error) {
alert('code: ' + error.code + '\n' +
'message: ' + error.message + '\n');
Android.TurnOnTuneIn();
}
now back in your Java, grep around your phonegap app to find wherever it has 'WebView'; if it's anything like my cordova implementations, it'll have webview.loadUrl() in it somewhere. Open the file that defines this class; this is the one to edit/place all the java we are working on in this question. After something like 'public class PhoneGapActivity extends Activity', insert 'implements CordovaInterface' ( I think not PhoneGapInterface ); if your IDE gives you an error with the option to implement abstract methods, pound that.
Make sure the WebView is a feild in the class, not a variable in a method, then
//CordovaWebView should work, but maybe it needs to be PhoneGapWebView, you're smart, you'll figure it out =]
webView = (CordovaWebView) findViewById(R.id.data_window);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setBuiltInZoomControls(false);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); //this always seemed like a good idea to me
webView.addJavascriptInterface(new JSInterface(this), "Android"); //see the interface name? =]
//this line should already be in this class, just re-use it
webView.loadUrl("file:///"+getContext().getFilesDir().toString()+"/index.html");
now make that interface:
public class JSInterface {
// these fcns are exposed to the WebView
private Context context;
public JSInterface(Context context)
{
context = context;
}
#JavascriptInterface
public void doEchoTest(String echo)
{
//A useful test; I usually use it in a webViewClient that runs the function that calls this in my javascript with this:
//webView.loadUrl("javascript:echo('"echo!!!"');"); //this exact formatting
//but webViewClient is outside the scope of your question and would just confuse the
//issue; however, you may need to implement
//webViewClient with an delaying asynctask to seperatly run the js you loaded with the webpage,
//again, I used to do this ground-up, and phoneGap Might Just Work.
Toast.makeText(context, echo, Toast.LENGTH_SHORT).show();
}
#JavascriptInterface
public void TurnOnTuneIn
{
aMethodThatHasThatFirstBitOfCodeIPublished();
}
}
I love interfaces =]
The rest is fairly boilerplate for your purposes ( but fun to play with nevertheless! ) and you might not need much if any of it. If you notice the method I put up top is not returning the way you want it to, you can use the startActivityForResult method to do the same and I'll bet it'll work nicely; Note the 'super.' call that the Override makes; you only need the Intent ( like I showed you up top ) and some reference number for receiving the result... again, out of the scope of the question. ALso, I included support for the ResultCallback b/c a guy I was working with used it, but I don't use it myself.
#Override
public Activity getActivity(){
return this;
}
#Override
public void setActivityResultCallback(CordovaPlugin plugin) {
this.activityResultCallback = plugin;
}
public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
this.activityResultCallback = command;
this.activityResultKeepRunning = this.keepRunning;
// If multitasking turned on, then disable it for activities that return results
if (command != null) {
this.keepRunning = false;
}
// Start activity
super.startActivityForResult(intent, requestCode);
}
#Override
public void cancelLoadUrl(){
// no op
}
#Override
public Object onMessage(String id, Object data) {
LOG.d("is", "onMessage(" + id + "," + data + ")");
if ("exit".equals(id)) {
super.finish();
}
return null;
}
#Override
public void onPause() {
Method pause = null; // Pauses the webview.
try {
pause = WebView.class.getMethod("onPause");
}
catch (SecurityException e) { }
catch (NoSuchMethodException e) { }
if (pause != null) {
try { pause.invoke(webView);
}
catch (InvocationTargetException e) { }
catch (IllegalAccessException e) { }
}
else {
// No such method. Stores the current URL.
suspendUrl = webView.getUrl(); // And loads a URL without any processing.
webView.loadUrl("file:///android_asset/nothing.html");
}
super.onPause();
}
#Override
public void onResume() {
super.onResume();
Method resume = null; // Resumes the webview.
try {
resume = WebView.class.getMethod("onResume");
}
catch (SecurityException e) { }
catch (NoSuchMethodException e) { }
if (resume != null) {
try {
resume.invoke(webView);
}
catch (InvocationTargetException e) { }
catch (IllegalAccessException e) { }
}
else if (webView != null) { // No such method. Restores the suspended URL.
if (suspendUrl == null) {
//this should be wherever you have your page; you can probably copy it from what is there now.
webView.loadUrl("file:///"+getContext().getFilesDir().toString()+"/index.html");
}
else {
webView.loadUrl(suspendUrl);
}
}
}
Hope that gets you further along; if you don't use it already, remember to use version control, so you can be reckless with your edits!
gl hf
I share with you, this phonegap plugin, that lets you do that. I added that functionality to that plugin as a contribuitor.
https://github.com/BastienL/GPSDetector

Categories

Resources