Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C2DMBroadcastReceiver's onReceive is not executing (For Registration)

Im developing a C2DM Messaging application. In that i order to receive the registration id im using the C2DMBroadcastReceiver, C2DMBaseReceiver and C2DMMessaging class. I will be C2DMReceiver in my package which extends the C2DMBaseReceiver.

Here is my code snippet

C2DMMessaging.java

package com.google.android.c2dm;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;

public class C2DMessaging {
    public static final String EXTRA_SENDER = "sender";
    public static final String EXTRA_APPLICATION_PENDING_INTENT = "app";
    public static final String REQUEST_UNREGISTRATION_INTENT = "com.google.android.c2dm.intent.UNREGISTER";
    public static final String REQUEST_REGISTRATION_INTENT = "com.google.android.c2dm.intent.REGISTER";
    public static final String LAST_REGISTRATION_CHANGE = "last_registration_change";
    public static final String BACKOFF = "backoff";
    public static final String GSF_PACKAGE = "com.google.android.gsf";

    // package
    static final String PREFERENCE = "com.google.android.c2dm";

    private static final long DEFAULT_BACKOFF = 30000;

    /**
     * Initiate c2d messaging registration for the current application
     */
    public static void register(Context context,
            String senderId) {
        Intent registrationIntent = new Intent(REQUEST_REGISTRATION_INTENT);
        registrationIntent.setPackage(GSF_PACKAGE);
        registrationIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT,
                PendingIntent.getBroadcast(context, 0, new Intent(), 0));
        registrationIntent.putExtra(EXTRA_SENDER, senderId);
        context.startService(registrationIntent);
        Log.e("C2DM Services","Service Started");

    }

    /**
     * Unregister the application. New messages will be blocked by server.
     */
    public static void unregister(Context context) {
        Intent regIntent = new Intent(REQUEST_UNREGISTRATION_INTENT);
        regIntent.setPackage(GSF_PACKAGE);
        regIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT, PendingIntent.getBroadcast(context,
                0, new Intent(), 0));
        context.startService(regIntent);
        Log.e("C2DM Services","unregister");
    }

    /**
     * Return the current registration id.
     *
     * If result is empty, the registration has failed.
     *
     * @return registration id, or empty string if the registration is not complete.
     */
    public static String getRegistrationId(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        String registrationId = prefs.getString("dm_registration", "");
        Log.e("C2DM Services","get registration id");
        return registrationId;

    }

    public static long getLastRegistrationChange(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        Log.e("C2DM Services","getlastregchange");
        return prefs.getLong(LAST_REGISTRATION_CHANGE, 0);
    }

    static long getBackoff(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        Log.e("C2DM Services","getbackoff");
        return prefs.getLong(BACKOFF, DEFAULT_BACKOFF);
    }

    static void setBackoff(Context context, long backoff) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        Editor editor = prefs.edit();
        editor.putLong(BACKOFF, backoff);
        editor.commit();
        Log.e("C2DM Services","setbackoff");
    }

    // package
    static void clearRegistrationId(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        Editor editor = prefs.edit();
        editor.putString("dm_registration", "");
        editor.putLong(LAST_REGISTRATION_CHANGE, System.currentTimeMillis());
        editor.commit();
        Log.e("C2DM Services","clearregid");
    }

    // package
    static void setRegistrationId(Context context, String registrationId) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        Editor editor = prefs.edit();
        editor.putString("dm_registration", registrationId);
        editor.commit();
        Log.e("C2DM Services","setregid");
    }
}

C2DMBroadcastReceiver.java

package com.google.android.c2dm;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class C2DMBroadcastReceiver extends BroadcastReceiver {

    @Override
    public final void onReceive(Context context, Intent intent) {
        // To keep things in one place.
           Log.e("C2DM Broadcast receiver","onReceive");
        C2DMBaseReceiver.runIntentInService(context, intent);
        setResult(Activity.RESULT_OK, null /* data */, null /* extra */);        
    }

}

Manifest file

<permission android:name="com.sample.gt.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
 <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="com.sample.gt.permission.C2D_MESSAGE" />
    <!-- Permissions -->
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <service android:name="com.sample.gt.c2dm.C2DMReceiver" />

    <!--
        Only C2DM servers can send messages for the app. If permission is not
        set - any other app can generate it
    -->
    <receiver android:name="com.google.android.c2dm.C2DMBroadcastReceiver"
        android:permission="com.google.android.c2dm.permission.SEND">
        <!-- Receive the actual message -->
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <category android:name="com.sample.gt.c2dm" />
        </intent-filter>
        <!-- Receive the registration id -->
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
            <category android:name="com.sample.gt.c2dm" />
        </intent-filter>
    </receiver>

C2DMMessaging.java

public class C2DMessaging {
    public static final String EXTRA_SENDER = "sender";
    public static final String EXTRA_APPLICATION_PENDING_INTENT = "app";
    public static final String REQUEST_UNREGISTRATION_INTENT = "com.google.android.c2dm.intent.UNREGISTER";
    public static final String REQUEST_REGISTRATION_INTENT = "com.google.android.c2dm.intent.REGISTER";
    public static final String LAST_REGISTRATION_CHANGE = "last_registration_change";
    public static final String BACKOFF = "backoff";
    public static final String GSF_PACKAGE = "com.google.android.gsf";


    // package
    static final String PREFERENCE = "com.google.android.c2dm";

    private static final long DEFAULT_BACKOFF = 30000;

    /**
     * Initiate c2d messaging registration for the current application
     */
    public static void register(Context context,
            String senderId) {
        Intent registrationIntent = new Intent(REQUEST_REGISTRATION_INTENT);
        registrationIntent.setPackage(GSF_PACKAGE);
        registrationIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT,
                PendingIntent.getBroadcast(context, 0, new Intent(), 0));
        registrationIntent.putExtra(EXTRA_SENDER, senderId);
        context.startService(registrationIntent);
        Log.e("C2DM Services","Service Started");
   }
   ..........

}

Now my problem is,

I calling the Register of the C2DMMessaging from my activity by passing the context, The service is created in the C2DMMessaging, After that im not receiving anything in the C2DMBroadcastReceiver's onReceive().

This is the code i got from vogille.de. This is working fine if i use this as such but when im using this in my application this problem is coming.

I have gone thro' some post n googled something in which i found that the problem may be in manifest file.

i don know where im wrong. Can anyone help on this?

like image 975
Hussain Avatar asked Jul 15 '11 10:07

Hussain


3 Answers

For an obscure reason, your receiver class, C2DMReceiver, has to be in the root of your package, which is declared as your package name in your manifest. This is the only way i managed to get this working... so instead of :

service android:name="com.sample.gt.c2dm.C2DMReceiver" 

try

service android:name=".C2DMReceiver"

here is my manifest excerpt (except the permissions)

 <!--C2DM -->
    <service android:name=".C2DMReceiver" />
    <receiver android:name="com.google.android.c2dm.C2DMBroadcastReceiver"
        android:permission="com.google.android.c2dm.permission.SEND">
        <!-- Receive the actual message -->
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <category android:name="my.package" />
        </intent-filter>
        <!-- Receive the registration id -->
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
            <category android:name="my.package" />
        </intent-filter>
    </receiver>
like image 160
olamotte Avatar answered Nov 12 '22 15:11

olamotte


I feel your pain, I struggled getting C2DM to work as well looking at vogille.de amongst other sites. What ended up working for me was to use the C2DM.jar file created by the eclipse "App Engine Connected Android Project" (under File > New > Project > Google).

Note: at the time of this writing you must install the beta version of the plugin to have this option! http://code.google.com/eclipse/beta/docs/download.html

Relevant portions of my manifest file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      ...
>
    <permission
        android:name="my_package_name.permission.C2D_MESSAGE"
        android:protectionLevel="signature"
    />
    <uses-permission android:name="my_package_name.permission.C2D_MESSAGE" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <application
       ...
    >
        <!-- Only C2DM servers can send messages for the app. If permission is not set - any other app can generate it --> 
        <receiver
            android:name="com.google.android.c2dm.C2DMBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND"
        >
            <!-- Receive the actual message -->
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="my_package_name" />
            </intent-filter>
            <!-- Receive the registration id -->
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="my_package_name" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

Here's the code I use to interact with the C2DM service:

package my_package_name

import com.google.android.c2dm.C2DMBaseReceiver;
import com.google.android.c2dm.C2DMessaging;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

/**
 * Receive C2DM state changes.
 * 
 * Be careful: the various onX receivers may be called from a mysterious
 * context -- in particular they cannot safely create new AsyncTask objects.
 */
public class C2DMReceiver extends C2DMBaseReceiver {

    // GMail account associated with the C2DM application.  Must agree with
    // what the 3rd party server uses to authenticate with C2DM.
    private static final String C2DM_SENDER = "[email protected]";

    // -----------------------------------------------------------------

    /**
     * Ask this device to register for C2DM messaging.  Will trigger
     * onRegistered (or onError) when finished.
     */
    public static void register(Context context) {
        C2DMessaging.register(context, C2DM_SENDER);
    }

    /**
     * Unregister this device from further C2DM messaging.
     */
    public static void unregister(Context context) {
        C2DMessaging.unregister(context);
    }

    // -----------------------------------------------------------------

    public C2DMReceiver() {
        super(C2DM_SENDER);
    }

    @Override
    protected void onMessage(Context context, Intent intent) {
        // Extras contains whatever your server put into the C2DM message.
        final Bundle extras = intent.getExtras();
    }

    @Override
    public void onError(Context context, String error_id) {
    }

    @Override
    public void onRegistered(Context context, String registration_id) {
    }

    @Override
    public void onUnregistered(Context context) {
    }
}

The sample code generated includes a Java based AppEngine app. I am using python, here's the relevant code to round out this post:

class C2dmAuthToken(db.Model):
    """Maintain an auth token used to talk to the C2DM service.  There is at
    most one of these records."""
    role_email = db.StringProperty(indexed=False, default='[email protected]')
    passwd = db.StringProperty(indexed=False, default='my_password')
    token = db.TextProperty(indexed=False, default='')

class C2dmRegistration(db.Model):
    """Map from user to the C2DM registration id needed for the C2DM
    service to send messages to the registered device."""
    user_id = db.IntegerProperty(required=True)
    registration_id = db.StringProperty(indexed=False)

class RegisterHandler(MyRequestHandler.MyRequestHandler):
    def post(self):
        # Parse arguments.
        user_id = self.parseId('user_id')
        registration_id = self.parseStr('registration_id')

        # Create or update the device record.
        record = C2dmRegistration.gql('WHERE user_id = :1', user_id).get()
        if record == None:
            record = C2dmRegistration(user_id=user_id)
        record.registration_id = registration_id
        record.put()

class UnregisterHandler(MyRequestHandler.MyRequestHandler):
    def post(self):
        # Parse arguments.
        user_id = self.parseId('user_id')

        # Unregister this device.
        record = C2dmRegistration.gql('WHERE user_id = :1', user_id).get()
        if record != None:
            record.delete()

def getAuthToken():
    """Return an auth token associated with the role account.  Login to
    Google and store the auth token if needed."""
    token_record = C2dmAuthToken.all().get()
    if token_record == None:
        token_record = C2dmAuthToken()

    if len(token_record.token) > 0:
        return token_record.token

    form_fields = {
        'accountType' : 'GOOGLE',
        'Email' : token_record.role_email,
        'Passwd' : token_record.passwd,
        'service' : 'ac2dm',
        'source' : 'my_source_name',
    }
    headers = {
        'Content-Type' : 'application/x-www-form-urlencoded',
    }
    result = urlfetch.fetch(url='https://www.google.com/accounts/ClientLogin',
                            payload=urllib.urlencode(form_fields),
                            method=urlfetch.POST,
                            headers=headers)
    if result.status_code != 200:
        logging.warning('getAuthToken: client login http error %d' % result.status_code)
        return None

    for line in result.content.split('\n'):
        if line.startswith('Auth='):
            token_record.token = line[5:]

    if len(token_record.token) == 0:
        logging.warning('getAuthToken: no token')
        return None

    logging.info('getAuthToken allocated new token %s' % token_record.token)
    token_record.put()
    return token_record.token

def setAuthToken(token):
    """Update the auth token."""
    token_record = C2dmAuthToken.all().get()
    if token_record == None:
        token_record = C2dmAuthToken()
    token_record.token = token
    token_record.put()

def sendMessage(dst_user_id, message):
    """Send a message to the dst user's device using C2DM."""

    registration_record = C2dmRegistration.gql('WHERE user_id = :1', dst_user_id).get()
    if registration_record == None:
        logging.warning('SendMessage: no such dst_user_id %ld' % dst_user_id)
        return False

    # Use http and not https to avoid an invalid certificate error.
    # Since the request originates inside google hopefully it is
    # never snoop-able to the outside world, and does not contain
    # critically secure info (such as the role password).
    form_fields = {
        'registration_id' : registration_record.registration_id,
        'collapse_key' : '%d' % int(time.time() * 1000),
        'data.message' : message,
    }
    headers = {
        'Content-Type' : 'application/x-www-form-urlencoded',
        'Authorization': 'GoogleLogin auth=%s' % getAuthToken(),
    }
    result = urlfetch.fetch(url='http://android.apis.google.com/c2dm/send',
                            payload=urllib.urlencode(form_fields),
                            method=urlfetch.POST,
                            headers=headers)
    if result.status_code != 200:
        logging.warning('sendMessage: http error %d' % result.status_code)
        return None
    if 'Update-Client-Auth' in result.headers:
        logging.info('updating auth token')
        setAuthToken(result.headers['Update-Client-Auth'])
    return True

def main():    
    application = webapp.WSGIApplication([
        ('/c2dm/register', RegisterHandler),
        ('/c2dm/unregister', UnregisterHandler),
       ], debug=True)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
    main()

Your Android app should invoke the /c2dm/register and /c2dm/unregister methods to set and clear the devices c2dm token with the backend. Other backend code should call sendMessage to ask Google to relay the message to a device.

This code includes your gmail password. I use a throwaway gmail address for my c2dm needs and actually set the password via direct datastore manipulation instead of having it in plaintext in code. Even still if anyone knows a better way to manage authentication I'd love to hear about it.

I hope this helps!

like image 43
Darrell Avatar answered Nov 12 '22 15:11

Darrell


Let me start by saying your IDE setup is not the problem here, and this process doesn't even need a server(for now) with app engine which is what adds the Google option under file > New > Project

The android device contacts google C2DM servers to get the registration id if the process succeeds Google replies with the registration id, which later you can send to your server, for now we will be trying to get the process to work and have the registration id on the device, then later you could handle that.

As i understood, you created an Android Project, took the Google classes which they used in their chrome to phone example, which vogella.de provided in their tutorial

after doing so in your startup activity you called the method C2DMMessaging.register(this, "[email protected]);

there are a number of things that can be wrong here: p.s. these are ordered according to their likeliness to occur by my standards, also i have excluded some cases that appear not to apply to the snippets you mentioned.

  1. you have not registered you role email account with google.
    Please head over to http://code.google.com/android/c2dm/signup.html and do the following accept the license after reading it carefully
    fill all the required information as accurately as possible, and pay attention to the following fields:
    "Package name of your Android app" and "Role (sender) account email"
    this role email is the one you will be using in the C2DMMessaging.register method and your application's server later on
  2. You are testing on an android emulator that is not correctly configured for this test
    For your android emulator to be configured for this task the following should be done:
    Create a new AVD (Android Virtual Device) by clicking the menu Window > Android SDK and AVD Manager > Virtual Devices > New
    choose the target Google APIs (Google Inc.) - API Level 8 ( if this option is not available please download it from Available packages > Third Party Add-ons > Google Inc. )
    fill the rest as you please
    start the new AVD and navigate to Settings > Accounts & Sync > add account > Google > any google account.
    repeat your test
  3. Before i can fill in the other cases i need to see the rest of the Google classes you are using and the Service C2DMReceiver you created


i can't comment due to being a low reputation user

like image 3
2 revs Avatar answered Nov 12 '22 16:11

2 revs