Can someone please point a link where i can easily understand how to use GCM for device to device notification. What is required to make device as server and receiver.
Let me explain my requirement , you guys might have a better solution to my problem
I want to send a notification(pre defined message) to one or group of people on a button click, the other user can do the same.
Thanks
Let me try to explain the basic working
You need- A server and two mobile phones
Step 1 You have a server that both the phones connect to.
Step 2 You go to Google Developers page at this link
Step 3 You create a new project there and then add a GCM API to your project. Save the sender_id of the project. Also check the credentials of the API and add a new browser key and save that key.
Step 4 Add GCM to your project. For that, goto the root directory where your Eclipse is installed. Then navigate to the folder extras/google/google-play-services/libsproject/ and then copy the folder that is there to another location. Then import that folder into your workspace in Eclipse and change its properties to make it a library. Then add this to your app project.
Step 5 You need to register your mobile with GCM. For that you can work with a sample code like this
// to start process
public void registerDevice() {
// TODO Auto-generated method stub
try {
if (checkPlayServices()) {
regid = getRegistrationId();
if (regid.isEmpty()) { // creating a new reg id
registerInBackground();
} else
saveid();
} else{
}
} catch (Exception e) {
}
}
// to create a new id
private void registerInBackground() {
// TODO Auto-generated method stub
try {
AsyncRegister logMeIn = new AsyncRegister("<you project id here>", context);
logMeIn.doneWith = this;
logMeIn.execute();
System.out.println("execute complete");
} catch (Exception e) {
}
}
// to fetch stored registration id
private String getRegistrationId() {
// TODO Auto-generated method stub
try {
int i=myPrefs.getInt("cuid", 0);
if(id!=i){
myPrefs.edit().putInt("cuid", id).commit();
int registeredVersion = myPrefs.getInt("appversion",
Integer.MIN_VALUE); // to check if app is updated
int currentVersion = getAppVersion();
if (registeredVersion != currentVersion) {
myPrefs.edit().putInt("appversion",currentVersion).commit();
}
return "";
}
String registrationId = myPrefs.getString("regid", "");
if (registrationId.isEmpty()) {
return "";
}
int registeredVersion = myPrefs.getInt("appversion",
Integer.MIN_VALUE); // to check if app is updated
int currentVersion = getAppVersion();
if (registeredVersion != currentVersion) {
myPrefs.edit().putInt("appversion", currentVersion).commit();
return "";
}
// when id is stored previously then this is called
return registrationId;
} catch (Exception e) {
}
}
//to fetch the current app version
private int getAppVersion() throws NameNotFoundException {
// TODO Auto-generated method stub
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
return packageInfo.versionCode;
}
// to check if play services are there on the device
private boolean checkPlayServices() {
// TODO Auto-generated method stub
try {
int resultCode = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(context);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
toSend = new Bundle();
//tell user google play needs to be updated
} else {
//tell user their system does not support GCM
}
myPrefs.edit().putBoolean("support", false).commit();
return false;
}
myPrefs.edit().putBoolean("support", true).commit();
return true;
} catch (Exception e) {
}
}
Step 6 Save the registration ID and pass it to your server to save it.
Step 7 When user clicks the send button, pass a message to your server. The server will pull out the list of ids stored with the DB and will then push the message to GCM along with those ids.
Step 8 Now GCM will send that message to all the phones whose ids were given to it.
Step 9 Add a receiver in your app that receives the text from GCM and then displays it to the user.
This is a brief explanation. If you want to understand this in details please read online tutorials. Else you can also text me on my group (link you will find in my profile description) and i will gladly help you out.
String postData = "{ \"registration_ids\": [ \"" + OtherDeviceToken+ "\" ], " +
"\"delay_while_idle\": true, " +
"\"data\": {\"tickerText\":\"Your title\", "+
"\"contentTitle\":\"AppName\", " +
"\"message\": \" " + edtYourMessage.getText().toString() + "\"}}";
MediaType JSON= MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, postData);
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://android.googleapis.com/gcm/send")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "key=" + SERVER_API_KEY)
.post(body)
.build();
Execute the okkhttp request.
Related
Hello we are working on an android application in which GCM plays very important role in such as marketing purpose, push some important information to users etc.
It's working fine in 60-70% cases but other 30-40% it does not work. So rest of users never receive any notification which is useful for only to them.
This is the reason we are loosing users everyday. Below is my code to get the registration ID of GCM.
String msg = "";
int exceptionOccurRetry = 0;
while (exceptionOccurRetry < 5) {
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
int retry = 0;
while (retry < 5 && regid.length() == 0) {
regid = gcm.register(SENDER_ID);
++retry;
}
msg = "Device registered, registration ID=" + regid;
if (!regid.equals("")) {
// You should send the registration ID to your
// server
// over HTTP, so it
// can use GCM/HTTP or CCS to send messages to your
// app.
sendRegistrationIdToBackend();
}
break;
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
exceptionOccurRetry++;
}
}
We are looking what are the reasons such that GCM id is not available for some users.
We know only one reason that If user device doesn't have a Google Play Services installed on user phone then it does not work.
We are looking some more reasons to solve this problem.
One of the most common reasons why it is not available is that the user does not have google play services installed or is using a blocker.
You should also note that the GCM id should be refreshed if your application version has changed. You should be saving a unique device id to SharedPreferences and always check if it is the same, otherwise you should initiate the registration process again.
It is also a good idea to refresh the id from time to time.
Our team members are trying to send the notification, if it fails, they wait about 60 miliseconds or seconds (i'm not sure) for this push notification to be send again, if it still does not work, they wait twice the time, and so on ...
And you have to evaluate the response from google, there is a error string under:
std::string error = response["results"][0]["error"].asString();
Which gives you the information if a users account has been moved to, if so you can use:
Json::Value newRegistrationId = response["results"][0]["registration_id"];
to get the new ID.
if gcm id is null try to start a background task and get the id.check the below code
private void registerInBackground() {
new AsyncTask() {
#Override
protected String doInBackground(Object[] params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + regid;
sendRegistrationIdToBackend();
// Persist the regID - no need to register again.
storeRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
}
return msg;
}
#Override
protected void onPostExecute(Object msg) {
// mDisplay.append(msg + "\n");
}
}.execute(null, null, null);
}
Check if app was updated; if so, it must clear the registration ID since the existing registration ID is not guaranteed to work with the new app version.
When the application version changes, the registration id should change too. so you should save the last registration id to backend size. Maybe your problem is this.
If your question all focus on the id registration you can ignore my answer..
Sorry my answer may be the wrong answer to your question.
I just want to show there may be some other 'factor' influence the message delivery..
Do your send all message on only single recipients?
One of the most useful features in GCM is support for up to 1,000
recipients for a single message.
http://developer.android.com/training/cloudsync/gcm.html#
Sometimes our PHP also lose message sending to the registered device...
The message must less than 4K(do your GCM contains Pictures?)
You may have already read this..
Implementing GCM Client on Android
http://developer.android.com/google/gcm/client.html
Is that piece of code inside an asyncTask if not that might be your error in certain version (dont remember which) gcm registration gives NetworkOnMainThreadException for some reason, they updated that later but I had that same problem some time ago, this is the piece of code I have used hope it helps you out:
private void performRegisterGCM(){
//Check for GCM availability
if(checkPlayServices(this)){
// If this check succeeds, proceed with normal processing.
// Otherwise, prompt user to get valid Play Services APK.
gcm = GoogleCloudMessaging.getInstance(this);
String regId = mPreferences.getGcmRegistrationId();
if (regId.isEmpty()){
RegisterGCM();
}else{
log.d("regId: "+regId);
}
} else {
// Status is a random integer
GooglePlayServicesUtil.getErrorDialog(status, this, RQS_GooglePlayServices).show();
}
}
public static boolean checkPlayServices(Activity mActivity){
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mActivity);
return resultCode == ConnectionResult.SUCCESS
}
private void RegisterGCM(){
new AsyncTask<Void,Void,String>() {
#Override
protected String doInBackground(Void... params) {
String regid = "";
try{
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(mContext);
}
regid = gcm.register(Util.SENDER_ID);
}catch(Exception e){
log.e(e.getMessage());
}
return regid;
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
mPreferences.setGcmRegistrationId(s);
//TODO send regid to server with all the other info
sendGCMIDtoBackend(s);
}
}.execute(null, null, null);
}
Note that the checkPlayServices also gave me a lot of problems I had it like this:
public static boolean checkPlayServices(Activity mActivity){
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mActivity);
if(resultCode!= ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)){
GooglePlayServicesUtil.getErrorDialog(
resultCode,
mActivity,
PLAY_SERVICES_RESOLUTION_REQUEST
).show();
} else{
log.d("DEVICE NOT SUPPORTED");
exit(true);
}
return false;
}
return true;
}
Then changed it as you can see in the first piece of code, because for some reason when it falls in isUserRecoverableError(result) it gives a lot of headaches... Everything here is from an actual working project and the code snippets were obtained in http://developer.android.com/google/gcm/client.html and modified to work correctly. Hope this helps you out, Good Luck...
How about the backend? If you delete the ID from the server's database, the user will never receive a notification unless you update the app version?
Im triying to authenticate with google, I´m currently using the way i´t is recomended on his documentation, but.. is there any EASY way to get the refresh token?, I make the auth and get the token, but it have been impossible for me to take the refresh token , and I need id.
I have tried lots of ways, I have spend more than a week with this issue, is it possible to get that token? I´ve tried with lots of manuals, tutorials... but I can´t.
Anyone Knows any place where I can Know how to get the resfresh_token and it is good explained and that is currently working?.
Thanks a lot!!
Pd: is a native android App.
EDIT:
Ok, for More info, I´m making the auth as is in google´s documentation to auth with GoogleApiClient with little variations( because I´m using it as a cain of manager) . THIS PART RUN´S OK:
Firs instead of calling on create I call:
public void logginGooglePlus(GooglePlusAuthCallback googlePlusAuthCallback) {
gPAuthCallback = googlePlusAuthCallback;
// Initializing google plus api client
String scope = "audience:server:client_id:" + SERVER_CLIENT_ID;
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).addApi(Plus.API)
.addScope(Plus.SCOPE_PLUS_LOGIN).build();
mGoogleApiClient.connect();
mSignInClicked = true;
signInWithGplus(gPAuthCallback);
}
I continue just with copy&paste with the google´s:
#Override
public void onConnectionFailed(ConnectionResult result) {
if (!result.hasResolution()) {
// GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),
// this,
// 0).show();
if (gPAuthCallback != null) {
gPAuthCallback.onLoginError(result.toString());
}
return;
}
if (!mIntentInProgress) {
// Store the ConnectionResult for later usage
mConnectionResult = result;
if (mSignInClicked) {
// The user has already clicked 'sign-in' so we attempt to
// resolve all
// errors until the user is signed in, or they cancel.
resolveSignInError();
}
}
}
#Override
public void onActivityResult(int requestCode, int responseCode,
Intent intent) {
super.onActivityResult(requestCode, responseCode, intent);
if (requestCode == RC_SIGN_IN) {
if (responseCode != RESULT_OK) {
mSignInClicked = false;
}
mIntentInProgress = false;
if (!mGoogleApiClient.isConnecting()) {
mGoogleApiClient.connect();
}
}
}
#Override
public void onConnected(Bundle arg0) {
mSignInClicked = false;
// Get user's information
if (gPAuthCallback != null) {
gPAuthCallback.onLoginSuccesful();
}
}
#Override
public void onConnectionSuspended(int arg0) {
mGoogleApiClient.connect();
}
/**
* Sign-in into google
* */
public void signInWithGplus(GooglePlusAuthCallback googlePlusAuthCallback) {
gPAuthCallback = googlePlusAuthCallback;
if (!mGoogleApiClient.isConnecting()) {
mSignInClicked = true;
resolveSignInError();
}
}
/**
* Method to resolve any signin errors
* */
private void resolveSignInError() {
if (mConnectionResult.hasResolution()) {
try {
mIntentInProgress = true;
mConnectionResult.startResolutionForResult(this, RC_SIGN_IN);
} catch (SendIntentException e) {
mIntentInProgress = false;
mGoogleApiClient.connect();
}
}
}
And finally I call to get the persons data:
public void getProfileInformation(
GooglePlusGetPersonCallback getPersonCallback) {
this.googlePlusGetPersonCallback = getPersonCallback;
try {
if (Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null) {
currentPerson = Plus.PeopleApi
.getCurrentPerson(mGoogleApiClient);
String personName = currentPerson.getDisplayName();
String personPhotoUrl = currentPerson.getImage().getUrl();
String personGooglePlusProfile = currentPerson.getUrl();
String email = Plus.AccountApi.getAccountName(mGoogleApiClient);
Log.e("GPlus", "Name: " + personName + ", plusProfile: "
+ personGooglePlusProfile + ", email: " + email
+ ", Image: " + personPhotoUrl);
new getTokenAsyncTask().execute();
}
} catch (Exception e) {
if (googlePlusGetPersonCallback != null) {
// googlePlusGetPersonCallback.ongeGooglePersonError(e.getCause()
// .toString());
}
e.printStackTrace();
}
}
Ok, leaving here is easy, now It starts the funny part: I need the Refresh Token because I have to sign in with a server, and I have to pass the access_token, refresh_token and user_id.
reading this: https://developers.google.com/accounts/docs/CrossClientAuth
I understand that I have to make the getToken call with a different Scope, so I change it: the method for get token is :
// GET TOKEN 2o plano
public class getTokenAsyncTask extends AsyncTask<Void, Boolean, String> {
#Override
protected String doInBackground(Void... params) {
try {
String acountname = Plus.AccountApi
.getAccountName(mGoogleApiClient);
// agregamos el scope del server para que me loguee para la app
// "crossclient"
String serverScope = "audience:server:client_id:"
+ SERVER_CLIENT_ID;
String token = GoogleAuthUtil.getToken(GooglePlusManager.this,
acountname, serverScope);
return token;
} catch (UserRecoverableAuthException e) {
// startActivityForResult(e.getIntent(), "NECESITA AUT");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace(); // TODO: handle the exception
}
return null;
}
#Override
protected void onPostExecute(String code) {
String token = code;
if (googlePlusGetPersonCallback != null) {
googlePlusGetPersonCallback.ongeGooglePersonSuccesful(
currentPerson, token);
}
}
}
According to documentation, with this I´ll get a token that: "The ID token will contain several data fields", and I´m only retrieving a string token ( but it does not give any crash or issue so I suppose it is Ok). I haven´t got access to the Server, but I´ve suppose that it is ok, because the ios app is running ok already ( another company have done it in Ios), do I have to ask them to make in the server any thing so I can authenticate my android app with the server?
The ios app is passing to the server the parameter I´ve already said (acces, refres, id) So I Imagine that I have to pass the same in android, I have acces to the console and I have declared the android app in the same project.
well, from the part I am, that I have an supposed valid token.. how can I get the refresh token? I´m completely lost...
If anyone knows how to get it.. I´ll invite as much beer as you can ( I have lost so many hours with this :S ).
xcuses for the really really big post :( ( it´s my first one!).
According to the google documentation you can exchange tokens.
So, if you post required parameters to below link, then you will obtain a refresh token
https://www.googleapis.com/oauth2/v3/token
Parameters,
var params = {
code: 'FROM ANDROID [ONE TIME CODE]',
client_id: 'FROM YOUR GOOGLE CONSOLE',
client_secret: 'FROM YOUR GOOGLE CONSOLE',
redirect_uri: 'FROM YOUR GOOGLE CONSOLE',
grant_type: 'authorization_code'
access_type:'offline'
};
The only thing you must send from android is one-time code. Other parameters are static, store they in a config file.
var params = {
code: 'FROM ANDROID [ONE TIME CODE]',
Android side, Enable server-side API access for your app, after implementing this, you will have an one-time code
I hope these will be helpful for you.
I,m developing GCM app and recieve "error:AUTHENTICATION_FAILED". I,m using my samsung tab device.My code is below:
private void registerInBackground() {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
Log.i(TAG, "11111");
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
Log.i(TAG, "11dfsfsd111");
}
Log.i(TAG, "11dfsfsd111fsdfsdf");
regid = gcm.register(SENDER_ID);
Log.i(TAG, "id = :"+regid);
Log.i(TAG, "2222");
msg = "Device registered, registration ID=" + regid;
// You should send the registration ID to your server over HTTP, so it
// can use GCM/HTTP or CCS to send messages to your app.
// sendRegistrationIdToBackend();
// For this demo: we don't need to send it because the device will send
// upstream messages to a server that echo back the message using the
// 'from' address in the message.
// Persist the regID - no need to register again.
// storeRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}
#Override
protected void onPostExecute(String msg) {
Log.i(TAG, "sdfdsfs:" + msg);
mDisplay.append(msg + "\n");
}
}.execute(null, null, null);
}
i Get this msg when the control comes in postExecute function and print the "msg" variable. I searched for the error and found that this error come due to wrong password of gmail sync acount, But i checked it and my password is correct. Kindly help
public void registerClient() {
try {
// Check that the device supports GCM (should be in a try / catch)
GCMRegistrar.checkDevice(viewLogin);
// Check the manifest to be sure this app has all the required
// permissions.
GCMRegistrar.checkManifest(viewLogin);
// Get the existing registration id, if it exists.
regId = GCMRegistrar.getRegistrationId(viewLogin);
if (regId.equals("")) {
registrationStatus = "Registering...";
// register this device for this project
GCMRegistrar.register(viewLogin, GCMIntentService.PROJECT_ID);
regId = GCMRegistrar.getRegistrationId(viewLogin);
registrationStatus = "Registration Acquired";
// This is actually a dummy function. At this point, one
// would send the registration id, and other identifying
// information to your server, which should save the id
// for use when broadcasting messages.
} else {
registrationStatus = "Already registered";
}
Log.d(TAG, regId);
sendRegistrationToServer();
} catch (Exception e) {
e.printStackTrace();
registrationStatus = e.getMessage();
}
Log.d(TAG, registrationStatus);
// This is part of our CHEAT. For this demo, you'll need to
// capture this registration id so it can be used in our demo web
// service.
}
please use this..its working in my project.
add in your manifestfile:
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
I think you are using old GCM implementation. New GCM implementation code is available on developer site(http://developer.android.com/google/gcm/client.html). Account authentication and other type of authentication is properly handled here
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!
Is there any example available on how to use RESTORE_TRANSACTIONS request to restore an In-app product purchase information? I came up with this code, but it always returns 0, so it doesn't recognize if the product is purchased or not: Everything is set up correctly.
Bundle request = BillingHelper.makeRequestBundle("RESTORE_TRANSACTIONS");
request.putLong("NONCE", 32436756l);
try
{
Bundle response = BillingHelper.mService.sendBillingRequest(request);
int response_code = response.getInt("RESPONSE_CODE", -1);
if (response_code == 0)
{
// Product purchased
}
}
catch (RemoteException e)
{
e.printStackTrace();
}
I found no examples on google and in the documentation, so any guidance would be great.
I used this method:
public static void restoreTransactionInformation(Long nonce)
{
if (amIDead())
{
return;
}
Log.i(TAG, "confirmTransaction()");
Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
// The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate
request.putLong("NONCE", nonce);
try
{
Bundle response = mService.sendBillingRequest(request);
//The REQUEST_ID key provides you with a unique request identifier for the request
Long requestIndentifier = (Long) response.get("REQUEST_ID");
Log.i(TAG, "current request is:" + requestIndentifier);
//The RESPONSE_CODE key provides you with the status of the request
Integer responseCodeIndex = (Integer) response.get("RESPONSE_CODE");
C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
Log.i(TAG, "RESTORE_TRANSACTIONS Sync Response code: "+responseCode.toString());
}
catch (RemoteException e)
{
Log.e(TAG, "Failed, internet error maybe", e);
Log.e(TAG, "Billing supported: " + isBillingSupported());
}
}
, The call is as follows:
BillingHelper.restoreTransactionInformation(BillingSecurity.generateNonce());