I want to use AlarmService to trigger a notification at a certain time. Think of it as something similar as a calendar app that is showing a reminder as notification for an upcoming event.
The code to schedule the intent (via alarm service) looks like this:
fun scheduleNotification(event : CalendarEvent)
val startTime : Instant = event.startTime
val intent = buildPendingIntent(event)
val notificationTime = startTime.minusMillis(TimeUnit.MINUTES.toMillis(10)) // 10 Minutes earlier
if (Build.VERSION.SDK_INT < 23) {
alarmService().setExact(AlarmManager.RTC_WAKEUP,
notificationTime.toEpochMilli(), intent)
} else {
alarmService().setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
notificationTime.toEpochMilli(), intent)
}
}
fun buildPendingIntent(event : CalendarEvent){
val intent = Intent(context, NotificationReceiver::class.java)
intent.putExtra(EVENT_ID, event.id)
return PendingIntent.getBroadcast(context, 0, realIntent, 0)
}
class NotificationReceiver : WakefulBroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// build and display the notification
}
}
So 1 of 10 times the notification is tirggered and shown (by NotificationReceiver) correctly, also at the desired time. So I think the scheduling part is working properly.
Which leads me to another question: Whenever the user creates a new CalendarEvent the method scheduleNotification(newEvent) is called. It seems to me that AlarmService is internally updating the PendingIntents of existing and that this is the reason why 1 of 10 (usually the first scheduled PendingIntent) is triggered, but the others are not.
How many alarms can I schedule for an Android App? Do you spot any other issue with my code?
Related
I have implemented alarm manager to trigger every 5 minutes in my application. But when using it on my Redmi note 8 device, alarm manager not triggered in wifi enabled mode and at the same time it works for mobile data enabled mode. For other device's it works fine in both wifi and mobile data.
I know that wifi or mobile-data is not related to alarm manager triggering process. But, I'm facing this weird issue.
Could anyone help me out ?
This is my alarm triggering code.
private var alarmManager: AlarmManager? = null
private lateinit var pendingIntent: PendingIntent
override fun startAlarm(url: String, status: String) {
alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java).apply {
action = Constants.ACTION_ALARM_MANAGER
putExtra(KeyConstants.STATUS, status)
putExtra(KeyConstants.URL, url)
}
pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val timeInterval = TimeUnit.MINUTES.toMillis(5)
alarmManager?.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), timeInterval, pendingIntent)
}
override fun cancelAlarm() {
alarmManager?.cancel(pendingIntent)
}
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Is triggered when alarm goes off, i.e. receiving a system broadcast
if (intent.action == Constants.ACTION_ALARM_MANAGER) {
val tripStatus = intent.getStringExtra(KeyConstants.STATUS)
val directionUrl = intent.getStringExtra(KeyConstants.URL)
// my logic here
}
}
}
and also added receiver in Manifest.xml
<receiver android:name=".AlarmReceiver" />
Every OEM has made it worse to use Alarm manager, mostly it's their battery saving app which kills the app in the background thus no triggering of alarm events.
Android's system makes sure that no alarm should trigger very often, for saving battery and minimizing wakeups, android shifts these alarms for future for this.
As your alarm goes off every 5 minutes, most probably android is shifting your alarm too.
You can check all the scheduled alarm events of the device using following command adb shell dumpsys alarm
BootReceiver
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
val array = MyDatabase(context).getAllAlarm()
val receiver = Intent(context, BootReceiver::class.java)
receiver.action = "ALARM_SETTING"
val trigger = Calendar.getInstance()
for (i in array) {
trigger.time = SimpleDateFormat("yyyy/MM/dd hh:mm", Locale.getDefault()).parse(i.time)!!
receiver.putExtra("trigger", trigger.timeInMillis)
AlarmUtil.setAlarm(context, receiver, i.id, trigger.timeInMillis)
}
} else (intent.action == "ALARM_SETTING") {
val trigger = intent.getLongExtra("trigger", 0)
NotificationUtil.sendNotification(context, name, id, trigger)
}
}
AlarmUtil
class AlarmUtil {
companion object {
fun setAlarm(context: Context, intent: Intent, id: Int, calendar: Long) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getBroadcast(context.applicationContext, id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar, pendingIntent)
} else {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar, pendingIntent)
}
}
}
}
As you can see, when the mobile phone reboots, this code brings the alarm array stored in SQLiteDatabase and makes the alarm sound. If you look at setAlarm() in AlarmUtil, I wrote a code that makes it cry at the time specified in AlarmManager. The problem is that all the alarms stored in the array will be cleared at once regardless of the time specified in the alarm array (I implemented it as Notification). Isn't it normal to cry at the calendar time you put in the second argument of setExactAndAllowWhileIdle or setExact? Why do all the alarms cry at once as soon as the phone turns on again?
[EDIT]
The following picture is a situation where the phone is turned off and on after setting the alarm to ring at 02:22. The system time is 2020/11/02 02:21, but the trigger time I set to ring is 2020/11/02 02:22. In other words, the alarm goes off as soon as the time runs out. I want to take a log and show it, but the moment I turn off my phone, the app dies, so I couldn't see the log.
Hi you are passing older time as trigger time.
You should pass time in the future.
Check db time if it is less than now() than add a day
So i'm trying to run a service that sends certain room data to a server every midnight(roughly) and deletes it from the local db. Reading the android docs i came to the conclusion that the best approach currently was to setup an alarm that triggers at midnight and starts a Service that executes the syncing). While i'm not entirely sure of what type of Service i should i use my files look like this:
MainActivity.kt
alarmMgr = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager?
alarmIntent = Intent(this, SendTakesReceiver::class.java).let { intent ->
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
// Set the alarm to send takes at approximately 00:00 a.m.
val calendar: Calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY, 0)
}
// With setInexactRepeating(), you have to use one of the AlarmManager interval
// constants--in this case, AlarmManager.INTERVAL_DAY.
alarmMgr?.setInexactRepeating(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
AlarmManager.INTERVAL_DAY,
alarmIntent
)
SendtakesReceiver.kt
class SendTakesReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//these following logs are always shown
Log.d("TAKES INTENT", intent.toString())
if (intent != null) {
context?.run {
Log.d("TAKES SERVICE", intent.toString())
startService(intent)
}
}
}
}
SendTakesService.kt
class SendTakesService : Service() {
val sp: SharedPreferences = getSharedPreferences("login", Context.MODE_PRIVATE)
private var prescriptionTakeDao =
AppDatabase.getDatabase(applicationContext).prescriptionTakeDao()
override fun onCreate() {
super.onCreate()
Log.d("TakesService", "onCreate()")
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
And finally in my manifest file i have (inside the application tag):
<receiver
android:name=".src.ui.prescriptions.SendTakesReceiver"
android:enabled="true" />
<service
android:name="com.glik.glik.src.services.SendTakesService"
android:enabled="true"
android:process=":SendTakes">
</service>
But the problem is that none of the service Classes(Service, IntentService, JobIntentService) that i've tried using are calling their main method. For example here the onCreate() log is never shown. I've seen countless of similar questions regarding services not being started but i can't seem to pinpoint the exact mistake that i am making here that causes the service to not be started. If any one could help me find it or maybe recommend a more elegant approach to do the db sync i would be really grateful. Thanks in advance.
Depending on the OEM of the phone you are on, you will face various challenges with this approach. See https://dontkillmyapp.com/
You should use WorkManager in order to decrease battery usage and to avoid having to manually handle many corner cases: https://developer.android.com/topic/libraries/architecture/workmanager/basics
I have a requirement to perform a task at exactly every 5 minutes. I have considered multiple options and have attempted to implement this using the AlarmManager class to trigger the task. However I cannot get the alarm to trigger when the application has been killed.
The alarm seems to work flawlessly when the app is open, or running in the background, but as soon as I exit the app, it seems to completely stop.
My implementation is to use the setExactAndAllowWhileIdle() function and to handle the repeating of this myself. The initial alarm is triggered after 5 seconds, then every 5 minutes after this.
I have tested this with 5 and 10 minute increments, but again this never runs when the app is closed.
Please take a look at my implementation:
My Activity.kt:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_landing)
initAlarm()
}
private fun initAlarm() {
val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java).apply { action = "MY_ALARM" }
val sender = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val ALARM_DELAY_IN_SECOND = 5
val alarmTimeAtUTC = System.currentTimeMillis() + ALARM_DELAY_IN_SECOND * 1_000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.(TAG, "initAlarm() 23+ - $alarmTimeAtUTC")
alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
} else {
alarm.setExact(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
}
}
AlarmReceiver.kt:
class AlarmReceiver : BroadcastReceiver() {
companion object {
private val TAG = AlarmReceiver::class.java.simpleName
}
override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAG, "onReceive()")
if (intent?.action == "MY_ALARM") {
Log.d(TAG, "onReceive() - starting service")
context?.startService(Intent(context, MyService::class.java))
initAlarm(context)
}
}
private fun initAlarm(context: Context?) {
val alarm = context?.applicationContext?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, AlarmReceiver::class.java).apply { action = "MY_ALARM" }
val sender = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val ALARM_DELAY_IN_SECOND = 600
val alarmTimeAtUTC = System.currentTimeMillis() + ALARM_DELAY_IN_SECOND * 1_000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d(TAG, "initAlarm() 23+ - $alarmTimeAtUTC")
alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
} else {
alarm.setExact(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
}
}
}
MyService.kt:
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate()")
doMyTask()
}
private fun doMyTask() {
job = CoroutineScope(Dispatchers.IO).launch {
// Perform task here and once complete stop service
stopSelf()
}
}
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy()")
job?.cancel()
job = null
}
The problem is that the code is calling startService() while the app is in the background.
This was allowed prior to Android 8, but now is subject to the following restrictions:
While an app is in the foreground, it can create and run both
foreground and background services freely. When an app goes into the
background, it has a window of several minutes in which it is still
allowed to create and use services. At the end of that window, the app
is considered to be idle. At this time, the system stops the app's
background services, just as if the app had called the services'
Service.stopSelf() methods.
Background Execution Restrictions
The service created in the sample above is a "Background Service" in the language of the Android docs because it does not call startForeground to post a Notification to make the user aware that it is running.
To start a "Foreground service" from an alarm while the app is in the background, ContextCompat.startForegroundSerivce must be used. It will call startForegroundService on Android 8 and above and startService on older devices.
In the service, you will need to call startForeground to post an ongoing service Notification to make the user aware that the service is running. If the Notification is not posted, the service will be killed after 5 seconds.
It would also worth considering whether the task can be accomplished via WorkManager or with Firebase Cloud Messaging.
Lastly, you probably need to inform your client that running a task "exactly every 5 minutes" is not possible on modern Android devices. I have not looked at the Doze implementation recently, but in the past have observed routine delays of up 10 minutes during maintenance windows when using setExactAndAllowWhileIdle. But the delays can be longer under certain circumstances.
Regarding onReceive not being called in the BroadcastReceiver:
Disable Battery Optimisations
Do not kill my app
Lastly, you could try passing in a unique requestCode in getBroadcast instead of passing 0 each time.
I am trying to schedule SMS messages in my app, using alarm manager with the RTC_WAKEUP type so that it will fire even when the device is in doze mode (as it needs to be exact). I have had reports of it not working and in my own testing it seems that if I for example schedule a message for an hour ahead it will fire, but then scheduling for the next day does not fire. (I have verified that the times being scheduled are correct).
Here is the code for the scheduling:
fun scheduleTimeMessage(activity: Context, time: Long) {
val manager = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(activity, TimeSendReceiver::class.java)
intent.putExtra("TIME", time)
Log.e("ERROR?", "Time is " + time + SimpleDateFormat(" dd/MM/yy hh:mm", Locale.US).format(Date(time)))
if (Build.VERSION.SDK_INT >= 23)
manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, PendingIntent.getBroadcast(activity, time.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT))
else
manager.setExact(AlarmManager.RTC_WAKEUP, time, PendingIntent.getBroadcast(activity, time.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT))
}
Here is the BroadcastReceiver:
class TimeSendReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val epoch = intent.getLongExtra("TIME", 0)
val database = SchedulerDatabase(context)
val cursor = database.getTimeCursor(epoch)
if (cursor.moveToFirst()) {
do {
if (cursor.getString(cursor.getColumnIndex("Address")).contains(","))
sendGroupMessage(context, cursor)
else {
if (cursor.getString(cursor.getColumnIndex("Image")) == "")
sendSMSMessage(context, cursor)
else
sendMMSMessage(context, cursor)
}
} while (cursor.moveToNext())
database.deleteMessageData(epoch)
cursor.close()
}
}
}
Ok this one is hard to test (I would have to wait for a day for one test), but here a some things you might try:
Check out the WakefulBroadcastReceiver, explained in this post
Check whether your device has some battery optimization settings. This can result in your app being killed after some time and all it's alarms being canceled. You should be able to turn off optimization for certain apps.
I ended up using the JobScheduler API and I set the JobInfo.Builder to:
val jobInfo = JobInfo.Builder(Constants.JOB_SCEDULE_ID, ComponentName())
.setOverrideDeadLine(millis + 100)
.setMinimumLatency(millis - 100)
.build()
This makes it so that the scheduler wakes up the device on the necessary time as needed by the user, but uses smart scheduling.