Question

I have one project as a library of another one because I need whitelabeling it (and more projects for other partners). In the library project, I have implemented push notifications system. I've executed the library project as a normal project and the pushs work like a charm. My problem happens when i am importing this project as a library in another project. The receiver is never called.

There is one post with the same issue but the solution is not working for me. I am getting crazy!!!

Android C2DM and lib project

The main problem is that I am not receiving the message from C2DM. My code is the next:

Manifests of the both project (I have the same piece of code in both projects) (I am using the tags lib_project and app_project to be clear):

    <receiver
        android:name="com.lib_project.C2DMBroadcastReceiver"
        android:permission="com.google.android.c2dm.permission.SEND" >
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <category android:name="com.app_project.android" />
        </intent-filter>
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
            <category android:name="com.app_project.android" />
        </intent-filter>
    </receiver> 

And the permissions:

  <uses-permission android:name="com.app_project.android.permission.C2D_MESSAGE" />
  <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
  <permission
    android:name="com.app_project.android.permission.C2D_MESSAGE"
    android:protectionLevel="signature" />

And also I have declared in the manifest the service that manage the push notifications but it is called properly.

   <service android:name="com.lib_project.android.C2DMReceiver" />

Really, I don't know what is wrong. I think is ok but is not working. Thanks in advance.

It is my class C2DMBaseReceiver :

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) {
    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);
    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());
        }
    }
}

}

Ant this one is my C2DMReceiver:

public class C2DMReceiver extends C2DMBaseReceiver {

public static final String EXTRA_DATETIME = "datetime";
public static final String EXTRA_CAM_ID = "cam_id";
public static final String EXTRA_TYPE = "type";
public static final String GCM_PROJECT_ID = "58312821729";


public static void getC2DMRegistration(Context context){
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO){
        String id = C2DMessaging.getRegistrationId(context);
        if(id.equals(""))
            C2DMessaging.register(context, C2DMReceiver.GCM_PROJECT_ID);
        else                
            C2DMReceiver.registerPushDevice(context, id);

        Log.d("restored id: " + id);
    }
}

public static String getDeviceID(Context context){
    String out = null;

    try {
        TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(TELEPHONY_SERVICE);
        out = telephonyManager.getDeviceId();
    } catch (Exception e) {
        Log.w("Error getting device uid", e);
    }

    return out;
}

public static void registerPushDevice(Context context, String registrationId){
    try {
        CameraManager app = null;
        if(context instanceof Activity)
        {
            app = (CameraManager)(((Activity)context).getApplication());
        }
        else if(context instanceof Service)
        {
            app = (CameraManager)(((Service)context).getApplication());
        }
        else if(context instanceof Application)
        {
            app = (CameraManager)context;
        }

        if(app != null && app.isLoggedIn())
        {
            HashMap<String, String> keyValues = new HashMap<String, String>(app.getUserSessionKeys());
            keyValues.put("imei", getDeviceID(context));
            keyValues.put("registration_id", registrationId);
            keyValues.put("application_id", context.getString(R.string.application_id));
            keyValues.put("gcm", "true");

            new ServerCall(context, Script.MOBILE, Method.ADD_REGISTRATION_ID, keyValues, null)
            .execute();
        }
    } catch (Exception e) {
        Log.e("Failed to register C2DM", e);
    }
}

public C2DMReceiver() {
    super(GCM_PROJECT_ID);
}

@Override
public void onRegistered(Context context, String registrationId) {
    Log.i("onRegistered: " + registrationId);
    registerPushDevice(context, registrationId);
}

@Override
public void onUnregistered(Context context) {
    Log.i("onUnregistered");
}

@Override
public void onError(Context context, String errorId) {
    Log.w("onError: " + errorId);
}

@SuppressWarnings("unchecked")
@Override
protected void onMessage(Context context, Intent receiveIntent){

    Bundle extras = receiveIntent.getExtras();

    CameraManager app = null;
    if(context instanceof Activity)
    {
        app = (CameraManager)(((Activity)context).getApplication());
    }
    else if(context instanceof Service)
    {
        app = (CameraManager)(((Service)context).getApplication());
    }
    else if(context instanceof Application)
    {
        app = (CameraManager)context;
    }

    boolean activateNotificationsphone = app.getUserStorage().getBoolean(Constants.PUSH_NOTIFI_ACTIVATE_FROM_PHONE, true);

    if(extras != null && activateNotificationsphone)
    {
        Log.e(""+extras.keySet());
        Iterator<String> i = extras.keySet().iterator();
        while(i.hasNext())
        {
            String key = i.next();

            if(key.equalsIgnoreCase(Constants.EXTRA_ALARM_MOTION) || key.equalsIgnoreCase(Constants.EXTRA_CAMERA_DOWN) || key.equalsIgnoreCase(Constants.EXTRA_LSU_DOWN))
            {
                NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                Notification notification = new Notification(R.drawable.ic_launcher, context.getString(R.string.app_name), System.currentTimeMillis());

                Intent notificationIntent = new Intent(context, FragmentTabs.class);

                String type = key.toUpperCase();
                String value = receiveIntent.getStringExtra(key);
                String collapse_key = receiveIntent.getStringExtra("collapse_key");

                String message = "";
                String[] pair = value.split("[:]");


                if(pair.length == 2)
                {
                    notificationIntent
                    .putExtra(EXTRA_TYPE, type)
                    .putExtra(EXTRA_CAM_ID, pair[0])
                    .putExtra(EXTRA_DATETIME, pair[1])
                    .setAction(collapse_key);

                    Log.e("Type c2dm:"+type);
                    Log.e("Cam ID c2dm: " + pair[0]);
                    Log.e("DateTime c2dm: " + pair[1]);

                    ArrayList<CamerasFeedItem> cameras = null;
                    XMLObject settings = null;
                    ArrayList<EventItem> listEvents = null;
                    User user = null;
                    try 
                    {
                        user = (User)Utils.deserializeObject(new File(getFilesDir(), CameraManager.USER_OBJ_FILE));
                        cameras = (ArrayList<CamerasFeedItem>)Utils.deserializeObject(new File(getFilesDir(), user.getUserId() + "_" + CameraManager.CAMERAS_OBJ_FILE));
                        settings = (XMLObject)Utils.deserializeObject(new File(getFilesDir(), user.getUserId() + "_" + CameraManager.SETTINGS_OBJ_FILE));

                        //List of events:
                        if(user!=null)
                        {
                            listEvents = (ArrayList<EventItem>)Utils.deserializeObject(new File(getFilesDir(), user.getUserId() + "_" + CameraManager.LIST_EVENTS_OBJ_FILE));
                        }
                    } 
                    catch (Exception e) 
                    { }

                    CamerasFeedItem item = null;
                    if(settings == null || cameras == null || (item = isItemExists(cameras, pair[0])) == null)
                    {
                        return;
                    }


                    if(type.equals(Constants.EXTRA_ALARM_MOTION))
                    {
                        if(settings.getValue("motion", "no").equals("no"))
                        {
                            return;
                        }
                        GregorianCalendar curTime = new GregorianCalendar();
                        long offset = curTime.get(Calendar.ZONE_OFFSET) + curTime.get(Calendar.DST_OFFSET);
                        Calendar c = Calendar.getInstance();
                        c.setTimeZone(TimeZone.getTimeZone("UTC"));
                        c.setTimeInMillis(Long.parseLong(pair[1]) + offset);

                        String when = DateFormat.format("dd-MM-yyyy kk:mm:ss", c).toString();

                        message = context.getString(R.string.push_motion_on_camera, item.getName(), when);
                    }
                    else if(type.equals(Constants.EXTRA_CAMERA_DOWN))
                    {
                        if(settings.getValue("cameraDown", "no").equals("no"))
                        {
                            return;
                        }
                        message = context.getString(R.string.push_camera_is_down, item.getName(), getDownString(pair[1]));
                        //typeIndex = 1;
                    }
                    else if(type.equals(Constants.EXTRA_LSU_DOWN))
                    {
                        //typeIndex = 3;
                        message = "";
                    }
                }

                if(!message.equals(""))
                {
                    notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_CLEAR_TOP);

                    notification.defaults |= Notification.DEFAULT_SOUND;
                    notification.flags |= Notification.FLAG_AUTO_CANCEL;

                    RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.notification);
                    contentView.setTextViewText(R.id.title, context.getString(R.string.app_name));
                    contentView.setTextViewText(R.id.text, message);
                    notification.contentView = contentView;
                    notification.contentIntent = PendingIntent.getActivity(this, (int) System.currentTimeMillis(), notificationIntent, 0);

                    mNotificationManager.notify(collapse_key, (int)Math.random(), notification);
                }  

                return;
            }
        }
    }   
}

private CamerasFeedItem isItemExists(ArrayList<CamerasFeedItem> cameras, String id){        
    for(CamerasFeedItem item: cameras)
    {
        if(item.getID().equals(id))
        {
            return item;
        }

        if(item.isFolderItem())
        {
            LSUItem lsu = ((FolderItem)item).getLsuItem();

            if(lsu != null && lsu.getID().equals(id))
            {
                return lsu;
            }               
            CamerasFeedItem result = isItemExists(CamerasFeedItem.parse(item), id);
            if(result != null)
            {
                return result;
            }
        }
    }

    return null;
}

private String getDownString(String hours){
    StringBuilder out = new StringBuilder();
    int total = Integer.parseInt(hours);

    int m = total / 720;
    total = total % 720;        

    int w = total / 168;
    total = total % 168; 

    int d = total / 24;
    total = total % 24;

    if(m > 0)
    {
        out.append(getResources().getQuantityString(R.plurals.push_month, m, m));
        out.append(" ");
    }
    if(w > 0)
    {
        out.append(getResources().getQuantityString(R.plurals.push_weeks, w, w));
        out.append(" ");
    }
    if(d > 0)
    {
        out.append(getResources().getQuantityString(R.plurals.push_days, d, d));
        out.append(" ");
    }
    if(total > 0)
    {
        out.append(getResources().getQuantityString(R.plurals.push_hours, total, total));
        out.append(" ");
    }

    return out.toString().trim();
}

C2DMBroadcastReceiver.java

public class C2DMBroadcastReceiver extends BroadcastReceiver {

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

And the last one: C2DMessaging:

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) {
    try {
        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);
    } catch (Exception e) {
        Log.w("Couldn't use C2DM, check OS version", e);    
    }
}

/**
 * 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);
}

/**
 * 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", "");
    return registrationId;
}

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

static long getBackoff(Context context) {
    final SharedPreferences prefs = context.getSharedPreferences(
            PREFERENCE,
            Context.MODE_PRIVATE);
    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();

}

// 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();

}

// 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();

  }
}
Was it helpful?

Solution

Your problem is in this method :

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);

}

In the following line context.getPackageName() returns the package of your app (com.app_project.android).

String receiver = context.getPackageName() + ".C2DMReceiver";

However, .C2DMReceiver is located in your library project (com.lib_project.android), and that's why this class is not found when you try to use your library project from your app.

The way to fix it is to refer to the C2DMReceiver class explicitly :

String receiver = C2DMReceiver.class.getName ();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top