Android Alarm Not Set - android

I used to set alarms using the following code-segment in other projects as repeating and non-repeating, but it's now driving me crazy about what may the silly mistake be that I've made for not the alarm speaking to my current implementation :\ :
private fun setAlarm(obj: MyObject, time: Long) {
val intent = Intent(applicationContext, MyAlarmIntentService::class.java)
intent.putExtra(C.KEY_ME, obj)
val pendingIntent = PendingIntent.getService(applicationContext, 43, intent,
PendingIntent.FLAG_UPDATE_CURRENT)
val context = this#MyActivity
val cal = Calendar.getInstance()
cal.timeInMillis = time * 1000 // time is in seconds
Log.d("setAlarm", "setting time -> $time for obj = $obj")
Log.d("setAlarm", "Set calendar: " + cal.toString())
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
#RequiresApi(Build.VERSION_CODES.LOLLIPOP) // Overriding doze-mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val alarmClockInfo =
AlarmManager.AlarmClockInfo(cal.timeInMillis, null)
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) // Overriding doze-mode
// Not-working in Nexus 6 - API 25 (7.1.1), but works in API 23 (6.0.1, tested in multiple devices)
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
cal.timeInMillis, pendingIntent)
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) // Works
alarmManager.setExact(AlarmManager.RTC_WAKEUP, cal.timeInMillis, pendingIntent)
else // works
alarmManager.set(AlarmManager.RTC_WAKEUP, cal.timeInMillis, pendingIntent)
}
The IntentService class is as the following:
class MyAlarmIntentService : IntentService("Notification") {
override fun onHandleIntent(intent: Intent?) {
val obj = intent?.getParcelableExtra<MyObj>(C.KEY_ME) ?: return
Lg.d(TAG, "My alarm fired for - \n" + obj.toString())
}
companion object {
private val TAG = ContestAlarmIntentService::class.java.simpleName
}
}
The service is declared in manifest as:
<service android:name=".services.MyAlarmIntentService" />
The following is an example of the mentioned log inside setAlarm() method:
E/setAlarm: setting time -> 1514993850 for obj = MyObj{...}
E/setAlarm: Set calendar: java.util.GregorianCalendar[time=1514993850000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=libcore.util.ZoneInfo[id="Asia/Dhaka",mRawOffset=21600000,mEarliestRawOffset=23400000,mUseDst=false,mDstSavings=0,transitions=7],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2018,MONTH=0,WEEK_OF_YEAR=1,WEEK_OF_MONTH=1,DAY_OF_MONTH=3,DAY_OF_YEAR=3,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=1,AM_PM=1,HOUR=9,HOUR_OF_DAY=21,MINUTE=37,SECOND=30,MILLISECOND=0,ZONE_OFFSET=21600000,DST_OFFSET=0]
This used to be a straight-forward code to me, but which fundamental may I be missing for these 3 days' of test sessions?

Sorry I did not go through all your code as I was busy in another task, so here's my code used with Broadcast
Intent intent = new Intent(context, AlarmReceiver.class);
intent.setAction(UtilAlarmConstants.ALARM_ACTION);
pIBroadCast = PendingIntent.getBroadcast(context, ALARM_PI_REQ_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager = (AlarmManager) context.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
if (alarmManager != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
alarmManager.setExactAndAllowWhileIdle(ALARM_TYPE, calendar.getTimeInMillis(),pendingIntent);
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AlarmManager.AlarmClockInfo alarmClockInfo
= new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + fireAT, null);
alarmManager.setAlarmClock(alarmClockInfo, pIBroadCast);
}
//setExact for 19
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireAT, pIBroadCast);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, fireAT, pIBroadCast);
}
}
and Broadcast
if (intent != null && intent.getAction() != null) {
if (intent.getAction().equalsIgnoreCase(UtilAlarmConstants.ALARM_ACTION)) {
setUpNotification(context, intent);}
sorry for not giving answer related to service as they are all same wacky dacky :)

Related

Android - AlarmManager not firing BroadcastReceiver to show Local Notifications

I am writing a prayer application which requires the application to show Local Notifications on PrayerTimes. Prayer times and different for each day, thus I am using the following bit of code to show a Location Notification from BroadcastReceiver and right after that schedule next notification.
The problem is, the application is required to open at least once a day for the notifications to keep firing on their specific timings.
Is there a way to schedule BroadcastReceiver using Alarm Manager to fire Local Notifications without opening the app?
fun MakkahPrayer.setNotificationForPrayer(prayer: Prayer, date: Date) {
val app = App.instance!!.applicationContext
val preferences = PreferenceManager.getInstance(app)
if(!preferences.isPrayerAlarmSet(prayer.name)) {
val calendar = Calendar.getInstance()
calendar.add(Calendar.DAY_OF_YEAR, 0)
val dayOfYear = calendar[Calendar.DAY_OF_YEAR]
NotificationUtils.instance.setNotification(date.time, prayer.name, dayOfYear.toString())
preferences.setPrayerIsAlarmOn(prayer.name, true)
}
}
NotificationUtils.kt
class NotificationUtils {
companion object {
val instance = NotificationUtils()
}
fun setNotification(timeInMilliSeconds: Long, name: String, day: String) {
val cal = Calendar.getInstance()
cal.time = Date()
val millis = cal.timeInMillis
if (timeInMilliSeconds > 0 && timeInMilliSeconds > millis) {
val key = name + day
val alarmManager =
App.instance?.getSystemService(Activity.ALARM_SERVICE) as AlarmManager
val alarmIntent = Intent(App.instance?.applicationContext, AlarmReceiver::class.java)
alarmIntent.putExtra("prayer", name)
alarmIntent.putExtra("timestamp", timeInMilliSeconds)
alarmIntent.putExtra("notificationID", key)
val calendar = Calendar.getInstance()
calendar.timeInMillis = timeInMilliSeconds
val pendingIntent = PendingIntent.getBroadcast(
App.instance,
timeInMilliSeconds.toInt(),
alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMilliSeconds, pendingIntent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMilliSeconds, pendingIntent)
} else {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMilliSeconds, pendingIntent)
}
}
}
}
AlarmReceiver.kt
class AlarmReceiver : BroadcastReceiver() {
companion object {
private lateinit var mNotification: Notification
const val CHANNEL_ID = "CHANNEL_ID"
const val CHANNEL_NAME = "Prayer Notification"
}
override fun onReceive(context: Context, intent: Intent) {
val manager = createChannel(context)
showNotification(context, intent, manager)
setNextPrayerAlarm(intent)
}
private fun setNextPrayerAlarm(intent: Intent) {
if (intent.extras != null) {
val prayerName = intent.extras!!.getString("prayer", "Prayer")
val prayer = Prayer.valueOf(prayerName)
MakkahPrayer.instance.removePrayerNotification(prayer)
}
val (nextPrayer, date) = MakkahPrayer.instance.nextPrayerWithTime()
MakkahPrayer.instance.setNotificationForPrayer(nextPrayer, date)
}
private fun showNotification(
context: Context,
intent: Intent,
notificationManager: NotificationManager
) {
var timestamp: Long = 0
var prayerName = "Prayer"
var mNotificationId = ""
if (intent.extras != null) {
timestamp = intent.extras!!.getLong("timestamp")
prayerName = intent.extras!!.getString("prayer", "Prayer")
mNotificationId = intent.extras!!.getString("notificationID", "")
}
if (timestamp > 0) {
val notifyIntent = Intent(context, MainActivity::class.java)
val title = capitalize(prayerName)
val message = "It is $title time"
notifyIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
val calendar = Calendar.getInstance()
calendar.timeInMillis = timestamp
val pendingIntent = PendingIntent.getActivity(
context,
0,
notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
mNotification = NotificationCompat.Builder(context, NotificationService.CHANNEL_ID)
.setContentIntent(pendingIntent)
.setSmallIcon(R.drawable.ic_alarm_black_24dp)
.setLargeIcon(
BitmapFactory.decodeResource(
context.resources,
R.mipmap.ic_launcher
)
)
.setSound(uri)
.setAutoCancel(true)
.setContentTitle(title)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(message)
)
.setColor(ContextCompat.getColor(context, R.color.colorSecondary))
.setContentText(message).build()
notificationManager.notify(timestamp.toInt(), mNotification)
}
}
#SuppressLint("NewApi")
private fun createChannel(context: Context): NotificationManager {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val soundUri =
Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + App.instance?.applicationContext?.packageName + "/" + R.raw.azan)
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build()
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance)
channel.enableVibration(true)
channel.setShowBadge(true)
channel.canShowBadge()
channel.enableLights(true)
channel.lightColor = context.getColor(R.color.colorSecondary)
channel.description =
context.getString(R.string.notification_channel_description)
channel.setSound(soundUri, audioAttributes)
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
notificationManager.createNotificationChannel(channel)
return notificationManager
} else {
return context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
}
}
Edit:
After using the following methods, as described by people below, it still is not working, i.e app must be open at least one time in 24 hours, for it to produce local notifications.
I am looking for a solution, where the app should not have to be open for leats say 4,5 days and the app should deliver local notifications.
For now, it works for only 24 hours, when the next day comes, notifications stop firing, requiring the app to be open for at least once a day.
You can create a PrayerWorker using Androidx Work Manager to schedule a background API/setting of notifications (all without using opening app, and instead being trigered when notification is received.
Documentation can be found here
Your setNextPrayerAlarm function will instead have the logic moved to the PrayerWorker and look something like this :
private fun setNextPrayerAlarm(intent: Intent) {
if (intent.extras != null) {
val oneTimeWorkRequestBuilder = OneTimeWorkRequest.Builder(PrayerWorker::class.java)
oneTimeWorkRequestBuilder.setInputData(`put your input data here`)
WorkManager.getInstance(context).enqueueUniqueWork("setPrayerWorker",ExistingWorkPolicy.REPLACE, oneTimeWorkRequestBuilder.build())
}
}
and the PrayerWorker may look something like this
class PrayerWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
//Insert logic to determine alarms to set
return Result.success() //for success case
}
}
EDIT 1 :
Hi, i should have been clearer in the method, sorry. There's two ways you can make this a repeating alarm.
Method 1:
Modify the OneTimeWorkRequest to a PeriodicWorkRequest(refer to documentation here). Using this method, you can specify how you want the worker that sets to repeat (e.g. every 2 hours, every 24 hours). The min interval is 15 mins.
Method 2:
Modify PrayerWorker to also schedule the next worker. This will utilise the fact that you can add a delay to the triggering of the worker(refer to documentation), which in this case will be 24 hours. Below is the example
class PrayerWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
override fun doWork(): Result {
//Insert logic to determine alarms to set
val oneTimeWorkRequestBuilder = OneTimeWorkRequest.Builder(PrayerWorker::class.java)
oneTimeWorkRequestBuilder.setInputData(`put your input data here`)
oneTimeWorkRequestBuilder.setInitialDelay(`initialDelay`, `timeUnit`)
WorkManager.getInstance(context).enqueueUniqueWork("setPrayerWorker",ExistingWorkPolicy.REPLACE, oneTimeWorkRequestBuilder.build())
return Result.success() //for success case
}
}
Try following steps
1. In NotificationUtils.kt add an intent Flag FLAG_RECEIVER_FOREGROUND
as like below which will do the trick for you
val alarmIntent = Intent(App.instance?.applicationContext, AlarmReceiver::class.java)
alarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
alarmIntent.putExtra("prayer", name)
....
...
2. Also make sure you have registered AlarmReceiver in Manifest
like below
<receiver android:name="com.myapp.receiver.AlarmReceiver">
</receiver>
I don't know which android Sdk level your app is targeting, but Google has changed it's APIs starting from O. Declaring implicit Broadcast receiver from manifest will not work.
As part of the Android 8.0 (API level 26) Background Execution Limits, apps that target the API level 26 or higher can no longer register broadcast receivers for implicit broadcasts in their manifest. However, several broadcasts are currently exempted from these limitations. Apps can continue to register listeners for the following broadcasts, no matter what API level the apps target.
more on that here: https://developer.android.com/guide/components/broadcast-exceptions

Run a method at a specific time without repeat

I need to call a web API at a specific time without repeat it. I use AlarmManager but it never fired. I also implement handler.postDelayed but it repeats itself. I don't know how to implement it correctly.
this is my alarmManager :
alarmManager = (getCTX()?.getSystemService(Context.ALARM_SERVICE)) as (AlarmManager)
var intent: Intent = Intent(getCTX(), NextLiveReceiver::class.java)
intent.putExtra("next", next)
intent.putExtra("curTime", curTime)
myPendingIntent =
PendingIntent.getBroadcast(
getCTX(),
0,//next.toInt(),
intent,
0
)
var ALARM_TYPE = AlarmManager.RTC_WAKEUP
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
alarmManager.setExactAndAllowWhileIdle(ALARM_TYPE, next, myPendingIntent);
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
alarmManager.setExact(ALARM_TYPE, next, myPendingIntent);
else
alarmManager.set(ALARM_TYPE, next, myPendingIntent)
this is AlarmManagerReceiver :
class NextLiveReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
var next: Long = intent.getLongExtra("next", 0)
var current: Long = intent.getLongExtra("curTime", 0)
if (next > 0 && current > 0) {
EventBus.send(NextEvent(true, next, current))
}
}
}
and this is my handler :
var handler: Handler = Handler()
handler.postDelayed({
viewModel.fetchLiveInfo(viewModel.currentChannelId.value!!, next, 0)
}, delay)
handler.removeCallbacks(null)
WorkManager, Handler, and AlarmManager did not work as I expected. So, I resolved it with Kotlin delay in this way :
runBlocking {
scope.launch(Dispatchers.Main + handler) {
Timber.i("--Launch-- start : ${getUnixTime()}")
delay(delayTime)
removeRate()
refetchLiveInfo(channelId, liveInfoResult[0].next, 1)
Timber.i("--Launch-- ended : ${getUnixTime()}")
}
}

How to set an alarm on Android Q?

Background
Android Q seems to have plenty of new restrictions, but alarms shouldn't be one of them:
https://developer.android.com/guide/components/activities/background-starts
The problem
It seems that old code that I made for setting an alarm, which worked fine on P, can't work well anymore:
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var manager: AlarmManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
manager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
button.setOnClickListener {
Log.d("AppLog", "alarm set")
Toast.makeText(this, "alarm set", Toast.LENGTH_SHORT).show()
val timeToTrigger = System.currentTimeMillis() + 10 * 1000
setAlarm(this, timeToTrigger, 1)
}
}
companion object {
fun setAlarm(context: Context, timeToTrigger: Long, requestId: Int) {
val manager = 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.LOLLIPOP -> manager.setAlarmClock(AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
VERSION.SDK_INT >= VERSION_CODES.KITKAT -> manager.setExact(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
else -> manager.set(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
}
}
}
}
The receiver does get the Intent, but when it tries to open the Activity, sometimes nothing occurs:
AlarmReceiver.kt
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("AppLog", "AlarmReceiver onReceive")
context.startActivity(Intent(context, Main2Activity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
}
Seeing this as a bug, I reported here (including sample code)
What I've tried
I tried to find what's new on Q, to see what could cause it, and I couldn't find it.
I also tried (if you look at the code) to directly open the Activity instead of via a BroadcastReceiver.
And, I tried to set the BroadcastReceiver to run on a different process.
All of those didn't help.
What I have found is that while some alarm clock apps fail to work properly (such as Timely), some apps work just fine (such as "Alarm Clock Xtreme").
The questions
On Android Q, is there an official way to let alarms work correctly? To open an Activity that will be shown to the user, exactly as an alarm clock app should?
What's wrong in the code I've made? How come it works on P but not always on Q?
EDIT: OK after being adviced to have a notification shown while I start the Activity, and also use FullScreenIntent, I got something to work, but it's only working when the screen is turned off. When the screen is turned on, it only shows the notification, which is a bad thing because the whole point is to have an alarm being shown to the user, and some users (like me) don't want to have heads-up-notification for alarms, popping out in the middle of something and not pausing anything. I hope someone can help with this, as this used to be a very easy thing to do, and now it got way too complex...
Here's the current code (available here) :
NotificationId
object NotificationId {
const val ALARM_TRIGGERED = 1
#JvmStatic
private var hasInitialized = false
#UiThread
#JvmStatic
fun initNotificationsChannels(context: Context) {
if (hasInitialized || Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
return
hasInitialized = true
val channelsToUpdateOrAdd = HashMap<String, NotificationChannel>()
val channel = NotificationChannel(context.getString(R.string.channel_id__alarm_triggered), context.getString(R.string.channel_name__alarm_triggered), NotificationManager.IMPORTANCE_HIGH)
channel.description = context.getString(R.string.channel_description__alarm_triggered)
channel.enableLights(true)
channel.setSound(null, null)
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
channel.enableVibration(false)
channel.setShowBadge(false)
channelsToUpdateOrAdd[channel.id] = channel
//
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val existingChannels = notificationManager.notificationChannels
if (existingChannels != null)
for (existingChannel in existingChannels) {
// The importance of an existing channel will only be changed if the new importance is lower than the current value and the user has not altered any settings on this channel.
// The group an existing channel will only be changed if the channel does not already belong to a group. All other fields are ignored for channels that already exist.
val channelToUpdateOrAdd = channelsToUpdateOrAdd[existingChannel.id]
if (channelToUpdateOrAdd == null) //|| channelToUpdateOrAdd.importance > existingChannel.importance || (existingChannel.group != null && channelToUpdateOrAdd.group != existingChannel.group))
notificationManager.deleteNotificationChannel(existingChannel.id)
}
for (notificationChannel in channelsToUpdateOrAdd.values) {
notificationManager.createNotificationChannel(notificationChannel)
}
}
}
MyService.kt
class MyService : Service() {
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("AppLog", "MyService onStartCommand")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationId.initNotificationsChannels(this)
val builder = NotificationCompat.Builder(this, getString(R.string.channel_id__alarm_triggered)).setSmallIcon(android.R.drawable.sym_def_app_icon) //
.setPriority(NotificationCompat.PRIORITY_HIGH).setCategory(NotificationCompat.CATEGORY_ALARM)
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
builder.setShowWhen(false)
builder.setContentText("Alarm is triggered!")
builder.setContentTitle("Alarm!!!")
val fullScreenIntent = Intent(this, Main2Activity::class.java)
val fullScreenPendingIntent = PendingIntent.getActivity(this, 0,
fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT)
builder.setFullScreenIntent(fullScreenPendingIntent, true)
startForeground(NotificationId.ALARM_TRIGGERED, builder.build())
startActivity(Intent(this, Main2Activity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
Handler().postDelayed({
stopForeground(true)
stopSelf()
}, 2000L)
} else {
startActivity(Intent(this, Main2Activity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
return super.onStartCommand(intent, flags, startId)
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var manager: AlarmManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
manager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
button.setOnClickListener {
Log.d("AppLog", "alarm set")
Toast.makeText(this, "alarm set", Toast.LENGTH_SHORT).show()
val timeToTrigger = System.currentTimeMillis() + 10 * 1000
setAlarm(this, timeToTrigger, 1)
}
}
companion object {
fun setAlarm(context: Context, timeToTrigger: Long, requestId: Int) {
val manager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getBroadcast(context, requestId, Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
// val pendingIntent = PendingIntent.getBroadcast(context, requestId, Intent(context, Main2Activity::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
// val pendingIntent = PendingIntent.getService(context, requestId, Intent(context, MyService::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
when {
VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP -> manager.setAlarmClock(AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
VERSION.SDK_INT >= VERSION_CODES.KITKAT -> manager.setExact(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
else -> manager.set(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
}
}
}
}
AlarmReceiver.kt
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("AppLog", "AlarmReceiver onReceive")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(Intent(context, MyService::class.java))
} else context.startService(Intent(context, MyService::class.java))
}
}
What's wrong in the code I've made? How come it works on P but not always on Q?
You are attempting to start an activity from the background. That is banned on Android 10+ for the most part.
According to the docs, alarms shouldn't be harmed.
From the material that you quoted, with emphasis added: "The app receives a notification PendingIntent from the system". You are not using notifications. And, therefore, this exception does not apply.
On Android Q, is there an official way to let alarms work correctly? To open an Activity that will be shown to the user, exactly as an alarm clock app should?
Use a notification with a full-screen Intent, as is covered in the documentation. If the screen is locked, your activity will be displayed when the notification is raised. If the screen is unlocked, a high-priority ("heads up") notification will be displayed instead. In other words:
If the device is not being used, you get what you want
If the device is probably being used, the user find out about the event without your taking over the screen, so you do not interfere with whatever the user is doing (e.g., relying on a navigation app while driving)

Sending notification via AlarmManager and BroadcastReceiver does not work?

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

AlarmManager setEexact() not working

I have have two AlarmManager scheduled to fire once a day at 23:59 and another one that fires at the same time but once a week.
Below is the two functions that do this:
private fun midnightTimeClearCache(dataType: DataType, requestCode: Int) {
val calendar = Calendar.getInstance()
calendar.timeInMillis = System.currentTimeMillis()
calendar.set(Calendar.HOUR_OF_DAY, 23)
calendar.set(Calendar.MINUTE, 59)
val intent = Intent(context, ClearDataReceiver::class.java)
intent.putExtra(ClearDataReceiver.DATA_TYPE_EXTRA, dataType.name)
val pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT)
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent)
}
private fun weekTimeClearCache(dataType: DataType, requestCode: Int) {
val calendar = Calendar.getInstance()
calendar.timeInMillis = System.currentTimeMillis()
calendar.set(Calendar.HOUR_OF_DAY, 23)
calendar.set(Calendar.MINUTE, 59)
var i: Int = calendar.get(Calendar.WEEK_OF_MONTH)
calendar.set(Calendar.WEEK_OF_MONTH, i++)
val intent = Intent(context, ClearDataReceiver::class.java)
intent.putExtra(ClearDataReceiver.DATA_TYPE_EXTRA, dataType.name)
val pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT)
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent)
}
Below is the reciever that should get fired as part of the above pending Intents
class ClearDataReceiver : DaggerBroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
super.onReceive(context, intent)
var bundle = intent!!.extras
val dataTypeString = bundle.getString(DATA_TYPE_EXTRA)
val dataType = DataType.valueOf(dataTypeString)
Log.d("JJJ", "clearing data for " + dataTypeString)
when (dataType) {
DataType.CUSTOMER_DETAILS -> {
storage.clearDetails()
storageClearSchedular.setCustomerDetailsClear()
}
DataType.CUSTOMER_PIC -> {
storage.clearPic()
storageClearSchedular.setPicClear()
}
}
here is my manifest:
And the boot reciever so i can reset the alerm manager
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("JJJ", "onReceive")
storageClearSchedular = StorageClearSchedular(context!!, context!!.getSystemService(Context.ALARM_SERVICE) as AlarmManager)
storageClearSchedular.setAllSchedulars()
When i set the above alert manager schedules and then kill the app from the background before i head to bed, i then load up the app again to find that it diddnt execute my broadcast reciever(no details on the logs and data not cleared).
What should happen is that it should execute every dat at the time i set or once a week and then set it again after it has been fired but it doesnt seem to work at all.
I first tried setRepeating but that diddnt work as im targeting devices from api 19 and above
What am i missing?
For api version above 19 use this code
if (Build.VERSION.SDK_INT < 19) {
alarmManager.set(AlarmManager.RTC,
calendar.getTimeInMillis(), pendingIntent);
}
} else if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT <= 22) {
alarmManager.setExact(AlarmManager.RTC,
calendar.timeInMillis, pendingIntent)
} else if (Build.VERSION.SDK_INT >= 23) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC,
calendar.timeInMillis, pendingIntent)
}
For the daily alarm, you can check the condition. This example will set alarm for 9 AM Daily. Also, you may need to set the alarm again when the device restarts or boots up because all the alarms get canceled once the device is powered off or restarted.
Calendar calendar = Calendar.getInstance()
// set selected time from timepicker to calendar
calendar.setTimeInMillis(System.currentTimeMillis());
// if it's after or equal 9 am schedule for next day
if (Calendar.getInstance().get(Calendar.HOUR_OF_DAY) >= 9) {
Log.e("", "Alarm will schedule for next day!");
calendar.add(Calendar.DAY_OF_YEAR, 1); // add, not set!
}
else{
Log.e("", "Alarm will schedule for today!");
}
calendar.set(Calendar.HOUR_OF_DAY, 9);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);

Categories

Resources