1

I'm trying to implement Google sign in in order to use Wallet, or Pay, and sell a product to the user. I followed the steps from the official guide and I even cloned the project and it has the same issue as mine. The problem is that when I try to get the user's information from the OptionalPendingResult the system throws an IllegalStateException.

First, the user has to go through an Activity that uses a GoogleApiClient initialized this way:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_checkout);
    ButterKnife.bind(this);

    setSupportActionBar(toolbar);

    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addApi(Wallet.API, new Wallet.WalletOptions.Builder().build())
            .build();

    showProgressDialog();

    itemInfo = getIntent().getParcelableExtra(Constants.EXTRA_ITEM_INFO);

}

Afterwards, I show a com.google.android.gms.common.SignInButton in a fragment hosted by an Activity without a GoogleApiClient which I'm implementing on the fragment:

public class LoginFragment extends Fragment implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {
    public static final int REQUEST_CODE_PICK_ACCOUNT = 1000;
    public static final int REQUEST_CODE_RESOLVE_ERR = 1005;
    public static final int REQUEST_CODE_SIGN_IN = 1006;
    private static final String TAG = "LoginFragment";
    private static final String WALLET_SCOPE = "https://www.googleapis.com/auth/payments.make_payments";
    private static final String WALLET_SANDBOX_SCOPE = "https://www.googleapis.com/auth/paymentssandbox.make_payments";

    @Bind(R.id.sign_in_button)com.google.android.gms.common.SignInButton button;

    private ProgressDialog mProgressDialog;
    private GoogleApiClient mGoogleApiClient;
    private int mLoginAction;
    private String userEmail;

    public static LoginFragment newInstance(int intentCode) {
        LoginFragment fragment = new LoginFragment();
        Bundle args = new Bundle();
        args.putInt(LoginActivity.EXTRA_ACTION, intentCode);
        fragment.setArguments(args);
        return fragment;
    }

    public LoginFragment() {}

    @Override public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle args = getArguments();
        if (args != null) {
            mLoginAction = args.getInt(LoginActivity.EXTRA_ACTION);
        }

        if(savedInstanceState != null) {
            userEmail = savedInstanceState.getString("email");
        }

        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestEmail()
                .requestProfile()
                .requestScopes(new Scope(WALLET_SCOPE))
//                .requestIdToken(getString(R.string.client_id_test))
                .build();


        mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();

    }

    @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                                       @Nullable Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View v = inflater.inflate(R.layout.fragment_login, container, false);
        ButterKnife.bind(this, v);
        button.setSize(SignInButton.SIZE_WIDE);

        if(savedInstanceState != null && userEmail.isEmpty())
            userEmail = savedInstanceState.getString("email");
        button.setClickable(true);    
        return v;
    }

    @Override public void onStart() {
        super.onStart();
        mGoogleApiClient.connect();
    }

    @Override public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("email", userEmail);
    }

    @Override public void onStop() {
        super.onStop();
        mGoogleApiClient.disconnect();
    }

    @Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQUEST_CODE_SIGN_IN:
                logIn(data);
                button.setClickable(true);
                break;

            case REQUEST_CODE_PICK_ACCOUNT:
                if (resultCode == Activity.RESULT_OK) {
                    userEmail = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
                    getUsername();
                } else if (resultCode == Activity.RESULT_CANCELED) {
                    showToast(getString(R.string.please_select_account));
                }

            default:
                super.onActivityResult(requestCode, resultCode, data);
                break;
        }
    }

    @Override public void onDestroyView() {
        super.onDestroyView();
        ButterKnife.unbind(this);
        mGoogleApiClient = null;
    }

    @OnClick(R.id.sign_in_button) public void onSignIn() {
        if(button.isClickable()) {    
            button.setClickable(false);
            Intent intent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
            startActivityForResult(intent, REQUEST_CODE_SIGN_IN);
        }
    }

    @Override public void onConnected(@Nullable Bundle bundle) {
        if (mLoginAction == LoginActivity.Action.LOGOUT) {
            logOut();
        }
    }

    @Override public void onConnectionSuspended(int i) {}

    @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.e(TAG, "onConnectionFailed, error code:" + connectionResult.getErrorCode());

        switch (connectionResult.getErrorCode()) {
            case ConnectionResult.SERVICE_DISABLED:
                showToast(getString(R.string.error_gps_service_disabled));
                break;

            case ConnectionResult.SERVICE_INVALID:
                showToast(getString(R.string.error_gps_service_invalid));
                break;

            case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
                showToast(getString(R.string.error_gps_service_update));
                break;

        }
    }

    private void showToast(String message) {
        Toast.makeText(getActivity(), "Failed: " + message, Toast.LENGTH_SHORT).show();
    }

    private void logIn(Intent data) {    
        OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
        if (opr.isDone()) {
            try {
                GoogleSignInResult signInResult = opr.get();
                GoogleSignInAccount gsa = signInResult.getSignInAccount();

                Toast.makeText(getActivity(), getString(R.string.welcome_user, gsa.getDisplayName()),
                        Toast.LENGTH_LONG).show();

                ((MultaJustaApp) getActivity().getApplication()).login(gsa.getEmail(), gsa.getIdToken());

                success = true;
            } catch (IllegalStateException e) {
                e.printStackTrace();
                showToast(getString(R.string.error_sign_in));
            }


        } else {
            showToast(getString(R.string.network_error));
        }

        if(success) {
            getActivity().setResult(Activity.RESULT_OK);
            getActivity().finish();
        }
    }
}

Worth mentioning that right after the creation of the opr, when I check it on watch variables it has a Status{statusCode=INTERNAL_ERROR, resolution=null} property, and of course when opr.get() is called the IllegalStateException is thrown because The Result has already been consumed. I've tried everything, the API is enabled in my console, the checksum from the keystore is up, and the apiClient is connected at the time of the request. I'm having the problem on both emulators and physical devices. I'm desperate. I really appreciate any help.

My OAth client IDs: OAth client setup

Thanks

Chisko
  • 3,092
  • 6
  • 27
  • 45
  • So the problem seems to be related to the Scope my GoogleApiClient is being constructed. [According to this article](https://developers.google.com/android-pay/android/authutils-sso) there are URIs for production and sandbox enviroments but they are currently returning a 404. I'm pretty sure that this is not new, and that there should be another one that's working, but haven't been able to find it – Chisko Feb 12 '16 at 04:06

1 Answers1

2

First of all, you should not get IllegalStateException unless you call opr.get() before isDone() or call it multiple times (no matter it's a failure or success result): https://developers.google.com/android/reference/com/google/android/gms/common/api/OptionalPendingResult.html#get() Could you double check your code / paste your exact code here?

Secondly, the most common issue for INTERNAL_ERROR is missing the right OAuth2 client registration. (Unfortunately, for now, the status code is INTERNAL_ERROR 8, which is not helpful.) E.g. Take a look at this thread: Occured an INTERNAL_ERROR when requestEmail from GoogleSignInOptions Android. Since you already added the Wallet scope, if missing OAuth2 registration, you will see "unregistered Android application" in the consent dialog. (I attached a screenshot at the end)

Thirdly, you can save a lot of boiler plate code by using enableAutoManage. Please check out the official doc: https://developers.google.com/identity/sign-in/android/sign-in (e.g. all those error handling in your onConnectionFailed handler, connect() / disconnect() will both be called automatically). And it's okay to call Auth.GoogleSignInApi.silentSignIn() in your onActivityResult. But a simpler way to get the result is:

GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);

enter image description here

Community
  • 1
  • 1
Isabella Chen
  • 2,421
  • 13
  • 25
  • Hi Isabella, thanks for your reply. I have not doubled but quintupled checked my code and tried several logics and none of them works, plus, this is the basic way the sample app works. Anyhow, you can now see the question updated as you request – Chisko Feb 18 '16 at 02:56
  • 1
    You mentioned you checked opr in "watch variables", which also counts. e.g. you put a breakpoint on the line of "if (opr.isDone())", then continue executing the code, it will go to your catch block. – Isabella Chen Feb 18 '16 at 04:23
  • Could you also check your OAuth2 registration? (that's the most possible cause for INTERNAL_ERROR). I updated my answer with a screenshot. – Isabella Chen Feb 18 '16 at 04:26
  • I also added a better alternative for you without calling silentSignIn inline – Isabella Chen Feb 18 '16 at 04:35
  • Thanks for your suggestion. I do get to see the screen you just attached, but after tapping Allow the error shows up. I'm also aware that watching the opr variable causes it to be read, but the issue presents even after removing the watch or without stopping on the line. I also uploaded a screenshot with the OAth client IDs. Any other idea? – Chisko Feb 18 '16 at 15:37
  • 1. After removing all watches / not using debugging, are you sure it went into your if (opr.isDone()) {} block? Unless it's successful case, otherwise silentSignIn() needs to make IPC to Google Play services and won't be done immediately. Your else block also displays a toast. If it did go into the isDone() block, could you paste me the call stack you printed out? (I saw e.printStackTrace(); in your code). – Isabella Chen Feb 18 '16 at 16:21
  • 2. OAuth registration needs to match package name + signing cert hash. Please check the post I attached. Make sure your registration matches the hash of the cert you use to sign. – Isabella Chen Feb 18 '16 at 16:23
  • 3. Please read my third point in the answer. While I'm curious what's happening to your opr, your sign in result handling code is wrong, e.g. a) opr.isDone() doesn't mean it's success and you can't assume GoogleSignInAccount non-null b) !isDone() doesn't mean network error. Please follow the official sign-in doc for efficient / correct sign-in result parsing (i.e. calling Auth.GoogleSignInApi.getSignInResultFromIntent) – Isabella Chen Feb 18 '16 at 16:28
  • Do you think not having NFC on devices has to do with anything? When using Play Services 8.4.0 the only possible way to perform payments is with Android Pay as you may already know and none of my devices has it. – Chisko Feb 19 '16 at 04:03
  • Your problem is not with Wallet payment api. Your problem is missing OAuth2 client registration + some wrong code handling sign-in result. So it's definitely has nothing to do with NFC. Take a look at my 1-3 above... – Isabella Chen Feb 19 '16 at 04:20
  • The problem was caused by the lack of a break statement finishing the REQUEST_CODE_PICK_ACCOUNT case in onActivityResult(). Thank you for your time and patience @Isabella – Chisko Feb 26 '16 at 02:46