I'm setting up alarms using this code
//in onCreate()
mAlarmManager = (AlarmManager) getApplicationContext()
.getSystemService(ALARM_SERVICE);
//called for each timer I schedule
Intent intent = new Intent (Intents.MY_INTENT_ACTION);
PendingIntent pendIntent = PendingIntent.getBroadcast(
getApplicationContext(), alert.getID(),
intent, PendingIntent.FLAG_ONE_SHOT);
long delay = 1000 * alert.getDuration();
Calendar cal = Calendar.getInstance();
mAlarmManager.set(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis() + delay, pendIntent);
But the behavior I'm seeing doesn't match what I should see in the documtation1,
public void set(int type, long triggerAtTime, PendingIntent operation)
If there is already an alarm scheduled for the same IntentSender, it will first be canceled...If there is already an alarm for this Intent scheduled (with the equality of two intents being defined by filterEquals(Intent)), then it will be removed and replaced by this one...
which suggests that calling set(int type, long triggetAtTime, PendingIntent operation) for an already alarmed intent should replace the old alarm for that intent. I'm not seeing any alarms get dropped. Instead, every alarm i set fires, despite the fact that the intents that are fired by the pending intents should all match (by filterEquals(intent)), since all I've set on each intent is an identical action.
Am I doing something wrong, or is the API not behaving as documented?
Note: changing the PendingIntent instantiation to
PendingIntent pendIntent = PendingIntent.getBroadcast(
getApplicationContext(), CONSTANT_ID,
intent, PendingIntent.FLAG_ONE_SHOT);
Behaves as expected, dropping any already set alarm, and replacing it with a new alarm.
Maybe it is because you are giving each alarm a different ID (Does alert.getID() give different ID's or not?). By the documentation, it shouldn't matter but yet you should still try.
If it doesn't work too, hold a reference for your last set alarm, and when you need it to be canceled, cancel it yourself then set the next one.
Have you tried with PendingIntent flag : PendingIntent.FLAG_UPDATE_CURRENT intead of PendingIntent.FLAG_ONE_SHOT?
It appears the consensus is that the documentation for AlarmManager.set(), as well as other AlarmManager methods claiming that Intents (not just the wrapping PendingIntents) are compared to check whether a particular alarm is already set.
Do not rely on AlarmManager matching Intents, instead rely on the matching of PendingIntents, which appears to be working as advertised.
Related
I have an Android implementation in which I have to schedule notifications in the long term, I create the PendingIntent and use AlarmManager#setExact() method to do it:
Intent intent = new Intent(mContext, BirthdayReceiver.class);
intent.putExtra(BirthdayReceiver.INTENT_WHO, contact.getName());
PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext, id, intent, 0);
mAlarmManager.setExact(
AlarmManager.RTC,
notificationCalendar.getTimeInMillis(),
pendingIntent);
I've checked the time passed to setExact() method, if it's a short time (minutes, few hours) then the notification appears correctly.
But if I set a notification to fire, let's say the next day at 12:00 it doesn't work.
I am using an Android 8 device to test it.
Any help?
Seems like flag is not specified.
Could you try it with FLAG_UPDATE_CURRENT .
PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Also, in order to confirm if your PendingIntent is still available in the system, you can use FLAG_NO_CREATE.
PendingIntent broadcastIntent = PendingIntent.getBroadcast(mContext, id,
intent, PendingIntent.FLAG_NO_CREATE);
if broadcastIntent returns null then the PendingIntent is not avaialble (possibly cancelled) in the system.
As per the documentation of setexact
This method is like set(int, long, PendingIntent), but does not permit
the OS to adjust the delivery time. The alarm will be delivered as
nearly as possible to the requested trigger time.
So you may not expect the alarm to be triggered immediately.
Note: only alarms for which there is a strong demand for exact-time
delivery (such as an alarm clock ringing at the requested time) should
be scheduled as exact. Applications are strongly discouraged from
using exact alarms unnecessarily as they reduce the OS's ability to
minimize battery use.
So you can really consider whether you really need to call this API or not
If I create a PendingIntent with FLAG_ONE_SHOT, a subsequent PendingIntent with FLAG_NO_CREATE returns null.
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context,AlarmService.class);
PendingIntent pi = PendingIntent.getService(context,this.getId(),intent,PendingIntent.FLAG_ON_SHOT);
GregorianCalendar alarmtime = new GregorianCalendar(now.get(GregorianCalendar.YEAR),now.get(GregorianCalendar.MONTH),now.get(GregorianCalendar.DAY_OF_MONTH),0,0);
//Set the alarm
if (Build.VERSION.SDK_INT<Build.VERSION_CODES.KITKAT) {
am.set(AlarmManager.RTC_WAKEUP,alarmtime.getTimeInMillis(), pi);
} else {
am.setExact(AlarmManager.RTC_WAKEUP, alarmtime.getTimeInMillis(), pi);
}
//Now check if the alarm was set, if it was set, the following PendingIntent should return not null but it doesn't
PendingIntent piCheck = PendingIntent.getService(context,this.getId(),intent,PendingIntent.FLAG_NO_CREATE);
if (piCheck!=null) {
Log.d(TAG,"piCheck returned NOT NULL and probably returned pi");
} else if (piCheck==null) {
Log.d(TAG,"piCheck returned NULL pi does not exist");
However if I change the first pending intent to:
PendingIntent pi = PendingIntent.getService(context,this.getId(),intent,PendingIntent.FLAG_CANCEL_CURRENT);
Then my second PendingIntent returns not null as expected.
Both PendingIntents set an alarm properly, but I cannot "check" the FLAG_ONE_SHOT PendingIntent. What is the reason for this behaviour? What is the purpose of it?
I created a small test program to verify this behaviour. If you create a PendingIntent using FLAG_ONE_SHOT and then pass this to the AlarmManager, it looks like Android "consumes" the PendingIntent immediately so that it no longer exists (because it is a "one-shot", it can only be used once). I would have thought this would happen when the alarm actually triggers, but it looks like that isn't the case.
You learn something new every day :-)
To solve your problem, I would just remove the FLAG_ONE_SHOT as you probably don't need it (just pass 0 as the "flags" argument). If you set the alarm more than once, you can use FLAG_UPDATE_CURRENT if you set the alarm with different extras each time (but it looks like you aren't using any extras, so you probably don't need this). I don't think that you need FLAG_ONE_SHOT for what you are trying to do.
The problem here arises because FLAG_ONE_SHOT describes the PendingIntent and so will be needed to identify it.
You can check for its existence by using FLAG_ONE_SHOT | FLAG_NO_CREATE.
Your code snippet is essentially checking for a different PendingIntent (one without FLAG_ONE_SHOT) which doesn't exist and hence you get null.
You can also try this out with FLAG_IMMUTABLE which is another flag that describes the requested PendingIntent.
I don't think that alarms are involved.
I have created a configure activity for my widget, where the user can choose from various update frequencies.. Until now I started the alarm in the OnEnabled() method, like this:
Intent intent = new Intent(CLOCK_WIDGET_UPDATE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), 1000 * 60,
pendingIntent);
The settings are saved in shared preferences with a unique name (widgetId) and in this OnEnabled() method I can't retrieve the settings here because I can't get the widgetId yet.
There's an another problem, the user can change the frequency anytime, but this method is called just once, at the beginning. So I think I need to start the alarm in OnUpdate(), but I don't know how to do it, I don't want to make multiple instances of an alarm accidentally so I would like to ask for some advice.
To answer your second problem, calling setRepeating multiple times will not create multiple alarm as far as you provide same PendingIntent and same request code along with PendingIntent.FLAG_UPDATE_CURRENT flag. I would also suggest to use setInexactRepeating instead of setRepeating. So you can use the same code in OnUpdate() too with new frequency. Go through docs of FLAG_UPDATE_CURRENT and setInexactRepeating for more detials.
I have implemented an Alarm class wich should set a new pending Intent and always overriwrite the old one. (I would rather stop/delete all old ones but I dont know how to)
private void startAlarm(){
Intent intent = new Intent(source, Alarm_Activity.class);
// 10000 should be the ID of Intent
PendingIntent pendingIntent = PendingIntent.getActivity(source, 10000, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager am = (AlarmManager)source.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(),pendingIntent);
}
Unfortionatly I create this class multiple times and from different activities. I think this is the reason why it doesnt cancel the last intent (Flag_cancel_current). How can I make the Flag work throughout multiple instances of this class?
Given your code, so long as all places are using the same Intent (pointing to Alarm_Activity.class) and are using the same PendingIntent ID (10000 in your sample), your code will cancel any current PendingIntent.
This does not cancel any current alarms.
To cancel an alarm, call cancel() on AlarmManager. In particular, if you do this, get rid of PendingIntent.FLAG_CANCEL_CURRENT, so your cancelling of the old PendingIntent does not somehow interfere with your cancelling of the old alarm tied to that PendingIntent.
I have an app which reminds people to do their tasks. So there is one PendingIntent, now the user can delete the alarm when he wants to. In this code, there is just one PendingIntent for multiple user alarms so I am confused on cancelling that particular alarm where the intent extras is "pill". The remaining alarms should not be cancelled. I have no clue on this problem. Hope I am clear. Thanks
Intent intent = new Intent(this, AlarmNotifyReceiver.class);
intent.putExtra("Name_pill", "pill");
sender = PendingIntent.getBroadcast(this,
DatabaseConstants.NOTIFICATION_ID + 1, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP,cal.getTimeInMillis(), sender);
updateTheFlag(pillName[(pillName.length-1)]);
According to the Android documentation, in order to stop an alarm, you should create an Intent with the same data, but not necessarily the same extras:
public void cancel (PendingIntent operation)
Remove any alarms with a matching Intent. Any alarm, of any type, whose Intent matches this > one (as defined by filterEquals(Intent)), will be canceled.
filterEquals(Intent)
public boolean filterEquals (Intent other)
Determine if two intents are the same for the purposes of intent resolution (filtering). > That is, if their action, data, type, class, and categories are the same. This does not compare any extra data included in the intents.
As I stated in my comment, it appears that you simply need to recreate the exact same PendingIntent object, and put the same Extras into it. Then, you call
am.cancel(sender);
And your specific alarm should be canceled. I can't find a better way of doing it, personally. I found this information to confirm my expectation elsewhere.
It reads:
Repeating alarms have to be cancelled to stop them. AlarmManager provide a cancel() method that requires the same intent class with which the intent is created. This is how you can cancel the alarm.
alarmManager.cancel(pendingIntent);
Note the pendingIntent object does not need to be same object. The intent fields like action, class, category etc should be same while creating the alarm. The intent is used to identify the alarm to cancel it.
It is in the context of repeating alarms, but one-time alarms should be canceled in the same manner, if I am not mistaken. I am unable to test it more thoroughly on my own because I am at work, but this should work.
I think the requestCode parameter in getBroadcast() needs to be mentioned. I agree that all the alarms will be canceled matching with the given Intent. But an alarm can be made to be unique by using unique requestCode when defining the PendingIntent for canceling. So only those alarms will be canceled which has the same intent and requestCode:
int TIMER_1 = 1;
int TIMER_2 = 2;
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
Intent i = new Intent(this, AppReciever.class);
i.putExtra("timer", "one");
PendingIntent pending = PendingIntent.getBroadcast(this, TIMER_1, i,
PendingIntent.FLAG_CANCEL_CURRENT);
Calendar cal = Calendar.getInstance();
am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pending);
then check that the PendingIntent exist according to this:
PendingIntent pending1 = PendingIntent.getBroadcast(this, TIMER_2, i,
PendingIntent.FLAG_NO_CREATE);
boolean alarmUp = (pending1 != null);
alarmUp will be false (note FLAG_NO_CREATE is used not to create a new one if not exist) so trying with same requestCode:
PendingIntent pending2 = PendingIntent.getBroadcast(this, TIMER_1, i,
PendingIntent.FLAG_NO_CREATE);
alarmUp = (pending2 != null);
alarmUp will be true, now trying with a new intent contains different extra:
Intent i2 = new Intent(this, AppReciever.class);
i2.putExtra("timer", "two");
pending2 = PendingIntent.getBroadcast(this, TIMER_1, i2,
PendingIntent.FLAG_NO_CREATE);
alarmUp = (pending2 != null);
alarmUp will be true as well since the i and i2 are the same although the extra is not, so now you can remove this alarm:
am.cancel(pending2);
So there is one pending intent,now the user can delete the alram when
he wants to. Ib this code there is just one pending intent for
multiple user alarms so I am confused on cancelling that particular
alarm where extras is pill
intent.putExtra("Name_pill", "pill");
The extra wont wont work to cancel your pending intent .
pendingIntent.cancel() will only remove that pending intent which triggered with same filterEquals(Intent) and that method is not compare any extra data given to intent .
this is the contain from developer site of android filterEquals(Intent)
Determine if two intents are the same for the purposes of intent
resolution (filtering). That is, if their action, data, type, class,
and categories are the same. This does not compare any extra data
included in the intents.
if we consider your scenario , when you will pass that Extra to intent at that time you only need to save unique ID in some sharedpreference which given in parameter and one thing your should keep in mind that ID has to be an unique .
and and when you suppose to cancel that alarm , just pass same intent with that saved ID and cancel that pendingintent .
Create
preference_saved_value = DatabaseConstants.NOTIFICATION_ID + 1
sender = PendingIntent.getBroadcast(this,
preference_saved_value, intent,
PendingIntent.FLAG_UPDATE_CURRENT)
CANCEL
sender = PendingIntent.getBroadcast(this,
preference_saved_value, intent,PendingIntent.FLAG_UPDATE_CURRENT);
sender.cancel()
As it's stated in android documentation pending intents with intent that are equivalent as per Intent.filterEquals but have different request code are considered different:
If you truly need multiple distinct PendingIntent objects active at
the same time (such as to use as two notifications that are both shown
at the same time), then you will need to ensure there is something
that is different about them to associate them with different
PendingIntents. This may be any of the Intent attributes considered by
Intent.filterEquals, or different request code integers supplied to
getActivity(Context, int, Intent, int), getActivities(Context, int,
Intent[], int), getBroadcast(Context, int, Intent, int), or
getService(Context, int, Intent, int).
So, you can assign different request code and cancel the pending intents base on them and forget about the extra.
There was an interesting scenario that I figured out this behavior:
I scheduled an alarm in my code and run it on the device but never canceled it. Then I changed the request code and run it again. So a new alarm was created. I canceled the new alarm but the alarm was still executing from previous code. I get confused why the alarm is not canceled. After I found out it's from the previous code with different request code I uninstalled the app and installed it again and problem was solved.