Unable to retrieve access token for facebook on real device - android

I did Facebook integration in my project, everything is fine on emulator. When it comes to run on real device it is not working. I think the problem is Facebook access token, I don't know what to do now? So I am unable to retrieve friends information on real device. Can anybody help me how to get access token on real device.
I am using Android SDK only to get the Facebook friends information.
mFacebook = new Facebook("api_id");
mFacebook.authorize(this, new String[] {
"publish_stream", "read_stream", "offline_access", "friends_birthday", "user_birthday", "email", "read_friendlists", "manage_friendlists"
}, this);
sToken = mFacebook.getAccessToken();
public void onComplete(Bundle values) {
Log.e("oncomplete", "value");
if (values.isEmpty()) {
Log.e("oncomplete", "value is empty");
return;
}
if (!values.containsKey("POST")) {
sToken = mFacebook.getAccessToken();
getFriends()
}
}
private void getFriends() {
try {
sToken = mFacebook.getAccessToken();
StaticUtils.sResponseId = mFacebook.request("me/friends");
Log.w("response", StaticUtils.sResponseId);
try {
JSONObject jObj = Util.parseJson(StaticUtils.sResponseId);
JSONArray jArr = jObj.getJSONArray("data");
for (int i = 0; i < jArr.length(); i++) {
JSONObject jObjFren = jArr.getJSONObject(i);
Iterator it = jObjFren.keys();
while (it.hasNext()) {
String s = (String) it.next();
// Log.w("KEY",s);
String sname = jObjFren.getString(s);
if (s.equals("id")) {
StaticUtils.sFbId.add(sname);
StaticUtils.sFbPics.add(StaticUtils.sImgUrl + sname + "/picture");
} else if (s.equals("name")) {
StaticUtils.sFbName.add(sname.toLowerCase());
}
}
}
} catch (JSONException e) {
Log.w("json exception", e.toString());
} catch (FacebookError e) {
Log.w("facebook exception", e.toString());
}
} catch (MalformedURLException e) {
Log.w("malformed exception", e.toString());
} catch (IOException e) {
Log.w("io exception", e.toString());
}
}
Thanks,
Ammu

The code above given is absolutely correct...The problem is if we have already installed any facebook application in our device which are upgraded versions than we are using our application will not work...If we uninstall the facebook applications in our device it will work...and there is also another way to work our application and other facebook application in our device by following steps:::
if u r using eclipse then goto
Windows>Preferences>Android>Build and get the path of the debug keystore
now open terminal and run the following command
keytool -export -alias androiddebugkey -keystore "debug keystore path" | openssl sha1 -binary | openssl enc -a -e
it will ask for the password then enter android as ur password
Now add the HASH KEY to your Facebook API configuration located # http://www.facebook.com/developers/
under EDIT SETTINGS / Mobile and Devices screen.
Now i was able to run my application on HTC device

There is another answer for this question.
We can have pre-installed facebook application to our mobile device , but we will not be interacting with the already installed application,i.e. we will not use single sign on ,better known as SSO(though it is recommended to use).
We are having this method in facebook sdk.
single sign-on may be disabled by passing FORCE_DIALOG_AUTH
as the activityCode parameter in your call to authorize().
public void authorize(Activity activity, String[] permissions,
int activityCode, final DialogListener listener)
Here the third parameter plays the role i.e. activityCode.Now lets see its role
if (activityCode >= 0) {
singleSignOnStarted = startSingleSignOn(activity, mAppId,
permissions, activityCode);
}
// Otherwise fall back to traditional dialog.
if (!singleSignOnStarted) {
startDialogAuth(activity, permissions);
}
It means if we want to be untouched with the pre installed facebook application we have to pass activityCode with less than 0 (< 0) value.Now in your main activity where facebook api is called use
int FORCE_DIALOG_AUTH =-1;
mFacebook.authorize(this, new String[] { "publish_stream",
"read_stream", "offline_access", "friends_birthday",
"user_birthday", "email", "read_friendlists",
"manage_friendlists" },FORCE_DIALOG_AUTH , new LoginDialogListener());
If we would like to force the use of legacy
dialog-based authorization, pass FORCE_DIALOG_AUTH for this
parameter. Otherwise just omit this parameter and Facebook
will use a suitable default.
It was just to solve the problem from SSO.

you can save access token on sharedReference when login as follow
public void onComplete(Bundle values)
{
SharedPreferences sp;
Editor editor = sp.edit();
editor.putString("access_token",fb.getAccessToken());
editor.putLong("access_expires",fb.getAccessExpires());
editor.commit();
}
and Check this onCreate Method as follow
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_facebook_app);
if(access_token != null){
fb.setAccessToken(access_token);
}
if(expires != 0){
fb.setAccessExpires(expires);
}
}

Related

ArcGIS Android Offline Map Tiles - User Credentials

I'm developing an app using the ArcGIS Runtime SDK for Android. I'm accessing tiled basemaps from arcgis.com using the following code which works fine.
UserCredentials creds = new UserCredentials();
creds.setUserToken("token", "referer");
String mapUrlUsaTopo = "https://services.arcgisonline.com/arcgis/rest/services/USA_Topo_Maps/MapServer";
mBasemapLayer = new ArcGISTiledMapServiceLayer(mapUrlUsaTopo, creds);
But... when I attempt to download the map tiles for offline use I get the following error:
com.esri.core.io.EsriSecurityException: Message: Unable to generate token. Details: 'username' must be specified., 'password' must be specified.
Here's the download code:
String tileUrlUsaTopo = "https://tiledbasemaps.arcgis.com/arcgis/rest/services/USA_Topo_Maps/MapServer";
final ExportTileCacheTask exportTileCacheTask = new ExportTileCacheTask(tileUrlUsaTopo, creds);
Is the only option hard coding the username and password?
Andrew from Esri support was very helpful. To do "application" authentication (without a user name and password) using your app id and secret key you need something like the code below. However, there is a bug (BUG-000092420) in the android sdk and the code below does not work at the present time. I'm being told that the fix may make in into the Quartz final release.
private class AppLoginTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
Log.d("MyApp", "AppLoginTask");
Portal p = new Portal("https://www.arcgis.com", null);
try {
p.doOAuthAppAuthenticate(APP_SECRET, APP_ID, new CallbackListener<Portal>() {
#Override
public void onCallback(Portal portal) {
Log.d("MyApp", "login callback");
//mCreds = new UserCredentials();
mCreds = portal.getCredentials();
setMapDataMode(MapDataMode.ONLINE);
}
#Override
public void onError(Throwable throwable) {
Log.e("MyApp", "login error");
}
});
} catch (Exception e) {
Log.d("MyApp", "login exception");
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void results) {
Log.d("MyApp", "login post execute");
//setMapDataMode(MapDataMode.ONLINE);
}
}

How to logout/change Twitter account with Parse

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.

Gmail API: Authentication using token retrieval

I have read this whole article for the authentication using OAuth2.0.
But I didn't find a suitable method to do this on an android application. Please suggest a method to get the access token so that I can build a Gmail service object and access the inbox or any other method.
This is the example given by them in this link:
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Plus plus = new Plus.builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
.setApplicationName("Google-PlusSample/1.0")
.build();
Invoke the below method to get the token and the google account used in mobile. This method first retrieves the google account setup in your mobile and later retrieves the token.
You can save the token and account name using preferences for later use so that you dont have to retrieve the token each time.
private void chooseAccount() {
Intent intent = AccountPicker.newChooseAccountIntent(null, null,
new String[]{"com.google"}, false, null, null, null, null);
startActivityForResult(intent, 9009);
}
After the account is retrieved the below method is called,
public static final String MAIL_GOOGLE_COM = "https://mail.google.com";
public static final String GMAIL_COMPOSE = "https://www.googleapis.com/auth/gmail.compose";
public static final String GMAIL_MODIFY = "https://www.googleapis.com/auth/gmail.modify";
private static final String SCOPE = "oauth2:" + GMAIL_COMPOSE + " " + GMAIL_MODIFY + " " + MAIL_GOOGLE_COM;
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
//accountname - google account in your mobile is retrieved
//now use the google account to retrieve the token
new GetToken(getActivity().getApplicationContext(), SCOPE, accountName).execute();
showErrorDialog(exception);
}
} else if (requestCode == Activity.RESULT_CANCELED) {
Toast.makeText(getActivity(), "Cancelled!!!", Toast.LENGTH_SHORT).show();
}
}
Below class is used to get the token.
private class GetToken extends AsyncTask<Void, Void, Void> {
Context context;
String mScope, mEmail, token;
GetToken(Context context, String scope, String email) {
this.context = context;
this.mScope = scope;
this.mEmail = email;
}
#Override
protected Void doInBackground(Void... params) {
try {
token = GoogleAuthUtil.getToken(context, mEmail, mScope);
//save the token using preference for later use or do any good stuff using token here
Log.v("ranjapp", "Token is " + token);
} catch (UserRecoverableAuthException e) {
handleException(e);
} catch (GoogleAuthException ex) {
handleException(ex);
} catch (Exception e) {
//display a error dialog
}
return null;
}
void handleException(final Exception e) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
if (e instanceof UserRecoverableAuthException) {
Intent intent = ((UserRecoverableAuthException) e).getIntent();
startActivityForResult(intent, 10098);
} else if (e instanceof GooglePlayServicesAvailabilityException) {
int statusCode = ((GooglePlayServicesAvailabilityException) e)
.getConnectionStatusCode();
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(statusCode, getActivity(), 10099);
dialog.show();
}
}
});
}
}
You have to register your app in google play console to get the token successfully. Also ensure you have play services setup in your app.
To register your Android app with Google Cloud Console:
Visit Google Cloud Console.
If you have an existing project to which you're adding an Android app, select the project. Otherwise, click Create project at the top, enter your project name and ID, then click Create.
Note: The name you provide for the project is the name that appears to users in the Google Settings app in the list of Connected apps.
In the left-side navigation, select APIs & auth.
Enable the API you'd like to use by setting the Status to ON.
In the left-side navigation, select Credentials.
Click Create new client ID or Create new key as appropriate for your app.
Complete the form that appears by filling in your Android app details.
To get the SHA1 fingerprint for your app, run the following command in a terminal:
keytool -exportcert -alias <keystore_alias> -keystore <keystore_path> -list -v
For example, you're using a debug-key with Eclipse, then the command looks like this:
keytool -exportcert -alias androiddebugkey-keystore ~/.android/debug.keystore -list -v
Then the keystore password is "android".
Click Create.
For more information: https://developer.android.com/google/auth/http-auth.html
This library might make it easier for you:
https://github.com/Hafiz-Waleed-Hussain/EasySocial
Additionally you can check the source for the actual implementation.

Google account get Token

I have a problem with kitkat api while tringy to get access token of google account services, google music in my case. So, if user trying get token at first by using next method:
public String getAuthToken(Account account)
throws AuthenticatorException, IOException {
String s1;
if (account == null) {
Log.e("MusicAuthInfo", "Given null account to MusicAuthInfo.getAuthToken()", new Throwable());
throw new AuthenticatorException("Given null account to MusicAuthInfo.getAuthToken()");
}
String s = getAuthTokenType(mContext);
try {
s1 = AccountManager.get(mContext).blockingGetAuthToken(account, s, true);
} catch (OperationCanceledException operationcanceledexception) {
throw new AuthenticatorException(operationcanceledexception);
}
if (s1 == null) {
throw new AuthenticatorException("Received null auth token.");
}
return s1;
}
here i get s1 == null and the system push notification:
When user tap on notification, next dialog appear:
When user click "ok", all next iterations getting token get success.
Question: How to circumvent this confirmation or show just dialog, without click to notification ?
It's not a direct answer to your question, but you can use Google Play Services instead.
String token = GoogleAuthUtil.getToken(context, userEmail, "oauth2:https://mail.google.com/");
You just have to specify the oauth2 scope you need. For instance for Google+ you would need "https://www.googleapis.com/auth/plus.login" instead of what I post in the snippet for Gmail. You can also specify multiple scopes in one token request. The permission request pops up right away.
You can read all about it here: Authorizing with Google for REST APIs, Login scopes
Solved. Need use this method:
Bundle result = AccountManager.get(activity).getAuthToken(account, s, new Bundle(), activity, new AccountManagerCallback<Bundle>() {
#Override
public void run(AccountManagerFuture<Bundle> future) {
try {
Log.e("xxx", future.getResult().toString());
} catch (OperationCanceledException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (AuthenticatorException e) {
e.printStackTrace();
}
}
}, null).getResult();

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;
}
}
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!

Categories

Resources