Integración de Android en Android: repite UserRecoverableAuthException

Hemos contactado a Google sobre esto y estamos en el chat

El problema parece ser fijo para los dispositivos excepto los teléfonos Samsung.

Estoy agregando una opción de inicio de sesión de Google+ a una aplicación según las instrucciones oficiales . Una vez que el usuario haya seleccionado su cuenta, me gustaría que mi servidor recupere su información de perfil de Google+ y actualice su perfil en nuestro sitio para que coincida.

La primera parte, hacer que el usuario seleccione una cuenta de Google localmente, parece funcionar bien. Cuando bash solicitar un token para la cuenta seleccionada, aparece el cuadro de diálogo de autenticación de Google con los parámetros apropiados; sin embargo, cuando autorizo ​​la aplicación usando ese diálogo y GoogleAuthUtil.getToken(...) solicitar el token, GoogleAuthUtil.getToken(...) arroja nuevamente una UserRecoverableAuthException ( NeedPermission , no GooglePlayServicesAvailabilityException ) y obtengo el mismo diálogo que me pide que apruebe.

Este comportamiento está presente en un Samsung S3 con Android 4.1.1 (con 3 cuentas de Google) y un Acer A100 con 4.0.3. NO está presente en un HTC Glacier corriendo 2.3.4. En cambio, el HTC Glacier me da un código de autenticación válido. Todos los dispositivos tienen la iteración más reciente de los Servicios de Google Play instalados y usan diferentes cuentas de Google+.

¿Alguien ha visto esto antes? ¿Dónde puedo comenzar con la depuración?

Aquí está el código completo: ¿hay algo obvio?

 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 task = new AsyncTask() { @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); //  } public void onCompletedLoginAttempt(boolean success) { Log.w(LOG_TAG, "Login attempt "+(success ? "succeeded" : "failed")); mRequestingActivity.hideProgressDialog(); mRequestingActivity = null; } } 

He tenido este problema por un tiempo y se me ocurrió una solución adecuada.

 String token = GoogleAuthUtil.getToken(this, accountName, scopeString, appActivities); 

Esta línea devolverá el token de una vez o activará la UserRecoverableAuthException. En la guía de inicio de sesión de Google Plus, dice abrir la actividad de recuperación adecuada.

 startActivityForResult(e.getIntent(), RECOVERABLE_REQUEST_CODE); 

Cuando la actividad regrese con el resultado, volverá con pocos extras en el bash y ahí es donde reside el nuevo token:

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

Con el nuevo oneTimeToken dado desde el extra, puede enviarlo al servidor para conectarse correctamente.

¡Espero que esto ayude!

Es demasiado tarde para responder, pero puede ayudar a las personas que tienen la misma preocupación en el futuro.

Han mencionado en el tutorial que siempre lanzará UserRecoverableAuthException cuando invoque GoogleAuthUtil.getToken () por primera vez. Segunda vez tendrá éxito.

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

Utilicé el código siguiente para obtener el código de acceso de google.

ejecutar este new GetAuthTokenFromGoogle().execute(); una vez desde public void onConnected(Bundle connectionHint) y una vez desde protected void onActivityResult(int requestCode, int responseCode, Intent intent)

 private class GetAuthTokenFromGoogle extends AsyncTask{ @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) { } } 

He solucionado este problema utilizando un inicio de sesión basado en web. Abro una url como esta

 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; 

La url de redirección maneja la respuesta y regresa a mi aplicación.

En términos de mis hallazgos sobre el uso de Google Play Services, he encontrado:

HTC One es 3.1.59 (736673-30) – no funciona Galaxy Note es 3.1.59 (736673-36) – no funciona Nexus S es 3.1.59 (736673-34) – funciona

Y me gustaría participar en el chat que está ocurriendo, sin embargo, no tengo una reputación lo suficientemente alta como para hacerlo.

He experimentado el mismo problema recientemente: parece ser específico del dispositivo (tuve que pasar cada vez en un S3, pero en otro S3 que ejecuta el mismo sistema operativo no sucedió, incluso con la misma cuenta). Mi corazonada es que se trata de un error en una aplicación cliente, ya sea la aplicación G + o la aplicación Google Play Services. Pude resolver el problema en uno de mis dispositivos restableciéndolo de fábrica (un Motorola Defy), luego volví a instalar la aplicación Google Play Services, pero es una solución completamente inútil para decirle a los usuarios.

Editar (6 de agosto de 2013): Esto parece haber sido arreglado para mí sin ningún cambio en mi código.

El primer problema potencial que puedo ver es que llamas a GoogleAuthUtil.getToken() después de obtener la callback onConnected() . Esto es un problema porque solicitar un código de autorización para su servidor con GoogleAuthUtil.getToken() siempre mostrará una pantalla de consentimiento para sus usuarios. Por lo tanto, solo debe obtener un código de autorización para nuevos usuarios y, para evitar mostrar a los nuevos usuarios dos pantallas de consentimiento, debe buscar un código de autorización e intercambiarlo en su servidor antes de resolver cualquier falla de conexión de PlusClient .

En segundo lugar, asegúrese de que realmente necesita un PlusClient y un código de autorización para sus servidores. Solo necesita obtener un PlusClient y un código de autorización si tiene la intención de realizar llamadas a las API de Google desde el cliente de Android y su servidor. Como se explica en esta respuesta .

Estos problemas solo darían como resultado la visualización de dos diálogos de consentimiento (lo que claramente no es un bucle infinito): ¿está viendo más de dos diálogos de consentimiento?

Tuve un problema similar en el que un aparente auth loop siguió creando {leer: enviar correo no deseado} estos ” Inicio de sesión … ” y los diálogos de solicitud de permiso al mismo tiempo que daba la repetida excepción discutida.

El problema aparece en algún código de ejemplo ligeramente modificado que yo (y otros como yo, sospecho) “cargo-culted” de AndroidHive . La solución que funcionó para mí fue garantizar que solo una tarea de recuperación de tokens de fondo se ejecute en segundo plano en un momento dado.

Para que mi código sea más fácil de seguir, aquí está el flujo de autenticación en mi aplicación (que es casi idéntico al código de ejemplo en AndoidHive): Activity -> onConnected(...) -> getProfileInformation() -> getOneTimeToken() .

Aquí es donde se llama a getOneTimeToken() :

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

Aquí está mi getOneTimeToken() :

 private void getOneTimeToken(){ if (task==null){ task = new AsyncTask() { @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!"); } 

Tenga en cuenta que tengo una doble seguridad {probablemente redundante} contra la ejecución de la tarea varias veces:

  1. if(task .getStatus() != AsyncTask.Status.RUNNING){...} - asegura que la tarea no se está ejecutando antes de intentar ejecutarla.
  2. task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); - se asegura de que las copias de esta tarea estén "sincronizadas" (es decir, que haya una cola en el lugar de modo que solo se pueda ejecutar una tarea de este tipo en un momento determinado).

Clarificación de PS Minor: LogHelper.log('e',...) es equivalente a Log.e(...) etc.

deberías instalarlo en el hilo de UI

 try { .... } catch (IOException transientEx) { .... } catch (final UserRecoverableAuthException e) { .... runOnUiThread(new Runnable() { public void run() { startActivityForResult(e1.getIntent(), AUTH_CODE_REQUEST); } }); } 

Tenía el mismo error con el bucle infinito de solicitud de permiso. Para mí fue porque cambió el tiempo en mi teléfono. Cuando compruebo el tiempo de detección, este error desapareció. ¡Espero que esto ayude!