5

I'm trying to login into my application using GoogleAccountCredential for the authentication:

mGoogleAccountCredential = GoogleAccountCredential.usingOAuth2(context, Arrays.asList(Scopes.EMAIL, Scopes.PLUS_LOGIN));
mGoogleAccountCredential.setSelectedAccountName(accountName);
String token = mGoogleAccountCredential.getToken();

It works just fine on real devices, but on the android emulator mGoogleAccountCredential.getToken() fails with the following exception:

java.lang.IllegalArgumentException: the name must not be empty: null
03-01 19:41:31.604 3203-3361/com.myapp W/System.err:     at android.accounts.Account.<init>(Account.java:48)
03-01 19:41:31.604 3203-3361/com.myapp  W/System.err:     at com.google.android.gms.auth.GoogleAuthUtil.getToken(Unknown Source)
03-01 19:41:31.604 3203-3361/com.myapp  W/System.err:     at com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential.getToken(GoogleAccountCredential.java:255)
  • Google Play Services present on the emulator (GoogleApiAvailability.isGooglePlayServicesAvailable(context) returns 0)
  • accountName is set and correct when passed to the setSelectedAccountName (set to "myuser@gmail.com")
  • All the permissions, dependecies and configurations exist in the project (as a matter of fact, it works on all the real devices)

Any clue why isn't it working on the emulator?

UPD:
After digging a bit in Google's code: the issue occurs in setSelectedAccountName(accountName) method. This method asks GoogleAccountManager to give him an account associated with the given account name. If there is no such an account, the account name is being set to null:

  public final GoogleAccountCredential setSelectedAccountName(String accountName) {
    selectedAccount = accountManager.getAccountByName(accountName);
    // check if account has been deleted
    this.accountName = selectedAccount == null ? null : accountName;
    return this;
  }

AccountManager, in turn, goes over all the existing account and compares their names to the given account name. If there is a match, the appropriate account is returned:

  public Account getAccountByName(String accountName) {
    if (accountName != null) {
      for (Account account : getAccounts()) {
        if (accountName.equals(account.name)) {
          return account;
        }
      }
    }
    return null;
  }

  public Account[] getAccounts() {
    return manager.getAccountsByType("com.google");
  }

The thing is that getAccounts() returns empty array on the emulator. On a real device, however, it returns a proper list.

JeB
  • 11,653
  • 10
  • 58
  • 87
  • Do you use Google API System Image ? – Anthony Mar 09 '16 at 09:39
  • Yes of course, I specified it in my question – JeB Mar 09 '16 at 10:15
  • Just to be sure (as I'm not deep experience in this topic), did you use the "Google APIs Item x86 System Image" to build your Virtual Device, and not just "Item x86 System Image" ? I didn't see where you specified this (maybe indirectly) – Anthony Mar 09 '16 at 11:13
  • Yes I used the Google APIs image. Otherwise `GoogleApiAvailability.isGooglePlayServicesAvailable(context)` wouldn't return 0. – JeB Mar 09 '16 at 12:28
  • 1
    @binyan Checkout this [answer](http://stackoverflow.com/a/34701518/645762). TL;DR You need to have `GET_ACCOUNTS` permission in your manifest and request it at runtime, as appropriate. – blizzard Mar 09 '16 at 17:43
  • I do have this permission in my manifest. In fact the whole flow works on all the real devices. The problem appears only on the emulator. – JeB Mar 09 '16 at 17:50
  • What is the value passed on "accountName" variable? – Gustavo Morales Mar 10 '16 at 12:39
  • A valid account (let's say myuser@gmail.com). Checked it with the debugger, the value is valid. – JeB Mar 10 '16 at 12:47
  • You're a lifesaver. I could have spent a lot of time trying to track this down, since I only have an emulator (no device). My understanding is that they are working on getting play services onto the emulator this year. – Stevey Feb 23 '17 at 05:21

3 Answers3

2

Well, as always things are easier than they seem.
Thanks to this post and to b1izzar for pointing to the right answer.

All the real devices I checked on are running Android 5.1 Lollipop.
All the emulators I checked on are running Android 6.0 Marshmallow.

On Marshmallow, i.e. on my emulator, it's not enough to specify GET_ACCOUNTS permission in manifest. It is mandatory to request this permission at runtime with a specific code:

Request permissions:

// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
                Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {

    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            Manifest.permission.READ_CONTACTS)) {

        // Show an expanation to the user *asynchronously* -- don't block
        // this thread waiting for the user's response! After the user
        // sees the explanation, try again to request the permission.

    } else {

        // No explanation needed, we can request the permission.

        ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);

        // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
        // app-defined int constant. The callback method gets the
        // result of the request.
    }
}

Note: in Marshmallow GET_ACCOUNTS, WRITE_CONTACTS and READ_CONTACTS permissions are in the same permission group, so once READ_CONTACTS is granted, GET_ACCOUNTS is granted as well.

Note 2: in Android Nougat GET_ACCOUNTS is deprecated, so it makes sense to use READ_CONTACTS instead of GET_ACCOUNT even in Marshmallow.

Community
  • 1
  • 1
JeB
  • 11,653
  • 10
  • 58
  • 87
1

Perhaps the emulator is running an older version of Google Services. It appears that the latest version would throw GoogleAuthException as opposed to IllegalArgumentException.

API Doc

public String getToken()
                throws IOException,
                       com.google.android.gms.auth.GoogleAuthException
Returns an OAuth 2.0 access token.
Must be run from a background thread, not the main UI thread.

Throws:
IOException
com.google.android.gms.auth.GoogleAuthException
Ryan Ford
  • 326
  • 2
  • 9
  • IllegalArgumentException is a runtime exception, not a checked one. Therefore it is not specified in the signature of the function. – JeB Mar 09 '16 at 20:48
1

I think that the problem is that you must use a physical device for developing and testing because Google Play services cannot be installed on an emulator.

I don't see another reason, but here you have a code snippet taken from tasks-android-sample that also use GoogleAccountCredential.

Gustavo Morales
  • 2,614
  • 9
  • 29
  • 37
  • I use the image that does have the Google Play Services installed ("Google API System Image"). In fact, I'm able to login into my gmail on the emulator. – JeB Mar 10 '16 at 13:32