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);
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.
The problem why you do not get any notifications after a while is because of DOZE.
You have 2 options for solving your issue:
"Remove" Doze from your app (https://www.greenbot.com/article/2993199/android/how-to-turn-off-doze-mode-for-specific-apps-in-android-marshmallow.html) (not recommended, each user has to do it, you cannot set it as default )
Use FCM High Priority as per documentation:
*Doze checklist
If possible, use FCM for downstream messaging. If your users must see a notification right away, make sure to use an FCM high priority message. Provide sufficient information within the initial message payload, so subsequent network access is unnecessary. Set critical alarms with setAndAllowWhileIdle() and setExactAndAllowWhileIdle(). Test your app in Doze.*
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.
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