3

UPDATE

SOLVED - Thank's to @MusiGenesis persistence with this I solved the problem by registering a new Google mail account and a new C2DM account. After updating the relevant credentials in the web server and the android app all started working like magic.

END OF UPDATE

I'm looking for a definitive list for causes of 401 unauthorised errors when sending a push notification so I can try to eliminate my problem.

I have a google email registered with C2DM I can use curl to get an authorisation code

I have the auth token from the registered user on my android app

Using the 2 auth tokens (refreshed) I get 401 unauthorised errors from my web server when sending a push notification request from my web server.

As far as I can tell I am doing everything I need to do so I'm looking for what I may be missing. I have scoured the internet and lots of people seem to be having the same problem with no definite answer. Any help greatly appreciated

UPDATE

As mentioned in the answer below it seems there is a second stage required to get a registration ID which appears to be different from the auth token received by the registered user on the android app. Having looked at the jumpnote code and these two resources

http://www.vogella.de/articles/AndroidCloudToDeviceMessaging/article.html#implementation_mobileregistration

and

http://marakana.com/forums/android/general/272.html

I see no info regarding a second registration call to get a regisration ID in addition to the auth token. I'm obviously missing something and would be gratefull if someone could spell this out for me.

** UPDATE 2 **

My C2DM Receiver looks like this

public class C2DMReceiver extends C2DMBaseReceiver {
    public C2DMReceiver() {
        super(REGISTERED_GOOGLE_MAIL_ADDRESS);
    }

    @Override
    public void onRegistered(Context context, String registrationId)
            throws java.io.IOException {
        // The registrationId should be sent to your application server.
        Log.e("C2DM", "Registration ID arrived!");
        Log.e("C2DM", registrationId);
        Intent webSeverReg = new Intent(this, RegService.class);
        startService(webServerReg);     
    };

    @Override
    protected void onMessage(Context context, Intent intent) {
        Log.e("C2DM", "Message: Fantastic!!!");
        // Extract the payload from the message
        Bundle extras = intent.getExtras();
        if (extras != null) {
            System.out.println(extras.get("payload"));
            // Now do something smart based on the information
        }
    }

    @Override
    public void onError(Context context, String errorId) {
        Log.e("C2DM", "Error occured!!!");
    }
}

C2DMBaseReceiver taken from jumpnote app looks like this

/**
 * Base class for C2D message receiver. Includes constants for the
 * strings used in the protocol.
 */
public abstract class C2DMBaseReceiver extends IntentService {
    private static final String C2DM_RETRY = "com.google.android.c2dm.intent.RETRY";

    public static final String REGISTRATION_CALLBACK_INTENT = "com.google.android.c2dm.intent.REGISTRATION";
    private static final String C2DM_INTENT = "com.google.android.c2dm.intent.RECEIVE";

    // Logging tag
    private static final String TAG = "C2DM";

    // Extras in the registration callback intents.
    public static final String EXTRA_UNREGISTERED = "unregistered";

    public static final String EXTRA_ERROR = "error";

    public static final String EXTRA_REGISTRATION_ID = "registration_id";

    public static final String ERR_SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE";
    public static final String ERR_ACCOUNT_MISSING = "ACCOUNT_MISSING";
    public static final String ERR_AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED";
    public static final String ERR_TOO_MANY_REGISTRATIONS = "TOO_MANY_REGISTRATIONS";
    public static final String ERR_INVALID_PARAMETERS = "INVALID_PARAMETERS";
    public static final String ERR_INVALID_SENDER = "INVALID_SENDER";
    public static final String ERR_PHONE_REGISTRATION_ERROR = "PHONE_REGISTRATION_ERROR";

    // wakelock
    private static final String WAKELOCK_KEY = "C2DM_LIB";

    private static PowerManager.WakeLock mWakeLock;
    private final String senderId;

    /**
     * The C2DMReceiver class must create a no-arg constructor and pass the 
     * sender id to be used for registration.
     */
    public C2DMBaseReceiver(String senderId) {
        // senderId is used as base name for threads, etc.
        super(senderId);
        this.senderId = senderId;
    }

    /**
     * Called when a cloud message has been received.
     */
    protected abstract void onMessage(Context context, Intent intent);

    /**
     * Called on registration error. Override to provide better
     * error messages.
     *  
     * This is called in the context of a Service - no dialog or UI.
     */
    public abstract void onError(Context context, String errorId);

    /**
     * Called when a registration token has been received.
     */
    public void onRegistered(Context context, String registrationId) throws IOException {
        // registrationId will also be saved
    }

    /**
     * Called when the device has been unregistered.
     */
    public void onUnregistered(Context context) {
    }


    @Override
    public final void onHandleIntent(Intent intent) {
        Log.d(TAG, "@@@@ - onHandleIntent Messaging request received");
        try {
            Context context = getApplicationContext();
            if (intent.getAction().equals(REGISTRATION_CALLBACK_INTENT)) {
                handleRegistration(context, intent);
            } else if (intent.getAction().equals(C2DM_INTENT)) {
                onMessage(context, intent);
            } else if (intent.getAction().equals(C2DM_RETRY)) {
                C2DMessaging.register(context, senderId);
            }
        } finally {
            //  Release the power lock, so phone can get back to sleep.
            // The lock is reference counted by default, so multiple 
            // messages are ok.

            // If the onMessage() needs to spawn a thread or do something else,
            // it should use it's own lock.
            mWakeLock.release();
        }
    }


    /**
     * Called from the broadcast receiver. 
     * Will process the received intent, call handleMessage(), registered(), etc.
     * in background threads, with a wake lock, while keeping the service 
     * alive. 
     */
    static void runIntentInService(Context context, Intent intent) {
        if (mWakeLock == null) {
            // This is called from BroadcastReceiver, there is no init.
            PowerManager pm = 
                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 
                    WAKELOCK_KEY);
        }
        mWakeLock.acquire();

        // Use a naming convention, similar with how permissions and intents are 
        // used. Alternatives are introspection or an ugly use of statics. 
        String receiver = context.getPackageName() + ".C2DMReceiver";
        intent.setClassName(context, receiver);

        context.startService(intent);

    }


    private void handleRegistration(final Context context, Intent intent) {
        final String registrationId = intent.getStringExtra(EXTRA_REGISTRATION_ID);
        Log.d(TAG, "@@@@ - HandleRegistration Messaging request received");
        String error = intent.getStringExtra(EXTRA_ERROR);
        String removed = intent.getStringExtra(EXTRA_UNREGISTERED);

        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "dmControl: registrationId = " + registrationId +
                ", error = " + error + ", removed = " + removed);
        }

        if (removed != null) {
            // Remember we are unregistered
            C2DMessaging.clearRegistrationId(context);
            onUnregistered(context);
            return;
        } else if (error != null) {
            // we are not registered, can try again
            C2DMessaging.clearRegistrationId(context);
            // Registration failed
            Log.e(TAG, "Registration error " + error);
            onError(context, error);
            if ("SERVICE_NOT_AVAILABLE".equals(error)) {
                long backoffTimeMs = C2DMessaging.getBackoff(context);

                Log.d(TAG, "Scheduling registration retry, backoff = " + backoffTimeMs);
                Intent retryIntent = new Intent(C2DM_RETRY);
                PendingIntent retryPIntent = PendingIntent.getBroadcast(context, 
                        0 /*requestCode*/, retryIntent, 0 /*flags*/);

                AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
                am.set(AlarmManager.ELAPSED_REALTIME,
                        backoffTimeMs, retryPIntent);

                // Next retry should wait longer.
                backoffTimeMs *= 2;
                C2DMessaging.setBackoff(context, backoffTimeMs);
            } 
        } else {
            try {
                onRegistered(context, registrationId);
                C2DMessaging.setRegistrationId(context, registrationId);
            } catch (IOException ex) {
                Log.e(TAG, "Registration error " + ex.getMessage());
            }
        }
    }
}
jamesc
  • 12,423
  • 15
  • 74
  • 113
  • It seems there is no problem with my code and there is no step 2 as mentioned in the below answer – jamesc Oct 12 '11 at 21:51

1 Answers1

3

You say you "have the auth token from the registered user on my android app". You may have just written that incorrectly, but if you mean literally that you're using the registered user's auth token and not the registration id the user got back from the C2DM server, then there's your problem right there.

Edit: Your client app (running on the device) uses a 3-step process for C2DM: 1) call the C2DM server passing the client's gmail account id and password, get back an auth token; 2) call the C2DM server again using the auth token from step 1, get back a registration id (which is 96-120 characters of ASCII splooge); 3) call your server app and pass the registration id obtained in step 2 (not the auth token obtained in step 1).

When your server app then wants to push something to the client, it makes a call to the C2DM server to get an auth token (passing the email and password you used to sign up for the C2DM server, not the client user's email and password), then uses that auth token along with the client's registration id to execute the push.

Edit 2: my description here of what happens on the client is wrong - the client code does not involve getting an oauth token at any point. All of that stuff is handled by the Android OS itself. This tutorial:

http://www.vogella.de/articles/AndroidCloudToDeviceMessaging/article.html

shows nicely how everything works for C2DM.

Edit 3: The most common mistake I've seen with C2DM comes from the documentation's use of the phrase "sender's email". This term refers to the gmail account that was "registered" for use with C2DM, and does not refer to the gmail account of the Android user. This gmail account is used by your web server app (along with the matching password) to get an oauth token from C2DM. This same account needs to be used by the Android client app (without the matching password, which it doesn't know) to make the call that gets back a registrationId that it then sends to your web server.

MusiGenesis
  • 74,184
  • 40
  • 190
  • 334
  • Thank you for the response - I am missing that second stage. Is this documented anywhere - All the documentation I have seen just shows getting the auth token for the app user and no mention is made of a second call to get a registration ID. – jamesc Oct 09 '11 at 13:50
  • 1
    This is a good C2DM tutorial that covers everything: http://www.vogella.de/articles/AndroidCloudToDeviceMessaging/article.html – MusiGenesis Oct 09 '11 at 13:55
  • Thanks again, there is no mention on either of those links of a second call from the android app. I'm obviously missing something. – jamesc Oct 09 '11 at 17:37
  • @jamesw: I think I mis-spoke in my answer here. I looked at my code again and the client app does not get an oauth token at any point in the process - instead it just makes the registration request once, and then the C2DMReceiver instance (in the vogella.de sample) gets a callback to its onRegistered method, where your app receives the registration id. The oauth token is only involved in your server app, which 1) gets the oauth token, and then 2) uses the oauth token to send a payload to the C2DM server, which in turn pushes it on to your client app. – MusiGenesis Oct 19 '11 at 16:21
  • If your client is getting an oauth token at any point, then probably what's happened is that you took the code meant to run on your server app and you're instead running that code on the client. Again, the vogella.de sample works perfectly - I have used it on many occasions. – MusiGenesis Oct 19 '11 at 16:23
  • As detailed in my original question I have an auth token for the app and an auth token for the web server - My code is working fine but still no explanation as to why C2DM is giving 401 unauthorised errors when sending a push notification? What causes them? – jamesc Oct 19 '11 at 17:08
  • Your app should not have an auth token at all - it should have a registration id, which it gets from the C2DM callback to your app's onRegistered method. If you're getting an oauth token in your client app (which is possible if you take the code meant to run on the web server and instead run it from your client app), then the push attempt will fail. – MusiGenesis Oct 19 '11 at 18:07
  • Also, I'm not sure what you mean by "my code is working fine" if you're getting a 401 response when attempting to push something. That suggests your code is not working end-to-end. – MusiGenesis Oct 19 '11 at 18:08
  • Thanks for the responses. I get the correct tokens in the app and in the web server. What I meant by "My code is working fine" is that all the code that gets the tokens and sends the tokens is correct and my issue does not seem to be related to the actual auth tokens/registration ID's/whatever you want to call them. It's something else, most likely something in my manifest file or some kind of setup/configuration issue. – jamesc Oct 19 '11 at 20:22
  • 1
    The most common mistake I've seen with C2DM comes from the documentation's use of the phrase "sender's email". This term refers to the gmail account that was "registered" for use with C2DM, and does not refer to the gmail account of the Android user. This gmail account is used by your web server app (along with the matching password) to get an oauth token from C2DM. This same account needs to be used by the Android client app (without the matching password, which it doesn't know) to make the call that gets back a registrationId that it then sends to your web server. – MusiGenesis Oct 19 '11 at 21:25
  • ok. I've set up a new C2DM account with a new Google eMail address and you know what? Everything works! Great newsd in one way but I'm left wondering why my original eMail address/c2dm account wouldn't work! Thank you. – jamesc Oct 20 '11 at 02:05
  • If you would like to add your last comment to your answer (So others reading this will know the solution I'll happily accept your answer and thank you for sticking with this – jamesc Oct 20 '11 at 02:07
  • @jamesw: no problem. I'm sorry I started this out with fundamentally wrong information. – MusiGenesis Oct 20 '11 at 12:36