GDPR Consent form crashing: WindowManager BadTokenException - android

I am showing Google's GDPR consent form and I am noticing a lot of these reports:
Fatal Exception: android.view.WindowManager$BadTokenException
Unable to add window -- token android.os.BinderProxy#38734f2 is not valid; is your activity running?
com.my.project.MainActivity$4.onConsentFormLoaded
As context I use MainActivity.this:
private void displayConsentForm() {
consentForm = new ConsentForm.Builder(MainActivity.this, GeneralUtils.getAppsPrivacyPolicy())
.withListener(new ConsentFormListener() {
#Override
public void onConsentFormLoaded() {
consentForm.show(); // crashing here for some users
}
#Override
public void onConsentFormOpened() { }
#Override
public void onConsentFormClosed(
ConsentStatus consentStatus, Boolean userPrefersAdFree) {
if(userPrefersAdFree) {
ConsentInformation.getInstance(MainActivity.this)
.setConsentStatus(NON_PERSONALIZED);
} else {
ConsentInformation.getInstance(MainActivity.this)
.setConsentStatus(consentStatus);
}
initAds();
}
#Override
public void onConsentFormError(String errorDescription) {
Log.e("Error",errorDescription);
}
})
.withPersonalizedAdsOption()
.withNonPersonalizedAdsOption()
.withAdFreeOption()
.build();
consentForm.load();
}
Here is additional Firebase crash report:
Why is this happening and how to prevent it? I am not sure what additional check to put before consentForm.show() and I can not reproduce the issue. Maybe it would suffice if I put this check before showing the form:
if(!MainActivity.this.isFinishing() && !MainActivity.this.isDestroyed())
?

The easiest way around this would be to just put a try-catch block around consentForm.show() and catch the BadTokenException.
It's not really clean, but it's likely that this is happening when the Activity finishes (maybe the user closes the app from Recents right as the Dialog is loading).
If this were my project, I'd first try adding that if statement you have (although you don't need the MainActivity.this. part; you can just call isFinishing() and isDestroyed() directly). Since you're referencing an Activity Context, this should take care of it.
However, if it still crashes, you should first look into reproducing it. Try getting to just before displayConsentForm() is called, then closing the app from Recents. Play around with the timing and you'll probably reproduce the crash. If not, then just add the try-catch. The Activity isn't displayed, since it's throwing that error, so the user isn't actually in the app.

Related

React native: Callback in Native Module not being called?

I'm facing a issue in an app I'm developing that someone may be able to help me with.
I'm working on an app for payment machines that use Android (7.1), with it you are able to pay with credit card. The company that developed these machines offer a java sdk. Since this app is using React Native, I wrote a Native Module to connect with it. The SDK is really simple.
After the app is authenticated (done just once), All I have to do is call the doAsyncPayment method with the right parameters, and once its done, it will call the onSuccess or onError method of the listener.
When either of these methods is called, I call the callback parameter I got from the React Native side. In theory it's supposed to work, and it does, most of the time. Every once in a while, something happens that these callbacks are not called, so the client stays on the loading payment screen forever. No error is reported on sentry.
The only thing I'm thinking is that somehow the bridge is losing it? Maybe because these method runs asyncronously in the native side (another thread?), the connection is maybe lost?
I haven't been able to reproduce it, because it happens rarely and at random (I suppose).
I don't know much of Native Android, so any help is appreciated.
Here is a resumed piece of code of what's happening:
Native method being called:
#ReactMethod
public void startPayment(...other params,
Callback onErrorCallback, Callback onSuccessCallback) {
setPlugPag();
PlugPagPaymentData paymentData = new PlugPagPaymentData(paymentType, amountCents,
installmentType, installments, userReference, printReceipt);
PlugPagInitializationResult initResult = plugPag.initializeAndActivatePinpad(new
PlugPagActivationData(activationCode));
if(initResult.getResult() == PlugPag.RET_OK) {
plugPag.doAsyncPayment(paymentData, new PlugPagPaymentListener() {
PlugPagPrintResult printResult;
#Override
public void onSuccess(#NonNull PlugPagTransactionResult plugPagTransactionResult) {
if(printResult != null) {
WritableMap printResultMap = Arguments.createMap();
printResultMap.putInt("result", printResult.getResult());
printResultMap.putString("message", printResult.getMessage());
printResultMap.putString("errorCode", printResult.getErrorCode());
onSuccessCallback.invoke(TransactionResult.toRNWritableMap(plugPagTransactionResult), printResultMap);
}else {
onSuccessCallback.invoke(TransactionResult.toRNWritableMap(plugPagTransactionResult));
}
}
#Override
public void onError(#NonNull PlugPagTransactionResult plugPagTransactionResult) {
onErrorCallback.invoke(TransactionResult.toRNWritableMap(plugPagTransactionResult));
}
#Override
public void onPaymentProgress(#NonNull PlugPagEventData plugPagEventData) {
WritableMap eventDataMap = Arguments.createMap();
eventDataMap.putString("customMessage", plugPagEventData.getCustomMessage());
eventDataMap.putInt("eventCode", plugPagEventData.getEventCode());
sendEvent(Constants.EVENT_PLUG_PAG_PAYMENT_PROGRESS_CHANGED, eventDataMap);
}
#Override
public void onPrinterSuccess(#NonNull PlugPagPrintResult plugPagPrintResult) {
}
#Override
public void onPrinterError(#NonNull PlugPagPrintResult plugPagPrintResult) {
}
});
}else {
onErrorCallback.invoke(String.valueOf(initResult.getResult()));
}
}
on React native side, I just call:
startPayment(...other params, errorCallback, successCallback);
Maybe because the way the listener is instantiated?
Thanks in advance.

LibGDX http request not working

I want to send a String message to database when user presses a specific button in the LibGDX game I am designing for android. How do I go about doing that? Following is the code I tried. But it does not work.
Net.HttpRequest httpRequest = new Net.HttpRequest();
httpRequest.setMethod("POST");
httpRequest.setUrl("URL is here");
httpRequest.setContent("INSERT INTO `game_table` (`Button`) VALUES ('Button 1 Pressed')");
Net.HttpResponseListener httpResponseListener = new Net.HttpResponseListener() {
#Override
public void handleHttpResponse(Net.HttpResponse httpResponse) {
Gdx.app.log("Log httpResponse", httpResponse.getResultAsString());
}
#Override
public void failed(Throwable t) {
}
#Override
public void cancelled() {
}
};
Gdx.net.sendHttpRequest(httpRequest,httpResponseListener);
Log does not provide anything in android monitor. I also tried using AsyncTask and without AsyncTask to implement this code. But neither works.
Am I missing something? If so could you give me small code snippet that will work?
You don't need to use an AsyncTask, libGDX' HTTPRequest is async out of the box.
You did not log anything if the request fails or is cancelled so probably that's the case.

Robospice. Retry failed request on demand

I need to implement quite popular template of app behaviour - give opportunity to user to retry failed requests. Right now I catch failed request with SpiceServiceListener, and shows dialog where user can press "Retry" button. Unfortunately, using the same CachedSpiceRequest object with SpiceManager.execute() don't give desired behaviour, because RS removing all request listeners from mapRequestToLaunchToRequestListener if request wasn't successful. So request can work fine, but it will not return any information to my Activity.
Is there easy way (without modifying code of library) to implement this?
Unfortunately looks like there are no abstract solution for situation like this, so I had to add code like this in every request.
getSpiceManager().execute(r, new RequestListener<CountProfiles>() {
#Override
public void onRequestFailure(SpiceException spiceException) {
if (act.getSupportFragmentManager().findFragmentByTag("network_problem") == null) {
NetworkProblemDialogFragm.newInstance(r, this).show(act.getSupportFragmentManager(), "network_problem");
} else {
((NetworkProblemDialogFragm) act.getSupportFragmentManager().findFragmentByTag("network_problem")).setSpiceRequest(r);
((NetworkProblemDialogFragm) act.getSupportFragmentManager().findFragmentByTag("network_problem")).setRequestListener(this);
}
}
#Override
public void onRequestSuccess(CountProfiles countProfiles) {
}
});
NetworkProblemDialogFragm is a DialogFragment with Retry button, on click on this button I re execute failed request, using given RequestListener.
Not very beautiful solution, but looks like there no better one.

Android : DbxAccountManager.hasLinkedAccount() always return false on Remote Service

On my Activity :
public class MainActivity extends Activity {
private mDbxAccountManager mDbxAccountManager = null;
...
#Override
public void onCreate(Bundle savedInstanceState) {
...
mDbxAccountManager = DbxAccountManager.getInstance(getApplicationContext(), getString(R.string.dbx_app_key), getString(R.string.dbx_app_secret));
...
}
...
public void buttonOnClick(View view) {
if(mDbxAccountManager.hasLinkedAccount()) {
//Do something
}
else {
mDbxAccountManager.startLink(this, 0);
}
...
}
}
And on my Remote Service :
public class CloudService extends Service {
private mDbxAccountManager mDbxAccountManager = null;
#Override
public void onCreate() {
...
mDbxAccountManager = DbxAccountManager.getInstance(getApplicationContext(), getString(R.string.dbx_app_key), getString(R.string.dbx_app_secret));
if(!mDbxAccountManager.hasLinkedAccount()) {
return;
stopSelf();
}
...
}
}
The result is, after I link my app with dropbox using installed dropbox client, the hasLinkedAccount() on my Activity return true, meanwhile the same code on my Remote Service always return false.
I also check the logcat and it showed that my app already linked with dropbox.
My suspect is that the dropbox API create some SharedPreferences when it successfully link with my app, but my Remote Service can't access that or get a cached version of that SharedPreferences... I don't know...
Please help...
Thank you
Edited :
If I reinstall the app, then the result is as expected and hasLinkedAccount() return true, but if I uninstall and install again which cause clearing the user-data, then I link my app again with Dropbox, then the same strange behaviour appear again.
What I'm doing wrong? I'm turning my head almost 24-hours....
Solved!!!
After trying and trying...
I get conclusion that the Service that runs before the app linked with dropbox will always get DbxAccountManager.hasLinkedAccount() return false.
I try to kill the process by calling Process.killProcess(myservicePid) after I link my app with Dropbox and start the service again and it work.
So... I solved it by not starting the service before the app was linked with dropbox and start the service only if it already linked, because stopSelf() on the service doesn't kill the process.
I think this issue have something to do with the Context getApplicationContext() which is passed to DbxAccountManager.getInstance(), and I don't know why looks like the Context is not updated when the dropbox was link with the app.
Thank you.

Android - security through inheritence

I want to extend a common security check to nearly every view of my application. To do this, I have made this class
public class ProtectedActivity extends ActivityBase {
boolean isAuthenticated = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread validationThread = new Thread()
{
#Override
public void run()
{
try
{
isAuthenticated = UserService.validateToken();
}
catch (FTNIServiceException e)
{
//eat it
}
finally
{
if (!isAuthenticated)
{
startActivity(new Intent(ProtectedActivity.this, SignInActivity.class));
finish();
}
}
}
};
validationThread.start();
}
}
The logic is simple. Validate the user against my restful api to make sure they are signed in. If they aren't, show them to the signin page.
This works great, because to add the security check, all I need to do is inherit from my ProtectedActivity.
public class MainMenuActivity extends ProtectedActivity{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
The problem is, however, that I periodically receive View not attached to window manager errors. I understand why this is happening. I am starting a new intent in the parent class, and the child lives on. to attempt to alter it's view even though a new intent has started. What is a better way to handle this so that if a user is not authenticated (such as their session expires serverside), it won't error when sending the user to the sign in screen?
Don't you Thread. Use AsyncTask instead which should handle your references to windows correctly.
On a different note, I would change this to a different implementation. Why don't use the Preferences storage on the phone to store some kind token. If the token is not valid then request a new token and all the stuff you are doing currently. This way is better because you don't want to request a REST call every time.
I imagine something like this (pseudo code)
Check if credentials exist in Preference
if(valid) then do nothing
else use AsyncTask and pop up a loader screen "Waiting..."

Categories

Resources