This article talks about how you can trigger Alarms exactly when you want even on Android Oreo and above.

Android is getting mature with every major release and we are heading towards more user-centric and privacy first Operating System.

Android Oreo was released back in 2018, one of the major changes were related to background limitations, which changed the way most apps would function. It was one of the turning points for android, as a lot of apps were running in the background even without users knowing about it and this also drained a lot of battery, to prevent that from happening to a certain extent the android team came up with this major change.

As welcoming as it was for users, a lot of developers were affected by this change, with the release of Android R around the corner (by the time of writing this article) some of the developers still run into issues with AlarmManager API as it doesn’t trigger alarm sometimes the way it would in previous Android versions (below oreo).

In this article, we’ll cover 

  1. Basics of AlarmManager and its working
  2. BroadcastReceiver’s role when using AlarmManager
  3. Changes/requirements to make it work seamlessly in Android Oreo and above.

Basics of AlarmManager

AlarmManager is an API that is exposed by the Android framework to schedule your app to run at a specific point in the future. In simple words, if you want some work (showing notification, making an API call to your servers) in the future irrespective of the fact that if the app is running in the foreground, you can use AlarmManager API.

We’ll use AlarmManagerCompat which is a wrapper class over the existing AlarmManager class to make our life easier.

To set up an alarm using AlarmManagerCompat API (specifically the functions which allow you to set alarm to a very specific time) we require an instance of AlarmManager class, the UTC at which alarm should be triggered, the type of alarm and the pending intent which will fire when its time.

Getting an instance of AlarmManager is fairly simple

Use the Sidebar to add the URL of the GitHub Repository to embed.

val alarmManager = context.getSystemService<AlarmManager>() // Using KTX extensions library

Getting UTC is based on the logic of your app. In this example, I am setting it 10 minutes after the current time.

val tenMinsFromNow = System.currentTimeMillis() + (10 * 60 * 1000) // current time in ms + (Mins * seconds * millisecond)

Alarm types are

  1. RTC
  2. RTC_WAKEUP
  3. ELAPSED_REALTIME
  4. ELAPSED_REALTIME_WAKEUP

You can learn when to use which type here in section Alarm types.

PendingIntent

While creating pending intent we’ll use PendingIntent.getBroadcast() function, also make sure to register your BroadcastReceiver in the manifest file.

// Make sure to put a class which extends BroadcastReceiver as second parameter for Intent
val intent = Intent(context.applicationContext, ExampleBroadcastReceiver::class.java).apply {
                putExtra("id", id) // Any data that you want to pass with intent
                putExtra("name", name)
            }

val pIntent = PendingIntent.getBroadcast(context.applicationContext, id, intent, PendingIntent.FLAG_UPDATE_CURRENT)

Now the functions with setExactXxxx ironically aren’t as exact as we want them to be on Android Oreo and above. But for now, here’s how you’ll set an alarm.

AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, tenMinsFromNow, pIntent)

BroadcastReceiver’s role when using AlarmManager

When we use AlarmManager, we pass a PendingIntent which gets fired by the OS when the alarm is triggered in other words when our device’s clock hit the time we set in our alarm request.

Now, our PendingIntent’s intent, in which we mentioned a BroadcastReceiver class, that class’s onReceive method is called automatically with the same Intent which we passed.

Making AlarmManager work like before

Now if we look into the reason why our AlarmManager is not working the way we expect it to is because of the Battery optimizations set by android framework and OEMs

There are at least 2 ways we can make it work.

  1. Foreground Service for registering the BroadcastReceiver for our Alarms
  2. Request user to whitelist your app from framework’s Battery Optimization

ForegroundService

It may not be ideal for some apps as it will show a persistent notification in the Notification panel, but its a good thing that from Android Oreo and above, allow users to hide notifications if they want and it won’t affect the functioning of the app.

While using foreground service we will use it’s onStartCommand method to register our receiver and if due to some reason our service is killed we can do the cleanup in onDestroy method.

class ExampleForegroundService() : Service() {
    
    private val exampleReceiver by lazy { ExampleBroadcastReceiver() }
    
    
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Create notification
        val notification = NotificationCompat.Builder(this, getString(R.string.channel_desc)).apply {
            // Configure your notification
        }.build()
        
        // Call startForeground service with a ID and notification object
        startForeground(Constants.KEEP_ALIVE_NOTIFICATION_CODE, notification)
        
        // Register the Broadcast Receiver
        registerReceiver(exampleReceiver, IntentFilter())
        
        // Return value
        return START_NOT_STICKY
    }
    
    override fun onDestroy() {
        // Unregister the Broadcast Receiver
        unregisterReceiver(exampleReceiver)
        super.onDestroy()
    }
}

Starting a foreground service is fairly simple.

ContextCompat.startForegroundService(applicationContext, Intent(applicationContext, ExampleBroadcastReceiver::class.java))

Using this method we can ensure that our broadcast receiver is always listening.

Whitelisting our android app from Battery Optimization

If you don’t want your app to show a persistent notification, they can use this method but with a lot of caution, this workaround can easily be misused and if your app is doing a lot of heavy lifting in the background including this alarm manager, well then this may not be the ideal for your app.

Before you continue reading further, I would heavily recommend that you read this.

To whitelist our app we need to take the user to battery optimization settings and ask the user to select our app and set its status to “Not optimized” or something along the same lines as this can vary from device to device.

Note: Be mindful that this may not be available for devices of a certain company like “Huawei”, if you run this code on a Huawei device you’ll most likely get an ActivityNotFoundException

Use this method at your own risk and test it on multiple devices before you ship it in production.

Always make sure to tell users the reason why you need them to whitelist the app with the steps they might have to follow to complete the process, through a dialog or a note, then if the user accepts/agrees to it, only then fire the intent and wait for users to do its thing.

You can always verify if your app is whitelisted or not using the following piece of code.

private fun isAppBlacklisted(): Boolean {
        val pwrm = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager
        val name = requireContext().packageName
        return !pwrm.isIgnoringBatteryOptimizations(name)
    }

The intent which takes the user to battery optimization settings

startActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))

My suggestion would be to only use this method if there’s no other option.

I hope this article was informative and helped you learn something new.

References

  1. Android AlarmManager As Deep As Possible
  2. Classic chain AlarmManager
  3. Scheduling repeating alarms

If you have any queries or suggestions feel free to DM me on twitter @unused_variable or comment down below and please subscribe to our TryCatchBolg to get the latest post.

0