Background:
I'm using PendingIntent for alarms via AlarmManager.
The problem:
At first I thought that in order to cancel previous ones, I must provide the exact requestCode that I've used before to start the alarm.
But then I've found out I was wrong, as the cancellation API says:
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.
looking at "filterEquals", the documentation says:
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.
so I don't get what the "requestCode" is for...
The question:
What is "requestCode" used for?
What if I create multiple alarms with the same "requestCode" ? do they override each other?
requestCode is used to retrieve the same pending intent instance later on (for cancelling, etc).
Yes, my guess is the alarms will override each other. I would keep the request codes unique.
I just want to add to #Minhaj Arfin answer
1- requestCode is used to get the same pending intent later on (for cancelling etc)
2- Yes, they will get override as long as your specify the same Receiver to your Intent that you specify on your PendingIntent
example:
Intent startIntent1 = new Intent(context, AlarmReceiverFirst.class);
PendingIntent pendingIntent1 = PendingIntent.getBroadcast(context, 0, startIntent1, 0);
Intent startIntent2 = new Intent(context, AlarmReceiverSecond.class);
PendingIntent pendingIntent2 = PendingIntent.getBroadcast(context, 0, startIntent2, 0);
From above example, they will not override each other because the receiver is different(AlarmReceiverFirst and AlarmReceiverSecond)
Intent startIntent2 = new Intent(context, AlarmReceiverSecond.class);
PendingIntent pendingIntent2 = PendingIntent.getBroadcast(context, 0, startIntent2, 0);
Intent startIntent3 = new Intent(context, AlarmReceiverSecond.class);
PendingIntent pendingIntent3 = PendingIntent.getBroadcast(context, 0, startIntent3, 0);
From above example, they will override each other, because the receiver is same(AlarmReceiverSecond)
Actually, the documentation clearly states what the request code is used for:
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(Intent), 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).
Since it seems that it still isn't that clear, let me try to explain:
When you want to use a PendingIntent object, you don't just instantiate one. Rather, you obtain one from the system using the PendingIntent static methods (getActivity, getBroadcast, getService etc). The system keeps a bunch of PendingIntent instances and gives you one. Which one it gives you, it depends on the input parameters you pass to these getter methods. Those input parameters are: Context, i.e. the target receiver of the intent, the Intent to use, requestCode and flags. When you pass the same Context, the same requestCode and the same Intent (meaning an intent that filterEquals with another intent), you get the same PendingIntent object. The point is that the system wants to have as few PendingIntent objects as possible, so it tends to reuse the existing ones, as much as possible.
For example, you have two calendar notifications, for two different dates. When you click on one of them, you want your app to open to the corresponding date of that notification. In that scenario, you have the same Context target, and the Intent object you are passing differ only in the EXTRA_DATA (which specifies the date that should be open). If you provide the same requestCode when obtaining the PendingIntent object, then you will end up with the same PendingIntent object. So, when creating the second notification, you will replace the old Intent object with the new EXTRA_DATA, and end up with two notifications pointing to the same date.
If you want to have two different PendingIntent objects, as you should in this scenario, you should specify a different requestCode when obtaining the PendingIntent object.
in my case i want to open the same activity with two different intents so if two or more FCMS are there in the tray, any one of them will only open other will not, so i changed the requests codes of pending intent then it worked.
PendingIntent pendingIntent =
PendingIntent.getActivity(this, **Some unique id for all GCMS** /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
one important thing about requestCode that will seriously trouble your app is when using widgets.
widgets will not work after phone reboot if their requestCode are the same.
that means the pendingIndent you set on the remoteViews of your widget must be set unique requestCode, usually the widgetId accompanying a number.
Related
I'm dealing with PendingIntent with notification.
In my project, I've been using PendingIntent.FLAG_UPDATE_CURRENT in some of code. and the definition of it is below.
Flag indicating that if the described PendingIntent already exists,
then keep it but replace its extra data with what is in this new
Intent. For use with getActivity(Context, int, Intent, int),
getBroadcast(Context, int, Intent, int), and getService(Context, int,
Intent, int).
This can be used if you are creating intents where only the extras
change, and don't care that any entities that received your previous
PendingIntent will be able to launch it with your new extras even if
they are not explicitly given to it.
FLAG_UPDATE_CURRENT still works even if FLAG_IMMUTABLE is set - the
creator of the PendingIntent can always update the PendingIntent
itself. The IMMUTABLE flag only limits the ability to alter the
semantics of the intent that is sent by send() by the invoker of
send().
But I have to choose between FLAG_IMMUTABLE or FLAG_MUTABLE because of Android12. In my case, i don't need to use PendingIntent.FLAG_MUTABLE. the definition of it is below.
Flag indicating that the created PendingIntent should be immutable.
This means that the additional intent argument passed to the send
methods to fill in unpopulated properties of this intent will be
ignored.
FLAG_IMMUTABLE only limits the ability to alter the semantics of the
intent that is sent by send() by the invoker of send(). The creator of
the PendingIntent can always update the PendingIntent itself via
FLAG_UPDATE_CURRENT.
as you can see, they say "FLAG_UPDATE_CURRENT still works even if FLAG_IMMUTABLE is set..." and "The creator of the PendingIntent can always update the PendingIntent itself via FLAG_UPDATE_CURRENT."
So, what i want to know is that FLAG_IMMUTABLE can perfectly replace FLAG_UPDATE_CURRENT in any Android version?
For example
PendingIntent.getActivity(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
=> PendingIntent.getActivity(context, requestCode, intent, PendingIntent.FLAG_IMMUTABLE);
FLAG_IMMUTABLE and FLAT_MUTABLE controls if other apps can modify your PendingIntent. For instance, if you were using a direct reply action in a notification, the system would need you to use FLAT_MUTABLE to allow it to fill in the text the user typed and send it to you.
FLAG_UPDATE_CURRENT is the ability for your app to update its own PendingIntent. This means if you create the same PendingIntent where the only difference is the extras attached to your Intent, FLAG_UPDATE_CURRENT would ensure that your new extras are actually used (instead of just the original set being used again).
So in every case where you were using FLAG_UPDATE_CURRENT, you'd want to continue to use FLAG_UPDATE_CURRENT, adding in the correct mutability flag for your case (which, in 99% of cases, is FLAG_IMMUTABLE).
To apply both flags, use the | symbol in Java (or the word or in Kotlin):
PendingIntent.getActivity(context, requestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Documentation for PendingIntent.FLAG_NO_CREATE reads:
Flag indicating that if the described PendingIntent does not already exist, then simply return null instead of creating it.
My question: What criteria are used to compare PendingIntents?
I'm guessing under the hood this flag uses PendingIntent.equals, but I'm not really sure what criteria that function is using. Is it using the action, requestCode, categories, extras (I'm guessing no), etc.?
Context:
I want to start an alarm with a pending intent if my alarm is not already setup. Specifically, I'm following this answer.
Intent i = new Intent(applicationContext, MyService.class);
i.setAction("myAction");
PendingIntent pi = PendingIntent.getService(applicationContext, /*requestCode*/0, i, PendingIntent.FLAG_NO_CREATE);
if (pi != null) {
AlarmManager alarmMgr = (AlarmManager)applicationContext.getSystemService(Context.AlarmService);
alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, AlarmManager.INTERVAL_HOUR, AlarmManager.INTERVAL_HOUR, pi);
}
To determine if 2 PendingIntents match, the following must be equal:
The requestCode parameter used when the PendingIntent was created
The Intent ACTION
The Intent CATEGORIES
The Intent DATA
The Intent MIMETYPE
The Intent PACKAGE
The Intent COMPONENT
Extras are not taken into consideration.
You can read more in the PendingIntent summary documentation and Intent.filterEquals().
I'm guessing under the hood this flag uses PendingIntent.equals, but
I'm not really sure what criteria that function is using. Is it using
the action, requestCode, categories, extras (I'm guessing no), etc.?
Actually the hint is in class description:
A description of an Intent and target action to perform with it.
Instances of this class are created with
getActivity(android.content.Context,int,android.content.Intent,int),
getActivities(android.content.Context,int,android.content.Intent[],int),
getBroadcast(android.content.Context,int,android.content.Intent,int),
getService(android.content.Context,int,android.content.Intent,int);
the returned object can be handed to other applications so that they
can perform the action you described on your behalf at a later time.
By giving a PendingIntent to another application, you are granting it
the right to perform the operation you have specified as if the other
application was yourself (with the same permissions and identity). As
such, you should be careful about how you build the PendingIntent:
almost always, for example, the base Intent you supply should have the
component name explicitly set to one of your own components, to ensure
it is ultimately sent there and nowhere else.
A PendingIntent itself
is simply a reference to a token maintained by the system describing
the original data used to retrieve it. This means that, even if its
owning application's process is killed, the PendingIntent itself will
remain usable from other processes that have been given it. If the
creating application later re-retrieves the same kind of PendingIntent
(same operation, same Intent action, data, categories, and components,
and same flags), it will receive a PendingIntent representing the same
token if that is still valid, and can thus call cancel() to remove it.
Because of this behavior, it is important to know when two Intents are
considered to be the same for purposes of retrieving a PendingIntent.
A common mistake people make is to create multiple PendingIntent
objects with Intents that only vary in their "extra" contents,
expecting to get a different PendingIntent each time. This does not
happen. The parts of the Intent that are used for matching are the
same ones defined by Intent.filterEquals. If you use two Intent
objects that are equivalent as per Intent.filterEquals, then you will
get the same PendingIntent for both of them.
There are two typical ways to deal with this.
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(android.content.Context,int,android.content.Intent,int),
getActivities(android.content.Context,int,android.content.Intent[],int),
getBroadcast(android.content.Context,int,android.content.Intent,int),
or getService(android.content.Context,int,android.content.Intent,int).
If you only need one PendingIntent active at a time for any of the
Intents you will use, then you can alternatively use the flags
FLAG_CANCEL_CURRENT or FLAG_UPDATE_CURRENT to either cancel or modify
whatever current PendingIntent is associated with the Intent you are
supplying.
from:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.0.0_r1/android/app/PendingIntent.java#PendingIntent
I'm trying to implement a reminder app.I have all reminder details stored in sqlite database such id,title,dateInfo,timeInfo etc.
I want to notify the user at appropriate time about the reminder for which i would be using AlarmManager.
Is the below given steps feasible.
Providing id of row as requestCode in pendentingIntents.
Then setting an alarm that would call a service once triggered.
The service would use this id to get data from the database.
If this is feasible can anyone pls provide me with a code snippet.
Any help would be highly appreciated
You can simply put your rowId as extra in the called intent. Here's a code snippet that might help:
Intent i = new Intent(this, YourServiceOrActivity.class);
i.putExtra("rowId", rowId);
PendingIntent pi = PendingIntent.getActivity(this, 123123, i, 0); //Notice the random requestCode '123123' as it doesn't matter, yet.
And you can set the alarm as follows:
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, AlarmManager.INTERVAL_DAY, pi);
//Set the alarm to wake up the device after one day calling the pending intent 'pi'.
Finally you can get the rowId from the intent (onCreate() for example if intent is an Activity) when called as follows:
Bundle extras = getIntent().getExtras();
if (extras != null) rowId = extras.getInt("rowId", 0); //Be safe
Hope this helps.
As others mentioned I would put rowId as an extra, BUT do not be mistaken - as wroten in PendingIntent doc, intents are distinct as below:
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 if you put your request with the same id for all intents for the same row, with different data, you can get lost with obsolete/repeated data issues.
According to the docs on PendingIntent, the requestCode of the getService(...) method is not used. It's not clear to me what happens to it, so I wouldn't rely on it in any way.
Just pass the row ID as an extra in the Intent.
How do I create a pending intent everytime? Currently my existing pending intent is getting replaced with a new one. I tried using FLAG_ONE_SHOT as well as CANCEL_CURRENT but it didn't work.
add a random number to the request code like that:
Intent intent = new Intent(context, YourClassname.class);
intent.putExtra("some data", "txt"); // for extra data if needed..
Random generator = new Random();
PendingIntent i=PendingIntent.getActivity(context, generator.nextInt(), intent,PendingIntent.FLAG_UPDATE_CURRENT);
FLAG_CANCEL_CURRENT- if the described PendingIntent already exists, the current one is canceled before generating a new one.
FLAG_NO_CREATE - if the described PendingIntent does not already exist, then simply return null instead of creating it.
FLAG_ONE_SHOT - this PendingIntent can only be used once.
FLAG_UPDATE_CURRENT- if the described PendingIntent already exists, then keep it but its replace its extra data with what is in this new Intent.
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).
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.