43

Note: I tried various solutions that are written about here on StackOverflow (example here). Please do not close this without checking if your solution from what you've found works using the test I've written below.

Background

There is a requirement on the app, that the user sets a reminder to be scheduled at a specific time, so when the app gets triggered on this time, it does something tiny in the background (just some DB query operation), and shows a simple notification, to tell about the reminder.

In the past, I used a simple code to set something to be scheduled at a relatively specific time:

            val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
            val pendingIntent = PendingIntent.getBroadcast(context, requestId, Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
            when {
                VERSION.SDK_INT >= VERSION_CODES.KITKAT -> alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
                else -> alarmManager.set(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
            }
class AlarmReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d("AppLog", "AlarmReceiver onReceive")
        //do something in the real app
    }
}

Usage:

            val timeToTrigger = System.currentTimeMillis() + java.util.concurrent.TimeUnit.MINUTES.toMillis(1)
            setAlarm(this, timeToTrigger, 1)

The problem

I've now tested this code on emulators on new Android versions and on Pixel 4 with Android 10, and it doesn't seem to trigger, or maybe it triggers after a very long time since what I provide it. I'm well aware of the terrible behavior that some OEMs added to removing apps from the recent tasks, but this one is on both emulators and Pixel 4 device (stock).

I've read on the docs about setting an alarm, that it got restricted for apps so that it won't occur too often, but this doesn't explain how to set an alarm at a specific time, and it doesn't explain how come Google's Clock app succeeds doing it.

Not only that, but according to what I understand, it says the restrictions should be applied especially for low power state of the device, but in my case, I didn't have this state, on both the device and on the emulators. I've set the alarms to be triggered in about a minute from now.

Seeing that many alarm clock apps don't work anymore as they used to, I think there is something that is missing on the docs. Example of such apps is the popular Timely app that was bought by Google but never got new updates to handle the new restrictions, and now users want it back.. However, some popular apps do work fine, such as this one.

What I've tried

To test that indeed the alarm works, I perform these tests when trying to trigger the alarm in a minute from now, after installing the app for the first time, all while the device is connected to the PC (to see the logs) :

  1. Test when the app is in the foreground, visible to the user. - took 1-2 minutes.
  2. Test when the app was sent to the background (using the home button, for example) - took about 1 minute
  3. Test when app's task was removed from the recent tasks. - I waited more than 20 minutes and didn't see the alarm being triggered, writing to logs.
  4. Like #3, but also turn off the screen. It would probably be worse...

I tried to use the next things, all don't work:

  1. alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)

  2. alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)

  3. AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)

  4. combination of any of the above, with :

    if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) alarmManager.setWindow(AlarmManager.RTC_WAKEUP, 0, 60 * 1000L, pendingIntent)

  5. Tried to use a service instead of BroadcastReceiver. Also tried on a different process.

  6. Tried making the app be ignored from the battery optimization (didn't help), but since other apps don't need it, I shouldn't use it either.

  7. Tried using this:

            if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP)
                alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
            AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
  1. Tried having a service that will have a trigger of onTaskRemoved , to re-schedule the alarm there, but this also didn't help (the service worked fine though).

As for Google's Clock app, I didn't see anything special about it except that it shows a notification before being triggered, and I also don't see it in the "not optimized" section of the battery-optimization settings screen.

Seeing that this seems like a bug, I reported about this here, including a sample project and video to show the issue.

I've checked on multiple versions of the emulator, and it seems that this behavior started from API 27 (Android 8.1 - Oreo). Looking at the docs, I don't see AlarmManager being mentioned, but instead, it was written about various background work.

The questions

  1. How do we set something to be triggered at a relatively exact time nowadays?

  2. How come the above solutions don't work anymore? Am I missing anything? Permission? Maybe I'm supposed to use a Worker instead? But then wouldn't it mean that it might not trigger on time at all?

  3. How does Google "Clock" app overcome all of this, and triggers anyway on the exact time, always, even if it was triggered just a minute ago? Is it only because it's a system app? What if it gets installed as a user app, on a device that doesn't have it built-in?

If you say that it's because it's a system app, I've found another app that can trigger an alarm twice in 2 minutes, here, though I think it might use a foreground service sometimes.

EDIT: made a tiny Github repository to try ideas on, here.


EDIT: finally found a sample that is both open-sourced and doesn't have this issue. Sadly it's very complex and I still try to figure out what makes it so different (and what's the minimal code that I should add to my POC) that lets its alarms stay scheduled after removing the app from the recent tasks

Fred
  • 3,365
  • 4
  • 36
  • 57
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • its been a long time I worked on service(I'm not even pro developer to suggest), but I can suggest you avoid alarmManager for cases like to set alarm below 5 mins, cuz due to android restrictions after a time service running in the backend it gets called after every 5 min or greater not less than 5 mins. Instead, I used Handler. And for running my service continues in the background I referred [https://github.com/fabcira/neverEndingAndroidService] – Blu Feb 05 '20 at 16:52
  • So what are the exact restrictions? What is the minimal time that is guaranteed that a trigger will work in a relatively precise give time ? – android developer Feb 05 '20 at 17:11
  • I can't remember exact restrictions but when I was working on it I googled for like days to overcome background service getting killed automatically. And from personal observation I noticed issue on Samsung, Xiaomi, etc, you cant call alarmManger in between interval of 5 mins, I had a data upload service implemented using alarmManger which get triggered every 1 min, but it disappointed our client who complained the service isn't running at all. For emulators it works well. – Blu Feb 05 '20 at 17:20
  • I know you cannot start activity from background in android Q, but it does not look like its your case. – marcinj Feb 05 '20 at 18:16
  • @greeble31 I tried now. Which solution there do you see working? For some reason I still don't get it to work. I set the alarm, I remove the app from recent tasks, and I don't see the alarm being triggered, even though the screen is turned on and the device is connected to a charger. It happens on both a real device (Pixel 4 with Android 10) and on emulator (API 27 for example). Does it work for you? Can you please share the full code? Maybe in Github? – android developer Feb 06 '20 at 08:16
  • From what I remember from the docs I was reading while setting up some bg tasks to execute without using the alarm manager but using [WorkManager](https://developer.android.com/guide/background#workmanager) the minimum time for intervals was 15min – Igor Ilic Feb 06 '20 at 09:04
  • @IgorIlic So how come other alarm clock apps work? I've provided both Google Clock example and a third party app, both can have 2 alarms, one minute after another... – android developer Feb 06 '20 at 09:22
  • @androiddeveloper [This answer](https://stackoverflow.com/a/47371829/6759241) refers to the actual code Google uses in their Alarm Clock app, I don't know if you're going to get any better than that. – greeble31 Feb 06 '20 at 13:23
  • @greeble31 Sadly I tried it too, and wrote about it on the question (number 6 currently). – android developer Feb 06 '20 at 17:14
  • Try to use `adb shell dumpsys alarm` to check if your alarm is properly scheduled. If you initially see it then it disappears after you clear the app from recent tasks, I'm afraid this means the OS clears the alarm when you ask it to kill the app and there is nothing you can do but reschedule the alarm when the app is restarted. – BladeCoder Feb 25 '20 at 23:56
  • @BladeCoder I already made a similar test, by using an easier method: use the framework "getNextAlarmClock()" function to get when is the nearest alarm to be triggered: https://issuetracker.google.com/issues/150080941 (has sample too) . And indeed it gets reset. But somehow, as I wrote, some apps overcome this and it doesn't matter for them if I remove from recent tasks. That's why I asked this question. Why does it get reset (even on emulator and Pixel device) and how can I overcome it? – android developer Feb 26 '20 at 00:28
  • Note that getNextAlarmClock() only returns the next AlarmClock, not general PendingIntents you set using `setExact()`. You should either use an AlarmClock or `setExact()` but not both. Can you be more specific about which "certain apps" still have their alarms scheduled? It may be that apps signed with a system certificate don't get killed. – BladeCoder Feb 26 '20 at 16:35
  • @BladeCoder I was very specific - I gave links. And I already tried "setExact" (and of course it was alone), and getNextAlarmClock is the best one for getting the next alarm of the device. It works well as long as you don't remove the app from the recent tasks. The moment you do it, it will show you either nothing or of another app's alarm. You can use any method you wish to see that indeed the alarm doesn't get triggered. I already used logs, toasts and getNextAlarmClock – android developer Feb 26 '20 at 22:55

7 Answers7

8

Found a weird workaround (sample here) that seems to work for all versions, including even Android R:

  1. Have the permission SAW permission declared in the manifest:
      <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

On Android R you will have to also have it granted. On before, doesn't seem like it's needed to be granted, just declared. Not sure why this changed on R, but I can say that SAW could be required as a possible solution to start things in the background, as written here for Android 10.

EDIT: Here is a guide on how to request it.

  1. Have a service that will detect when the tasks was removed, and when it does, open a fake Activity that all it does is to close itself:
class OnTaskRemovedDetectorService : Service() {
    override fun onBind(intent: Intent?) = null

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) = START_STICKY

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        Log.e("AppLog", "onTaskRemoved")
        applicationContext.startActivity(Intent(this, FakeActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
        stopSelf()
    }

}

FakeActivity.kt

class FakeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("AppLog", "FakeActivity")
        finish()
    }
}

You can also make this Activity almost invisible to the user using this theme:

    <style name="AppTheme.Translucent" parent="@style/Theme.AppCompat.NoActionBar">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>

Sadly, this is a weird workaround. I hope to find a nicer workaround to this.

The restriction talks about starting Activity, so my current idea is that maybe if I start a foreground service for a split of a second it will also help, and for this I won't even need SAW permission.

EDIT: OK I tried with a foreground service (sample here), and it didn't work. No idea why an Activity is working but not a service. I even tried to re-schedule the alarm there and tried to let the service stay for a bit, even after re-schedule. Also tried a normal service but of course it closed right away, as the task was removed, and it didn't work at all (even if I created a thread to run in the background).

Another possible solution that I didn't try is to have a foreground service forever, or at least till the task is removed, but this is a bit weird and I don't see the apps I've mentioned using it.

EDIT: tried to have a foreground service running before removal of the app's task, and for a bit afterwards, and the alarm still worked. Also tried to have this service to be the one in charge of task-removed event, and to close itself right away when it occurs, and it still worked (sample here). The advantage of this workaround is that you don't have to have the SAW permission at all. The disadvantage is that you have a service with a notification while the app is already visible to the user. I wonder if it's possible to hide the notification while the app is already in the foreground via the Activity.


EDIT: Seems it's a bug on Android Studio (reported here, including videos comparing versions). When you launch the app from the problematic version I tried, it could cause the alarms to be cleared.

If you launch the app from the launcher, it works fine.

This is the current code to set the alarm:

        val timeToTrigger = System.currentTimeMillis() + 10 * 1000
        val pendingShowList = PendingIntent.getActivity(this, 1, Intent(this, SomeActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
        val pendingIntent = PendingIntent.getBroadcast(this, 1, Intent(this, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
        manager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingShowList), pendingIntent)

I don't even have to use "pendingShowList". Using null is also ok.

DropDrage
  • 725
  • 8
  • 9
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • I just want to sart an activity onReceive() on AndroidQ. Is there any workarounds for that without the `SYSTEM_ALERT_WINDOW` permission? – doctorram Feb 23 '20 at 19:14
  • 10
    Why does Google always make Android developers' lives a living hell over simple stuff?! – doctorram Feb 23 '20 at 19:18
  • @doctorram Yes, it's written on the docs about the various exceptions: https://developer.android.com/guide/components/activities/background-starts#exceptions . I just chose SYSTEM_ALERT_WINDOW because it's the easiest to test it. – android developer Feb 24 '20 at 22:05
  • From your last edit, do you mean that now we don't have to use any workarounds that you mentioned for persisting the alarms after removing the app from recent list ?? – K Pradeep Kumar Reddy Mar 21 '20 at 02:47
  • I want to run a piece of code every day between 6 am and 7 am in background even if the app is removed from recent list. I should use WorkManager or AlarmManager ?? Tried the following code for my usecase and it did not work. What is the problem with below code ? calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, 6); alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP,calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent); – K Pradeep Kumar Reddy Mar 21 '20 at 03:19
  • I tried the following piece of code in my OnePlus3 phone and it did not work when the app is reomved from recent app list, though i launched the app from launcher. alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + 60 * 1000, null), pendingIntent); – K Pradeep Kumar Reddy Mar 21 '20 at 07:20
  • Just try on the stable version of Android Studio. Or run the app via the launcher instead of from the Android Studio. It should be fine. You can also check the POC I made on Github. – android developer Mar 21 '20 at 13:56
  • I tried it on android studio version 3.6.1 It did not work. Though i launched the app from launcher, it dd not work. The following code is working only if the app is present in recent apps list, else it is not working. alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + 60 * 1000, null), pendingIntent); However your work around sample code using foreground service is working great. – K Pradeep Kumar Reddy Mar 22 '20 at 09:53
  • Did you try the exact same sample I have? – android developer Mar 22 '20 at 10:27
  • which sample are you referring to ?? The four lines of code that you have added in your last edit of your answer right ? – K Pradeep Kumar Reddy Mar 23 '20 at 07:40
  • The one on Github. I wrote "made a tiny Github repository to try ideas on..." . – android developer Mar 23 '20 at 08:26
6

We Don't have anything to do.

Once your app is not whitelisted it will be always killed once removed from recent-apps.

Because Original Equipment Manufacturer (OMEs) constantly violating Android compliance.

So If your app is not whitelisted from the device Manufacture it won't fire any background work even alarms - in case your app is removed from recent-apps.

You can find a list of devices with that behavior here ALSO you might find a side-solution, However, it won't work well.

Ibrahim Ali
  • 1,237
  • 9
  • 20
  • I'm well aware of this issue of Chinese OEMs . But as I wrote, it happens even on emulator and Pixel 4 device . It's not some Chinese OEM that made it as such. Please check on emulator and/or Pixel device. The issue exists there too. Set an alarm, remove the app from recent tasks, and see that the alarm isn't triggered. I see this as a bug, and reported here (it includes a video and a sample project if you want to try) : issuetracker.google.com/issues/149556385 . I've updated my question to make it clear. Question is how come some app succeeded. – android developer Feb 20 '20 at 12:06
  • @androiddeveloper I believe it should work on Emulator.. Which emulator you have? – Ibrahim Ali Feb 20 '20 at 12:26
  • I also believed, until I tried. Just try it on API 29 for example, of what Android Studio has to offer. I'm sure the same will occur on a bit older versions too. – android developer Feb 20 '20 at 12:44
2
  1. Make sure the intent you broadcast is explicit and has the Intent.FLAG_RECEIVER_FOREGROUND flag.

https://developer.android.com/about/versions/oreo/background#broadcasts

Intent intent = new Intent(context, Receiver.class);
intent.setAction(action);
...
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);

PendingIntent operation = PendingIntent.getBroadcast(context, 0, intent, flags);
  1. Use setExactAndAllowWhileIdle() when targeting API 23+.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, operation);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    alarmManager.setExact(AlarmManager.RTC_WAKEUP, time, operation);
} else {
    alarmManager.set(AlarmManager.RTC_WAKEUP, time, operation);
}
  1. Start your alarm as a Foreground Service:

https://developer.android.com/about/versions/oreo/background#migration

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService(intent);
} else {
    context.startService(intent);
}
  1. And don't forget permissions:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Maksim Ivanov
  • 3,991
  • 31
  • 25
  • 1
    Why does it matter about the service, if the BroadcastReceiver itself doesn't get the Intent at all (or near the time)? That's the first step... Also, doesn't AlarmManagerCompat already offer this same code? Have you tried this using the tests I wrote, including removing the app from the recent tasks? Can you please show the entire code? Maybe share on Github? – android developer Feb 14 '20 at 07:26
  • Still doesn't seem to work. Here's a sample project: https://ufile.io/6qrsor7o . Please try on Android 10 (emulator also ok), set the alarm , and remove the app from recent tasks. If you don't remove from recent tasks, it works fine and gets triggered after 10 seconds. – android developer Feb 15 '20 at 11:44
  • I've also updated the question to have a link to a bug report which includes a sample project and video, because I think this is a bug as I don't see any other reason for this to occur. – android developer Feb 19 '20 at 09:51
1

I am the author of the open source project you have mentioned in your question (simple alarm clock).

I am surprised that using AlarmManager.setAlarmClock did not work for you, because my app does exactly that. The code is in the file AlarmSetter.kt. Here is a snippet:

  val pendingAlarm = Intent(ACTION_FIRED)
                .apply {
                    setClass(mContext, AlarmsReceiver::class.java)
                    putExtra(EXTRA_ID, id)
                    putExtra(EXTRA_TYPE, typeName)
                }
                .let { PendingIntent.getBroadcast(mContext, pendingAlarmRequestCode, it, PendingIntent.FLAG_UPDATE_CURRENT) }

            val pendingShowList = PendingIntent.getActivity(
                    mContext,
                    100500,
                    Intent(mContext, AlarmsListActivity::class.java),
                    PendingIntent.FLAG_UPDATE_CURRENT
            )

            am.setAlarmClock(AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingShowList), pendingAlarm)

Basically it is nothing special, just make sure that intent has an action and a target class, which is a broadcast receiver in my case.

Yuriy Kulikov
  • 2,059
  • 1
  • 16
  • 29
  • Sadly didn't work. That's what I tried. See files here:https://github.com/yuriykulikov/AlarmClock/issues/320#issuecomment-593146376 – android developer Mar 01 '20 at 21:13
  • 1
    I have checked out your code on GitHub. Broadcast receiver works after the app is removed from recents on Moto Z2 Play. I can try it on a Pixel, but code seems ok to me. Force stopping the application removes the scheduled alarm, but this will happen to any app which is force stopped. – Yuriy Kulikov Mar 01 '20 at 21:59
  • I've already shown multiple times: all I do after scheduling is to remove from recent tasks. And I did it on both emulator and Pixel 4. – android developer Mar 01 '20 at 23:59
  • Please check if using pendingShowList circumvents the issue and if yes, I will update the answer. Maybe it will be useful for someone. – Yuriy Kulikov Mar 02 '20 at 21:20
1

Actually what worked for me for Android 12 to schedule the exact time alarm is adding to your app's AndroidManifest file a permission:

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

And to change the Flag for your PendingIntent:

  val pendingIntent = alarmIntent.let { intent ->
    val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        PendingIntent.FLAG_MUTABLE //this is needed in Android 12
    } else {
        PendingIntent.FLAG_CANCEL_CURRENT
    }
    PendingIntent.getBroadcast(
        context,
        DailyAlarmReceiver.REQUEST_DAILY_NOTIFICATION,
        intent,
        flag
    )
}

You can also check if the app can schedule the alarm with checking on canScheduleExactAlarms:

val alarmManager: AlarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager

if(alarmManager.canScheduleExactAlarms()){
  //schedule the alarm 
}
androidEnthusiast
  • 1,140
  • 1
  • 12
  • 21
0

I know this isn't efficient but might be more consistent with an accuracy of 60 seconds.

https://developer.android.com/reference/android/content/Intent#ACTION_TIME_TICK

if this broadcast receiver is used inside a foreground service , you can check the time every minute and make decision on taking an action.

Harsh
  • 363
  • 4
  • 14
  • If I have a foreground service, why would I need this? I could just use a Handler.postDelayed or any other similar solution if I wish... – android developer Feb 22 '20 at 20:33
0

I think you can ask for the user to set the permission so it disables the energy-saving mode, and warn the user that if he does not use it, exact times won't be achieved.

Here's the code to request it:

PowerManager powerManager = (PowerManager) getApplicationContext().getSystemService(POWER_SERVICE);
            String packageName = "your Package name";
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                Intent i = new Intent();
                if (!powerManager.isIgnoringBatteryOptimizations(packageName)) {
                    i.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
                    i.setData(Uri.parse("package:" + packageName));
                    startActivity(i);
user2638180
  • 1,013
  • 16
  • 37