How does android compare pending intents - android

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

Related

What are all the flags in pending intent

I know the concept of pending intent but the flags are confusing.
Even android documentation is very hard to understand
Can someone provide explanation of pending intent flags particularly FLAG_ONE_SHOT and FLAG_NO_CREATE with examples?
PendingIntents are managed by the Android framework. When you call one of the PendingIntent.getXXX() methods, the framework tries to find an existing PendingIntent that matches the parameters you pass to the getXXX() method. If it finds a matching PendingIntent it will just return that to the caller. If it doesn't find a matching PendingIntent it will (usually) create a new PendingIntent and return that to the caller. You can alter this standard behaviour by using the flags:
FLAG_NO_CREATE is used to get an existing PendingIntent. If a matching PendingIntent exists, it will be returned to the caller. If no matching PendingIntent exists, nothing happens. The framework will not create a new PendingIntent and the method returns null to the caller. You can use this method to determine if a specific PendingIntent exists. You can also use this method to get an existing PendingIntent so that you can cancel it.
FLAG_ONE_SHOT is strange. According to the documentation, this flag should cause the PendingIntent to be deleted after it is used (sent). However, there are other side-effects of this flag. For example, if you create a PendingIntent using this flag, and then try to get this PendingIntent (or test the existence of it) by calling PendingIntent.getXXX() with FLAG_NO_CREATE, the framework will always return null. For this reason I never use it and I also recommend that it never be used.
FLAG_CANCEL_CURRENT is used to delete an existing PendingIntent and create a new one. The framework first tries to find a matching PendingIntent. If it finds one, it cancels (deletes) this PendingIntent. That means that any applications holding this PendingIntent will not be able to trigger (send) it. The framework then creates a new PendingIntent with the provided parameters and returns this to the caller.
FLAG_UPDATE_CURRENT is used to update an existing PendingIntent. The framework first tries to find a matching PendingIntent. If it finds one, the "extras" in the existing PendingIntent are overwritten with the "extras" in the provided Intent parameter. If no matching PendingIntent is found, a new one is created with the provided parameters. The found (or newly created) PendingIntent is returned to the caller.
NOTE: See this answer for information on how the Android framework tries to find a "matching" PendingIntent: https://stackoverflow.com/a/29590084/769265

How to check for Proximity Alerts and find them

I have a question regarding proximity alerts.
In all tutorials I ve read they are created and destroyed while the activity that create them is still running.
But what happens if say an activity creates n proximity alerts and then the activity itself is destroyed (the PA are not)
Then if I want to build another activity that finds these Proximity Alerts, how can I do that? Is that even possible?
You have to maintain your own list of proximity alerts. There is no way to get them back. However, #Mercato is correct when he says that you can remove a PA using only pending intents, but you don't have to store them. According to the docs:
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.
This means that the system will store your PendingIntent for you between app restarts, and you can retrieve it by passing the same Intent you used to create it. So for example, if you created the following PendingIntent:
Intent intent = new Intent(context, Foo.class);
PendingIntent pi = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Then all you have to store is the requestId (1) and the Class or class name (Foo.class or Foo.class.getName()). Then if you want to retrieve that same PendingIntent without creating a new one, you can do the following:
Class<Foo> className = retrieveClass(); //You implement this
//String clazz = retrieveClassName(); //This is another option
int requestId = retrieveId(); //You implement this
Intent intent = new Intent(context, className);
//The flag given attempts to retrieve the PendingIntent if it exists, returns null if it doesn't.
PendingIntent pi = PendingIntent.getBroadcast(context, requestId, intent, PendingIntent.FLAG_NO_CREATE);
if (pi != null) {
//This pending intent was registered once before.
//Go ahead and call the function to remove the PA. Also, go ahead and call pi.cancel() on this.
}
else {
//This pending intent was not registered, and therefore can't have a PA registered to it.
}
Technically, all proximity alerts need a PendingIntent defined and used as a parameter. Android's Documentation shows that if you know the list of PendingIntents then you can remove them as well.
removeProximityAlert(PendingIntent intent) Removes the proximity alert
with the given PendingIntent.
Since PendingIntent is Parecelable see here then you could add it as an Extra to any Intent. This means, that on starting another Activity, you can create an Parcelable[] array to hold all these PendingIntent, then
putExtra(String name, Parcelable[] value)
Add extended data to the intent.
then retrieve them in the next Activity via getIntent() and it's relevant methods.

Android - What is a PendingIntent ?

I am new to Android development and I had to use a repeating alarm using AlarmManager. This is where I first had an opportunity to use a PendingIntent. However after reading through the documentation ( http://developer.android.com/reference/android/app/PendingIntent.html ), I am really confused as to what a PendingIntent really is.
My questions are:
Q1. In what way is a PendingIntent 'pending' ? Apologies for this question, but I'd like to have an intuitive understanding of what a PendingIntent means.
Q2. The documentation says:
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.
How does,
reference to a token maintained by the system describing the original data
relate to my code here ?
pendingIntent = PendingIntent.getBroadcast(getApplicationContext(),0,photosIntent,0);
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME,SystemClock.elapsedRealtime(),
10000, pendingIntent);
Q3. I also do not understand what follows in the documentation:
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.
What are the extra contents ? Does this refer to the request code & flag parameters in the getBroadcast(Context context, int requestCode, Intent intent, int flags) method ?
Any help on this would be most appreciated. My online searches have not given me the answers I was looking for. Also, thank you very much for your time.
Q1 - In what way is it "Pending"?
The system stores the values you store in PendingIntent and lets you (or another part of the framework) look them up later on, as if the component that looked them up had created a new Intent spontaneously with that information.
Q2 - how does "reference to a token" relate to my code here?
The Android Framework doesn't actually store the PendingIntent object you create; it hashes the "identifying information" for the intent (in this case, the action, data, type, class, and categories) and uses that to look up the rest of the information. The literal PendingIntent object you create doesn't get saved, the information it represents does.
Q3 - What are the "extra contents"?
The "extras" it's referring to here are the parcelable items you store via putExtra(). The requestCode and flags values are also saved and retrieved, but when the documentation refers to "extras" it means the literal getExtras() Bundle that Intents can use to carry additional information.

Are PendingIntent names for sms aribtrary?

Pretty much all the examples I find for sending an SMS message define the strings
String SENT = "SMS_SENT";
String DELIVERED = "SMS_DELIVERED";
where PendingIntents are formed like this:
PendingIntent sentIntent = PendingIntent.getBroadcast (this, 0, new Intent(SENT), 0);
PendingIntent deliveryIntent = PendingIntent.getBroadcast (this, 0, new Intent(DELIVERED0), 0);
ultimately for the calls
sms.sendTextMessage (phoneNumber, null, message, sentIntent, deliveryIntent);
or (using an array of them)
sms.sendMultipartTextMessage (phoneNumber, null, parts, sentIntents, deliveredIntents);
Are these strings arbitrary? The best I can tell is that you simply need to filter for them in your BroadcastReceiver, but it doesn't matter what the actual strings are. Is that true?
Yes, that's true, since you are actually creating the Intent, all you have to do is to make sure that the action specified in the Intent matches the one in the IntentFilter when creating your broadcast receiver.
You should take on count that a pending intent has internally no difference from a regular intent, except that will be executed after some time and even when application's process is killed, the PendingIntent itself will remain usable from other processes that have been given it
This is the important part of them taken from android documentation:
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.
But going back to your question, you can specify any action without any problem as long as the action do not match other actions registered for another broadcastreceiver and even so, it should match in action as well as meta data, so chances that something like that happen are quiet low
Hope this Helps..
Regards!

Is the application context always the same no matter the class?

I'm new in this whole Android environment and I usually have some doubts that maybe you can consider very basic knowledge and a bit stupid. I will try to do my best explaining the doubt I have and why i have it to make me understand.
I'm doing an application where you can set notifications to remind you the scholar classes you want. I have done a class that extends BroadcastReceiver so it can reset all the alarms after the device has booted. I have a database where I keep information about the alarms: the class, the time it has to be configured, etc. I retrieve all the alarms and set them to the alarmManager this way:
intent = new Intent(ctxt.getApplicationContext(), Notificacion.class);
intent.putExtra("TAG", tag);
intent.putExtra("SUBJECT", cursor2.getString(0));
intent.putExtra("AULA", cursor2.getString(1));
displayIntent = PendingIntent.getBroadcast(ctxt, Integer.parseInt(tag), intent, PendingIntent.FLAG_UPDATE_CURRENT );
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY*7, displayIntent);
Well, I guess this should work fine until here. The problem is that when you use the app and you want to set a notification, you are doing it from the class "Schedule.class" so the intent would have this context:
Intent intent = new Intent(getApplicationContext(), Notification.class);
PendingIntent pend = PendingIntent.getBroadcast(this, Integer.parseInt(tag), intent, PendingIntent.FLAG_UPDATE_CURRENT);
In the app, you can delete an alarm, and you have to call alarmManager.cancel(pend) in order to do that. So my doubt is if it will be able to cancel it.
If the contexts are different, it won't find the match with the pending intent, because it was set from the context I got in my extension of BroadCastReceiver (ctxt), and the alarm was set with the context I got from Schedule.class.
So.. is the application context always the same? I know that the context is set in order to give information to other classes about what has been going on, but I'm not sure if the Intent filter will differentiate where the context was given.
Thank you in advance!
Looking at the AlarmManager documentation for the cancel method you're using:
public void cancel (PendingIntent operation)
Added in API level 1
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, the Intent.filterEquals documentation says the following:
public boolean filterEquals (Intent other)
Added in API level 1
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 can't think of any reason why the action, data, type, class, or category would be different from one explicit Intent to another (unless, obviously you went out of your way to change those things). The contexts do not appear to be in the criteria for the matching, so I think you can be fairly confident that it will be cancelled no matter which context was used to create it in the first place.

Categories

Resources