How to properly cancel pending alarms - android

I can't seem to find an answer to this, but what is the criteria for passing in a matching PendingIntent to be removed from an alarm? It it based on the just the name like com.blah.package.myclass or do the Extras matter?
For example, I'm doing something like this and all alarms fire the same intent:
public void setAlarm(Context context, long nextAlarmMillis) {
Intent intent = new Intent(context, AlarmReceiver.class);
intent.putExtra("alarm", this);
PendingIntent sender = PendingIntent.getBroadcast(context, 1234 /* unused */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Get the AlarmManager service
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
am.cancel(sender);
am.set(AlarmManager.RTC_WAKEUP, nextAlarmMillis, sender);
}
What happens is that the Alarm class can be altered, etc and passed in as an Extra. I am canceling the previous alarm, but I'm not sure if it's doing anything or if any left over alarms will remain.
Anyone know for sure?

Following is mentioned in AlarmManager documentation
Any alarm, of any type, whose Intent matches this one (as defined by filterEquals(Intent)), will be canceled.
Which, the filterEquals() is defined as:
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.
I think the easiest to do is you keep the reference of PendingIntent passed to AlarmManager, and pass it to cancel. Or have a factory method to construct such pi.

Related

Do I need to explicitely specify class using intent.setClass?

I want to use AlarmManager to schedule a repeating task. Basically, I have this code:
Intent intent = new Intent(INTENT_ACTION_TICK);
// The following line prevents the broadcast receiver from being notified:
intent.setClass(context, MyScheduler.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, intervalInMs, intervalInMs, pendingIntent);
I register MyScheduler as a broadcast receiver in its constructor:
context.registerReceiver(this, new IntentFilter(INTENT_ACTION_TICK));
Everything works as expected (receiver is triggered) unless I add the intent.setClass. Fine with me, however, I distinctly remember reading that you should use explicit intents (intent.setClass) for security reasons.
Is this something I have to consider for my use case?

cancel all the alarms set using alarmmanager

I want to cancel all the alarms that are set..... I have searched a lot and tried a lot of things but nothing worked... When I set alarm for say after 2 minutes and then I cancel it, it will still fire after 2 minutes.....
Any help will be appreciated.
Method for creating alarms :
Intent intent = new Intent(c, AlarmReceiver.class);
intent.putExtra("task", task);
intent.putExtra("id", id);
PendingIntent pendingIntent = PendingIntent.getActivity(c, code, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager am = (AlarmManager)c.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
This is to cancel alarms :
Intent intent = new Intent(c, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getActivity(c, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager am = (AlarmManager)c.getSystemService(Context.ALARM_SERVICE);
am.cancel(pendingIntent);
When you schedule your alarms, your second parameter to getActivity() is code.
When you try to cancel your alarms, your second parameter to getActivity() is 0. This will only successfully cancel an alarm whose code was 0.
If you want to consistently cancel the alarms, you need to create equivalent PendingIntents, and that means, among other things, that the second parameter to getActivity() needs to be the same.
You are setting the alarm with request code (second parameter for getActivity) equals to code, while when you are cancelling the alarm, the second parameter is 0. If the value of code is not 0 the alarm wouldn't be cancelled.
In order to fire and cancel multiple alarms, you need to use unique value for requestCode (second parameter) in PendingIntent.getActivity
see this code that will set 3 Alarms using three unique request Codes,
for (int requestCode = 1; requestCode <= 3; requestCode++) {
PendingIntent pendingIntent = PendingIntent.getActivity(c, requestCode, intent,PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager am = (AlarmManager)c.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
}
You can cancel the Alarm using the same requestCode (and other parameters) that were used to set it.
The other parameters which are needed to matched are,
The "action" in the Intent must be the same (or both null). Otherwise they do not match.
The "data" in the Intent must be the same (or both null). Otherwise they do not match.
The "type" (of the data) in the Intent must be the same (or both null). Otherwise they do not match.
The "package" and/or "component" in the Intent must be the same (or both null). Otherwise they do not match. "package" and "component" fields are set for "explicit" Intents.
The list of "categories" in the Intent must be the same. Otherwise they do not match.
Please see this answer for more details.
To cancel an alarm you need no re-create the same PendingIntent and pass the same request code.
You can see my solution here
When you cancel alarm its intent must be same with intent which used for set alarm .
so just add following 2 line
intent.putExtra("task", task);
intent.putExtra("id", id);
So your final code will be
Intent intent = new Intent(c, AlarmReceiver.class);
intent.putExtra("task", task);
intent.putExtra("id", id);
PendingIntent pendingIntent = PendingIntent.getActivity(c, code, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager am = (AlarmManager)c.getSystemService(Context.ALARM_SERVICE);
am.cancel(pendingIntent);

Is there a way to wipe out all of app's alarmmanager alarms without knowing the pending intent ids

In my app I set reminders in AlarmManager and as far as I know, in order to cancel an alarm, I need to remember the unique ID I gave to the pending intent that created the alarm.
Is there a way to generally cancel all of my app's alarms without having to remember that ID?
From doc AlarmManager.cancel()
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.
So use Class name while creating Intent and set it to pending intent. It does not need to use id, it will match the intent and cancel all alarms. Like below code
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// Create intent with you service class, which will be matched by alarm manager
Intent intent = new Intent(context, YourPendingIntentServiceClass.class);
// Set to pending intent
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
// And cancel alarms
try {
// Cancel all alarm created with YourPendingIntentServiceClass.
alarmManager.cancel(pendingIntent);
} catch (Exception e) {
Log.e(TAG, "Something went wrong. Alarms did not cancel. " + e.toString());
}

Schedule a GPS location check daily at a predefined time in Android

I'm from an iOS background with intermediate Android knowledge. Anyways for this task, I need to know what exactly I need to work with. My app needs to check if the user is at or close to the predefined location at a predefined time. Getting user location is not an issue.
My guess is working with a combination of AlarmManager, BroadcastReceiver and Service ? I haven't worked with either of these three.
Any help is appreciated. :)
These are the ingredients you need for you recipe:
An Intent that targets your BroadcastReceiver:
Intent intent = new Intent(context, YourBroadCastReceiver.class);
A PendingIntent that gets triggered by the AlarmManager and fires your already defined Intent:
PendingIntent pIntent = PendingIntent.getBroadcast(context, requestCode, intent, flags);
The AlarmManager that periodically activates your PendingIntent:
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, intervalMillis, pIntent);
The BroadCastReceiver that handles the event:
#Override
protected void handleReceive(Context context, Intent intent) {
// handle GPS
}

Multiple calls to AlarmManager.setRepeating deliver the same Intent/PendingIntent extra values, but I supplied different ones

Solved while writing this question, but posting in case it helps anyone:
I'm setting multiple alarms like this, with different values of id:
AlarmManager alarms = (AlarmManager)context.getSystemService(
Context.ALARM_SERVICE);
Intent i = new Intent(MyReceiver.ACTION_ALARM); // "com.example.ALARM"
i.putExtra(MyReceiver.EXTRA_ID, id); // "com.example.ID", 2
PendingIntent p = PendingIntent.getBroadcast(context, 0, i, 0);
alarms.setRepeating(AlarmManager.RTC_WAKEUP, nextMillis, 300000, p); // 5 mins
...and receiving them like this:
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_ALARM)) {
// It's time to sound/show an alarm
final long id = intent.getLongExtra(EXTRA_ID, -1);
The alarm is delivered to my receiver at the right times, but often with EXTRA_ID set to the wrong value: it's a value that I have used at some point, just not the one that I wanted delivered at that particular time.
The documentation for PendingIntent.getBroadcast() says:
Returns
Returns an existing or new PendingIntent matching the given parameters.
The problem is that two Intents differing only in extras seem to match for this purpose. So getBroadcast() will return some random old PendingIntent (with a different EXTRA_ID) instead of a new one around the Intent I just created. The fix is to supply a data Uri and make it differ with the id, like this:
Intent i = new Intent(MyReceiver.ACTION_ALARM, Uri.parse("timer:"+id));
You can then retrieve the id number using:
Long.parseLong(intent.getData().getSchemeSpecificPart());
...or of course supply the extra as well and use that.
You could also use the flag PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent p = PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
this should the work too
The solution for your problem is use Intent.FLAG_ACTIVITY_NEW_TASK
p = PendingIntent.getBroadcast(context, 0, i, Intent.FLAG_ACTIVITY_NEW_TASK);

Categories

Resources