Подтвердить что ты не робот

Интеграция Android Google+ - повторенное UserRecoverableAuthException

Мы связались с Google об этом и мы в чате

Проблема, похоже, исправлена ​​для устройств кроме телефонов Samsung.

Я добавляю параметр Google+ в приложение для официальных инструкций . После того, как пользователь выбрал свою учетную запись, я хотел бы, чтобы мой сервер получал их информацию о профиле Google+ и обновлял свой профиль на нашем сайте, чтобы он соответствовал.

Первая часть - если пользователь выбирает учетную запись Google локально - кажется, работает очень хорошо. Когда я пытаюсь запросить токен для выбранной учетной записи, откроется диалоговое окно Google auth с соответствующими параметрами; однако, когда я разрешаю приложение, используя этот диалог, и повторно запрашиваю токен, GoogleAuthUtil.getToken(...) снова бросает UserRecoverableAuthException (NeedPermission, not GooglePlayServicesAvailabilityException), и я получаю тот же диалог с просьбой одобрить!

Это поведение присутствует на Samsung S3 с Android 4.1.1 (с 3 учетными записями Google) и Acer A100 с 4.0.3. Он не присутствует на HTC Glacier, работающем на 2.3.4. Вместо этого HTC Glacier дает мне действительный код авторизации. На всех устройствах установлена ​​последняя итерация установленных сервисов Google Play и используются разные учетные записи Google+.

Кто-нибудь видел это раньше? Где я могу начать с отладки?

Здесь полный код - что-то явно не так?

public class MyGooglePlusClient {
private static final String LOG_TAG = "GPlus";
private static final String SCOPES_LOGIN = Scopes.PLUS_LOGIN + " " + Scopes.PLUS_PROFILE;
private static final String ACTIVITIES_LOGIN = "http://schemas.google.com/AddActivity";
private static MyGooglePlusClient myGPlus = null;
private BaseActivity mRequestingActivity = null;
private String mSelectedAccount = null;

/**
 * Get the GPlus singleton
 * @return GPlus
 */
public synchronized static MyGooglePlusClient getInstance() {
    if (myGPlus == null)
        myGPlus = new MyGooglePlusClient();
    return myGPlus;
}

public boolean login(BaseActivity requester) {
    Log.w(LOG_TAG, "Starting login...");
    if (mRequestingActivity != null) {
        Log.w(LOG_TAG, "Login attempt already in progress.");
        return false; // Cannot launch a new request; already in progress
    }

    mRequestingActivity = requester;
    if (mSelectedAccount == null) {
        Intent intent = AccountPicker.newChooseAccountIntent(null, null, new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, false,
                null, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE, null, null);
        mRequestingActivity.startActivityForResult(intent, BaseActivity.REQUEST_GPLUS_SELECT);
    }
    return true;
}

public void loginCallback(String accountName) {
    mSelectedAccount = accountName;
    authorizeCallback();
}

public void logout() {
    Log.w(LOG_TAG, "Logging out...");
    mSelectedAccount = null;
}

public void authorizeCallback() {
    Log.w(LOG_TAG, "User authorized");

    AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
        @Override
        protected String doInBackground(Void... params) {
            String token = null;
            try {
                Bundle b = new Bundle();
                b.putString(GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES, ACTIVITIES_LOGIN);
                token = GoogleAuthUtil.getToken(mRequestingActivity,
                        mSelectedAccount,
                        "oauth2:server:client_id:"+Constants.GOOGLE_PLUS_SERVER_OAUTH_CLIENT
                        +":api_scope:" + SCOPES_LOGIN,
                        b);
            } catch (IOException transientEx) {
                // Network or server error, try later
                Log.w(LOG_TAG, transientEx.toString());
                onCompletedLoginAttempt(false);
            } catch (GooglePlayServicesAvailabilityException e) {
                Log.w(LOG_TAG, "Google Play services not available.");
                Intent recover = e.getIntent();
                mRequestingActivity.startActivityForResult(recover, BaseActivity.REQUEST_GPLUS_AUTHORIZE);
            } catch (UserRecoverableAuthException e) {
                // Recover (with e.getIntent())
                Log.w(LOG_TAG, "User must approve "+e.toString());
                Intent recover = e.getIntent();
                mRequestingActivity.startActivityForResult(recover, BaseActivity.REQUEST_GPLUS_AUTHORIZE);
            } catch (GoogleAuthException authEx) {
                // The call is not ever expected to succeed
                Log.w(LOG_TAG, authEx.toString());
                onCompletedLoginAttempt(false);
            }

            Log.w(LOG_TAG, "Finished with task; token is "+token);
            if (token != null) {
                authorizeCallback(token);
            }

            return token;
        }

    };
    task.execute();
}

public void authorizeCallback(String token) {
    Log.w(LOG_TAG, "Token obtained: "+token);
    // <snipped - do some more stuff involving connecting to the server and resetting the state locally>
}

public void onCompletedLoginAttempt(boolean success) {
    Log.w(LOG_TAG, "Login attempt "+(success ? "succeeded" : "failed"));
    mRequestingActivity.hideProgressDialog();
    mRequestingActivity = null;
}
}
4b9b3361

Ответ 1

У меня была эта проблема на некоторое время и придумал правильное решение.

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

Эта строка либо вернет одноразовый токен, либо вызовет исключение UserRecoverableAuthException. В руководстве Google Plus Sign In говорится, что нужно открыть надлежащую операцию восстановления.

startActivityForResult(e.getIntent(), RECOVERABLE_REQUEST_CODE);

Когда действие вернется с результатом, оно вернется с небольшим количеством дополнительных объектов в намерении, и именно там находится новый токен:

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

С новым значением oneTimeToken, предоставленным из дополнительного, вы можете отправить сервер на правильное подключение.

Надеюсь, это поможет!

Ответ 2

Слишком поздно, чтобы ответить, но это может помочь людям, которые с такой же заботой в будущем.

Они упомянули в учебник, что он всегда будет вызывать UserRecoverableAuthException когда вы впервые вызываете GoogleAuthUtil.getToken(). Второй раз это будет успешным.

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

Я использовал ниже код для получения кода доступа из Google.

выполните этот new GetAuthTokenFromGoogle().execute(); один раз из public void onConnected(Bundle connectionHint) и один раз из protected void onActivityResult(int requestCode, int responseCode, Intent intent)

private class GetAuthTokenFromGoogle extends AsyncTask<Void, Integer, Void>{
        @Override  
        protected void onPreExecute()  
        {  

        }
        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub

            try {
                accessCode = GoogleAuthUtil.getToken(mContext, Plus.AccountApi.getAccountName(mGoogleApiClient), SCOPE);
                new ValidateTokenWithPhoneOmega().execute();
                Log.d("Token  -- ", accessCode);
            } catch (IOException transientEx) {
                // network or server error, the call is expected to succeed if you try again later.
                // Don't attempt to call again immediately - the request is likely to
                // fail, you'll hit quotas or back-off.

                return null;
            } catch (UserRecoverableAuthException e) {
                // Recover
                startActivityForResult(e.getIntent(), RC_ACCESS_CODE);
                e.printStackTrace();
            } catch (GoogleAuthException authEx) {
                // Failure. The call is not expected to ever succeed so it should not be
                // retried.
                authEx.printStackTrace();
                return null;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return null;  
        }

        @Override  
        protected void onPostExecute(Void result)  
        { 
        }
    }

Ответ 3

Изменить (6 августа 2013 г.): Кажется, это исправлено для меня без каких-либо изменений в моем коде.

Первой потенциальной проблемой, которую я вижу, является то, что вы вызываете GoogleAuthUtil.getToken() после получения обратного вызова onConnected(). Это проблема, потому что запрос кода авторизации для вашего сервера с помощью GoogleAuthUtil.getToken() будет всегда показывать экран согласия вашим пользователям. Таким образом, вы должны получить только код авторизации для новых пользователей и, чтобы не показывать новые экраны с двумя пользователями, вы должны получить код авторизации и обменять его на своем сервере, прежде чем разрешать какие-либо сбои подключения из PlusClient.

Во-вторых, убедитесь, что вам действительно нужны как PlusClient, так и код авторизации для ваших серверов. Вам нужно получить только PlusClient и код авторизации, если вы намерены совершать звонки в API Google с сервера Android и. Как объяснено в этом ответе.

Эти проблемы приведут только к отображению двух диалоговых окон согласия (что явно не бесконечный цикл) - вы видите более двух диалогов согласия?

Ответ 4

У меня возникла проблема с использованием веб-входа. Я открываю такой URL-адрес

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;

Затем URL-адрес перенаправления обрабатывает ответ и возвращается в мое приложение.

С точки зрения моих результатов по использованию Служб Google Play, я нашел:

HTC One - 3.1.59 (736673-30) - не работает Galaxy Note 3.1.59 (736673-36) - не работает Nexus S - 3,1,59 (736673-34) - работает

И я хотел бы участвовать в чате, который происходит, но у меня нет достаточной репутации для этого.

Ответ 5

В последнее время я столкнулся с одной и той же проблемой - она, по-видимому, специфична для устройства (я это делал каждый раз на одном S3, но на другой S3, работающей с той же ОС, этого не произошло, даже с той же учетной записью), Моя догадка заключается в том, что это ошибка в клиентском приложении, либо приложение G +, либо приложение Google Play Services. Мне удалось решить проблему на одном из моих устройств с помощью factory с его сбросом (Motorola Defy), а затем переустановить приложение Google Play Services, но это совершенно бесполезное решение для того, чтобы рассказать пользователям.

Ответ 6

У меня была аналогичная проблема, когда кажущаяся строка auth продолжала создавать {read: spamming} эти " Signing In..." и диалоговые окна запросов на разрешение, а также неоднократно выдавать обсуждаемое исключение.

Проблема возникает в некотором немного модифицированном примере кода, который я (и другой, как и я, я подозреваю) "обработанный грузом" от AndroidHive. Решение, которое сработало для меня, было , чтобы в любой момент времени выполнялось только одна задача поиска фона для фона.

Чтобы сделать мой код более удобным для использования, здесь поток auth в моем приложении (что почти идентично примеру кода на AndoidHive): ActivityonConnected(...)getProfileInformation()getOneTimeToken().

Здесь, где 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(); // <-------
            ...

Здесь мой getOneTimeToken():

private void getOneTimeToken(){
    if (task==null){
    task = new AsyncTask<Void, Void, String>() {
        @Override
        protected String doInBackground(Void... params) {
            LogHelper.log('d',LOGTAG, "Executing background task....");
            Bundle appActivities = new Bundle();
            appActivities.putString(
                         GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES,
                         ACTIVITIES_LOGIN);
            String scopes = "oauth2:server" + 
                            ":client_id:" + SERVER_CLIENT_ID + 
                            ":api_scope:" + SCOPES_LOGIN;
            String token = null;
            try {
                token = GoogleAuthUtil.getToken(
                        ActivityPlus.this,
                        Plus.AccountApi.getAccountName(mGoogleApiClient),
                        scopes,
                        appActivities
                );
            } catch (IOException transientEx) {
                /* Original comment removed*/
                LogHelper.log('e',LOGTAG, transientEx.toString());
            } catch (UserRecoverableAuthException e) {
                /* Original comment removed*/
                LogHelper.log('e',LOGTAG, e.toString());
                startActivityForResult(e.getIntent(), AUTH_CODE_REQUEST);
            } catch (GoogleAuthException authEx) {
                /* Original comment removed*/
                LogHelper.log('e',LOGTAG, authEx.toString());
            } catch (IllegalStateException stateEx){
                LogHelper.log('e',LOGTAG, stateEx.toString());
            }
            LogHelper.log('d',LOGTAG, "Background task finishing....");
            return token;
        }

        @Override
        protected void onPostExecute(String token) {
            LogHelper.log('i',LOGTAG, "Access token retrieved: " + token);
        }

    };
    }
    LogHelper.log('d',LOGTAG, "Task setup successful.");
    if(task.getStatus() != AsyncTask.Status.RUNNING){
        task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); //double safety!
    } else
        LogHelper.log('d',LOGTAG, 
                       "Attempted to restart task while it is running!");
}

Обратите внимание, что у меня есть {вероятно избыточная} двойная защита от выполнения задачи несколько раз:

  • if(task .getStatus() != AsyncTask.Status.RUNNING){...} - гарантирует, что задача не будет выполнена, прежде чем пытаться ее выполнить.
  • task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); - убедитесь, что копии этой задачи "синхронизированы" (т.е. очередь находится на месте, так что только одно задание этого типа может выполняться в заданное время).

P.S. Незначительное разъяснение: LogHelper.log('e',...) эквивалентно Log.e(...) и т.д.

Ответ 7

вы должны начать действовать в потоке пользовательского интерфейса

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

Ответ 8

Имел ту же ошибку с бесконечным циклом запроса разрешения. Для меня это было потому, что время на моем телефоне было сдвинуто. Когда я проверю время обнаружения автоматически, эта ошибка исчезла. Надеюсь, это поможет!