17

I have a background service that calls GoogleAuthUtl.getTokenWithNotification and it works properly but I'm trying to implement the callback portion of this function and that isn't working properly.

I've implemented a broadcast receiver and added it to the manifest, I also have an activity in my app. Below are the relevant pieces of code.

GoogleAuthUtil.getTokenWithNotification

GoogleAuthUtil.getTokenWithNotification(this.getContext(), account, "oauth2:" + GmailScopes.GMAIL_SEND, null, new Intent(AuthReceiver.AUTH_INTENT));

AuthReceiver

public class AuthReceiver extends BroadcastReceiver
{
    public final static String AUTH_INTENT = "com.testoauth.AUTH_INTENT";

    public AuthReceiver()
    {
    }

    @Override
    public void onReceive(Context context, Intent intent)
    {
        Log.d("RECEIVER", "Received Auth broadcast.");
        NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.cancelAll();
    }
}

AndroidManifest

<receiver android:name=".AuthReceiver" android:enabled="true" android:exported="true">
    <intent-filter>
        <action android:name="com.testoauth.AUTH_INTENT" />
    </intent-filter>
</receiver>

I have no clue why it is not receiving the broadcast. I don't see any exceptions in the logs and no indication that the receiver was called at all, it won't even break on a breakpoint when debugging. Am I doing something incorrectly?

EDIT

I'm using min sdk 16 and target sdk 25

From the GoogleAuthUtil.getTokenWithNotification API documentation:

This method is specifically provided for background tasks. In the event of an error that needs user intervention, this method takes care of pushing relevant notification. After the user addresses the notification, the callback is broadcasted. If the user cancels then the callback is not fired.

The callback is not fired regardless of the user canceling or not. Aside from the ActivityManager saying the notification has been displayed (Displayed com.google.android.gms/.auth.uiflows.gettoken.GetTokenActivity), there is no indication that the specified broadcast intent (in this case com.testoauth.AUTH_INTENT) has been sent in the logs. The "Received Auth broadcast." message is also absent from the logs.

The included SDK example of this functionality (<android-sdk>/extras/google/google_play_services/samples/auth/gau) doesn't even work.

vane
  • 2,125
  • 1
  • 21
  • 40
  • Did you find a solution to that problem? I tried the same but it is also not working for me. – Emanuel Nov 03 '16 at 13:37
  • @Emanuel Unfortunately no, I have not. – vane Nov 03 '16 at 18:46
  • Just checking, `AuthReceiver.AUTH_INTENT = "com.testoauth.AUTH_INTENT";`, right? ***///*** Possibly unrelated: Don't use implicit intents, there are limitations coming with each new platform release [(Android O right now)](https://developer.android.com/preview/features/background.html#broadcasts) and your case does not need implicit Intent at all. Just create `new Intent(context, com.package.AuthReceiver.class)`. – Eugen Pechanec May 06 '17 at 12:32
  • @EugenPechanec This isn't the issue, I've tested it and the same broken behavior is observed. – vane May 06 '17 at 16:55

4 Answers4

1

Migrate from GoogleAuthUtil and Plus.API

If you integrated with Google Sign-In in the past using GoogleAuthUtil.getToken or Plus.API, you should migrate to the newest Sign-In API for greater security and a better user experience.
Ref: https://developers.google.com/identity/sign-in/android/migration-guide

Also check this out whether it helps

http://www.programcreek.com/java-api-examples/index.php?source_dir=AndroidAppDeployer-master/AndroidAppDeployer/src/com/appjma/appdeployer/service/DownloadService.java

http://www.programcreek.com/java-api-examples/index.php?source_dir=AndroidAppDeployer-master/AndroidAppDeployer/src/com/appjma/appdeployer/receiver/AuthReceiver.java

Sri Kanth
  • 311
  • 4
  • 18
  • Those 2 examples I've tried before and they don't work either. I'm starting to think this is an actual bug with the Google Play Services. As for the Plus.API, from what I understand that's the have the user login to google and give the app access to certain aspects of their accounts whereas the app I'm trying to create only needs and only wants access to send email and using GoogleAuthUtil allows us to display the connected app and what it needs access to in the user's Google account. – vane May 04 '17 at 05:08
1

I tried following errors on Android API 25, but the callback function was never invoked:

  • No Internet
  • The user has not logged in yet
  • The user has not given permission to send emails on his/her behalf
  • Google Play Services is disabled
  • Google Play Services is out of date

If the callback method invocation is not crucial for your use case, you can follow Android Quickstart for Gmail API to send emails on user's behalf in Android. Check Sending Email to create Message. You can also check MyGoogleAuthUtilApplication created using the above tutorials.

Hope this helps.

BhalchandraSW
  • 714
  • 5
  • 13
  • The problem with this method is that the Gmail API example relies on `MimeMessage` which is in a lib that isn't available for Android, or at least it's not very clear where that lib is for Android. – vane May 06 '17 at 15:43
  • Yes, I had issues with finding the library for `MimeMessage`. In the end, "https://mvnrepository.com/artifact/com.sun.mail/android-mail" <- this library worked for me. You can check my GitHub project. – BhalchandraSW May 06 '17 at 15:46
  • I tested this and it's a great solution for sending email (which I will be migrating to) but it still doesn't solve the callback issue. If I use this I'll still have to either have a broken callback or create my own notification and not use the built in android one. – vane May 06 '17 at 17:53
  • The example you linked, `MyGoogleAuthUtilApplication`, uses the `getTokenWithNotification` and a callback so I'm going to test this but I'm almost positive that even though the sending of email works in that example, the callback isn't actually called. – vane May 06 '17 at 18:02
  • @vane Do not look at `MainActivity` (`MainActivity` is used for testing your question). Please look at and use `GmailActivity` (`GmailActivity` uses liked tutorials and doesn't include `getTokenWithNotification()` at all) in my example. Sorry for the confusion. – BhalchandraSW May 06 '17 at 18:05
  • I did, and it works great but still doesn't solve the problem of getting the callback to fire when the user presses the `Allow` button on the auth screen. Also, I can't use the `EasyPermissions` lib callbacks since I'm using API level 16+. I can use this for sending email in my background service but I'll still need to create my own notification when or if the email sending fails. – vane May 06 '17 at 18:09
  • You can use usual way (https://developer.android.com/training/permissions/requesting.html) instead of `EasyPermissions`. Email sending failure can be caught if it is due to either Google Play Services or Permissions. For example in the case of Permissions, if you `startActivityForResult()` using `UserRecoverableIOException.getIntent()`, you shall be able to catch the event when the user clicks 'Allow' on permissions dialogue. Check `onCancelled()` at line 342-357 in `GmailActivity$SendEmailTask`. – BhalchandraSW May 06 '17 at 18:45
  • I'm already doing this, this is exactly how my backend service is working currently. If I detect a failed email from permissions I create a notification for the user to click at a later time and choose allow or deny. `GoogleAuthUtil` is supposed to handle that for you and allow a callback but the callback is broken so I have to implement my own notification. I can't detect the `Allow` when I attempt to send the email because it's in a service and the user won't be there to choose `Allow` until much later. – vane May 06 '17 at 19:16
1

I have implemented demo i have used latest version auth gradle and its working. Its looks like there might be problem with auth version

public class AuthActivity extends Activity {


    private static final int AUTHORIZATION_CODE = 1993;
    private static final int ACCOUNT_CODE = 1601;

    private AuthPreferences authPreferences;
    private AccountManager accountManager;

    /**
     * change this depending on the scope needed for the things you do in
     * doCoolAuthenticatedStuff()
     */
    private final String SCOPE = "https://www.googleapis.com/auth/googletalk";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        accountManager = AccountManager.get(this);

        authPreferences = new AuthPreferences(this);
        if (authPreferences.getUser() != null
                && authPreferences.getToken() != null) {
            doCoolAuthenticatedStuff();
        } else {
            chooseAccount();
        }
    }

    private void doCoolAuthenticatedStuff() {
        // TODO: insert cool stuff with authPreferences.getToken()

        Log.e("AuthApp", authPreferences.getToken());
        clickSendEmail();
    }

    private void chooseAccount() {
        // use https://github.com/frakbot/Android-AccountChooser for
        // compatibility with older devices
        Intent intent = AccountManager.newChooseAccountIntent(null, null,
                new String[] { "com.google" }, false, null, null, null, null);
        startActivityForResult(intent, ACCOUNT_CODE);
    }

    private void requestToken() {
        Account userAccount = null;
        String user = authPreferences.getUser();
        for (Account account : accountManager.getAccountsByType("com.google")) {
            if (account.name.equals(user)) {
                userAccount = account;
Preferences.setAccount(AuthActivity.this,account.name, account.type);
                break;
            }
        }

        accountManager.getAuthToken(userAccount, "oauth2:" + SCOPE, null, this,
                new OnTokenAcquired(), null);
    }

    /**
     * call this method if your token expired, or you want to request a new
     * token for whatever reason. call requestToken() again afterwards in order
     * to get a new token.
     */
    private void invalidateToken() {
        AccountManager accountManager = AccountManager.get(this);
        accountManager.invalidateAuthToken("com.google",
                authPreferences.getToken());

        authPreferences.setToken(null);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (resultCode == RESULT_OK) {
            if (requestCode == AUTHORIZATION_CODE) {
                requestToken();
            } else if (requestCode == ACCOUNT_CODE) {
                String accountName = data
                        .getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
                authPreferences.setUser(accountName);

                // invalidate old tokens which might be cached. we want a fresh
                // one, which is guaranteed to work
                invalidateToken();

                requestToken();
            }
        }
    }

    private class OnTokenAcquired implements AccountManagerCallback<Bundle> {

        @Override
        public void run(AccountManagerFuture<Bundle> result) {
            try {
                Bundle bundle = result.getResult();

                Intent launch = (Intent) bundle.get(AccountManager.KEY_INTENT);
                if (launch != null) {
                    startActivityForResult(launch, AUTHORIZATION_CODE);
                } else {
                    String token = bundle
                            .getString(AccountManager.KEY_AUTHTOKEN);

                    authPreferences.setToken(token);

                    doCoolAuthenticatedStuff();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void clickSendEmail()
    {
        final Account account = Preferences.getAccount(this);
        final String token = Preferences.getToken(this);

        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {

                    Session session = Session.getDefaultInstance(new Properties(), null);

                    MimeMessage email = new MimeMessage(session);

                    email.setFrom(new InternetAddress(account.name));

                    //TODO: change email address to your email address for testing
                    email.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress("nasitraj2@gmail.com"));
                    email.setSubject("TEST OAUTH EMAIL");
                    email.setText("This is a test");
                    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
                    email.writeTo(bytes);
                    String encodedEmail = Base64.encodeBase64URLSafeString(bytes.toByteArray());
                    Message message = new Message();
                    message.setRaw(encodedEmail);

                    Intent intent = new Intent(AUTH_INTENT);
                    PendingIntent pendingIntent = PendingIntent.getBroadcast(AuthActivity.this, 0, intent, 0);
                    AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
                    alarmManager.set(AlarmManager.RTC_WAKEUP,  System.currentTimeMillis()
                            , pendingIntent);
                    String test = GoogleAuthUtil.getTokenWithNotification(AuthActivity.this, account, "oauth2:" + GmailScopes.GMAIL_SEND, null, intent);
                    GoogleCredential credential = new GoogleCredential();
                    credential.setAccessToken(test);

                    boolean changed = false;
                    if (!test.equals(token))
                    {
                        changed = true;
                        //   Snackbar.make(AuthActivity.this.getView(), "TOKEN CHANGED", Snackbar.LENGTH_LONG).show();
                        Preferences.setToken(AuthActivity.this, test);
                    }



                    Gmail service = new Gmail.Builder(AndroidHttp.newCompatibleTransport(),
                            AndroidJsonFactory.getDefaultInstance(), credential)
                            .setApplicationName("Test OAuth").build();

                    service.users().messages().send("me", message).execute();

                    String msg = "Email sent";
                    if (changed)
                        msg = "TOKEN CHANGED: " + msg;



                }
                catch (MessagingException e)
                {
                    Log.d( "ERROR", e.getMessage());
                }
                catch (GoogleJsonResponseException e)
                {
                    if (e.getDetails().getCode() == 401)
                    {
                        try
                        {
                            Intent intent = new Intent(AUTH_INTENT);
                            GoogleAuthUtil.clearToken(AuthActivity.this, Preferences.getToken(AuthActivity.this));
                            GoogleAuthUtil.getTokenWithNotification(AuthActivity.this, account, "oauth2:" + GmailScopes.GMAIL_SEND, null, intent);
                        }
                        catch (Exception e1)
                        {
                            //ignore
                        }
                    }
                }
                catch (IOException e)
                {
                    //  Snackbar.make(AuthActivity.this.getView(), "ERROR", Snackbar.LENGTH_LONG).show();
                    Log.d( "ERROR", e.getMessage());
                }
                catch (Exception e)
                {
                    //Snackbar.make(AuthActivity.this.getView(), "ERROR", Snackbar.LENGTH_LONG).show();
                    Log.d( "ERROR", e.getMessage());
                }
            }
        }).start();
    }
}
Rajesh N
  • 6,198
  • 2
  • 47
  • 58
  • This doesn't fix my problem in any way. The `AccountManager.getAuthToken` is for foreground operation only. I need to use `GoogleAuthUtil.getTokenWithNotification` because it's being done in a service in the background and it also puts a notification in the status bar and getAuthToken does not. – vane May 06 '17 at 17:12
  • oh right now it's too late night here. I will check for service tomorrow. But i have one question, you have to use AccountManager only 1 time to get token then there is no need of it. So is it not good for you if you take token from UI? Also without ui how user can select account? – Rajesh N May 06 '17 at 17:47
  • No, I need to check the token in a background service when the service is started. This is to be able to show a notification to the user if they removed the authorization for the app from their google account settings before the service is called at a later time. Otherwise, it would fail to use the token since the token is no longer valid. – vane May 06 '17 at 18:06
  • you mean if user is not authorized then authorization process will be done from notification in background service? can you post your service code ? – Rajesh N May 07 '17 at 03:42
  • No, I can't post the code sorry, wish I could. Basically it works like this: 1) service starts and attempts to get token 2) call to getTokenWithNotification which fails 3) a notification is placed in the status bar (via Google Play Services getTokenWithNotification) 4) at a later time the user clicks the notification 5) an Allow/Deny dialog is displayed (from Google Play Services) 6) NOT WORKING; the callback will fire when the user clicks allow so I can do some cleanup work. Step 6 does not work and is bugged – vane May 07 '17 at 04:06
0

It doesn't appear that anyone can give a proper answer to this question; plenty of absolutely great suggestions on how to work around the issue but nothing answering the actual question. I've come to the conclusion that this must be a bug in either Android or the Google Play Services. Unfortunately I've reported this issue to both the Android issue tracker and the Google Play Services support forum... Both of which point the finger at each other and refuse to even look at the issue.

vane
  • 2,125
  • 1
  • 21
  • 40