Handle GoogleFit in background in Android App - android

I am trying to connect my app to Google Fit. I am using an IntentService that needs to do the following things. Gets started when I have information about steps. At this point I am trying to create the GoogleApiClient by calling the following code:
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.HISTORY_API)
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
.addScope(new Scope(Scopes.FITNESS_LOCATION_READ_WRITE))
.addConnectionCallbacks(
new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
log.info("FITNESS_API: Connected!!!");
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
insertOrUpdateDataPoints();
}
});
thread.start();
}
#Override
public void onConnectionSuspended(int i) {
// If your connection to the sensor gets lost at some point,
// you'll be able to determine the reason and react to it here.
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
log.info("FITNESS_API: Connection lost. Cause: Network Lost.");
} else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
log.info("FITNESS_API: Connection lost. Reason: Service Disconnected");
}
}
}
).build();
mClient.connect();
After creating a DataSet and adding the steps details as DataPoint elemnets, I sync the information to Google Fit and close the GoogleApiClient with:
com.google.android.gms.common.api.Status insertStatus =
Fitness.HistoryApi.insertData(mClient, dataSet).await(1, TimeUnit.MINUTES);
// Before querying the data, check to see if the insertion succeeded.
if (!insertStatus.isSuccess()) {
log.info("FITNESS_API: There was a problem inserting the dataset. Status = " + insertStatus.getStatusCode());
}
mClient.disconnect();
mClient = null;
The problem is that by trying to manage the GoogleApiClient on my own (without enableAutoManage), I don't get prompted to allow the app to post data to Google Fit. This behaviour changes if I use enableAutoManage when creating the GoogleApiClient. However, in order to enableAutoManage for the client, I need to have a ActivityFragment due to the parameters required by enableAutoManage. I don't have access to an ActivityFragment in the IntentyService and I do want to keep the management of the client and the insert action in a separate service which can run in the background.
Also when I don't use enableAutoManage even though I have registered the connect callback for the GoogleApiClient nothing happens.
How can I ensure that my application prompts the user to allow the app to post to Google Fit? I need this to happen if the app doesn't have permission to post in Google Fit when the user opens the app. Any ideas? Thank you.

I have found the solution.
If you don't want to use "enableAutoManage", you need to register onConnectionFailed method like this:
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if( !authInProgress ) {
try {
authInProgress = true;
connectionResult.startResolutionForResult( MainActivity.this, REQUEST_OAUTH );
} catch(IntentSender.SendIntentException e ) {
}
} else {
Log.e( "GoogleFit", "authInProgress" );
}
}
This will present the dialog.

In your intent service use the above method mentioned by #Vlad. Create a notification(Sticky or otherwise depending on your importance) asking user to give you permission when onconnection failed is encountered. The notification will redirect user to an activity where you ask user to give you fitaccess again.

#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_OAUTH:
if (resultCode != Activity.RESULT_OK) {
return;
}
if (!googleApiClient.isConnected()) {
googleApiClient.connect(GoogleApiClient.SIGN_IN_MODE_OPTIONAL);
} else {
readDataTask();
}
return;
case RC_SIGN_IN:
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (result.isSuccess()) {
GoogleSignInAccount account = result.getSignInAccount();
String email = account.getEmail();//you can get OAuth user's email AT THIS POINT.
if (GoogleFitService.googleApiClient.isConnected()) {
readDataTask();
}
}
}
}
First time what you have to DO is Oauth Goole accout, then get your's email.
gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.setAccountName("user's email")
.requestScopes(
new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE),
new Scope(Scopes.FITNESS_BODY_READ_WRITE),
new Scope(Scopes.FITNESS_NUTRITION_READ_WRITE),
new Scope(Scopes.FITNESS_LOCATION_READ_WRITE))
.build();
when you get Google Fit data at background, you need to set Email.

Related

Request timeout in Google Fit - Android wear 2.0

I have a problem with getting google fit data on Android Wear 2.0.
My requests are getting TIMEOUT response. If the await() method has no parameters, there is no response (the await() method did not returned). Any clues whats wrong?
App uses Google Sign-In, and everything works on regular Android device.
Creating of GoogleApiClient and SignInAccount
GoogleSignInOptions signInConfig = new GoogleSignInOptions
.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestScopes(new Scope(Scopes.FITNESS_LOCATION_READ),new Scope(Scopes.FITNESS_ACTIVITY_READ))
.build();
client = new GoogleApiClient.Builder(this)
.enableAutoManage(this,this)
.addApi(Auth.GOOGLE_SIGN_IN_API, signInConfig)
.addApi(Fitness.HISTORY_API)
.addApi(Fitness.GOALS_API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
After login procedure is done I run:
new Thread(new Runnable() {
#Override
public void run() {
PendingResult<DailyTotalResult> result =
Fitness.HistoryApi.readDailyTotal(client, TYPE_STEP_COUNT_DELTA);
DailyTotalResult totalResult = result.await(60,TimeUnit.SECONDS);
if (totalResult.getStatus().isSuccess()) {
DataSet totalSet = totalResult.getTotal();
long total = totalSet.isEmpty()? 0 : totalSet.getDataPoints().get(0).getValue(FIELD_STEPS).asInt();
p("daily steps " + total);
}}).start();
}
You may want to check the correct process in inserting data wherein it was discussed that to insert data into the fitness history, first create a DataSet instance before using the HistoryApi.insertData method and wait synchronously or provide a callback method to check the status of the insertion.
For a more detailed information and sample codes, you may want to check the full documentation.
Similar problem was asked here at G+ GoogleFitDevelopersGroup. Thanks to PujieWear we managed to solve the problem. You have to use two different GoogleApiClient's, one to get authenticated, second to get the data.
I'm no sure wheather this is proper way of using Google Sign-In, but it works.
//Yet, it seems that the Scopes are not yet resolved properly on Wear 2.0.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult signInResult = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (signInResult.isSuccess()) {
acct = signInResult.getSignInAccount();
editor.putString(ACCOUNT_NAME_PREFS,acct.getEmail());
editor.commit();
dataGoogleApiClientBuilder.setAccountName(acct.getEmail());
dataGoogleApiClient = dataGoogleApiClientBuilder.build();
dataGoogleApiClient.connect();
[...]

Android Smart Lock setResultCallback is not getting called

I have been following https://developers.google.com/identity/smartlock-passwords/android/retrieve-credentials to try to automatically sign in a user if they have saved their credentials to the new Android Smart Lock feature in chrome. I have followed the guide exactly, but my callback that I pass into setResultCallback() is not getting called. Has anyone run into this problem before?
There is no error message or anything, it just doesn't get called.
The problem is likely that the Google API client is not connected, try calling connect() in the onStart() method of your activity, or if you are using a recent version of Play Services, we added automatic management of the API client to make this easier, really simplifying things and avoiding common problems.
Just call enableAutoManage() when building the GoogleApiClient:
// "this" is a reference to your activity
mCredentialsApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.enableAutoManage(this, this)
.addApi(Auth.CREDENTIALS_API)
.build();
Then you can make an API request without having to call mCredentialsApiClient.onConnect() at any point, the Google API client's lifecycle will be managed automatically for you. e.g.
#Override
public void onStart() {
CredentialRequest request = new CredentialRequest.Builder()
.setSupportsPasswordLogin(true)
.build();
Auth.CredentialsApi.request(mCredentialsApiClient, request).setResultCallback(
new ResultCallback<CredentialRequestResult>() {
public void onResult(CredentialRequestResult result) {
// result.getStatus(), result.getCredential() ... sign in automatically!
...
Check out a full sample app at on Github: https://github.com/googlesamples/android-credentials/blob/master/credentials-quickstart/app/src/main/java/com/google/example/credentialsbasic/MainActivity.java
I tired the official demo app here, and it worked.
Basically, the setResultCallback() will be get called when save, request and delete
For save:
Auth.CredentialsApi.save(mCredentialsApiClient, credential).setResultCallback(
new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.d(TAG, "SAVE: OK");
showToast("Credential Saved");
hideProgress();
} else {
resolveResult(status, RC_SAVE);
}
}
});
For request:
Auth.CredentialsApi.request(mCredentialsApiClient, request).setResultCallback(
new ResultCallback<CredentialRequestResult>() {
#Override
public void onResult(CredentialRequestResult credentialRequestResult) {
if (credentialRequestResult.getStatus().isSuccess()) {
// Successfully read the credential without any user interaction, this
// means there was only a single credential and the user has auto
// sign-in enabled.
processRetrievedCredential(credentialRequestResult.getCredential(), false);
hideProgress();
} else {
// Reading the credential requires a resolution, which means the user
// may be asked to pick among multiple credentials if they exist.
Status status = credentialRequestResult.getStatus();
if (status.getStatusCode() == CommonStatusCodes.SIGN_IN_REQUIRED) {
// This is a "hint" credential, which will have an ID but not
// a password. This can be used to populate the username/email
// field of a sign-up form or to initialize other services.
resolveResult(status, RC_HINT);
} else {
// This is most likely the case where the user has multiple saved
// credentials and needs to pick one
resolveResult(status, RC_READ);
}
}
}
});
For delete:
Auth.CredentialsApi.delete(mCredentialsApiClient, mCurrentCredential).setResultCallback(
new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
hideProgress();
if (status.isSuccess()) {
// Credential delete succeeded, disable the delete button because we
// cannot delete the same credential twice.
showToast("Credential Delete Success");
findViewById(R.id.button_delete_loaded_credential).setEnabled(false);
mCurrentCredential = null;
} else {
// Credential deletion either failed or was cancelled, this operation
// never gives a 'resolution' so we can display the failure message
// immediately.
Log.e(TAG, "Credential Delete: NOT OK");
showToast("Credential Delete Failed");
}
}
});
Also you can clone the project in my github here, set the SHA1 in your console here.
At this point you should be ready to go :)

GoogleApiClient Node is null

I'm writing an Android Wear app and I want to make the wear device start an intent on the phone when a button is clicked.
To do this, I've first set up the connection to the GoogleApiClient like this
googleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
googleApiClient.connect();
Then on button click the function sendMessage is run. This is the function
private void sendMessage(){
if(mNode != null && googleApiClient != null && googleApiClient.isConnected()){
Wearable.MessageApi.sendMessage(
googleApiClient, mNode.getId(),HELLO_WORLD_WEAR_PATH, null).setResultCallback(
new ResultCallback<MessageApi.SendMessageResult>() {
#Override
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
if(!sendMessageResult.getStatus().isSuccess()){
Log.e("TAG", "Failed to send message with status code: " + sendMessageResult.getStatus().getStatusCode());
}
else{
Log.e("TAG", "Success");
}
}
}
);
}
else {
Log.e("TAG","No nodes" + mNode);
}
}
The problem is that the function goes into the else statement of sendMessage which means mNode is null. mNode is a variable of type Node.
To get the node, I have this function
private void resolveNode() {
Wearable.NodeApi.getConnectedNodes(googleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
#Override
public void onResult(NodeApi.GetConnectedNodesResult nodes) {
for (Node node : nodes.getNodes()) {
mNode = node;
}
}
});
}
I've connected my phone with the Android Wear Virtual Device in Android Studio.
What am I doing wrong here?
When you call googleApiClient.connect(), the connection takes a short amount of time and this call does not block. So you need to wait until the connection succeeds with a call to onConnected() before you do any further calls.
Next, when you call resolveNode(), the mNode variable is not set instantly. The setResultCallback is executed a short time later, and so you should not call sendMessage() until the result is processed.
So you need to ensure proper ordering for everything here.
(Edit)
After going through the comments, it turns out the problem was caused by the connection to Google Play Services failing. This was because the emulator used was an old API 20 device, which has an old version of Google Play Services. Using the latest API 22 emulator will resolve the issue.
try with this to get Node
new Thread(new Runnable() {
#Override
public void run() {
NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
for (Node node : nodes.getNodes()){
MessageApi.SendMessageResult resultMessage = Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), ID_PATH, BYTES HERE).await();
if (resultMessage.getRequestId() == MessageApi.UNKNOWN_REQUEST_ID) {
Log.e("Message failed", ".......");
}
}
}
}).start();

Google Plus login demo keeps failing connection

I downloaded and imported the gplus-quickstart demo project from https://github.com/googleplus/gplus-quickstart-android
and I am unable to login with my g+ account, I keep getting the error "Google Play services error could not be resolved: 17"
I made no changes to the project itself other than adding two permissions
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
I figured these permissions may be needed and were just forgotten. The other steps I took were the following,
1) I got the SHA fingerprint from console
keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
2) I then enabled google+ API from the developers console and API section
3) I then created a new client ID from the credentials section on the developers console. I filled out the information, making sure to select "Installed application" as the application type and inputting the same package name as the demo package name, as well as the SHA I got from console.
After these steps, I ran the application and when trying to login I receive in my logs the following:
Google Play services error could not be resolved: 17
What am I doing incorrectly? Is there something in the gradle files I should fix? Do I need to make some further updates and changes? I will post my class below, maybe I have old version or something.
public class MainActivity extends FragmentActivity implements
ConnectionCallbacks, OnConnectionFailedListener,
ResultCallback<LoadPeopleResult>, View.OnClickListener,
CheckBox.OnCheckedChangeListener, GoogleApiClient.ServerAuthCodeCallbacks {
private static final String TAG = "android-plus-quickstart";
private static final int STATE_DEFAULT = 0;
private static final int STATE_SIGN_IN = 1;
private static final int STATE_IN_PROGRESS = 2;
private static final int RC_SIGN_IN = 0;
private static final String SAVED_PROGRESS = "sign_in_progress";
// Client ID for a web server that will receive the auth code and exchange it for a
// refresh token if offline access is requested.
private static final String WEB_CLIENT_ID = "WEB_CLIENT_ID";
// Base URL for your token exchange server, no trailing slash.
private static final String SERVER_BASE_URL = "SERVER_BASE_URL";
// URL where the client should GET the scopes that the server would like granted
// before asking for a serverAuthCode
private static final String EXCHANGE_TOKEN_URL = SERVER_BASE_URL + "/exchangetoken";
// URL where the client should POST the serverAuthCode so that the server can exchange
// it for a refresh token,
private static final String SELECT_SCOPES_URL = SERVER_BASE_URL + "/selectscopes";
// GoogleApiClient wraps our service connection to Google Play services and
// provides access to the users sign in state and Google's APIs.
private GoogleApiClient mGoogleApiClient;
// We use mSignInProgress to track whether user has clicked sign in.
// mSignInProgress can be one of three values:
//
// STATE_DEFAULT: The default state of the application before the user
// has clicked 'sign in', or after they have clicked
// 'sign out'. In this state we will not attempt to
// resolve sign in errors and so will display our
// Activity in a signed out state.
// STATE_SIGN_IN: This state indicates that the user has clicked 'sign
// in', so resolve successive errors preventing sign in
// until the user has successfully authorized an account
// for our app.
// STATE_IN_PROGRESS: This state indicates that we have started an intent to
// resolve an error, and so we should not start further
// intents until the current intent completes.
private int mSignInProgress;
// Used to store the PendingIntent most recently returned by Google Play
// services until the user clicks 'sign in'.
private PendingIntent mSignInIntent;
// Used to store the error code most recently returned by Google Play services
// until the user clicks 'sign in'.
private int mSignInError;
// Used to determine if we should ask for a server auth code when connecting the
// GoogleApiClient. False by default so that this sample can be used without configuring
// a WEB_CLIENT_ID and SERVER_BASE_URL.
private boolean mRequestServerAuthCode = false;
// Used to mock the state of a server that would receive an auth code to exchange
// for a refresh token, If true, the client will assume that the server has the
// permissions it wants and will not send an auth code on sign in. If false,
// the client will request offline access on sign in and send and new auth code
// to the server. True by default because this sample does not implement a server
// so there would be nowhere to send the code.
private boolean mServerHasToken = true;
private SignInButton mSignInButton;
private Button mSignOutButton;
private Button mRevokeButton;
private TextView mStatus;
private ListView mCirclesListView;
private ArrayAdapter<String> mCirclesAdapter;
private ArrayList<String> mCirclesList;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
mSignInButton = (SignInButton) findViewById(R.id.sign_in_button);
mSignOutButton = (Button) findViewById(R.id.sign_out_button);
mRevokeButton = (Button) findViewById(R.id.revoke_access_button);
mStatus = (TextView) findViewById(R.id.sign_in_status);
mCirclesListView = (ListView) findViewById(R.id.circles_list);
// Button listeners
mSignInButton.setOnClickListener(this);
mSignOutButton.setOnClickListener(this);
mRevokeButton.setOnClickListener(this);
// CheckBox listeners
((CheckBox) findViewById(R.id.request_auth_code_checkbox)).setOnCheckedChangeListener(this);
((CheckBox) findViewById(R.id.has_token_checkbox)).setOnCheckedChangeListener(this);
mCirclesList = new ArrayList<String>();
mCirclesAdapter = new ArrayAdapter<String>(
this, R.layout.circle_member, mCirclesList);
mCirclesListView.setAdapter(mCirclesAdapter);
if (savedInstanceState != null) {
mSignInProgress = savedInstanceState
.getInt(SAVED_PROGRESS, STATE_DEFAULT);
}
mGoogleApiClient = buildGoogleApiClient();
}
private GoogleApiClient buildGoogleApiClient() {
// When we build the GoogleApiClient we specify where connected and
// connection failed callbacks should be returned, which Google APIs our
// app uses and which OAuth 2.0 scopes our app requests.
GoogleApiClient.Builder builder = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API, Plus.PlusOptions.builder().build())
.addScope(Plus.SCOPE_PLUS_LOGIN);
if (mRequestServerAuthCode) {
checkServerAuthConfiguration();
builder = builder.requestServerAuthCode(WEB_CLIENT_ID, this);
}
return builder.build();
}
#Override
protected void onStart() {
super.onStart();
mGoogleApiClient.connect();
}
#Override
protected void onStop() {
super.onStop();
if (mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SAVED_PROGRESS, mSignInProgress);
}
#Override
public void onClick(View v) {
if (!mGoogleApiClient.isConnecting()) {
// We only process button clicks when GoogleApiClient is not transitioning
// between connected and not connected.
switch (v.getId()) {
case R.id.sign_in_button:
mStatus.setText(R.string.status_signing_in);
mSignInProgress = STATE_SIGN_IN;
mGoogleApiClient.connect();
break;
case R.id.sign_out_button:
// We clear the default account on sign out so that Google Play
// services will not return an onConnected callback without user
// interaction.
if (mGoogleApiClient.isConnected()) {
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
mGoogleApiClient.disconnect();
}
onSignedOut();
break;
case R.id.revoke_access_button:
// After we revoke permissions for the user with a GoogleApiClient
// instance, we must discard it and create a new one.
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
// Our sample has caches no user data from Google+, however we
// would normally register a callback on revokeAccessAndDisconnect
// to delete user data so that we comply with Google developer
// policies.
Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient);
mGoogleApiClient = buildGoogleApiClient();
mGoogleApiClient.connect();
break;
}
}
}
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
switch (buttonView.getId()) {
case R.id.request_auth_code_checkbox:
mRequestServerAuthCode = isChecked;
buildGoogleApiClient();
if (isChecked) {
findViewById(R.id.layout_has_token).setVisibility(View.VISIBLE);
} else {
findViewById(R.id.layout_has_token).setVisibility(View.INVISIBLE);
}
break;
case R.id.has_token_checkbox:
mServerHasToken = isChecked;
break;
}
}
/* onConnected is called when our Activity successfully connects to Google
* Play services. onConnected indicates that an account was selected on the
* device, that the selected account has granted any requested permissions to
* our app and that we were able to establish a service connection to Google
* Play services.
*/
#Override
public void onConnected(Bundle connectionHint) {
// Reaching onConnected means we consider the user signed in.
Log.i(TAG, "onConnected");
// Update the user interface to reflect that the user is signed in.
mSignInButton.setEnabled(false);
mSignOutButton.setEnabled(true);
mRevokeButton.setEnabled(true);
// Hide the sign-in options, they no longer apply
findViewById(R.id.layout_server_auth).setVisibility(View.GONE);
// Retrieve some profile information to personalize our app for the user.
Person currentUser = Plus.PeopleApi.getCurrentPerson(mGoogleApiClient);
mStatus.setText(String.format(
getResources().getString(R.string.signed_in_as),
currentUser.getDisplayName()));
Plus.PeopleApi.loadVisible(mGoogleApiClient, null)
.setResultCallback(this);
// Indicate that the sign in process is complete.
mSignInProgress = STATE_DEFAULT;
}
/* onConnectionFailed is called when our Activity could not connect to Google
* Play services. onConnectionFailed indicates that the user needs to select
* an account, grant permissions or resolve an error in order to sign in.
*/
#Override
public void onConnectionFailed(ConnectionResult result) {
// Refer to the javadoc for ConnectionResult to see what error codes might
// be returned in onConnectionFailed.
Log.i(TAG, "onConnectionFailed: ConnectionResult.getErrorCode() = "
+ result.getErrorCode());
Log.e(TAG, "result: " + result);
if (result.getErrorCode() == ConnectionResult.API_UNAVAILABLE) {
// An API requested for GoogleApiClient is not available. The device's current
// configuration might not be supported with the requested API or a required component
// may not be installed, such as the Android Wear application. You may need to use a
// second GoogleApiClient to manage the application's optional APIs.
Log.w(TAG, "API Unavailable.");
} else if (mSignInProgress != STATE_IN_PROGRESS) {
// We do not have an intent in progress so we should store the latest
// error resolution intent for use when the sign in button is clicked.
mSignInIntent = result.getResolution();
mSignInError = result.getErrorCode();
Log.d(TAG, "mSignInIntent(1): " + mSignInIntent);
Log.v(TAG, "mSignInProgress == : " + mSignInProgress);
if (mSignInProgress == STATE_SIGN_IN) {
// STATE_SIGN_IN indicates the user already clicked the sign in button
// so we should continue processing errors until the user is signed in
// or they click cancel.
resolveSignInError();
}
}
// In this sample we consider the user signed out whenever they do not have
// a connection to Google Play services.
onSignedOut();
}
/* Starts an appropriate intent or dialog for user interaction to resolve
* the current error preventing the user from being signed in. This could
* be a dialog allowing the user to select an account, an activity allowing
* the user to consent to the permissions being requested by your app, a
* setting to enable device networking, etc.
*/
private void resolveSignInError() {
Log.d(TAG, "mSignInIntent(2): " + mSignInIntent);
if (mSignInIntent != null) {
// We have an intent which will allow our user to sign in or
// resolve an error. For example if the user needs to
// select an account to sign in with, or if they need to consent
// to the permissions your app is requesting.
try {
// Send the pending intent that we stored on the most recent
// OnConnectionFailed callback. This will allow the user to
// resolve the error currently preventing our connection to
// Google Play services.
mSignInProgress = STATE_IN_PROGRESS;
startIntentSenderForResult(mSignInIntent.getIntentSender(),
RC_SIGN_IN, null, 0, 0, 0);
} catch (SendIntentException e) {
Log.i(TAG, "Sign in intent could not be sent: "
+ e.getLocalizedMessage());
// The intent was canceled before it was sent. Attempt to connect to
// get an updated ConnectionResult.
mSignInProgress = STATE_SIGN_IN;
mGoogleApiClient.connect();
}
} else {
// Google Play services wasn't able to provide an intent for some
// error types, so we show the default Google Play services error
// dialog which may still start an intent on our behalf if the
// user can resolve the issue.
createErrorDialog().show();
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
switch (requestCode) {
case RC_SIGN_IN:
if (resultCode == RESULT_OK) {
// If the error resolution was successful we should continue
// processing errors.
mSignInProgress = STATE_SIGN_IN;
} else {
// If the error resolution was not successful or the user canceled,
// we should stop processing errors.
mSignInProgress = STATE_DEFAULT;
}
if (!mGoogleApiClient.isConnecting()) {
// If Google Play services resolved the issue with a dialog then
// onStart is not called so we need to re-attempt connection here.
mGoogleApiClient.connect();
}
break;
}
}
#Override
public void onResult(LoadPeopleResult peopleData) {
if (peopleData.getStatus().getStatusCode() == CommonStatusCodes.SUCCESS) {
mCirclesList.clear();
PersonBuffer personBuffer = peopleData.getPersonBuffer();
try {
int count = personBuffer.getCount();
for (int i = 0; i < count; i++) {
mCirclesList.add(personBuffer.get(i).getDisplayName());
}
} finally {
personBuffer.close();
}
mCirclesAdapter.notifyDataSetChanged();
} else {
Log.e(TAG, "Error requesting visible circles: " + peopleData.getStatus());
}
}
private void onSignedOut() {
// Update the UI to reflect that the user is signed out.
mSignInButton.setEnabled(true);
mSignOutButton.setEnabled(false);
mRevokeButton.setEnabled(false);
// Show the sign-in options
findViewById(R.id.layout_server_auth).setVisibility(View.VISIBLE);
mStatus.setText(R.string.status_signed_out);
mCirclesList.clear();
mCirclesAdapter.notifyDataSetChanged();
}
#Override
public void onConnectionSuspended(int cause) {
// The connection to Google Play services was lost for some reason.
// We call connect() to attempt to re-establish the connection or get a
// ConnectionResult that we can attempt to resolve.
mGoogleApiClient.connect();
}
private Dialog createErrorDialog() {
if (GooglePlayServicesUtil.isUserRecoverableError(mSignInError)) {
return GooglePlayServicesUtil.getErrorDialog(
mSignInError,
this,
RC_SIGN_IN,
new DialogInterface.OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
Log.e(TAG, "Google Play services resolution cancelled");
mSignInProgress = STATE_DEFAULT;
mStatus.setText(R.string.status_signed_out);
}
});
} else {
return new AlertDialog.Builder(this)
.setMessage(R.string.play_services_error)
.setPositiveButton(R.string.close,
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Log.e(TAG, "Google Play services error could not be "
+ "resolved: " + mSignInError);
mSignInProgress = STATE_DEFAULT;
mStatus.setText(R.string.status_signed_out);
}
}).create();
}
}
#Override
public CheckResult onCheckServerAuthorization(String idToken, Set<Scope> scopeSet) {
Log.i(TAG, "Checking if server is authorized.");
Log.i(TAG, "Mocking server has refresh token: " + String.valueOf(mServerHasToken));
if (!mServerHasToken) {
// Server does not have a valid refresh token, so request a new
// auth code which can be exchanged for one. This will cause the user to see the
// consent dialog and be prompted to grant offline access. This callback occurs on a
// background thread so it is OK to do synchronous network access.
// Ask the server which scopes it would like to have for offline access. This
// can be distinct from the scopes granted to the client. By getting these values
// from the server, you can change your server's permissions without needing to
// recompile the client application.
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(SELECT_SCOPES_URL);
HashSet<Scope> serverScopeSet = new HashSet<Scope>();
try {
HttpResponse httpResponse = httpClient.execute(httpGet);
int responseCode = httpResponse.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(httpResponse.getEntity());
if (responseCode == 200) {
String[] scopeStrings = responseBody.split(" ");
for (String scope : scopeStrings) {
Log.i(TAG, "Server Scope: " + scope);
serverScopeSet.add(new Scope(scope));
}
} else {
Log.e(TAG, "Error in getting server scopes: " + responseCode);
}
} catch (ClientProtocolException e) {
Log.e(TAG, "Error in getting server scopes.", e);
} catch (IOException e) {
Log.e(TAG, "Error in getting server scopes.", e);
}
// This tells GoogleApiClient that the server needs a new serverAuthCode with
// access to the scopes in serverScopeSet. Note that we are not asking the server
// if it already has such a token because this is a sample application. In reality,
// you should only do this on the first user sign-in or if the server loses or deletes
// the refresh token.
return CheckResult.newAuthRequiredResult(serverScopeSet);
} else {
// Server already has a valid refresh token with the correct scopes, no need to
// ask the user for offline access again.
return CheckResult.newAuthNotRequiredResult();
}
}
#Override
public boolean onUploadServerAuthCode(String idToken, String serverAuthCode) {
// Upload the serverAuthCode to the server, which will attempt to exchange it for
// a refresh token. This callback occurs on a background thread, so it is OK
// to perform synchronous network access. Returning 'false' will fail the
// GoogleApiClient.connect() call so if you would like the client to ignore
// server failures, always return true.
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(EXCHANGE_TOKEN_URL);
try {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
nameValuePairs.add(new BasicNameValuePair("serverAuthCode", serverAuthCode));
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
final String responseBody = EntityUtils.toString(response.getEntity());
Log.i(TAG, "Code: " + statusCode);
Log.i(TAG, "Resp: " + responseBody);
// Show Toast on UI Thread
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(MainActivity.this, responseBody, Toast.LENGTH_LONG).show();
}
});
return (statusCode == 200);
} catch (ClientProtocolException e) {
Log.e(TAG, "Error in auth code exchange.", e);
return false;
} catch (IOException e) {
Log.e(TAG, "Error in auth code exchange.", e);
return false;
}
}
private void checkServerAuthConfiguration() {
// Check that the server URL is configured before allowing this box to
// be unchecked
if ("WEB_CLIENT_ID".equals(WEB_CLIENT_ID) ||
"SERVER_BASE_URL".equals(SERVER_BASE_URL)) {
Log.w(TAG, "WEB_CLIENT_ID or SERVER_BASE_URL configured incorrectly.");
Dialog dialog = new AlertDialog.Builder(this)
.setMessage(getString(R.string.configuration_error))
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.create();
dialog.show();
}
}
}
I am definitely sure that this error will be caused by not entering your(or entering incorrect) package name in the Google API console.
Just go through all the steps sincerely once again as noted here :
If you have created a new project, then also you must register a new Application(with package name and SHA1) in "Credentials" section of the Google Console API.
Hope it helps you :-)
Make sure that you have set a consent screen on your app and have selected an email in the project configuration. The consent page can be found here:
https://console.developers.google.com/project/<yourprojectid>/apiui/consent
The email field is the one that, if not selected from the listbox, causes the error code 17 to be returned.

Access google plus client from multiple activities

I'm developing an application in which i have integrated google plus. So far Its working fine, I am able to retrieve the user profile.
But now i want to do the following:
1)I have two activity signInActivity and shareActivity.
2)If user is already signin using signInActivity then it should not ask for signin again in
shareActivity and should directly share the content.
3)If user is not signedin in the signInActivity and try to share data using shareActivitythen app should signin the user and then only share the data. In this case if user goes back to the signInActivity then app should show that "you have already signedin"
In short i want user signin to be Central within application so that if it is alrady signedin it should be accessible from any activity.
I have heard about the access token but i dont know how to use it and document says that it expires in an hour which is not what i want.
How can i make central google plus signin? is it possible? or i need to authenticate user in each activity?
Managing a separate instance of GoogleApiClient in each activity will not result in the user being asked to sign in multiple times.
Google+ Sign-in (ie. GoogleApiClient) provides an interface to the Google accounts on the device and the Google Play services core service - it doesn't have state per GoogleApiClient instance. So once a device account has been authenticated for your app, new instances of GoogleApiClient will access the same state. GoogleApiClient is specifically designed to be a lightweight way to access the central state managed by Google Play services.
You're in luck regarding access tokens! Google Play services takes care of all token management for you. So although access tokens only last for one hour, as you say, if you try to use your PlusClient to access a Google API and your access token has expired, Google Play services will transparently request a new access token for you and complete the call.
Take a look at the first part of this Google I/O talk for more details:
http://www.youtube.com/watch?v=_KBHf1EODuk
0. TL;DR
For the impatient coder, a working version of the following implementation can be found on GitHub. This is the same answer written on another Stack Overflow post.
After rewriting the login activity code several times in many different apps, the easy (and not so elegant) solution was create the Google API client as a Application class object. But, since the connection state affect the UX flow, I never was happy about with this approach.
Reducing our problem only to the connection concept, we may consider that:
It hides the Google API client.
It has finite states.
It is a (rather) unique.
The current state affect the behavior of the app.
1. Proxy Pattern
Since the Connection encapsulates the GoogleApiClient, it will implement the ConnectionCallbacks and OnConnectionFailedListener:
#Override
public void onConnected(Bundle hint) {
changeState(State.OPENED);
}
#Override
public void onConnectionSuspended(int cause) {
changeState(State.CLOSED);
connect();
}
#Override
public void onConnectionFailed(ConnectionResult result) {
if (currentState.equals(State.CLOSED) && result.hasResolution()) {
changeState(State.CREATED);
connectionResult = result;
} else {
connect();
}
}
Activities can communicate to the Connection class through the methods connect, disconnect, and revoke, but their behaviors are decided by the current state. The following methods are required by the state machine:
protected void onSignIn() {
if (!googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
googleApiClient.connect();
}
}
protected void onSignOut() {
if (googleApiClient.isConnected()) {
Plus.AccountApi.clearDefaultAccount(googleApiClient);
googleApiClient.disconnect();
googleApiClient.connect();
changeState(State.CLOSED);
}
}
protected void onSignUp() {
Activity activity = activityWeakReference.get();
try {
changeState(State.OPENING);
connectionResult.startResolutionForResult(activity, REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
changeState(State.CREATED);
googleApiClient.connect();
}
}
protected void onRevoke() {
Plus.AccountApi.clearDefaultAccount(googleApiClient);
Plus.AccountApi.revokeAccessAndDisconnect(googleApiClient);
googleApiClient = googleApiClientBuilder.build();
googleApiClient.connect();
changeState(State.CLOSED);
}
2. State Pattern
This is a behavioral pattern the allow an object to alter its behavior when its internal state changes. The GoF Design Patterns book describes how a TCP connection can be represent by this pattern (which is also our case).
A state from a state machine should be a singleton, and the easiest away of doing it in Java was to create Enum named State as follows:
public enum State {
CREATED {
#Override
void connect(Connection connection) {
connection.onSignUp();
}
#Override
void disconnect(Connection connection) {
connection.onSignOut();
}
},
OPENING {},
OPENED {
#Override
void disconnect(Connection connection) {
connection.onSignOut();
}
#Override
void revoke(Connection connection) {
connection.onRevoke();
}
},
CLOSED {
#Override
void connect(Connection connection) {
connection.onSignIn();
}
};
void connect(Connection connection) {}
void disconnect(Connection connection) {}
void revoke(Connection connection) {}
The Connection class holds the context, i.e. the current state, which defines how the Connection methods connect, disconnect, and revoke will behave:
public void connect() {
currentState.connect(this);
}
public void disconnect() {
currentState.disconnect(this);
}
public void revoke() {
currentState.revoke(this);
}
private void changeState(State state) {
currentState = state;
setChanged();
notifyObservers(state);
}
3. Singleton Pattern
Since there is not need to recreate this class repeatedly, we provide it as a singleton:
public static Connection getInstance(Activity activity) {
if (null == sConnection) {
sConnection = new Connection(activity);
}
return sConnection;
}
public void onActivityResult(int result) {
if (result == Activity.RESULT_OK) {
changeState(State.CREATED);
} else {
changeState(State.CLOSED);
}
onSignIn();
}
private Connection(Activity activity) {
activityWeakReference = new WeakReference<>(activity);
googleApiClientBuilder = new GoogleApiClient
.Builder(activity)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API, Plus.PlusOptions.builder().build())
.addScope(new Scope("email"));
googleApiClient = googleApiClientBuilder.build();
currentState = State.CLOSED;
}
4. Observable Pattern
The Connection class extends Java Observable, so 1 or more activities can observe the state changes:
#Override
protected void onCreate(Bundle bundle) {
connection = Connection.getInstance(this);
connection.addObserver(this);
}
#Override
protected void onStart() {
connection.connect();
}
#Override
protected void onDestroy() {
connection.deleteObserver(this);
connection.disconnect();
}
#Override
protected void onActivityResult(int request, int result, Intent data) {
if (Connection.REQUEST_CODE == request) {
connection.onActivityResult(result);
}
}
#Override
public void update(Observable observable, Object data) {
if (observable != connection) {
return;
}
// Your presentation logic goes here...
}
For anyone reading this question you can also check this answer by Ian Barber and also the one below, answered by Lee, that explains three broad ways of working with Google plus login and multiple activies which I found very useful actually.

Categories

Resources