Use-case: I want my app to announce the current time ("the current time is $hour $minute") at 5 minute intervals. The idea is to help me stay on time while getting ready in the morning, so it needs to fire at the exact time, every 5 minutes, without fail, even with the screen turned off.
Problem: Since Android alarmManager's SetRepeating is no longer exact, I am using SetExactandAllowWhileIdle. I can't figure out how to set a new alarm within my broadcast receiver to make it repeating. Is it possible to set an alarm within a running alarm class in Kotlin? The only examples I can find are in Java.
I've also read that Android limits alarms, so even with SetExactandAllowWhileIdle you are limited to one alarm every 1-15 minutes, so it's possible this may still not work. Is there a better way to do all this that I am missing?
class MainActivity : AppCompatActivity(), TextToSpeech.OnInitListener {
var isRunning: Boolean = false
var tts: TextToSpeech? = null
private lateinit var alarmMgr: AlarmManager
private lateinit var pendingIntent: PendingIntent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager
var intent = Intent(this, MyAlarm::class.java)
var pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0)
button.setOnClickListener {
if (!isRunning) {
button.text = getString(R.string.stop)
button.setBackgroundColor(resources.getColor(R.color.colorRed))
isRunning = true
val calendar = Calendar.getInstance()
calendar.set(
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(HOUR_OF_DAY),
calendar.get(MINUTE),
0
)
startAnnouncing(calendar)
} //end if !isRunning
else if (isRunning) {
button.text = getString(R.string.start)
button.setBackgroundColor(resources.getColor(R.color.colorGreen))
isRunning = false
alarmMgr.cancel(pendingIntent)
Toast.makeText(this, "Announcing stopped!", Toast.LENGTH_SHORT).show()
} //end if isRunning
} //end button OnClickListener
tts = TextToSpeech(this, this)
} //end onCreate
private fun startAnnouncing(calendar: Calendar) {
var alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager
var intent = Intent(this, MyAlarm::class.java)
var pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0)
alarmMgr.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis + 1000*60*1,
pendingIntent
)
Toast.makeText(this, "Announcing started!", Toast.LENGTH_SHORT).show()
} //end startAnnouncing
class MyAlarm : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
val ttsService = Intent(context, TTS::class.java)
context.startService(ttsService)
val calendar = Calendar.getInstance()
calendar.set(
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(HOUR_OF_DAY),
calendar.get(MINUTE),
0
)
//THIS is where I'd like to call a new alarm to make this a repeating alarm cycle
} //end onReceive
} //end MyAlarm
override fun onInit(status: Int) {
if (status == TextToSpeech.SUCCESS) {
// set US English as language for tts
val result = tts!!.setLanguage(Locale.US)
}
} //end onInit
public override fun onDestroy() {
// Shutdown TTS
if (tts != null) {
tts!!.stop()
tts!!.shutdown()
}
super.onDestroy()
//alarmMgr.cancel(pendingIntent)
} //end onDestroy
} //end MainActivity
I've also read that Android limits alarms, so even with SetExactandAllowWhileIdle you are limited to one alarm every 1-15 minutes
This is correct, according the the documentation "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"
You may be able to get away with using setExactandAllowWhileIdle, but I don't recall if there is documentation on how long it takes before the device goes into low power/doze mode. Also, it might be even more likely to work how you want if you add the app to the battery optimization exception list. See these docs for Google's suggestions on dealing with doze/app standby.
Another option appears to be setAlarmClock (docs) but I recommend against this as the intended use of this is for creating an alarm clock style application
Related
I think I have set my alarm manager to run at 7am then at 24 hour intervals after that. It should change a image view and then send a notification. Instead it sends a notification a minute or 2 after closing or opening the app and occasionally changes the image. Can someone please explain where I went wrong? or how I can fix this?
main activity -
val mIntent = Intent(this, MyReceiver::class.java)
val calendar: Calendar = Calendar.getInstance()
calendar.setTimeInMillis(System.currentTimeMillis())
calendar.set(Calendar.HOUR_OF_DAY, 7)
calendar.set(Calendar.MINUTE, 0)
val mPendingIntent = PendingIntent.getBroadcast(this, 0, mIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val mAlarmManager = this
.getSystemService(Context.ALARM_SERVICE) as AlarmManager
mAlarmManager.setRepeating(
AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),86400000, mPendingIntent,
)
MyReciver -
class MyReceiver : BroadcastReceiver() {
#RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context, intent: Intent) {
val titles = arrayOf("Become inspired!", "Check out this quote!", "A new quote appeared!", "Daily quote available!")
val title = titles.random()
val notificationChannel =
NotificationChannel("My Channel", "New Quote", NotificationManager.IMPORTANCE_DEFAULT).apply {
description = "Alerts when A new daily quote is set!"
}
val builder = NotificationCompat.Builder(context!!, "My Channel")
.setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle(title)
.setContentText("A new daily quote is available for viewing")
with(NotificationManagerCompat.from(context)){
createNotificationChannel(notificationChannel)
notify(1, builder.build())
}
val quotes = arrayOf(R.drawable.i1, R.drawable.i2, R.drawable.i3, R.drawable.i5,
R.drawable.i6, R.drawable.i7, R.drawable.i8, R.drawable.i9, R.drawable.i10, R.drawable.i11, R.drawable.i12)
val quote = quotes.random()
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
with(prefs.edit()) {
putInt("paintings", quote)
apply()
}
}
}
Other -
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.other)
val imageView = findViewById<ImageView>(R.id.paininass)
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val quote = prefs.getInt("paintings", R.drawable.i5)
imageView.setImageResource(quote)
Like CommonsWare says in the comments, using the current day and setting the time to 7AM is usually going to put that in the past, and as the AlarmManager#setRepeating docs say:
If the stated trigger time is in the past, the alarm will be triggered immediately, with an alarm count depending on how far in the past the trigger time is relative to the repeat interval.
So yeah, you need to add a day if it's in the past. You can do that by comparing currentTimeMillis to Calendar#getTimeInMillis, or using its before function, or however you want to do it. Then just call add(Calendar.DAY_OF_MONTH, 1)
I'm struggling with notifications.
I want to randomly select text to display in the notification (using random() method on the array of String objects from separate object file NotificationData where I only had one immutable variable called notificationData), which should appear once a day at a specific time (but for now, for the purpose of the test, I wrote in the code to appear every hour). I'm using AlarmManager for scheduling. The first thing is the notifications does not appear at the specified hour when the app is not currently working (or during the device sleep mode), but after launching the app. And second thing is that after launching the app, notifications appear almost one by one within few seconds. I don't really understand why it happens in this way. It's quite strange for me.
Here's the NotificationUtils class, where I create my notification and put one String from Array inside setContentText() method:
package com.example.quit.notification
class NotificationUtils(context: Context) {
private var mContext = context
private lateinit var notificationBuilder: NotificationCompat.Builder
val notificationManager = NotificationManagerCompat.from(mContext)
private val CHANNEL_ID = "Notification_Channel"
init {
createNotificationChannel()
initNotificationBuilder()
}
fun launchNotification() {
with(NotificationManagerCompat.from(mContext)) {
notificationManager.notify(0, notificationBuilder.build())
}
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "Rady"
val description = "Codzienne rady dla zdrowiejących osób uzależnionych"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
this.description = description
}
val notificationManager: NotificationManager = mContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
private fun initNotificationBuilder() {
val sampleIntent = Intent(mContext, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(mContext, 0, sampleIntent, 0)
val data = NotificationData.notificationData.random()
notificationBuilder = NotificationCompat.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_support)
.setContentTitle("Rada na dziś")
.setContentText(data)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
}
}
Second class, where I create repeating alarm:
package com.example.quit.notification
#SuppressLint("UnspecifiedImmutableFlag")
class AlarmUtils(context: Context) {
private var mContext = context
private var alarmManager: AlarmManager? = null
private var alarmIntent: PendingIntent
init {
alarmManager = mContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmIntent = Intent(mContext, AlarmReceiver::class.java).let { mIntent ->
PendingIntent.getBroadcast(mContext, 100, mIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
}
fun initRepeatingAlarm(calendar: Calendar) {
calendar.apply {
set(Calendar.HOUR_OF_DAY, 20)
set(Calendar.MINUTE, 30)
set(Calendar.SECOND, 0)
}
alarmManager?.set(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
alarmIntent
)
}
}
Here's my AlarmReceiver, where I set next alarm after one hour in this case (:
package com.example.quit.notification
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val notificationUtils = NotificationUtils(context!!)
notificationUtils.launchNotification()
val calendar = Calendar.getInstance()
calendar.add(Calendar.HOUR_OF_DAY, 1)
val alarmUtils = AlarmUtils(context)
alarmUtils.initRepeatingAlarm(calendar)
}
}
In AndroidManifest.xml file I only added one line in tag:
<receiver android:name=".notification.AlarmReceiver" />
And the last thing - in MainActivity class I put those three lines just after setContentView() method:
val calendar = Calendar.getInstance()
val alarmUtils = AlarmUtils(this)
alarmUtils.initRepeatingAlarm(calendar)
I'll be very grateful for any help and explanations.
It is obvious why your notifications appear immediately. In AlarmReceiver you do this:
val calendar = Calendar.getInstance()
calendar.add(Calendar.HOUR_OF_DAY, 1)
val alarmUtils = AlarmUtils(context)
alarmUtils.initRepeatingAlarm(calendar)
You want to take the current time, add one hour and schedule an alarm at that time (1 hour in the future). However, look at what initRepeatingAlarm() is doing:
calendar.apply {
set(Calendar.HOUR_OF_DAY, 20)
set(Calendar.MINUTE, 30)
set(Calendar.SECOND, 0)
}
in initRepeatingAlarm() you are setting the hour, minute and seconds to a fixed value (which is the time that the alarm originally went off). You are overwriting the time that was set in the Calendar that was passed in. In this case, when the alarm is set, the time is in the past so the alarm triggers immediately.
Regarding why the alarms do not work when the device is sleeping, the call to AlarmManager.set() is not considered "exact" and the system may delay the alarm to ensure that the device is not constantly awakened (preservation of battery). See this explanation found in the documentation.
If you want the alarm to go off at exactly this time, you need to use a different method, such as setExact(), and you will need to declare an additional permission SCHEDULE_EXACT_ALARM (if you target API level 31 or higher)
I am trying to reboot my device and relaunch my alarm following this https://developer.android.com/training/scheduling/alarms#boot.
But unfortunately only the BOOT_COMPLETED action is received. Other intents from Alarm is not received.
During on BOOT_COMPLETED . I schedule my task for the alarm once again.
I also have confirmed using the command below
adb shell dumpsys alarm
That I still have the alarm after rebooting. but the when the alarm fires Broadcast receiver does not receive it.
Does anyone have an idea what I might missing? Thank you.
NOTE: If I use the same code without restarting the device. The alarm will be received by the Broadcast receiver. Issue only happens if I restart.
Here is the code for the Playground App im testing it on.
MainActivity
class MainActivity : Activity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val receiver = ComponentName(applicationContext, BootReceiver::class.java)
applicationContext.packageManager.setComponentEnabledSetting(
receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
val prefs = getSharedPreferences("TEST_PREF", MODE_PRIVATE)
binding.text.text = "Hello!! " + prefs.getString("message", "No message defined") //"No name defined" is the default value.
Log.d("TESTER", "app loaded")
}
}
BroadcastReceiver
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(context == null) return
if(intent == null) return
if (intent.action == "android.intent.action.BOOT_COMPLETED") {
// Set the alarm here.
ScheduleManager.scheduleAlarm(context, TEST_TIME)
Log.d("TESTER", "android.intent.action.BOOT_COMPLETED")
} else {
val message = intent.getStringExtra(MESSAGE_KEY)
val editor: SharedPreferences.Editor = context.applicationContext.getSharedPreferences("TEST_PREF", MODE_PRIVATE).edit()
editor.putString("message", message)
editor.apply()
Log.d("TESTER", "message from alarm received")
}
}
}
Alarm Manager
object ScheduleManager {
const val MESSAGE_KEY = "message"
const val TEST_TIME = 1647915300000L //2022-03-22 10:15:00
fun scheduleAlarm(context: Context, time: Long) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, BootReceiver::class.java)
intent.putExtra(MESSAGE_KEY, "This is from the alarm $time")
val pendingIntent = PendingIntent.getBroadcast(
context,
1111,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
time,
pendingIntent
)
}
}
Log Results
Launched the app at 2022-03-22 10:11:17.414
2022-03-22 10:11:17.414 4360-4360/com.wearosplayground D/TESTER: app loaded
proceeds rebooting WearOs device
Boot complete action received at 2022-03-22 10:13:49.586
2022-03-22 10:13:49.586 3576-3576/com.ausom.wearosplayground D/TESTER: android.intent.action.BOOT_COMPLETED
I have set the alarm at this time to be at 2022-03-22 10:15:00
but nothing happens
const val TEST_TIME = 1647915300L //2022-03-22 10:15:00
Is specified as seconds.
You should change this to 1647915300000L
See
println(java.util.Date(1647915300L)) //Tue Jan 20 01:45:15 UTC 1970
Using this link as a reference, the solution using accessibility works.
BUT, this is not advisable as it accessibility is for disabled users. So for now the real solution I can think of is communicate with your WearOS device manufacturer and ask for whitelisting. Other than that we will need to continue resorting to tricks listed in the link I have shared.
I am trying to create a basic alarm clock app. I set the alarm using the setExact method of the AlarmManager. And then I created a BroadcastReceiver that will display a toast and will start a new activity when the previously set alarm goes off. I also have a WakeLock in my BroadcastReceiver to try to turn on the screen. The BroadcastReceiver is working. It is able to receive the event and then created the activity. But the screen is not turning on. I've read and followed a lot of answers of posts like this in SO but I still cant make it work.
This is my code for setting the alarm:
alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
PendingIntent.getBroadcast(context, 1, intent, 0)
}
val alarmTime = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY, alarmForm.time.get(Calendar.HOUR_OF_DAY))
set(Calendar.MINUTE, alarmForm.time.get(Calendar.MINUTE))
set(Calendar.SECOND, 0)
}
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
alarmTime.timeInMillis,
alarmIntent
)
This is my BroadcastReceiver:
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val wakeLock: PowerManager.WakeLock =
(context.getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
acquire(10*60*1000L /*10 minutes*/)
}
}
Toast.makeText(context, "Wake Up!", Toast.LENGTH_LONG).show()
val alarmDisplayIntent = Intent(context, AlarmDisplayActivity::class.java)
alarmDisplayIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(alarmDisplayIntent)
wakeLock.release()
}
}
And this is the activity I display when alarm goes off:
class AlarmDisplayActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_alarm_display)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
override fun onResume() {
super.onResume()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setTurnScreenOn(true)
}
}
}
I am using a phone with android version 9. My minSdkVersion is 21.
What do you think I am doing wrong or am I missing something?
I'm scheduling a notification for 5 days ahead, so I create an alarm using the AlarmManager who fires a PendingIntent which triggers my BroadcastReceiver.
If I try the code for 10 seconds, it works. When I try it for 5 days, nothing happens.
The class NotificationScheduler is a helper class for setting and updating alarms.
The fire-dates are correct since I store them in a database, and I already proofed it.
Manifest:
<receiver android:name=".reminder.ReminderReceiver" />
NotificationScheduler:
class NotificationScheduler {
companion object {
const val NOTIFICATION_EXTRA_CLAIM_ID = "notification_extra_bookentry_id"
const val INTENT_ACTION_REMINDER = "at.guger.moneybook.reminder"
fun setReminder(context: Context, bookEntryId: Long, fireDate: Date? = null) {
val mCalendar = Calendar.getInstance()
val lFireDate = if (fireDate == null) {
mCalendar.timeInMillis += 5 * 24 * 3600 * 1000
mCalendar.set(Calendar.HOUR_OF_DAY, 12)
mCalendar.time
} else {
fireDate
}
create(context, bookEntryId, lFireDate.time)
AppDatabase.getInstance(context).reminderDao().insert(Reminder(bookEntryId, lFireDate))
}
fun updateReminder(context: Context, bookEntryId: Long) {
cancel(context, bookEntryId)
val mCalendar = Calendar.getInstance()
mCalendar.timeInMillis += 5 * 24 * 3600 * 1000
mCalendar.set(Calendar.HOUR_OF_DAY, 12)
create(context, bookEntryId, mCalendar.timeInMillis)
AppDatabase.getInstance(context).reminderDao().update(Reminder(bookEntryId, mCalendar.time))
}
fun cancelReminder(context: Context, bookEntryId: Long) {
cancel(context, bookEntryId)
AppDatabase.getInstance(context).reminderDao().delete(Reminder(bookEntryId))
}
private fun create(context: Context, bookEntryId: Long, fireDate: Long) {
val mAlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val mComponentName = ComponentName(context, ReminderReceiver::class.java)
context.packageManager.setComponentEnabledSetting(mComponentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP)
val mIntent = Intent(context, ReminderReceiver::class.java)
mIntent.action = INTENT_ACTION_REMINDER
mIntent.putExtra(NOTIFICATION_EXTRA_CLAIM_ID, bookEntryId)
val mPendingIntent = PendingIntent.getBroadcast(context, bookEntryId.toInt(), mIntent, PendingIntent.FLAG_UPDATE_CURRENT)
if (Utils.isKitKat()) {
mAlarmManager.setWindow(AlarmManager.RTC, fireDate, AlarmManager.INTERVAL_HOUR, mPendingIntent)
} else {
mAlarmManager.set(AlarmManager.RTC, fireDate, mPendingIntent)
}
}
private fun cancel(context: Context, bookEntryId: Long) {
val mAlarmManager: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val mComponentName = ComponentName(context, ReminderReceiver::class.java)
context.packageManager.setComponentEnabledSetting(mComponentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)
val mIntent = Intent(context, ReminderReceiver::class.java)
mIntent.putExtra(NOTIFICATION_EXTRA_CLAIM_ID, bookEntryId)
val mPendingIntent = PendingIntent.getBroadcast(context, bookEntryId.toInt(), mIntent, PendingIntent.FLAG_UPDATE_CURRENT)
mAlarmManager.cancel(mPendingIntent)
mPendingIntent.cancel()
}
}
}
BroadcastReceiver:
class ReminderReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (context != null && intent != null) {
when (intent.action) {
NotificationScheduler.INTENT_ACTION_REMINDER -> {
val mPowerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
val mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this::class.simpleName)
mWakeLock.acquire(WAKELOCK_TIME)
val iClaimEntryId = intent.getLongExtra(NotificationScheduler.NOTIFICATION_EXTRA_CLAIM_ID, -1)
showNotification(context, iClaimEntryId)
AppDatabase.getInstance(context).reminderDao().delete(Reminder(iClaimEntryId))
mWakeLock.release()
}
}
}
}
private fun showNotification(context: Context, claimEntryId: Long) {
val mNotificationManager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val nBuilder: Notification.Builder
if (Utils.isOreo()) {
val mNotificationChannel = NotificationChannel(NOTIFICATIONCHANNEL_CLAIMREMINDERID, context.getString(R.string.notificationchannel_claimreminder_title), NotificationManager.IMPORTANCE_DEFAULT)
mNotificationChannel.description = context.getString(R.string.notificationchannel_claimreminder_description)
mNotificationManager.createNotificationChannel(mNotificationChannel)
nBuilder = Notification.Builder(context, NOTIFICATIONCHANNEL_CLAIMREMINDERID)
} else {
nBuilder = Notification.Builder(context)
}
val mClaimEntry: BookEntry = AppDatabase.getInstance(context).bookEntryDao().get(claimEntryId)
val mCurrencyFormatter = DecimalFormat.getCurrencyInstance(Preferences.getInstance(context).currency.locale)
nBuilder.setSmallIcon(R.drawable.ic_money)
nBuilder.setContentTitle(context.getString(R.string.notification_claimreminder_title, mCurrencyFormatter.format(mClaimEntry.dValue)))
val sContacts = mClaimEntry.getContacts(context).joinToString().takeIf { it.isNotEmpty() }
?: "-"
nBuilder.setContentText(context.getString(R.string.notification_claimreminder_content, sContacts))
nBuilder.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
nBuilder.setAutoCancel(true)
mNotificationManager.notify(mClaimEntry.lId!!.toInt(), nBuilder.build())
}
companion object {
const val NOTIFICATIONCHANNEL_CLAIMREMINDERID = "notification_channel_claimreminder"
const val WAKELOCK_TIME: Long = 1000
}
}
I had issues with AlarmManager, so I am using WorkManager now.
WorkManager was introduced with Android Jetpack / Architecture Components.
Solution
First add dependency for WorkManager, you can find latest versions here.
Create a Worker class, and code for showing notification in doWork().
Schedule this work on your app start, also check if already not scheduled. I already created a method named isWorkScheduled() in below class for ease.
You can send additional data to your task (like putExtra()) by setInputData().
Schedule your one time task in first activity onCreate() or Application class onCreate.
Example
public static final String TAG_WORK = "myTag";
if(!MyWork.isWorkScheduled(TAG_WORK))
MyWork.scheduleOneTimeTask(TAG_WORK, 5, TimeUnit.DAYS)
MyWork.java
public class MyWork extends Worker {
public static void scheduleOneTimeTask(String tag, long duration, TimeUnit timeUnit) {
OneTimeWorkRequest compressionWork =
new OneTimeWorkRequest.Builder(MyWork.class).setInitialDelay(duration, timeUnit).addTag(tag)
.build();
WorkManager instance = WorkManager.getInstance();
if (instance != null) {
instance.enqueue(compressionWork);
}
}
private boolean isWorkScheduled(String tag) {
WorkManager instance = WorkManager.getInstance();
if (instance == null) return false;
LiveData<List<WorkStatus>> statuses = instance.getStatusesByTag(tag);
if (statuses.getValue() == null) return false;
boolean running = false;
for (WorkStatus workStatus : statuses.getValue()) {
running = workStatus.getState() == State.RUNNING | workStatus.getState() == State.ENQUEUED;
}
return running;
}
#NonNull
#Override
public Result doWork() {
// show notification
return Result.SUCCESS;
// (Returning RETRY tells WorkManager to try this task again
// later; FAILURE says not to try again.)
}
}
I suggested you WorkManger only, because I created a sample earlier
days with JobScheduler, EvernoteJobs,
AlarmManager, JobService, and WorkManager. In which I started periodic task of 15 minutes with each of these. and
wrote logs of each in separate file when invoked.
Conclusion of this test was that. WorkManager and EvernoteJobs were
most efficient to do jobs. Now because EvernoteJobs will use
WorkManager from next version. So I came up with WorkManager.
Update
Minimum time for scheduling periodic task is 15 minutes so please keep in mind. You can read more in documentation.
Is the device ever shut down during the five-day period? According to the documentation:
By default, all alarms are canceled when a device shuts down. To prevent this from happening, you can design your application to automatically restart a repeating alarm if the user reboots the device. This ensures that the AlarmManager will continue doing its task without the user needing to manually restart the alarm.
Set an alarm when the device restarts.
Another thing that comes to mind is you may want to use AlarmManager.RCT_WAKEUP instead of AlarmManager.RTC to make sure the device is awake to deliver the intent and create and dispatch your notification.
Try:
if (Utils.isKitKat()) {
mAlarmManager.setWindow(AlarmManager.RTC_WAKEUP, fireDate, AlarmManager.INTERVAL_HOUR, mPendingIntent)
} else {
mAlarmManager.set(AlarmManager.RTC_WAKEUP, fireDate, mPendingIntent)
}
Most probably , it is the case when your device is going in Doze mode and Alarm manager is not triggering your scheduled alarm
Try using( this is Java code ):
AlarmManager alarmManager = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
}
else{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent);
}
}
Take a look at setExactAndAllowWhileIdle