8

I am developing an android-application which main usage is displaying notifications at a set time (some specific calendar app). One of the main complaints is that users do not (always) receive notifications, and I am at wits end.

We have internally tested the code below against Android 4.4, 6, 7.0, 7.1, 8.0, 8.1 on emulators and used about 10 real devices (6 to 8.1), and all devices received their notifications on time. Even across reboots, the notifications were all received on time.

One of the things we have ran into was the SecurityException on Samsung devices (>500 registered alarms), which we had previously triggered due to unproper cancelling. It looks like that no longer is an issue.

So, what could be the cause for these missing notifications? Is it a device specific setting, is it a simple bug? Or are there other factors at play here?

This is the code we are using:

private void cancelAlarm(String notificationId, Class<? extends AbstractReceiver> receiverClass)
        throws BroadcastException {
    /*
     * Create an intent that looks similar, to the one that was registered using add. Making sure the notification id in the action is the same. Now we can search for
     * such an intent using the 'getService' method and cancel it.
     */
    final Intent intent = new Intent(this.context, receiverClass);
    intent.setAction(notificationId);

    final PendingIntent pi = PendingIntent.getBroadcast(this.context, 0, intent, 0);
    final AlarmManager am = getAlarmManager();

    try {
        am.cancel(pi);
    } catch (Exception e) {
        Log.e(this.getClass().getSimpleName(), e.getMessage());
        throw new BroadcastException(e.getMessage());
    }
}

private void addOrUpdateAlarm(...){
    try {
        cancelAlarm(notificationId, OurReceiver.class);
    } catch (BroadcastException e) {
        Log.e(AlarmHelper.class.getSimpleName(), "addOrUpdateAlarm: Can't cancel current alarm before reinserting.", e);
    }

    Intent intent = new Intent(this.context, receiverClass);
    intent.setAction(notificationId);
    // some intent.setExtra() calls.
    PendingIntent sender = PendingIntent.getBroadcast(this.context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    /* Get the AlarmManager service */
    final AlarmManager am = getAlarmManager();

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), sender);
    }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
        am.setExact(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), sender);
    }else{
        am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), sender);
    }

}

and then in OurReceiver we create a notificationchannel:

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationManager mNotificationManager =
                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        int importance = NotificationManager.IMPORTANCE_HIGH;
        NotificationChannel mChannel = new NotificationChannel(id, name, importance);
        // Configure the notification channel.
        mChannel.setDescription(description);
        mChannel.enableLights(true);
        // Sets the notification light color for notifications posted to this
        // channel, if the device supports this feature.
        mChannel.setLightColor(Color.RED);
        mChannel.enableVibration(true);
        mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
        mNotificationManager.createNotificationChannel(mChannel);
    }

and finally send a notification:

    PendingIntent pIntent = PendingIntent.getActivity(context, (int) System.currentTimeMillis(), intent, 0);

    Notification n = new NotificationCompat.Builder(context, channel)
            .setContentTitle(notificationTitle)
            .setContentText(notificationSubText)
            .setSmallIcon(R.drawable.logo)
            .setContentIntent(pIntent)
            .setDefaults(Notification.DEFAULT_SOUND|Notification.DEFAULT_VIBRATE)
            .setAutoCancel(true).build();

    NotificationManager notificationManager = 
              (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

            notificationManager.notify(0, n); 
Yuri
  • 2,008
  • 17
  • 36
  • check the registration token for a device which is unable to receive notification. Is it present or not?? – Fazal Hussain May 03 '18 at 09:33
  • Hey @FazalHussain, there are no remote notifications. It is all local. – Yuri May 03 '18 at 09:35
  • Did you set to the BroadcastReceiver in manifest the Boot_Complete action? – Agon Avdijaj May 03 '18 at 10:16
  • when is addOrUpdateAlarm method gets called ? – Gautam May 03 '18 at 10:22
  • @AgonAvdijaj Yes. Which should be supported by the fact that we get the notifications on our own devices. – Yuri May 03 '18 at 10:42
  • @Gautam After login. Uplon logging in the user receives a list of timestamps where the notification should occur. As this can happen multiple times, each alarm is first cancelled and then reprogrammed. – Yuri May 03 '18 at 10:42
  • Hi In Android Oreo (O) there are limitations in running Background services. The preferred solution is use JobScheduler instead of Alarm manager on API > 23. – Ambareesh B May 11 '18 at 11:39
  • @AmbareeshB That is interesting, I will definitely look into that, thanks! – Yuri May 14 '18 at 07:49
  • I have the same problem on Samsung `SecurityException on Samsung devices (>500 registered alarms)`. In my case was the Google Play Services Location library who caused this crashes, and in Fabric, only Samsung seems to be affected. No solution at the moment. – Marc Estrada May 14 '18 at 13:11
  • Checkout the question [here](https://stackoverflow.com/a/29610474/6611700) – riyaz-ali May 14 '18 at 13:51

6 Answers6

11

About notifications, I can tell you the following:

After a long time of developing an app which uses a lot of Alarms from AlarmManager I discovered a lot of things about some conflictive devices. (I didn't tried JobScheduler, but in most of cases this technology will also fail).

There are some manufacturers (well known, Huawei, Xiaomi, Samsung, etc) that interfers in the life cycle of AlarmManager.

Huawei and Xiaomi

Huawei by default kills all non protected apps when locking the screen. That's it, kills all resources of the app, including alarms, boradcast receivers and services. The app is completly killed after the screen is locked so the alarms are not received and the notifications are not shown logically. To avoid this situation, Huawei provides a way to put the apps in protected mode, which means that when the screen is locked, these protected apps are not killed. Protected apps, then, still receive alarms and broadcast receivers.

Samsung

Samsung has a "feature" (unwanted feature for developers) that do the "same" than Huawei and Xiaomi devices with a little difference. Samsung doesn't kills non protected apps when locking the scree, but when the app is not opened in 3 days. After 3 days of user inactivity the app is killed like Huawei and Xiaomi, so the notifications (alarms) are not received. Samsung also provides a way to protect the apps and avoid this situation.

Conclusion

There are other manufacturers with the same behavior, but I don't know all of them. I can tell you Huawei, Xiaomi and Samsung are the most known of their.

So, first try to know if all failing devices are manufactured by these conflictive manufacturers.

Then, there are some ways to let the user know that notifications may not fire in this devices, and invite them to put your application as a protected app.

Programmatically, you can do something like that (source):

if("huawei".equalsIgnoreCase(android.os.Build.MANUFACTURER)) { 
    AlertDialog.Builder builder  = new AlertDialog.Builder(this);
    builder.setTitle(R.string.huawei_headline).setMessage(R.string.huawei_text)
            .setPositiveButton(R.string.go_to_protected, new DialogInterface.OnClickListener() {
                @Override 
                public void onClick(DialogInterface dialogInterface, int i) {
                    Intent intent = new Intent();
                    intent.setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity"));
                    startActivity(intent);
                } 
            }).create().show(); 
}

And you can do the same with other conflictive manufacturers. This is how I handle this situation in these devices, and notifications works well, in most of cases.

Hope this helps.

Marc Estrada
  • 1,657
  • 10
  • 20
  • That's interesting, do you have any source to link ? – Alessandro.Vegna Jun 28 '18 at 11:00
  • @Alessandro.Vegna unfortunately I don't. I didn't found anything about this when I was investigating it. All this knowns are in my head and they are product of a hard and long investigation about notifications which were not shown or background services which were killed unexpectedly and this kind of stuff. So I found the solution through the power manager making the app protected. – Marc Estrada Jun 28 '18 at 12:42
  • Ok no problem, but I can confirm the Samsung's one, because my app was working perfectly and after exactly 3 days it stops. Thanks anyway =) – Alessandro.Vegna Jun 28 '18 at 12:43
  • https://dontkillmyapp.com/ is documenting this kind of thing :-) – Luke Needham Feb 11 '22 at 16:13
5

The problem why you do not get any notifications after a while is because of DOZE.

You have 2 options for solving your issue:

Alessandro.Vegna
  • 1,262
  • 10
  • 19
  • https://developer.android.com/training/monitoring-device-state/doze-standby seems to state that setExactAndAllowWhileIdle actually ignores Doze mode. Are you sure? – Yuri May 15 '18 at 09:32
  • Be aware, the *alarm* ignores doze, not the notification, so even if the alarm will call the service which shows the notification, I'm not sure you'll be able to see it. remember also that Doze cut all the network connections, so if you need to check something using internet it definitely won't work – Alessandro.Vegna May 15 '18 at 10:06
  • 2
    Wow. That is a sneaky caveat. Thanks for the clarification! – Yuri May 15 '18 at 11:33
2

The cause of this might be Doze. It works, when the device is steady and is not used, android makes background calls together with other application to save the wake up time of phone and save battery. I am not exactly sure if this is your case, but you might look into that.

This answer might help you in doing so.

1

Some docs says app can handle 50 notification maximum.(not sure)

But If you try to give different notification id:

for (int i...>500)
 notificationManager.notify(i, n);

Hope this works

1

@Mars Estrada is right, the problem is due to the phone implementation. Huawei have a software call powergenie which can kill (in linux term) your service. So the implementation clearly do not respect the doze Mode. Somme application just inform user that "since you have a huawei, be sure to set up the power management corectly". THe same goes for Xiaomi and Samsung. AS these implementation does not follow Android SDK there is no way to manage a background application. For My application for different Huawei ( HOnor 8x, Huawei P20, P10, P9 , P9 lite ), if the application is not protected for background execution in the setting, It is even impossible to start a service using Context.startService ( or startFroegroundService when available). regards Charles

0

Maybe my answer will save someone's time - in my case the reason of not getting notifications on particular device was simply because of 'Do not disturb' mode on the device.

Myroslav
  • 896
  • 12
  • 21