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?
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>
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!
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.
i can't comment due to being a low reputation user
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With