Stuck in connecting loop failure with GoogleApiClient

I am trying to implement com.google.android.gms.common.api.GoogleApiClient in my project.

The problem is that every time I try to connect, I get a call back to the onConnectionFailed listener with a pending intent that I am executing. On a clean installation, in the very first pending intention, the account selection screen will be launched. It is expected. Each subsequent restart of the application will bypass the choice of account if the application data is not deleted in the Application Manager.

After the account selection screen, the login screen will appear briefly. He never signs up. OnActivityResult is called after the login screen starts flashing, which is trying to connect the client. It does not connect and calls the onConnectionFailed listener again.

If I continue to try to fulfill my intentions, I loop in a loop with the appearance of the subscription on the screen, and then disappears, but never connects or becomes signed. ConnectionResult.toString indicates "Sign_In_Required" and returns an error code of 4 (the same as the Sign_In_Required constant.

On the API console, I implemented the Ouath 2.0 client identifier and the public API access key for Android applications. Remarkably, my application works using the older com.google.api.services.drive.Drive client.

As for my code:

I tried using two different implementations here and here . I tried to implement the second example, making as few changes as possible. It is reproduced below:

public class MainActivity extends Activity implements ConnectionCallbacks, OnConnectionFailedListener { private static final String TAG = "android-drive-quickstart"; private static final int REQUEST_CODE_CAPTURE_IMAGE = 1; private static final int REQUEST_CODE_CREATOR = 2; private static final int REQUEST_CODE_RESOLUTION = 3; private GoogleApiClient mGoogleApiClient; private Bitmap mBitmapToSave; /** * Create a new file and save it to Drive. */ private void saveFileToDrive() { // Start by creating a new contents, and setting a callback. Log.i(TAG, "Creating new contents."); final Bitmap image = mBitmapToSave; Drive.DriveApi.newContents(mGoogleApiClient).setResultCallback(new ResultCallback<DriveApi.ContentsResult>() { @Override public void onResult(DriveApi.ContentsResult result) { // If the operation was not successful, we cannot do anything // and must // fail. if (!result.getStatus().isSuccess()) { Log.i(TAG, "Failed to create new contents."); return; } // Otherwise, we can write our data to the new contents. Log.i(TAG, "New contents created."); // Get an output stream for the contents. OutputStream outputStream = result.getContents().getOutputStream(); // Write the bitmap data from it. ByteArrayOutputStream bitmapStream = new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.PNG, 100, bitmapStream); try { outputStream.write(bitmapStream.toByteArray()); } catch (IOException e1) { Log.i(TAG, "Unable to write file contents."); } // Create the initial metadata - MIME type and title. // Note that the user will be able to change the title later. MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder() .setMimeType("image/jpeg").setTitle("Android Photo.png").build(); // Create an intent for the file chooser, and start it. IntentSender intentSender = Drive.DriveApi .newCreateFileActivityBuilder() .setInitialMetadata(metadataChangeSet) .setInitialContents(result.getContents()) .build(mGoogleApiClient); try { startIntentSenderForResult( intentSender, REQUEST_CODE_CREATOR, null, 0, 0, 0); } catch (SendIntentException e) { Log.i(TAG, "Failed to launch file chooser."); } } }); } @Override protected void onResume() { super.onResume(); if (mGoogleApiClient == null) { // Create the API client and bind it to an instance variable. // We use this instance as the callback for connection and connection // failures. // Since no account name is passed, the user is prompted to choose. mGoogleApiClient = new GoogleApiClient.Builder(this) .addApi(Drive.API) .addScope(Drive.SCOPE_FILE) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build(); } // Connect the client. Once connected, the camera is launched. mGoogleApiClient.connect(); } @Override protected void onPause() { if (mGoogleApiClient != null) { mGoogleApiClient.disconnect(); } super.onPause(); } @Override protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { switch (requestCode) { case REQUEST_CODE_CAPTURE_IMAGE: // Called after a photo has been taken. if (resultCode == Activity.RESULT_OK) { // Store the image data as a bitmap for writing later. mBitmapToSave = (Bitmap) data.getExtras().get("data"); } break; case REQUEST_CODE_CREATOR: // Called after a file is saved to Drive. if (resultCode == RESULT_OK) { Log.i(TAG, "Image successfully saved."); mBitmapToSave = null; // Just start the camera again for another photo. startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE), REQUEST_CODE_CAPTURE_IMAGE); } break; } } @Override public void onConnectionFailed(ConnectionResult result) { // Called whenever the API client fails to connect. Log.i(TAG, "GoogleApiClient connection failed: " + result.toString()); if (!result.hasResolution()) { // show the localized error dialog. GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this, 0).show(); return; } // The failure has a resolution. Resolve it. // Called typically when the app is not yet authorized, and an // authorization // dialog is displayed to the user. try { result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION); } catch (SendIntentException e) { Log.e(TAG, "Exception while starting resolution activity", e); } } @Override public void onConnected(Bundle connectionHint) { Log.i(TAG, "API client connected."); if (mBitmapToSave == null) { // This activity has no UI of its own. Just start the camera. startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE), REQUEST_CODE_CAPTURE_IMAGE); return; } saveFileToDrive(); } @Override public void onConnectionSuspended(int cause) { Log.i(TAG, "GoogleApiClient connection suspended"); } 

}

+5
source share
2 answers

This is a tricky question, as I don’t have time to completely repeat and analyze your code. And, without starting it, I do not see anything obvious.

But, since I have this application and it works in my application, I would like to help. Unfortunately, the Google Apps connection and authorization code are scattered across all my fragments and actions. So, I tried to create a fictitious activity and pull out all the things in it. "All things" I mean the wrapper of the account manager (GA) and the associated account selection code.

The result is about 300 lines of gibberish that can work, but I make no complaints. Look and good luck.

 package com.......; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Activity; import android.app.Dialog; import android.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; import android.util.Log; import android.widget.Toast; import com.google.android.gms.auth.GoogleAuthUtil; import com.google.android.gms.common.AccountPicker; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.common.api.GoogleApiClient; public class GooApiClient extends Activity implements GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks { private static final String DIALOG_ERROR = "dialog_error"; private static final String REQUEST_CODE = "request_code"; private static final int REQ_ACCPICK = 1; private static final int REQ_AUTH = 2; private static final int REQ_RECOVER = 3; private GoogleApiClient mGooApiClient; private boolean mIsInAuth; //block re-entrancy @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (checkPlayServices() && checkUserAccount()) { gooInit(); gooConnect(true); } } @Override public void onConnected(Bundle bundle) { Log.d("_", "connected"); } @Override public void onConnectionSuspended(int i) { } @Override public void onConnectionFailed(ConnectionResult result) { Log.d("_", "failed " + result.hasResolution()); if (!mIsInAuth) { if (result.hasResolution()) { try { mIsInAuth = true; result.startResolutionForResult(this, REQ_AUTH); } catch (IntentSender.SendIntentException e) { suicide("authorization fail"); } } else { suicide("authorization fail"); } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent it) { Log.d("_", "activity result " + requestCode + " " + resultCode); switch (requestCode) { case REQ_AUTH: case REQ_RECOVER: { mIsInAuth = false; if (resultCode == Activity.RESULT_OK) { gooConnect(true); } else if (resultCode == RESULT_CANCELED) { suicide("authorization fail"); } return; } case REQ_ACCPICK: { // return from account picker if (resultCode == Activity.RESULT_OK && it != null) { String emil = it.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); if (GA.setEmil(this, emil) == GA.CHANGED) { gooInit(); gooConnect(true); } } else if (GA.getActiveEmil(this) == null) { suicide("selection failed"); } return; } } super.onActivityResult(requestCode, resultCode, it); // DO NOT REMOVE } private boolean checkPlayServices() { Log.d("_", "check PS"); int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); if (status != ConnectionResult.SUCCESS) { if (GooglePlayServicesUtil.isUserRecoverableError(status)) { mIsInAuth = true; errorDialog(status, LstActivity.REQ_RECOVER); } else { suicide("play services failed"); } return false; } return true; } private boolean checkUserAccount() { String emil = GA.getActiveEmil(this); Account accnt = GA.getPrimaryAccnt(this, true); Log.d("_", "check user account " + emil + " " + accnt); if (emil == null) { // no emil (after install) if (accnt == null) { // multiple or no accounts available, go pick one accnt = GA.getPrimaryAccnt(this, false); Intent it = AccountPicker.newChooseAccountIntent(accnt, null, new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, true, null, null, null, null ); this.startActivityForResult(it, LstActivity.REQ_ACCPICK); return false; //--------------------->>> } else { // there only one goo account registered with the device, skip the picker GA.setEmil(this, accnt.name); } // UNLIKELY BUT POSSIBLE, emil OK, but the account have been removed since (through settings) } else { accnt = GA.getActiveAccnt(this); if (accnt == null) { accnt = GA.getPrimaryAccnt(this, false); Intent it = AccountPicker.newChooseAccountIntent(accnt, null, new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, true, null, null, null, null ); this.startActivityForResult(it, LstActivity.REQ_ACCPICK); return false; //------------------>>> } } return true; } private void gooInit(){ String emil = GA.getActiveEmil(this); Log.d("_", "goo init " + emil); if (emil != null){ mGooApiClient = new GoogleApiClient.Builder(this) .setAccountName(emil).addApi(com.google.android.gms.drive.Drive.API) .addScope(com.google.android.gms.drive.Drive.SCOPE_FILE) .addConnectionCallbacks(this).addOnConnectionFailedListener(this) .build(); } } private void gooConnect(boolean bConnect) { Log.d("_", "goo connect " + bConnect); if (mGooApiClient != null) { if (!bConnect) { mGooApiClient.disconnect(); } else if (! (mGooApiClient.isConnecting() || mGooApiClient.isConnected())){ mGooApiClient.connect(); } } } private void suicide(String msg) { GA.removeActiveAccnt(this); Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); finish(); } private void errorDialog(int errorCode, int requestCode) { Bundle args = new Bundle(); args.putInt(DIALOG_ERROR, errorCode); args.putInt(REQUEST_CODE, requestCode); ErrorDialogFragment dialogFragment = new ErrorDialogFragment(); dialogFragment.setArguments(args); dialogFragment.show(getFragmentManager(), "errordialog"); } public static class ErrorDialogFragment extends DialogFragment { public ErrorDialogFragment() { } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { int errorCode = getArguments().getInt(DIALOG_ERROR); int requestCode = getArguments().getInt(DIALOG_ERROR); return GooglePlayServicesUtil.getErrorDialog(errorCode, getActivity(), requestCode); } @Override public void onDismiss(DialogInterface dialog) { getActivity().finish(); } } private static class GA { private static final String ACC_NAME = "account_name"; public static final int FAIL = -1; public static final int UNCHANGED = 0; public static final int CHANGED = +1; private static String mCurrEmil = null; // cache locally private static String mPrevEmil = null; // cache locally public static Account[] getAllAccnts(Context ctx) { return AccountManager.get(acx(ctx)).getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); } public static Account getPrimaryAccnt(Context ctx, boolean bOneOnly) { Account[] accts = getAllAccnts(ctx); if (bOneOnly) return accts == null || accts.length != 1 ? null : accts[0]; return accts == null || accts.length == 0 ? null : accts[0]; } public static Account getActiveAccnt(Context ctx) { return emil2Accnt(ctx, getActiveEmil(ctx)); } public static String getActiveEmil(Context ctx) { if (mCurrEmil != null) { return mCurrEmil; } mCurrEmil = ctx == null ? null : pfs(ctx).getString(ACC_NAME, null); return mCurrEmil; } public static Account getPrevEmil(Context ctx) { return emil2Accnt(ctx, mPrevEmil); } public static Account emil2Accnt(Context ctx, String emil) { if (emil != null) { Account[] accounts = AccountManager.get(acx(ctx)).getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); for (Account account : accounts) { if (emil.equalsIgnoreCase(account.name)) { return account; } } } return null; } /** * Stores a new email in persistent app storage, reporting result * @param newEmil new email, optionally null * @param ctx activity context * @return FAIL, CHANGED or UNCHANGED (based on the following table) * OLD NEW SAVED RESULT * ERROR FAIL * null null null FAIL * null new new CHANGED * old null old UNCHANGED * old != new new CHANGED * old == new new UNCHANGED */ public static int setEmil(Context ctx, String newEmil) { int result = FAIL; // 0 0 mPrevEmil = getActiveEmil(ctx); if ((mPrevEmil == null) && (newEmil != null)) { result = CHANGED; } else if ((mPrevEmil != null) && (newEmil == null)) { result = UNCHANGED; } else if ((mPrevEmil != null) && (newEmil != null)) { result = mPrevEmil.equalsIgnoreCase(newEmil) ? UNCHANGED : CHANGED; } if (result == CHANGED) { mCurrEmil = newEmil; pfs(ctx).edit().putString(ACC_NAME, newEmil).apply(); } return result; } public static void removeActiveAccnt(Context ctx) { mCurrEmil = null; pfs(ctx).edit().remove(ACC_NAME).apply(); } private static Context acx(Context ctx) { return ctx == null ? null : ctx.getApplicationContext(); } private static SharedPreferences pfs(Context ctx) { return ctx == null ? null : PreferenceManager.getDefaultSharedPreferences(acx(ctx)); } } } 

By the way, I know how to write "e-mail", "Emil" just turned out to be my uncle's name, and I could not resist :-)

UPDATE (2015-Apr-11):

I recently visited a code that handles authorization in Google Drive and account switching. The result can be found here , and it supports both REST and GDAA apis.

+3
source

This is because after the first login / authorization android continues to use the same default account settings. If you want to avoid the loop and make sure the collector is displayed again, you must completely clear the default account by calling Plus.AccountApi.clearDefaultAccount (mGoogleApiClient) before reconnecting.

To achieve this, you must add the Plus.API scope to the GoogleApiClient builder:

  mGoogleApiClient = new GoogleApiClient.Builder(this) .addApi(Drive.API) .addApi(Plus.API) .addScope(Drive.SCOPE_FILE) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build(); 

And then you can clear the default account before rebuilding the api client and connecting to another account (rebuilding the api client when changing accounts fixes the problems):

  // if the api client existed, we terminate it if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { Plus.AccountApi.clearDefaultAccount(mGoogleApiClient); mGoogleApiClient.disconnect(); } // build new api client to avoid problems reusing it mGoogleApiClient = new GoogleApiClient.Builder(this) .addApi(Drive.API) .addApi(Plus.API) .addScope(Drive.SCOPE_FILE) .setAccountName(account) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build(); mGoogleApiClient.connect(); 

No additional permissions or api activations are required to use the Plus.API domain in this way. Hope this helps with your issue.

+3
source

Source: https://habr.com/ru/post/1206254/


All Articles