UserRecoverableAuthException and "don't keep Activities" option.

While working on a mobile application in Espeo I came across a strange error.

It appeared sometimes when I tried to sign in with Google Account. Typically, when you first log in to Google Services, you see the screen generated by Google with a request for access, e.g. for offline access.

Then another view is displayed, informing that signing in is in progress. After a moment you are signed in.

When you do it for the first time, there will be UserRecoverableAuthException thrown. This is how you should handle it according to official documentation:

catch (UserRecoverableAuthException e) {
// Requesting an authorization code will always throw
// UserRecoverableAuthException on the first call to GoogleAuthUtil.getToken
// because the user must consent to offline access to their data.  After
// consent is granted control is returned to your activity in onActivityResult
// and the second call to GoogleAuthUtil.getToken will succeed.
startActivityForResult(e.getIntent(), AUTH_CODE_REQUEST_CODE);
}

Then you should also handle onActivityResult() method:

onActivityResult(int requestCode, int resultCode, Intent data) {
(...)
final Bundle extra = data.getExtras();
mAuthToken = extra.getString("authtoken");
(...)
}

This way you can get the authorization code, which can be replaced with the access token on the server side.
GoogleAuthUtil.getToken (MyActivity.this, account, scopes) returns only the authorization code, and not the correct access token. It is safer, because Android code is easy to extract. Retrieving access token is a task for the server.

When UserRecoverableAuthException is handled this way, application will return proper authorization code regardless whether the exception is thrown or not.

This solution worked perfectly on every device but one. One particular
Samsung Galaxy was throwing UserRecoverableAuthException in loop, so the signing in screen was shown repeatedly, rendering use of the application impossible. It turned out, that “don”t keep activities” option was enabled in developer options in this device.

What is this function actually for?

If your device needs memory it destroys activities which are not visible. So you have to consider that your activity can be recreated any time. “Don”t keep activities” option is there to simulate your application when your device needs memory and destroys your backstack activities. But the order of destroying Activities is not random – the previous activity being not available when your app is in the foreground is the rarest of rare use cases, almost impossible on newer devices.

What happens when it occurs anyway?

First we should consider what startActivityForResult() function actually does. You use this function to launch an activity for which you would like a result when it finishes. When this activity exits, your onActivityResult() method will be called with the given requestCode.

So we send an intent to start Google-provided sign in Activity, result of which is sent back to our Activity. But, our Activity does not exist any more! It was killed, because it had not been in foreground.
Our Activity”s state will be restored, and UserRecoverableAuthException will be thrown again and again.
That is why method onActivityResult() will never be invoked.

Even though this situation is highly unlikely there”s an approach that you should always take when handling situations like this, because every Activity that is not on the foreground can be killed any time.

To handle signing into Google with “don”t keep Activities” option enabled, you can for example use boolean variable, which persists information whether current Activity was killed before, and if it was, you won”t call signing in function again and let the onActivityResult() function to be called and handle response. Here”s a code example:

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mUserRecoverableThrown = savedInstanceState.getBoolean(RECOVERABLE_FLAG);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SAVED_PROGRESS, mSignInProgress);
outState.putBoolean(RECOVERABLE_FLAG, mUserRecoverableThrown);
}

And at the point where downloading token AsyncTask was executed, you must first check whether the Activity was not already killed with UserRecoverableAuthException thrown:

if (!mUserRecoverableThrown) {
getProfileInformation();
}

To sum up, always test your application with the option “don”t keep activities” – it should also work fine in a situation in which all the other activities have been killed.

It is worth to disable it again after testing – apparently many developers forget about this option, and some of the popular apps installed on your phone may not work properly.
So if your device suddenly starts behaving strangely (often during signing in to apps) – check if the option is enabled.

author: Iga Stępniak