2014-11-05 18 views
5

Sto tentando di implementare com.google.android.gms.common.api.GoogleApiClient nel mio progetto.Stuck in Connection Failed loop con GoogleApiClient

Il problema è che ogni volta che provo a connettermi, ottengo la chiamata al listener onConnectionFailed con un intento in sospeso che eseguo. In un'installazione pulita, il primo tentativo in sospeso avvierà una schermata di selezione dell'account. Questo è previsto. Ogni successivo riavvio dell'app supererà la selezione dell'account, a meno che i dati dell'app non vengano cancellati in Application Manager.

Dopo la schermata di selezione dell'account, la schermata di accesso apparirà breifly. Ma non è mai entrato. L'onActivityResult verrà chiamato dopo che la schermata di accesso lampeggerà, che tenta di connettere il client. Non si connette e chiama di nuovo il listener onConnectionFailed.

Se continuo a tentare di eseguire gli intenti, rimango bloccato in loop con lo schermo di accesso che appare rapidamente, quindi scompare, ma non mi connetto o non mi collego. ConnectionResult.toString indica "Sign_In_Required" e restituisce un errore codice di 4 (uguale alla costante Sign_In_Required

Nella console API, ho implementato un ID client Ouath 2.0 e una chiave di accesso API pubblica per le applicazioni Android. In particolare, la mia app funziona utilizzando la versione precedente di com. cliente google.api.services.drive.Drive

per quanto riguarda il mio codice:.

Ho provato a utilizzare due diverse implementazioni here e here. Ho cercato di implementare il secondo esempio apportando il minor numero possibile di modifiche. Si è riprodotto qui di seguito:

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"); 
} 

}

+0

Ho lo stesso identico problema qui. La cosa strana è che funzionava già. Sto eseguendo la mia app su un dispositivo 4.1.2 (API 16). – m02ph3u5

risposta

3

Si tratta di un duro, dal momento che non ho tempo di completamente ri-run e analizzare il codice. E senza eseguirlo, non vedo nulla di ovvio.

Ma, dato che ho installato questa roba nella mia app, mi piacerebbe aiutare. Sfortunatamente la connessione ai servizi di Google Play e il codice di autorizzazione sono sparsi in tutti i frammenti e attività della mia app. Così, ho fatto un tentativo di creare un'attività fittizia e di tirarci dentro tutte le cose. Per "tutte le cose" intendo il wrapper gestore account (GA) e il codice di selezione account associato.

Il risultato è di circa 300 righe di parole senza senso che potrebbero funzionare, ma non ho alcuna intenzione di farlo. Dai un'occhiata e buona fortuna.

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's only one goo account registered with the device, skip the picker 
     GA.setEmil(this, accnt.name); 
     } 

    // UNLIKELY BUT POSSIBLE, emil's 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)); 
    } 
    } 
} 

BTW, so come si scrive 'e-mail', 'Emil' è capitato di essere il nome di mio zio e non ho potuto resistere :-)

UPDATE (2015-Apr-11) :

Ho recentemente rivisto il codice che gestisce l'autorizzazione di Google Drive e il cambio di account. Il risultato può essere trovato here e supporta sia le API REST che GDAA.

+0

** booleano mIsInAuth ** ha fatto il trucco per me - chiedo perché questo non è presente nei documenti di sviluppo di Google ... – m02ph3u5

+0

@ m02ph3u5 - è * è * nei loro documenti, si chiama __mIntentInProgress__ (vedi: https: // sviluppatori .google.com/+/mobile/android/sign-in) –

+0

@ Dev-iL non riesce a trovare * mIntentInProgess * più è nei documenti G + - perché non è nei documenti di GoogleApiClient? – m02ph3u5

3

Ciò accade perché dopo il primo accesso/autorizzazione Android continua a utilizzare gli stessi parametri di account predefiniti. Se si desidera evitare il ciclo e assicurarsi che il selettore ritorni, è necessario cancellare completamente l'account predefinito chiamando Plus.AccountApi.clearDefaultAccount (mGoogleApiClient) prima di riconnettersi.

Per ottenere ciò, è necessario aggiungere il Plus.ambito API per il costruttore GoogleApiClient:

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

E poi è possibile cancellare l'account predefinito prima di ricostruire il client API e la connessione a un account diverso (la ricostruzione del client API quando si cambia account per evitare problemi):

// 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(); 

Non sono necessarie ulteriori autorizzazioni o attivazioni API per l'utilizzo dell'ambito Plus.API in questo modo. Spero che questo aiuti con il tuo problema.

+0

Con l'esempio che ho, non ha funzionato. Puoi suggerire un buon esempio per effettuare il login con Google? ........ Ho provato questo dove l'esempio che ho ha questa riga per addApi: .addApi (Plus.API, Plus.PlusOptions.builder(). Build()) .. e ha ottenuto il seguente errore: Failure consegna risultato ResultInfo {who = null, request = 0, result = 0, data = null} all'attività {com ... androidgoogleplusloginexample.AndroidGooglePlusExample}: java.lang.IllegalStateException: GoogleApiClient deve essere connesso. ..... Grazie – gnB

+0

Ho modificato la mia risposta per rendere l'esempio di connessione più dettagliato e aggiornato. Come puoi vedere, spengo solo l'account predefinito se il client API è già stato collegato a un account. In caso contrario, riceverai l'errore che hai descritto. Immagino tu stia cercando di cancellare l'account predefinito prima della prima connessione, ed è per questo che ricevi l'errore. Fallo solo quando ti ricolleghi a un altro account. – jmart