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.
Related
I'd like to ask you for help as after trying to figure this issue out for a couple of hours still can't get it works.
I have a notification manager which process incoming GCM messages and creates notifications, however an intent, that is passed to pending intent, always got old extras (intent recycle) within activity.
intent.putExtra("user_id", id);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
Lets say that I will receive two notifications from two different users and intent starts the same activity which displays that user ID. After click on first notification, activity is launched as usually and intent's extras contains user ID of first user. However, if I will remain within this activity, and click on another push notification, an activity is recreated (onDestroy is called) but, intent's extras contains user id of first user, not the second one.
Here is my question. How can I retrieve new intent extras? I've already tried to implement onNewIntent callback method, but it never get called, also tried to change flags but unsuccessfully and what's kinda weird to me is, that even after onDestroy callback is called, intent extras in next instance of that activity have old data...
Thanks in advance
You need to pass unique Id in place of just 0 when fetching Activity from PendingIntent:
int iUniqueId = (int) (System.currentTimeMillis() & 0xfffffff);
builder.setContentIntent(PendingIntent.getActivity(context, iUniqueId, intent, PendingIntent.FLAG_UPDATE_CURRENT));
I think you fire your notifications either with the same id or with a inappropriate intent flag. As it is mentioned here, when creating the pending intent you can set its flag. If you don't like the previous pending intent to be updated or overridden, you should set its flag to FLAG_ONE_SHOT. It indicates that although you have more than one pending intents sticking around in the system, each can be executed only once!
Conclusion: Your code should be sth like this:
PendingIntent pIntent = PendingIntent.getActivity(context, id,intent,PendingIntent.FLAG_ONE_SHOT);
In this code the "id" is unique per pending intent and "intent" is the actual intent for the target activity.
Cheers
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've got a navigation app. I want to get position information that may not arrive for a while, depending on how long it takes GPS to lock up -- or ever if reception is bad.
I was planning to use LocationManager.requestLocationUpdates() to request location information to be sent to a BroadcastReciever whenever it becomes available, and to also set a timeout via AlarmManager.set().
If the location update arrives, I want to cancel the timeout. If the timeout arrives, I want to cancel the location update. Assuming that my app could be killed before either happens, I'll have lost the PendingIntent for the thing I want to cancel.
Is there a way to save the PendingIntent somehow, so I can use them to cancel the timeout and/or location update later? Or is there a better way to go about this?
You don't need to save the PendingIntent instance itself. The documentation for AlarmManager.cancel(PendingIntent operation) 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.
If you look at Intent.filterEquals(Intent), it 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 you can just create a PendingIntent with the same action and do am.cancel() with that new pending intent, and it will cancel the previous pending intent as well.
Here's a quick code sample:
private static final String ALARM_ACTION = "foo.bar.MY_ALARM_ACTION";
private PendingIntent getAlarmIntent() {
Intent alarmIntent = new Intent(ALARM_ACTION);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT); // or whatever flag you need
return pi;
}
And now you can call am.set() with the PendingIntent returned from the above function, and also call am.cancel() with the PendingIntent returned from the same function as well. It doesn't matter whether the PendingIntent is the same instance or not, it just has to match the Intent.filterEquals() test (so basically just the Intent action has to match only).
So basically just use the same action to create the intent to set/cancel the alarm and it will work.
I am writing an Android application where the user can choose several stocks to watch and gets alerted if an predefined alert condition is matched. The stock data is saved to 5 objects of a custom Parcelable class "alert" (one object per stock and condition). The periodic data update is done via a service started by an AlarmManager. The alert objects are passed to the service via putting them into the Intent which is put into the PendingIntent of the AlarmManager.
Intent intent = new Intent(this, UpdateService.class);
Bundle b = new Bundle();
saveAlertsToBundle(b);
intent.putExtras(b);
intent.setData(Uri.parse("updateManager"));
PendingIntent pendIntent = PendingIntent.getService(this,0,intent,0);
// 1min intervall
long intervall = DateUtils.MINUTE_IN_MILLIS * 1;
// time of first start
long firstStartDelay = DateUtils.SECOND_IN_MILLIS * 30;
long firstStart = System.currentTimeMillis() + firstStartDelay;
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
// kill running
am.cancel(pendIntent);
//start new
am.setRepeating(AlarmManager.RTC_WAKEUP,firstStart,intervall,pendIntent);
My problem is:
When starting the service for the first time when there is only one object of alert passed to the service everything works fine. As soon as there are more alerts objects existing they also need to be passed to the service but this does not work with the code above. The service does not receive the updated intent with the additional alert objects , but only the initial one with only one alert object. The code above correctly creates an Intent holding the additional alert object, but they never get to the service.
So my question is, how to pass the updated intent to the already running AlarmManager.
I already tried stopping the AlarmManager (the line at the // kill running comment) and restarting it, but this does not work. Perhaps because of the intent not holding the same alert objects as at the time when he was created ? I tried to fix this by setting an uri in the data part of the intent but this also did not help.
Thanks for help.
Your problem is the way PendingIntent works. The system manages a pool of PengingIntents. When your code does:
PendingIntent pendIntent = PendingIntent.getService(this,0,intent,0);
This causes the system to search for a PendingIntent that matches the parameters you've passed in (in this case, your Intent. However, the matching algorithm that PendingIntent uses only compares certain fields of the Intent to determine if it is the one that you are looking for. In particular, it does not compare extras. So this means after you've created the first PendingIntent, the call to PendingIntent.getService() will always return the same PendingIntent from the pool (and not create a new one, which is what you want).
In order to make the call to PendingIntent.getService() create a new PendingIntent every time you call it, try making the parameters you pass to the call unique, like this:
int requestCode = (int) System.currentTimeMillis(); // Create unique request code
PendingIntent pendIntent = PendingIntent.getService(this, requestCode, intent, 0);
Since requestCode will be different for each call to PendingIntent.getService(), this should solve your problem.
EDIT Based on OP's comments below
You want to cancel the existing alarm and create a new one with new data. In that case you don't need to use unique identifiers because you only want to have a single PendingIntent in the pool. But, you want to change the data for that. Try this:
// Create a PendingIntent (or update the existing PendingIntent with new values
PendingIntent pendIntent = PendingIntent.getService(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
// cancel any pending alarms
am.cancel(pendIntent);
//start new
am.setRepeating(AlarmManager.RTC_WAKEUP,firstStart,intervall,pendIntent);
This questions somehow relates to the question when I was looking to get the extras back in startActivityForResult but now I face another challenge.
I have subscribed to receive ProximityAlerts and I have explicitly constructed the Intent to include some Extras. But when I got the service the extras are not there.
After the answers here is the working code:
Intent intent = new Intent(this, PlacesProximityHandlerService.class);
intent.setAction("PlacesProximityHandlerService");
intent.putExtra("lat", objPlace.getLat());
intent.putExtra("lon", objPlace.getLon());
intent.putExtra("error_m", objPlace.getError()+ALERT_RANGE_IN_METERS);
PendingIntent sender=PendingIntent.getService(this, 0, intent, 0);
LocationUtils.addProximity(this, objPlace.getLat(), objPlace.getLon(),objPlace.getError()+ALERT_RANGE_IN_METERS, -1, sender);
The documentation says param PendingIntent to be sent for each location update
For some unspecified reason, extras will be delivered only if you've set some action, for example setAction("foo"). What CommonsWare refers to applies only when obtaining PendingIntent instances, if you haven't set FLAG_ONE_SHOT. That can be fixed by the requestCode argument in PendingIntent.get... factory methods. Although documentation says it's currently not used, it actually takes into count when distinguishing PendingIntents.
In your case, you don't need to set anything else than some dummy action string. LocationManagerService reuses the PendingIntent you have subscribed for proximity alerts, and only adds a flag if phone has entered or exited the alarm range.
If you have multiple outstanding PendingIntents, you need to make sure that the underlying Intents differ on more than their extras. Otherwise, Android will keep reusing the first PendingIntent you created for your first Intent, using that first Intent's extras all of the time.
For example, you could add a unique action via setAction() -- that will not change your Intent routing (since you are specifying the component), but it will make your Intents different.
I had this problem and the solution I found was quite simple, though I can't explain why it worked.
Initially my pending intent looked like this:
notificationIntent = new Intent(ctx, FragmentTabsPager.class);
notificationIntent.setData(Uri.parse("content://com.sbs.mobile.workorder.WorkOrder/notes/"));
notificationIntent.putExtra("NOTIFICATION", true);
notificationIntent.putExtra(WorkOrder.WorkOrderColumns.WORKORDERID, submessage);
When creating the intent like this, no extras would be passed when the notification was clicked, the extras map would be empty in the receiving activity. I made the following change to the line initializing the notificationIntent:
notificationIntent = new Intent().setClass(ctx, FragmentTabsPager.class);
Now the extras are populated in the receiving activity. Again, I can't explain why this works but it fixed my problem.
None of the answers worked for me. Setting action to a specific string works for the first time but if you use the same notification with different extras at a later time, it would not work. I replaced the string for the setAction method with a randomly generated one and it works without any issues:
intent.setAction(new Random().nextInt(50) + "_action");
If you think that you might use the notification a lot (Like for downloading different files) then pass a larger number to nextInt()
The key is to set the extras and the unique action into the intent before calling
PendingIntent sender=PendingIntent.getService(this, 0, intent, 0);
if you set the extras and action into the intent after calling the above, it won't work.
This will not work:
Intent intent;
PendingIntent sender=PendingIntent.getService(this, 0,
intent=new Intent(this, PlacesProximityHandlerService.class), 0);
intent.setAction("PlacesProximityHandlerService");
intent.putExtra("lat", objPlace.getLat());