setExactAndAllowWhileIdle - is not exact as of developer reference

AndroidAlarmmanagerAndroid 6.0-Marshmallow

Android Problem Overview


AlarmManager on API19 has the method setExact() to set an exact alarm.

Exact means --> If I set an alarm to 2:01 pm it will be triggered at 2:01 pm

On API 23 - Marhsmwallow (6.0) there is a new method setExactAndAllowWhileIdle(), but as of the reference it is not EXACT because it will trigger only every minute and in low power idle mode only every 15 minutes.

Exact != every 15 minutes :-)

So how can I achieve an exact alarm with AlarmManager in 6.0?

If a user adds a reminder or a calendar appointment and wants to be informed 10 minutes before the event it should show the alarm EXACT 10 minutes before the event. With setExactAndAllowWhileIdle() this seems is not possible.

Reference Link: http://developer.android.com/reference/android/app/AlarmManager.html#setExactAndAllowWhileIdle(int, long, android.app.PendingIntent)

Android Solutions


Solution 1 - Android

> So how can I achieve an exact alarm with AlarmManager in 6.0?

You are welcome to try setAlarmClock(), as AFAIK it is unaffected by Doze mode. Otherwise, AlarmManager is not a viable option for you. Even having your app on the battery optimization whitelist will not help, as AlarmManager behavior does not change based on the whitelist.

You are welcome to use GCM, as a high-priority message should give you an opportunity to alert the user. This, of course, requires network connectivity.

The only offline solution that I am aware of — and that I am presently testing — is to have the user add your app to the battery optimization whitelist, then use a foreground service (to try to keep your process around), a ScheduledExecutorService (for the timing), and a partial WakeLock (to keep the CPU on). This will be fairly devastating to the user's battery.

Solution 2 - Android

Using setExactAndAllowWhileIdle() for a one-time alarm will fire exactly on the given time even in Doze idle mode. So this probably is the way to go.

Problems start, if you want to repeat the alarm at a rate of < 15 min (or set any other at a time < 15 min away from the last one), as this will not work in Doze idle mode, where such alarms are forced to the next 15 min or are executed when idle maintenance starts, which happens for about ten minutes first after 1 hour, then after another 2 hours, then after another 4 hours and so on.

- EDIT -

As of today Nov 17, Dianne Hackborn writes in this Post's comments: "For what it's worth, the minimum time between while idle alarms will be changing to 9 minutes at some point relatively soon (even on devices running the current Marshmallow builds)."

This doesn't change anything fundamentally though.

Solution 3 - Android

Here are my discussion with Ian Lake on Google+!

setExactAndAllowWhileIdle() is exact and should work. The 15 minutes time frame is wrong in the java doc.

enter image description here

Solution 4 - Android

I have found that so far the best option is to use a SyncAdapter that extends AbstractThreadedSyncAdapter. I schedule it to automatically keep running my code at the required interval:

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
ContentResolver.addPeriodicSync(account, AUTHORITY, settingsBundle, syncInterval);

Solution 5 - Android

I was trying to create an automation system running in the background. My frequency range was between 1-15 minutes. My wish was not to use a foreground service. By looking at the name of the method "setExactAndAllowWhileIdle", I thought that yeah it is safe to go with one-time alarms, scheduling the next one when done.

However, I couldn't find a way to run code in doze mode with alarms running more frequent than 15 minutes. Instead, I choose to start a foreground service when doze mode gets activated and stop that foreground service when phone awakes. User won't be seeing your foreground notification while using his/her phone. I don't care much about the ones in doze mode.

PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if(intent.getAction().equals("android.os.action.DEVICE_IDLE_MODE_CHANGED")){
        if (pm.isDeviceIdleMode()) {
            //startAutomationForegroundService();
        } else {
            //stopAutomationForegroundService();
            return;
        }
        AutomationReceiver.completeWakefulIntent(intent);
        return;
    }
}

You need to register "android.os.action.DEVICE_IDLE_MODE_CHANGED" intent filter into your WakefulBroadcastReceiver. Care putting it into manifest may not help.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionchrisonlineView Question on Stackoverflow
Solution 1 - AndroidCommonsWareView Answer on Stackoverflow
Solution 2 - Androidsec_awView Answer on Stackoverflow
Solution 3 - AndroidchrisonlineView Answer on Stackoverflow
Solution 4 - AndroidHashim AkhtarView Answer on Stackoverflow
Solution 5 - AndroidMertcan ÇüçenView Answer on Stackoverflow