I am using android provided BiometricPrompt class to provide Biometric Authentication in our application it works fine but when i click on back button of phone then blank page is getting displayed. Instead I want app to be closed onclick of back button. Any pointers will be helpful.,
public class FingerprintLoginActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BiometricPrompt biometricPrompt = new BiometricPrompt(this, Executors.newSingleThreadExecutor(), 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.
Context.Fingerprint = true;
Intent fingerprintIntent = new Intent(FingerprintLoginActivity.this, MainActivity.class);
fingerprintIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(fingerprintIntent);
}
#Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
//TODO: Called when a biometric is valid but not recognized.
}
});
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("touch to fingerprint scanner")
.setNegativeButtonText("Cancel")
.build();
biometricPrompt.authenticate(promptInfo);
}
}
I implement based on the instructions given here and here. The back button works fine for me: the dialog/prompt simply closes and I'm back to the Activity. Do you recognize the blank page you are seeing? perhaps you are implementing the API in a blank Activity? Try following the blog posts mentioned above and let us know how it goes.
UPDATE: based on your edit
Since you want to exit the Activity when the user clicks the back button, you have to handle BiometricPrompt.ERROR_USER_CANCELED in your code by calling finish():
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
Log.d(TAG, "onAuthenticationError -> $errorCode :: $errString")
if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
loginWithPassword()
}else if(errorCode == BiometricPrompt.ERROR_USER_CANCELED){
finish()
}
}
For dismiss the Fingerprint dialog, you must call to:
biometricPrompt.cancelAuthentication()
Related
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:
I'm working on an Android app that communicates with a Cast receiver app.
Connecting to the app works (I can see the app appear on the tv), but I'm having difficulties getting the custom channel to work.
In the onCreate of my Activity I get the CastContext and add my SessionManagerLister.
mCastContext = CastContext.getSharedInstance(this);
mCastContext.getSessionManager().addSessionManagerListener(getSessionManagerListener(), CastSession.class);
getSessionManagerListener() returns the listener where I register my MessageReceivedCallback:
private SessionManagerListener<CastSession> getSessionManagerListener()
{
return new SessionManagerListener<CastSession>()
{
#Override
public void onSessionStarted(CastSession castSession, String s)
{
try
{
castSession.setMessageReceivedCallbacks("urn:x-cast:be.myappname.player.cast.v1", new Cast.MessageReceivedCallback()
{
#Override
public void onMessageReceived(CastDevice castDevice, String s, String s1)
{
System.out.println("never reaches this callback");
}
});
}
catch (IOException e)
{
e.printStackTrace();
}
}
... other methods omitted ...
}
}
When I tap the Toolbar cast button I can select a device, which triggers the onSessionStarted in the SessionManagerListener (this also starts the receiver app on the tv). I then add the MessageReceivedCallback, but its callback never gets called.
Inspecting my Cast device in Chrome does show the data I'm expecting to receive, it just never seems to reach my Android code.
cast_receiver.js:67 [667.202s] [cast.receiver.IpcChannel] IPC message
[667.202s] [cast.receiver.IpcChannel] IPC message sent: {"namespace":"urn:x-cast:be.myappname.player.cast.v1","senderId":"7c442884-74e6-a388-243c-58b4ab3a4527.3471:com.google.sample.cast.refplayer.tutorial-512","data":"{\"type\":\"login request\"}"}
A colleague is working on the iOS app and that one does receive the callback.
Try the following in onSessionStarted
CastContext cc = CastContext.getSharedInstance(this);
SessionManager sm = cc.getSessionManager();
if (sm != null) {
CastSession cs = sm.getCurrentCastSession();
if (cs != null) {
try {
MyCastChannel mcc = new MyCastChannel();
cs.setMessageReceivedCallbacks("urn:x-cast:be.myappname.player.cast.v1",mcc);
}
catch (IOException e) {
}
}
}
public class MyCastChannel implements Cast.MessageReceivedCallback
{
#Override
public void onMessageReceived(CastDevice castDevice, String namespace, String message)
{
// do your thing
}
}
I had the same problem, this is how I managed to get the message to be sent:
context.sendCustomMessage(namespace, undefined, JSON.stringify({
"a": "b"
}));
This is the javascript on the receiver side. So you need the "undefined" param and also use JSON.stringify(), otherwise the message gets silently dropped.
The undefined means "send to all", but you should probably specify sender-id there.
This is in the v3 API.
In my case, it was more subtle.
The callback worked absolutely fine when the cast session was initiated for the first time. When the user presses the cast button the receiver is registered for the message callback.
override fun onSessionStarted(castSession: CastSession?, p1: String?) {
liveViewModel.requestPause()
castSession?.let {
setCastChannelReceiver(it, this#myActivity)
loadRemoteMedia(it, buildChromeCastInfo())
}
}
fun setCastChannelReceiver(castSession: CastSession?, receiver: CastMessageReceiver) {
castSession?.let {
castChannel.addReceiver(receiver, castSession)
it.setMessageReceivedCallbacks(castChannel.nameSpace, castChannel)
}
}
Although when the user use to kill the Activity which initiated the cast session and then after traversing other parts of app use to again visit the Activity, the callback failed to work.
Remember, when the user visits the Activity for the second time, the CastSession is already connected. As a result the onSessionStarted(castSession: CastSession, p1: String) method is never called.
I was under the assumption that once the receiver has been registered for the session, it need not be registered again. But still for some reason the callback never worked.
As a final resort, just to be assured I re-registered the receiver in the OnCreate() of the Activity.
override fun onCreate(out:Bundle){
....
setCastChannelReceiver(castSession, receiver)
....
}
fun setCastChannelReceiver(castSession: CastSession?, receiver: CastMessageReceiver) {
castSession?.let {
castChannel.addReceiver(receiver, castSession)
it.setMessageReceivedCallbacks(castChannel.nameSpace, castChannel)
}
}
And it worked!!
NOTE: For me, the communication between the Sender(Android App) and Cast Receiver only occurred when the string messages were in JSON format.
I am new to Rxjava. It is so complex......
I need to handle a complex scenario, I want to know how to make it using Rxjava.
It's a logic about app start:
app start and splash page open
make http reqesut check_update to check if app need update.
if app has new version and must update, pop up a notice dialog with only one update button; if app has new version, but not must update, then pop up a dialog with 2 button(update and ignore); if no new version, go next.
no need to update, so we need to jump to home page or login page, according to current auth_token state.
read auth_token from sharedPreference. then call http request check_auth to check if it is valid or not. if valid, jump to home page; if not, jump to login page.
In this process, 2 http requests and lots of condition checks are involved. http request error also need to be handled, Can this process be written in one Rxjava?
I think I can do it in this way, but I am not sure if it is right.
mRetrofitService.checkUpdate(new CheckUpdateRequest("android", "0.0.1")) // check update request
.compose(RxUtil.applyScheduler()) // scheduler
.flatMap(RxUtil.applyFunction()) // handle the response.
.flatMap((Function<CheckUpdateResponse, ObservableSource<?>>) checkUpdateResponse -> {
int updateMode;
if (checkUpdateResponse.isHas_new_version()) {
if (checkUpdateResponse.isForce_update()) {
updateMode = FORCE_UPDATE; // 1
} else {
updateMode = CHOOSE_UPDATE; // 2
}
} else {
updateMode = NO_UPDATE; // 0
}
return Observable.just(updateMode);
})
.flatMap(o -> {
if (Integer.parseInt(o.toString()) == NO_UPDATE) {
return mRetrofitService.checkAuth();
}
return null; //what should I return for pop up dialog?
})
.flatMap(RxUtil.applyFunction())
.subscribe(new Observer<CheckAuthResponse>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(CheckAuthResponse checkAuthResponse) {
// valid
gotoHome();
}
#Override
public void onError(Throwable e) {
// should I handle pop here?
// should I handle auth invalid here?
// should I handle checkUpdate request error here?
// should I handle checkAuth here?
// how to distinguish these errors?
}
#Override
public void onComplete() {
}
});
Am I right? And would you please answer my questions(comments in the code)?
According to the documentation onError "Notifies the Observer that the Observable has experienced an error condition.". So this callback is not relevant to handle request errors.
I suggest you to use the Response class of retrofit to handle your differents cases.
For example you define your service like that :
#method(url)
Call<CheckAuthResponse> checkUpdate(...);
and in your rx workflow :
mRetrofitService.checkUpdate(new CheckUpdateRequest("android", "0.0.1")) // check update request
.compose(RxUtil.applyScheduler()) // scheduler
.flatMap(RxUtil.applyFunction()) // handle the response.
.map((Function<Response<CheckUpdateResponse>, CheckUpdateResponse>) retrofitResponse -> {
if ( retrofitResponce.code() == authInvalidCode ) {
//do something
} else if (...) {
}
return retrofitResponse.body()
})
.flatMap((Function<CheckUpdateResponse, ObservableSource<?>>) checkUpdateResponse -> {
int updateMode;
if (checkUpdateResponse.isHas_new_version()) {
if (checkUpdateResponse.isForce_update()) {
updateMode = FORCE_UPDATE; // 1
} else {
updateMode = CHOOSE_UPDATE; // 2
}
} else {
updateMode = NO_UPDATE; // 0
}
return Observable.just(updateMode);
})
.flatMap(o -> {
if (Integer.parseInt(o.toString()) == NO_UPDATE) {
return mRetrofitService.checkAuth();
}
return null; //what should I return for pop up dialog?
})
.flatMap(RxUtil.applyFunction())
.subscribe(new Observer<CheckAuthResponse>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(CheckAuthResponse checkAuthResponse) {
// valid
gotoHome();
}
#Override
public void onError(Throwable e) {
// should I handle pop here?
// should I handle auth invalid here?
// should I handle checkUpdate request error here?
// should I handle checkAuth here?
// how to distinguish these errors?
}
#Override
public void onComplete() {
}
});
Hope this helps;
Sorry for my english.
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.
I have seen some related questions but none focusing on the specific problem I have:
I'm using the PayPal MPL Library.
I build my PayPalPayment object, then create the activity for the checkout to occur. That runs fine. My problem is, on the ResultDelegate I need to call a function from my activity, that occurs after the payment and makes some changes (such as storing SharedPreferences, etc.).
So something like this:
public class ResultDelegate implements PayPalResultDelegate, Serializable {
public void onPaymentSucceeded(String payKey, String paymentStatus) {
System.out.println("SUCCESS, You have successfully completed your transaction.");
System.out.println("PayKey: "+payKey);
System.out.println("PayStatus: "+paymentStatus);
callMyCustomAfterPaymentFunction();
}
...
}
Now the thing is, I tried to create a constructor for ResultDelegate that accepts my activity. My existing code is:
//On the activity class
public class MainMenuActivity extends Activity {
public void onCreate(Bundle savedInstanceState)
{
...
Button buy = (Button) findViewByID(R.id.buy_button);
buy.setOnClickListener(new View.OnClickListener(){
public void onClick(View v)
{
new PurchaseTask(activity).execute();
}
}
}
}
public class PurchaseTask extends AsyncTask <String, Void, String> {
protected String doInBackground()
{
...
PayPal pp = PayPal.getInstance();
CheckoutButton cb = pp.getCheckoutButton(...);
cb.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
ResultDelegate delegate = new ResultDelegate(myActivity);
Intent checkout = PayPal.getInstance().checkout(paument, activity, delegate);
activity.StartActivity(checkoutIntent);
}
}
}
}
//On the ResultDelegate class
public class ResultDelegate implements PayPalResultDelegate, Serializable {
private Activity myActivity;
public void onPaymentSucceeded(String payKey, String paymentStatus) {
System.out.println("SUCCESS, You have successfully completed your transaction.");
System.out.println("PayKey: "+payKey);
System.out.println("PayStatus: "+paymentStatus);
myActivity.performAfterPaymentOperations();
}
...
}
So the goal is to call the activity function from the ResultDelegate. Or even simpler, just to be able to store some SharedPreference changes when the ResultDelegate onPaymentSucceeded() fires.
But I get a NotSerializableException mentioning that the my MyActivity field is not Serializable.
So, then I added the transient identifier to my activity field inside the ResultDelegate, but now I get a NullPointerException.
Paypal Mobile Chekout guide
Implementation provided on paypal website is different from yours. They are doing startActivityForResult() to start PaypalActivity. and when in onActivityResult() method they are checking statusCode to check transaction status and act accordingly.
Follow that document for your implementation.
Here in your code, I donot find a point for using AsyncTask. Your ResultDelegate is Serializable where as Activity is not thats why it is throwing NotSerializableException.
Edit:
As you are developing for Google Android platform, then why not to use Google Checkout In-App?
Edit:
This method will be called when your PaypalActivity will finish. That activity will pass resultCode to this onActivityResult method.
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (resultCode) {
case Activity.RESULT_OK:
// The payment succeeded
String payKey = data.getStringExtra(PayPalActivity.EXTRA_PAY_KEY);
// Tell the user their payment succeeded
break;
case Activity.RESULT_CANCELED:
// The payment was canceled
// Tell the user their payment was canceled
break;
case PayPalActivity.RESULT_FAILURE:
// The payment failed -- we get the error from the EXTRA_ERROR_ID
// and EXTRA_ERROR_MESSAGE
String errorID = data.getStringExtra(PayPalActivity.EXTRA_ERROR_ID);
String errorMessage = data
.getStringExtra(PayPalActivity.EXTRA_ERROR_MESSAGE);
// Tell the user their payment was failed.
}
}
regards,
Aqif Hamid
You can create you custom listener something like this :
Create a custom listener :
OnDoubleTap mListener;
// Double tap custome listenre to edit photoes
public interface OnDoubleTap {
public void onEvent(Uri imgPath, int mPos);
}
public void setDoubleTapListener(OnDoubleTap eventListener) {
mListener = eventListener;
}
Now call this wherever you want like this :
mListener.onEvent(Uri, 1));
Now whenever you call this listener this will fire in your activity where you use this listener like this :
myCanvas.setDoubleTapListener(new OnDoubleTap() {
#Override
public void onEvent(Uri imgPath, int Pos) {
// TODO Auto-generated method stub
Toast.makeText(mContext, "LISTENER WORKING !!!", Toast.LENGTH_SHORT).show();
}
});
Where myCanvas is object of class where you create you listener.
Try this solution:
PayPalPayment thingToBuy = new PayPalPayment(new BigDecimal(price),getResources().getString(R.string.curruncy_code), getResources().getString(R.string.app_name),
PayPalPayment.PAYMENT_INTENT_SALE);
Intent intent = new Intent(CreateEventStep4.this, PaymentActivity.class);
intent.putExtra(PaymentActivity.EXTRA_PAYMENT, thingToBuy);
startActivityForResult(intent, REQUEST_PAYPAL_PAYMENT);
PaymentConfirmation confirm = data.getParcelableExtra(PaymentActivity.EXTRA_RESULT_CONFIRMATION);
if (confirm != null) {
try {
Log.e("paymentExample", confirm.toJSONObject().toString());
JSONObject jsonObj=new JSONObject(confirm.toJSONObject().toString());
String paymentId=jsonObj.getJSONObject("response").getString("id");
System.out.println("payment id:-=="+paymentId);
screenShotFile = takeScreenshot(frmTemplate);
uploadImage(myEvent.getEvent_id(),screenShotFile);
} catch (JSONException e) {
Log.e("paymentExample", "an extremely unlikely failure occurred: ", e);
}