3

I'm making an reminder app, so I need to send notification every x minutes, as I researched I found that I need to use AlarmManager ( idk if I can use any other method ). So I'm trying to use AlarmManager to fire Notification. It works only when I'm using the phone if phone is locked alarm does not fire, when I unlock the phone then fires.

This is my AndroidManifest.xml:

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


    <receiver android:name=".util.NotificationAlarmReceiver"/>

    <receiver android:name=".util.BootCompletedReceiver"
        android:permission="android.permission.RECEIVE_BOOT_COMPLETED" android:enabled="true" android:exported="true">
        <intent-filter>
            <category android:name="android.intent.category.DEFAULT"/>
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
            <action android:name="android.intent.action.ACTION_BOOT_COMPLETED"/>
            <action android:name="android.intent.action.REBOOT"/>
            <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
            <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
            <action android:name="android.intent.action.ACTION_SHUTDOWN"/>
        </intent-filter>
    </receiver>

So I have to receivers one for notification and one to Activate alarmManager on boot.

On NotificationAlarmReceiver that extends BroadcastReceiver I'm also trying to use WakeLock:

@Override public void onReceive(Context context, Intent intent) {

    PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "com.test.reminder:WakeLockForNotification");
    wl.acquire(60 * 1000L /*1 minute*/);

    // Notification code is here.

    wl.release();
}

So I'm trying to wakeup show notification then release, but only does not wakeup. And this is the code I use to set Alarm:

 Intent intentAlarm = new Intent(this, NotificationAlarmReceiver.class);
        AlarmManager alarmManager = (AlarmManager) this.getSystemService(ALARM_SERVICE);
        PendingIntent pi = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.getBroadcast(this, Constants.ALARM_ID, intentAlarm, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE) : PendingIntent.getBroadcast(this, Constants.ALARM_ID, intentAlarm, PendingIntent.FLAG_UPDATE_CURRENT);
        alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+tinyDB.getLong(Constants.notification_interval_key,AlarmManager.INTERVAL_HALF_HOUR), tinyDB.getLong(Constants.notification_interval_key,AlarmManager.INTERVAL_HALF_HOUR), pi);
    

So is there any solution to show notification every time is needed. I also read on android documentation to not use exact alarm manager, I also saw someone used job scheduler, but i'm not familiar with that, but also with that they had problems, so any idea or solution will be appriciated.

Web.11
  • 406
  • 2
  • 8
  • 23
  • Not clear if you have seen: https://developer.android.com/about/versions/12/behavior-changes-12#exact-alarm-permission – Morrison Chang Jun 19 '22 at 08:33
  • I used exact alarm permission, when I tried with .setExcat, but still notification does not show on specified time, sometimes delay more then 60 minutes... – Web.11 Jun 21 '22 at 11:08

1 Answers1

3

I went through this process myself. The documentation surrounding alarms/timers is unnecessarily confusing. To simplify things, if your app is actually a reminder app and the user truly expects a notification to occur precisely every X minutes, then it is OK to use exact alarms.

But be careful because the Android gods hate common sense and any sort of consistency. The only reliable exact alarm is setAlarmClock(), especially when you are dealing with Doze mode. Also, while the name setExactAndAllowWhileIdle() may sound like it will always execute exactly, it doesn't per the documentation:

To reduce abuse, there are restrictions on how frequently these alarms will go off for a particular application. Under normal system operation, it will not dispatch these alarms more than about every minute (at which point every such pending alarm is dispatched); when in low-power idle modes this duration may be significantly longer, such as 15 minutes.

The last part is the part the ruins any hope for using setExactAndAllowWhileIdle() as a repeating alarm.

And contrary to what you might think, you do not want setInexactRepeating() or even setRepeating(). If you look closely enough, the little note at the bottom of the documentation says:

Note: as of API 19, all repeating alarms are inexact. If your application needs precise delivery times then it must use one-time exact alarms, rescheduling each time as described above. Legacy applications whose targetSdkVersion is earlier than API 19 will continue to have all of their alarms, including repeating alarms, treated as exact.

So, to actually implement reliable repeating exact alarms, you must schedule the first alarm using setAlarmClock() and everytime the alarm goes off, schedule the next notification/alarm using setAlarmClock(). As a tip for developing this, you can store information about when to schedule the next alarm in the extras of the Broadcast Intent.

Here is the sad part: the above may work for AOSP but other vendors take liberties in how they manage alarms. See https://dontkillmyapp.com/ for who those vendors are.

Some extra information:

AlarmManager already provides a wake lock in the BroadcastReceiver's onRecieve() method, as described here:

The Alarm Manager holds a CPU wake lock as long as the alarm receiver's onReceive() method is executing. This guarantees that the phone will not sleep until you have finished handling the broadcast. Once onReceive() returns, the Alarm Manager releases this wake lock. This means that the phone will in some cases sleep as soon as your onReceive() method completes. If your alarm receiver called Context.startService(), it is possible that the phone will sleep before the requested service is launched. To prevent this, your BroadcastReceiver and Service will need to implement a separate wake lock policy to ensure that the phone continues running until the service becomes available.

As pointed out in the comments of the question, in order to use exact alarms (and therefore setAlarmClock()), you need to declare the SCHEDULE_EXACT_ALARM permission in your manifest. Be aware that this is a special permission and can be revoked. You can use the canScheduleExactAlarms to determine if the permission is revoked. I recommend looking at the documentation for that and for ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, to see how to handle the permission being revoked.

  • I can accept delay to 3 minutes, but as I tried also with "alarmManager.setExact(AlarmManager.RTC_WAKEUP...." it delays more, for example I made every 30 minutes (this is minimum user can choose), alarm fired 7:20 then 7:50 then 9:13.. This means delay is 60 minutes somewhere ... so you prefer me to use setAlarmClock and it should work ? – Web.11 Jun 21 '22 at 10:34
  • @Web.11 I don't know why `setExact()` would be delayed by upwards of an hour (that isn't stated in the documentation, however if your are not running AOSP that may be the source of it). Either way, yes you should use `setAlarmClock()` instead. `setAlarmClock()` is the only method that will reliably go off at the provided time, even while the phone is sleeping. –  Jun 22 '22 at 19:35
  • You should also be pay careful attention to the use of wake locks. As the quote says in the answer, if you need to do things outside of the receivers `onReceive()` method (e.g. start a service), you have to make sure a wake lock is held until the service starts and acquires a wake lock for itself. –  Jun 22 '22 at 19:39