Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Google+ integration - repeated UserRecoverableAuthException

We have contacted Google about this and we are on chat

The issue seems to be fixed for devices except Samsung phones.

I'm adding a Google+ sign in option to an app per the official instructions. Once the user has selected their account I would like my server to retrieve their Google+ profile info and update their profile on our site to match.

The first part - having the user select a Google account locally - seems to work just fine. When I try to request a token for the selected account, the Google auth dialog displays with the appropriate parameters; however, when I authorize the app using that dialog and re-request the token, GoogleAuthUtil.getToken(...) again throws a UserRecoverableAuthException (NeedPermission, not GooglePlayServicesAvailabilityException) and I get the same dialog asking me to approve!

This behavior is present on a Samsung S3 running Android 4.1.1 (with 3 Google accounts) and an Acer A100 running 4.0.3. It is NOT present on an HTC Glacier running 2.3.4. Instead, the HTC Glacier gives me a valid auth code. All devices have the latest iteration of Google Play Services installed and are using different Google+ accounts.

Anyone seen this before? Where can I start with debugging?

Here's the complete code - is anything obviously awry?

public class MyGooglePlusClient {
private static final String LOG_TAG = "GPlus";
private static final String SCOPES_LOGIN = Scopes.PLUS_LOGIN + " " + Scopes.PLUS_PROFILE;
private static final String ACTIVITIES_LOGIN = "http://schemas.google.com/AddActivity";
private static MyGooglePlusClient myGPlus = null;
private BaseActivity mRequestingActivity = null;
private String mSelectedAccount = null;
    
/**
 * Get the GPlus singleton
 * @return GPlus
 */
public synchronized static MyGooglePlusClient getInstance() {
    if (myGPlus == null)
        myGPlus = new MyGooglePlusClient();
    return myGPlus;
}

public boolean login(BaseActivity requester) {
    Log.w(LOG_TAG, "Starting login...");
    if (mRequestingActivity != null) {
        Log.w(LOG_TAG, "Login attempt already in progress.");
        return false; // Cannot launch a new request; already in progress
    }
    
    mRequestingActivity = requester;
    if (mSelectedAccount == null) {
        Intent intent = AccountPicker.newChooseAccountIntent(null, null, new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, false,
                null, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE, null, null);
        mRequestingActivity.startActivityForResult(intent, BaseActivity.REQUEST_GPLUS_SELECT);
    }
    return true;
}

public void loginCallback(String accountName) {
    mSelectedAccount = accountName;
    authorizeCallback();
}
    
public void logout() {
    Log.w(LOG_TAG, "Logging out...");
    mSelectedAccount = null;
}

public void authorizeCallback() {
    Log.w(LOG_TAG, "User authorized");

    AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
        @Override
        protected String doInBackground(Void... params) {
            String token = null;
            try {
                Bundle b = new Bundle();
                b.putString(GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES, ACTIVITIES_LOGIN);
                token = GoogleAuthUtil.getToken(mRequestingActivity,
                        mSelectedAccount,
                        "oauth2:server:client_id:"+Constants.GOOGLE_PLUS_SERVER_OAUTH_CLIENT
                        +":api_scope:" + SCOPES_LOGIN,
                        b);
            } catch (IOException transientEx) {
                // Network or server error, try later
                Log.w(LOG_TAG, transientEx.toString());
                onCompletedLoginAttempt(false);
            } catch (GooglePlayServicesAvailabilityException e) {
                Log.w(LOG_TAG, "Google Play services not available.");
                Intent recover = e.getIntent();
                mRequestingActivity.startActivityForResult(recover, BaseActivity.REQUEST_GPLUS_AUTHORIZE);
            } catch (UserRecoverableAuthException e) {
                // Recover (with e.getIntent())
                Log.w(LOG_TAG, "User must approve "+e.toString());
                Intent recover = e.getIntent();
                mRequestingActivity.startActivityForResult(recover, BaseActivity.REQUEST_GPLUS_AUTHORIZE);
            } catch (GoogleAuthException authEx) {
                // The call is not ever expected to succeed
                Log.w(LOG_TAG, authEx.toString());
                onCompletedLoginAttempt(false);
            }

            Log.w(LOG_TAG, "Finished with task; token is "+token);
            if (token != null) {
                authorizeCallback(token);
            }
            
            return token;
        }

    };
    task.execute();
}

public void authorizeCallback(String token) {
    Log.w(LOG_TAG, "Token obtained: "+token);
    // <snipped - do some more stuff involving connecting to the server and resetting the state locally>
}

public void onCompletedLoginAttempt(boolean success) {
    Log.w(LOG_TAG, "Login attempt "+(success ? "succeeded" : "failed"));
    mRequestingActivity.hideProgressDialog();
    mRequestingActivity = null;
}
}
like image 812
Arkaaito Avatar asked Jul 18 '13 02:07

Arkaaito


4 Answers

I've had this issue for a while and came up with a proper solution.

String token = GoogleAuthUtil.getToken(this, accountName, scopeString, appActivities);

This line will either return the one time token or will trigger the UserRecoverableAuthException. On the Google Plus Sign In guide, it says to open the proper recovery activity.

startActivityForResult(e.getIntent(), RECOVERABLE_REQUEST_CODE);

When the activity returns with the result, it will come back with few extras in the intent and that is where the new token resides :

@Override
protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
    if (requestCode == RECOVERABLE_REQUEST_CODE && responseCode == RESULT_OK) {
        Bundle extra = intent.getExtras();
        String oneTimeToken = extra.getString("authtoken");
    }
}

With the new oneTimeToken given from the extra, you can submit to the server to connect properly.

I hope this helps!

like image 111
Calvin Park Avatar answered Oct 24 '22 11:10

Calvin Park


Its too late to reply but it may help to people having same concern in future.

They have mentioned in the tutorial that it will always throw UserRecoverableAuthException when you invoke GoogleAuthUtil.getToken() for the first time. Second time it will succeed.

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);
  return;
}

i used below code to get access code from google.

execute this new GetAuthTokenFromGoogle().execute(); once from public void onConnected(Bundle connectionHint) and once from protected void onActivityResult(int requestCode, int responseCode, Intent intent)

private class GetAuthTokenFromGoogle extends AsyncTask<Void, Integer, Void>{
        @Override  
        protected void onPreExecute()  
        {  

        }
        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub

            try {
                accessCode = GoogleAuthUtil.getToken(mContext, Plus.AccountApi.getAccountName(mGoogleApiClient), SCOPE);
                new ValidateTokenWithPhoneOmega().execute();
                Log.d("Token  -- ", accessCode);
            } catch (IOException transientEx) {
                // network or server error, the call is expected to succeed if you try again later.
                // Don't attempt to call again immediately - the request is likely to
                // fail, you'll hit quotas or back-off.

                return null;
            } catch (UserRecoverableAuthException e) {
                // Recover
                startActivityForResult(e.getIntent(), RC_ACCESS_CODE);
                e.printStackTrace();
            } catch (GoogleAuthException authEx) {
                // Failure. The call is not expected to ever succeed so it should not be
                // retried.
                authEx.printStackTrace();
                return null;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return null;  
        }

        @Override  
        protected void onPostExecute(Void result)  
        { 
        }
    }
like image 39
appdroid Avatar answered Oct 24 '22 12:10

appdroid


I have got around this issue by using a web based login. I open a url like this

String url = "https://accounts.google.com/o/oauth2/auth?scope=" + Scopes.PLUS_LOGIN + "&client_id=" + webLoginClientId + "&response_type=code&access_type=offline&approval_prompt=force&redirect_uri=" + redirect;

The redirect url then handles the response and returns to my app.

In terms of my findings on using the Google Play Services, I've found:

HTC One is 3.1.59 (736673-30) - not working Galaxy Note is 3.1.59 (736673-36) - not working Nexus S is 3.1.59 (736673-34) - works

And I'd like to be involved in the chat that is occurring, however I don't have a high enough reputation to do so.

like image 2
user2608643 Avatar answered Oct 24 '22 10:10

user2608643


I've experienced the same issue recently - it appears to be device-specific (I had it happen every time on one S3, but on another S3 running the same OS it didn't happen, even with the same account). My hunch is that it's a bug in a client app, either the G+ app or the Google Play Services app. I managed to solve the issue on one of my devices by factory resetting it (a Motorola Defy), then reinstalling the Google Play Services app, but that's a completely useless solution to tell to users.

like image 2
Adam Avatar answered Oct 24 '22 12:10

Adam