I am trying to create intents that will be set using alarmmanager. Currently, I can do this with one intent, add extra data to it (strings, but i send them as one string with a seperator), and everything works fine and goes off at the correct time. However, when I try to send multiple intents like this, they are overwritten and only one goes off at the correct time. How can i structure my intents so that they appear different to the alarmmanager (i think they are getting deleted when filterIntent() is run).
long story short- putExtra() makes all the intents look the same still... how can i make them look different so they wont get deleted (and keep track of them in case i want to delete a specific one)
This is how I scheduled updates for my widgets. Each one allowed to be unique because they have a unique widget id number.
Intent widgetUpdate = new Intent();
widgetUpdate.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
widgetUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { appWidgetId });
widgetUpdate.putExtra(EXTRA_POSITION,0);
widgetUpdate.putExtra(EXTRA_URL, URL);
// make this pending intent unique by adding a scheme to it
widgetUpdate.setData(Uri.withAppendedPath(Uri.parse(URI_SCHEME + "://widget/id/"), String.valueOf(appWidgetId)));
PendingIntent newPending = PendingIntent.getBroadcast(context, 0, widgetUpdate, PendingIntent.FLAG_UPDATE_CURRENT);
// schedule the updating
AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarms.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), updateRateSeconds * 1000, newPending);
According to the docs, AlarmManager checks if two Intents are equivalent using filterEquals(). Check out the docs for filterEquals() to see how it decides whether two Intents are equivalent. Also, chris324's solution is a pretty good one.
Related
I'm making an alarm clock app using PendingIntent and AlarmManager. I know in order to cancel a PendingIntent, you need to recreate it exactly. In my app, much like many other alarm clock apps, there is a list of alarms with a switch to toggle each alarm on/off. When toggled off, I currently recreate the PendingIntent and cancel it using the following:
Intent intent = new Intent(context, MyBroadcastReceiver.class);
String id = this.getId().replaceAll("[^0-9]+", "");
PendingIntent alarmIntent = PendingIntent.getBroadcast(context, id, intent, 0);
alarmIntent.cancel();
The first 3 lines of the code above I believe are unnecessary. When I toggle the alarm, I have access to the custom alarm object which contains the id along with many other details of the alarm. When I'm creating a new alarm, if I store the newly created PendingIntent in the alarm object, I can have access to the PendingIntent used to create the alarm, and just retrieve it and cancel it in 1 line, like this:
this.getPendingIntent().cancel();
I wouldn't have to recreate the intent, get the id, or recreate the PendingIntent from the code shown earlier. This would ultimately save time and resources (not much but it's good practice), so I have a couple questions:
1) Is there anything wrong with storing the PendingIntent in an object and use it later instead of recreating it? It seems like a straightforward answer but I haven't seen anyone do this before.
2) Is there an advantage to recreating the PendingIntent that I'm not aware of?
Thanks!
Is there anything wrong with storing the PendingIntent in an object and use it later instead of recreating it?
Assuming that the underlying Intent doesn't have some massive payload (e.g., Bitmap in an extra), you should be OK.
It seems like a straightforward answer but I haven't seen anyone do this before.
https://github.com/commonsguy/cw-omnibus/tree/v8.7/AlarmManager/Simple, though it's a trivial example.
Is there an advantage to recreating the PendingIntent that I'm not aware of?
It works for cases where you do not have the PendingIntent. Your process does not live forever. If you want to use a cached PendingIntent as an optimization, that's fine, but you need to be in position to create the PendingIntent if it is needed.
I am developing an alarm application in Android. The flow is really simple, Im just creating a PendingIntent and then I call the setExact() method in the AlarmManager much like below.
Intent myIntent = new Intent(context, BroadcastReceiver.class);
pendingIntent = PendingIntent.getBroadcast(context, 0, myIntent, 0);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE);
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + difference, pendingIntent);
After some tests I realized that with the above snippet I can set only one alarm because I set the requestCode of the pendingIntent to zero, and if I set another alarm with the requestCode set to 0 then it will overwrite the previous one. Is there a way to fix this without changing the requestCode? I was thinking maybe the flags can help me but I didn't find a flag that doesn't overwrite the previous pendingIntent.
I know that the obvious solution is to change 0 to another int and then keep track of all my ints, picking one that is not used. That solution would be fine if I was just starting the project, however I am already in the middle and I use as request codes predefined Enums. It is very difficult to change this mechanic and keep track of individual ints thats why I am asking if there is a way of not overwriting a pendingIntent when a new one with the same requestCode is registered. Thank you in advance.
You can make the Intents unique be setting a different ACTION on each one. Then you could still use the same requestCode and would have different PendingIntents.
You'll need to keep track of the ACTIONs you use if you want to be able to cancel the alarms later.
I am asking if there exists a certain type of flag that will be able to differentiate them
There is not, and it makes sense because this is already the purpose of the requestCode parameter.
For information, these are your options regarding flags:
You will have to change your mechanism to make it possible to have different requestCodes for Pending Intents. It may be a lot of work, but it is what you have to do.
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'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.
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);