setting up the Android Firebase on Android.
I need to get the unique identifier of the user, so that I can have consistency accross all platforms no matter if the email is hided or not.
Based upon https://firebase.google.com/docs/auth/android/apple , using .startActivityForSignInWithProvider(this, provider.build()) we can see:
public void onSuccess(AuthResult authResult) {
Log.d(TAG, "checkPending:onSuccess:" + authResult);
// Get the user profile with authResult.getUser() and
// authResult.getAdditionalUserInfo(), and the ID
// token from Apple with authResult.getCredential().
And from Apple https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple#3383773 we can also see
The identity token is a JSON Web Token (JWT) and contains
and
sub
The subject registered claim identifies the principal that’s the subject of the identity token. Because this token is for your app, the value is the unique identifier for the user.
Question:
What I got from firebase is AuthCredential , but I am expecting JWT with "sub" in it.
How can I get it?
thanks to Ricardo from Firebase support I got this working.
in Android the sub value from Apple is in
"firebase":{"identities":{"apple.com":["001814.24f212edfsdfdfsfd69b7b5fee972e.1722"],"email":["a#b.com"]},"sign_in_provider":"apple.com"}
You get it using
auth.startActivityForSignInWithProvider(this, provider.build()).addOnSuccessListener( new OnSuccessListener<AuthResult>() {
#Override
public void onSuccess(AuthResult authResult) {
// Sign-in successful!
FirebaseUser user = authResult.getUser();
AuthCredential identityToken = authResult.getCredential();
authResult.getUser().getIdToken(false).addOnSuccessListener(new OnSuccessListener<GetTokenResult>() {
#Override
public void onSuccess(GetTokenResult result) {
String idToken = result.getToken();
//Do whatever
Log.d(TAG, "GetTokenResult result = " + idToken);
try {
decodeJWT(idToken);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
})
Where the JWT is decoded with
private static void decodeJWT(String JWTEncoded) throws Exception {
try {
String[] split = JWTEncoded.split("\\.");
Log.d("JWT_DECODED", "Header: " + getJson(split[0]));
Log.d("JWT_DECODED", "Body: " + getJson(split[1]));
} catch (UnsupportedEncodingException e) {
//Error
}
}
private static String getJson(String strEncoded) throws UnsupportedEncodingException{
byte[] decodedBytes = Base64.decode(strEncoded, Base64.URL_SAFE);
return new String(decodedBytes, "UTF-8");
}
OK, I had this working at one point, but then wanted to add a silent login (optional) at app startup. I now going crazy and am getting following error
com.google.api.client.googleapis.json.GoogleJsonResponseException: 401 Unauthorized
{
"code": 401,
"errors": [
{
"domain": "global",
"location": "Authorization",
"locationType": "header",
"message": "Login Required",
"reason": "required"
}
],
"message": "Login Required"
}
This occurs on the line (should I be using "me"?)
Message messageResult = mGmailService.users().messages().send("me", message).execute();
whenever I try to send (in the case above) or read email (not shown). Ultimately, all I want to do with the app is to read user emails and send on their behalf. The app is registered, has the keys, permissions, etc.
I am setting up the data as (you can see my various comments as I tried to things working):
mGso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestScopes(new Scope(Scopes.PROFILE))
.requestScopes(new Scope(GmailScopes.GMAIL_READONLY))
.requestScopes(new Scope(GmailScopes.GMAIL_SEND))
.requestScopes(new Scope(GmailScopes.GMAIL_COMPOSE))
.requestScopes(new Scope(GmailScopes.GMAIL_LABELS))
.requestScopes(Plus.SCOPE_PLUS_LOGIN)
.requestScopes(new Scope(Scopes.EMAIL))
.requestScopes(new Scope(Scopes.PLUS_ME))
.build();
// set up Google client
// mGoogleApiClient = new GoogleApiClient.Builder(this)
// .addConnectionCallbacks(this)
// .addOnConnectionFailedListener(this)
// .addApi(Plus.API)
// .addApi(Auth.GOOGLE_SIGN_IN_API, mGso)
// .build();
mGoogleApiClient = new GoogleApiClient.Builder(this)
// .enableAutoManage(this, this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API)
.addApi(Auth.GOOGLE_SIGN_IN_API, mGso)
.build();
//mGoogleApiClient.connect(GoogleApiClient.SIGN_IN_MODE_REQUIRED); // try to connect right away
mGmailService = new com.google.api.services.gmail.Gmail.Builder(
AndroidHttp.newCompatibleTransport(), GsonFactory.getDefaultInstance(), null)
.setApplicationName("com.company.appname")
.build();
The silent login is done in the onStart() method of an embedded fragment as:
// try to silently sign in ...
OptionalPendingResult<GoogleSignInResult> pendingResult =
Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (pendingResult.isDone()) {
// There's an immediate result available.
mSignInAcct = pendingResult.get().getSignInAccount();
mEmailAcct = mSignInAcct.getEmail();
} else {
// There's no immediate result ready, wait for the async callback.
pendingResult.setResultCallback(new ResultCallback<GoogleSignInResult>() {
#Override
public void onResult(#NonNull GoogleSignInResult result) {
mSignInAcct = result.getSignInAccount();
mEmailAcct = mSignInAcct.getEmail();
} // if fails, forget it and force a real login later.
});
}
mGoogleApiClient.connect(GoogleApiClient.SIGN_IN_MODE_OPTIONAL);
boolean b = mGoogleApiClient.isConnected();
which sometimes connects right away with a valid user account and sometimes not. I do see that b from the ...isConnected() call is false, although isConnected() returns true later in later calls.
Login button code:
gmail_signin_button = (com.google.android.gms.common.SignInButton) rootView.findViewById(R.id.gmail_sign_in_button);
gmail_signin_button.setScopes(mGso.getScopeArray());
gmail_signin_button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// User clicked the sign-in button, so begin the sign-in process and automatically
// attempt to resolve any errors that occur.
mShouldResolve = true;
// connect if unconnected...
if (!mGoogleApiClient.isConnected()) {
mGoogleApiClient.connect();
tv_email_status.setText("G+ connecting");
// Show a message to the user that we are signing in.
Log.d(Constants.TAG, "Google API connecting ...");
}
else {
// sign in anew ...
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
startActivityForResult(signInIntent, Constants.RC_SIGN_IN);
}
}
});
onActivityResult code (in part):
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// G+
if (requestCode == Constants.RC_SIGN_IN) {
// If the error resolution was not successful we should not resolve further.
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (!result.isSuccess()) {
mShouldResolve = false;
Log.d(Constants.TAG, "Error: code: " + resultCode);
tv_email_status.setText("Error: code: " + resultCode);
} else {
mSignInAcct = result.getSignInAccount();
mEmailAcct = mSignInAcct.getEmail();
Log.d(Constants.TAG, "Gmail signin OK - " + mEmailAcct);
tv_email_status.setText("Email: " + mEmailAcct);
}
mIsResolving = false;
if(!mGoogleApiClient.isConnected()) {
mGoogleApiClient.connect(GoogleApiClient.SIGN_IN_MODE_OPTIONAL);
}
} else if (requestCode == Constants.REQUEST_ACCOUNT_PICKER) {
if (resultCode == RESULT_OK && data != null && data.getExtras() != null) {
mEmailAcct = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (mEmailAcct != null) {
//mCredential.setSelectedAccountName(accountName);
SharedPreferences settings = mActivity.getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putString(Constants.PREF_ACCOUNT_NAME, mEmailAcct);
editor.commit();
Log.d(Constants.TAG, "Gmail: account: " + mEmailAcct);
tv_email_status.setText("Gmail: account: " + mEmailAcct);
}
} else if (resultCode == RESULT_CANCELED) {
tv_email_status.setText("Account unspecified.");
}
}
//else if (requestCode == Constants.REQUEST_AUTHORIZATION) {
//if (mCredential != null && resultCode != RESULT_OK) {
// startActivityForResult(mCredential.newChooseAccountIntent(), Constants.REQUEST_ACCOUNT_PICKER);
//}
//}
OK, I will get a good onActivityResult and get valid SignInAcct and EmailAcct values. It seems that we are logged in fine, but then the gMail service always fails with a 401 error as explained above.
To do the silent login made me have to add the GOOGLE_SIGN_IN_API which then seems to require the GoogleApiClient to connect with SIGN_IN_MODE_OPTIONAL which doesn't make sense to me.
I also see that my AccountPicker does nothing now as well. But that is another story/problem, as I said I thought I had this all working until I tried to add that silent feature.
Whew, well I have probably made this harder than it is, can anyone steer me in the right direction? As I said all I want to do is:
silently log in if the user has previously done so
allow the user to log in or re-log in if needed
read the users emails and contacts
send email on their behalf.
OK, I figured this out, as usual its something simple wrong. The credential in the GmailService constructor must be set, but you don't have this until you try to connect. So I have code like this
void setGmailAccess(GoogleSignInResult result) {
mSignInAcct = result.getSignInAccount();
mEmailAcct = mSignInAcct.getEmail();
final Thread task = new Thread()
{
#Override
public void run()
{
try {
// run in background thread
String token = GoogleAuthUtil.getToken(MainActivity.this, Plus.AccountApi.getAccountName(mGoogleApiClient), "oauth2:profile email");
GoogleCredential credential = new GoogleCredential().setAccessToken(token);
mGmailService = new com.google.api.services.gmail.Gmail.Builder(
AndroidHttp.newCompatibleTransport(), GsonFactory.getDefaultInstance(), credential)
.setApplicationName("com.company.appname")
.build();
}
catch (Exception e) {
// ignore errors
String error = e.getMessage();
}
}
};
task.start();
}
To set up the GmailService object. It is called from onConnected, where I have code like this in the result success block:
try {
// try to silently sign in ...
OptionalPendingResult<GoogleSignInResult> pendingResult =
Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (pendingResult.isDone()) {
// There's an immediate result available.
setGmailAccess(pendingResult.get());
} else {
// There's no immediate result ready, wait for the async callback.
pendingResult.setResultCallback(new ResultCallback<GoogleSignInResult>() {
#Override
public void onResult(#NonNull GoogleSignInResult result) {
setGmailAccess(result);
} // if fails, forget it and force a real login later.
});
}
} catch(Exception e) {
String me = e.getLocalizedMessage();
}
And in the activity result for starting a new login. Seems to work.
I know how to login:
ParseTwitterUtils.logIn(loginView.getCurrentContext(), new LogInCallback() {
#Override
public void done(ParseUser parseUser, ParseException e) {
if (e == null) {
String welcomeMessage = "";
if (parseUser.isNew()) {
welcomeMessage = "Hello new guy!";
} else {
welcomeMessage = "Welcome back!";
}
loginView.showLoginSuccess(parseUser, welcomeMessage);
} else {
String errorMessage = "Seems we have a problem : " + e.getLocalizedMessage();
loginView.showLoginFail(errorMessage);
}
}
});
And to logout :
ParseUser.logOutInBackground(new LogOutCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
homeView.goLogin(true, "See you soon");
} else {
homeView.goLogin(false, "Error detected : " + e.getLocalizedMessage());
}
}
});
But when I want to log in again, I don't have the alert dialog asking me to choose accounts (i use the webview since Twitter app is not installed on the emulator).
How to truly logout from Parse using Twitter login?
In iOS, you can revise the source code of Parse in PFOauth1FlowDialog.m
- (void)loadURL:(NSURL *)url queryParameters:(NSDictionary *)parameters {
NSMutableDictionary *_parameter = [[NSMutableDictionary alloc] init];
[_parameter setObject:#"true" forKey:#"force_login"];
[_parameter addEntriesFromDictionary:parameters];
_loadingURL = [[self class] _urlFromBaseURL:url queryParameters:_parameter];
NSURLRequest *request = [NSURLRequest requestWithURL:_loadingURL];
[_webView loadRequest:request];
}
Then everything should work fine, And this should also work in Android.
Use the unlink functions from ParseTwitterUtils:
https://parse.com/docs/android/api/com/parse/ParseTwitterUtils.html#unlink(com.parse.ParseUser)
This will remove the link between the twitter account and the parse user.
The confusion seems to stem from the fact that the api is so straightforward.
What you're doing in the login is associating a twitter account with a parse user and logging in as that parse user. Then when you are logging out, you are only logging out of the parse user, and the twitter account is still linked to the parse user. Therefore when you go to log in again it automatically uses the twitter account to log in as the parse user.
I need to get access token and send it to the server. With that access token server should get all user details, like name, profile picture and email.
I can get access token using Scopes.PLUS_LOGIN and Scopes.PLUS_ME, but with that access token server can't get user email.
Here is my code:
#Override
public void onConnected(Bundle arg0) {
mSignInClicked = false;
AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String token = null;
String scope = "oauth2:" + Scopes.PLUS_LOGIN + " " + Scopes.PLUS_ME;
try {
token = GoogleAuthUtil.getToken(
getApplicationContext(),
Plus.AccountApi.getAccountName(mGoogleApiClient),
scope);
appUser.setToken(token);
} catch (IOException transientEx) {
// Network or server error, try later
Log.e(TAG, transientEx.toString());
} catch (UserRecoverableAuthException e) {
// Recover (with e.getIntent())
} catch (GoogleAuthException authEx) {
// The call is not ever expected to succeed
// assuming you have already verified that
// Google Play services is installed.
Log.e(TAG, authEx.toString());
}
return token;
}
#Override
protected void onPostExecute(String token) {
Log.i(TAG, "Access token retrieved:" + appUser.getToken());
// Get user's information
}
};
}
Does anybody know how to solve this problem?
You are missing the scope
https://www.googleapis.com/auth/userinfo.email
I tested the other scopes and only that one appears to return the users email. You can test the different scopes and what they return here: People: get.
Note: I'm not an android programmer, you will probably have better luck finding out how to request that scope with android. I am looking, but haven't been able to find it.
Looks like the scope might just be email https://developers.google.com/+/api/oauth#email
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;
}
}
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!
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)
{
}
}
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.
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.
Edit (6th Aug 2013): This seems to have been fixed for me without any changes to my code.
The first potential issue I can see is that you are calling GoogleAuthUtil.getToken() after you get the onConnected() callback. This is a problem because requesting an authorization code for your server using GoogleAuthUtil.getToken() will always show a consent screen to your users. So you should only get an authorization code for new users and, to avoid showing new users two consent screens, you must fetch an authorization code and exchange it on your server before resolving any connection failures from PlusClient.
Secondly, make sure you actually need both a PlusClient and an authorization code for your servers. You only need to get a PlusClient and an authorization code if you are intending to make calls to the Google APIs from both the Android client and your server. As explained in this answer.
These issues would only result in two consent dialogs being displayed (which is clearly not an endless loop) - are you seeing more than two consent dialogs?
I had a similar problem where an apparent auth loop kept creating {read: spamming} these "Signing In..." and Permission request dialogs while also giving out the discussed exception repeatedly.
The problem appears in some slightly-modified example code that I (and other like me, I suspect) "cargo-culted" from AndroidHive. The solution that worked for me was ensuring that only one background token-retrieval task runs at the background at any given time.
To make my code easier to follow, here's the auth flow in my app (that is almost identical to the example code on AndoidHive): Activity -> onConnected(...) -> getProfileInformation() -> getOneTimeToken().
Here's where getOneTimeToken() is called:
private void getProfileInformation() {
try {
if (Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null) {
Person currentPerson = Plus.PeopleApi
.getCurrentPerson(mGoogleApiClient);
String personName = currentPerson.getDisplayName();
String personPhotoUrl = currentPerson.getImage().getUrl();
String personGooglePlusProfile = currentPerson.getUrl();
String email = Plus.AccountApi.getAccountName(mGoogleApiClient);
getOneTimeToken(); // <-------
...
Here's my getOneTimeToken():
private void getOneTimeToken(){
if (task==null){
task = new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
LogHelper.log('d',LOGTAG, "Executing background task....");
Bundle appActivities = new Bundle();
appActivities.putString(
GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES,
ACTIVITIES_LOGIN);
String scopes = "oauth2:server" +
":client_id:" + SERVER_CLIENT_ID +
":api_scope:" + SCOPES_LOGIN;
String token = null;
try {
token = GoogleAuthUtil.getToken(
ActivityPlus.this,
Plus.AccountApi.getAccountName(mGoogleApiClient),
scopes,
appActivities
);
} catch (IOException transientEx) {
/* Original comment removed*/
LogHelper.log('e',LOGTAG, transientEx.toString());
} catch (UserRecoverableAuthException e) {
/* Original comment removed*/
LogHelper.log('e',LOGTAG, e.toString());
startActivityForResult(e.getIntent(), AUTH_CODE_REQUEST);
} catch (GoogleAuthException authEx) {
/* Original comment removed*/
LogHelper.log('e',LOGTAG, authEx.toString());
} catch (IllegalStateException stateEx){
LogHelper.log('e',LOGTAG, stateEx.toString());
}
LogHelper.log('d',LOGTAG, "Background task finishing....");
return token;
}
#Override
protected void onPostExecute(String token) {
LogHelper.log('i',LOGTAG, "Access token retrieved: " + token);
}
};
}
LogHelper.log('d',LOGTAG, "Task setup successful.");
if(task.getStatus() != AsyncTask.Status.RUNNING){
task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); //double safety!
} else
LogHelper.log('d',LOGTAG,
"Attempted to restart task while it is running!");
}
Please note that I have a {probably redundant} double-safety against the task executing multiple times:
if(task .getStatus() != AsyncTask.Status.RUNNING){...} - ensures that the task isn't running before attempting to execute it.
task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);- makes sure that copies of this task are "synchronized" (i.e. a queue is in place such that only one task of this type can executed at a given time).
P.S.
Minor clarification: LogHelper.log('e',...) is equivalent to Log.e(...) etc.
you should startactiviy in UI thread
try {
....
} catch (IOException transientEx) {
....
} catch (final UserRecoverableAuthException e) {
....
runOnUiThread(new Runnable() {
public void run() {
startActivityForResult(e1.getIntent(), AUTH_CODE_REQUEST);
}
});
}
Had the same bug with infinite loop of permission request. For me it was because time on my phone was shifted. When I check detect time automatically this bug disappeared. Hope this helps!