Search code examples
androidnotificationsalarmmanagerandroid-9.0-pieandroid-doze

Android Notifications triggered by Alarm Manager not Firing when App is in Doze Mode


I have the following requirements. A user needs to be able to schedule a recurring reminder in my app that will trigger a push notification at an exact time every day.

This is one of those questions that I hoped I would end up not submitting as similar questions were recommended while writing it. However several team members have spent hours and hours looking through the Android Developer Docs and Stackoverflow and we seem no closer to an answer, so here we are.

If I create a reminder and set it to trigger the notification 5 minutes in the future, the notification fires just fine.

I suspect this may be a problem caused by the changes to battery saving, wake lock, etc introduced in Android P as we did not have this problem prior to updating our target SDK to 28. That being said I am not positive that this is the only problem but I can consistently reproduce the issue on a Pixel and Pixel 3 XL running Android P.

An example where the notification is not firing occurs when for example a user sets the reminder to sometime in the middle of the night (presumably when the user is asleep and thus will not have used the phone for several hours). These reminders are not firing ever.

I am currently trying to accomplish this using the Alarm Manager.

This problem appears to be similar to another question that uses the Alarm Manager's setRepeating method which we have found does not work. We are instead using the Alarm Manager's setExactAndAllowWhileIdle method. We also tried this same implementation with the Alarm Managers setAlarmClock method which according to the Android documentation "will be allowed to trigger even if the system is in a low-power idle (a.k.a. doze) mode" however this was also unsuccessful.

I suspect that the reason this is not working is because setExactAndAllowWhileIdle will not fire when the phone is in doze mode, similar to the problems expressed in this question. This question recommends using Firebase JobDispatcher but as this is an internal notification I am required to fire the notification with or without network connectivity which seems to eliminate the Firebase JobDispatcher as an option. This question also indicated that once the phone exits doze mode the user gets the notification however we are never getting the notification, they seem to be lost for lack of a better term.

I have added the wake lock permission to my AndroidManifest.xml:

<uses-permission android:name="android.permission.WAKE_LOCK" />

Here is how my receiver is registered in the AndroidManifest.xml

<receiver android:name="com.myapp.receiver.AlarmReceiver">
    </receiver>

Here is my current implementation:

Pending Intent for Handling Notifications

Intent i = new Intent(context, ScheduleAllReceiver.class);
    PendingIntent scheduleAllPendingIntent = PendingIntent.getBroadcast(context, SCHEDULER_DAILY_ALL, i, PendingIntent.FLAG_UPDATE_CURRENT);

I subsequently call a method "createAlarm" as follows

createAlarm(context, scheduleAllPendingIntent, calendar.getTimeInMillis());

Creating the Alarms

public static void createAlarm(Context context, PendingIntent pendingIntent, long timeinMilli) {
    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

    if(alarmManager != null) {

        if (Build.VERSION.SDK_INT >= 23) {
            alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeinMilli, pendingIntent);
        } else {
            alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeinMilli, pendingIntent);
        }
    }
}

Solution

  • Adding an intent Flag FLAG_RECEIVER_FOREGROUND

    https://developer.android.com/reference/android/content/Intent#FLAG_RECEIVER_FOREGROUND prior to calling the broadcast receiver should do the trick

    Intent intent = new Intent(context, ScheduleAllReceiver.class);
    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    PendingIntent scheduleAllPendingIntent = PendingIntent.getBroadcast(context, SCHEDULER_DAILY_ALL, intent, PendingIntent.FLAG_UPDATE_CURRENT);