I have an Android implementation in which I have to schedule notifications in the long term, I create the PendingIntent and use AlarmManager#setExact() method to do it:
Intent intent = new Intent(mContext, BirthdayReceiver.class);
intent.putExtra(BirthdayReceiver.INTENT_WHO, contact.getName());
PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext, id, intent, 0);
mAlarmManager.setExact(
AlarmManager.RTC,
notificationCalendar.getTimeInMillis(),
pendingIntent);
I've checked the time passed to setExact() method, if it's a short time (minutes, few hours) then the notification appears correctly.
But if I set a notification to fire, let's say the next day at 12:00 it doesn't work.
I am using an Android 8 device to test it.
Any help?
Seems like flag is not specified.
Could you try it with FLAG_UPDATE_CURRENT .
PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Also, in order to confirm if your PendingIntent is still available in the system, you can use FLAG_NO_CREATE.
PendingIntent broadcastIntent = PendingIntent.getBroadcast(mContext, id,
intent, PendingIntent.FLAG_NO_CREATE);
if broadcastIntent returns null then the PendingIntent is not avaialble (possibly cancelled) in the system.
As per the documentation of setexact
This method is like set(int, long, PendingIntent), but does not permit
the OS to adjust the delivery time. The alarm will be delivered as
nearly as possible to the requested trigger time.
So you may not expect the alarm to be triggered immediately.
Note: only alarms for which there is a strong demand for exact-time
delivery (such as an alarm clock ringing at the requested time) should
be scheduled as exact. Applications are strongly discouraged from
using exact alarms unnecessarily as they reduce the OS's ability to
minimize battery use.
So you can really consider whether you really need to call this API or not
Related
I have created a configure activity for my widget, where the user can choose from various update frequencies.. Until now I started the alarm in the OnEnabled() method, like this:
Intent intent = new Intent(CLOCK_WIDGET_UPDATE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), 1000 * 60,
pendingIntent);
The settings are saved in shared preferences with a unique name (widgetId) and in this OnEnabled() method I can't retrieve the settings here because I can't get the widgetId yet.
There's an another problem, the user can change the frequency anytime, but this method is called just once, at the beginning. So I think I need to start the alarm in OnUpdate(), but I don't know how to do it, I don't want to make multiple instances of an alarm accidentally so I would like to ask for some advice.
To answer your second problem, calling setRepeating multiple times will not create multiple alarm as far as you provide same PendingIntent and same request code along with PendingIntent.FLAG_UPDATE_CURRENT flag. I would also suggest to use setInexactRepeating instead of setRepeating. So you can use the same code in OnUpdate() too with new frequency. Go through docs of FLAG_UPDATE_CURRENT and setInexactRepeating for more detials.
I'm attempting to use AlarmManager to schedule a delayed check in my app. (Specifically, N minutes after a user approaches a location, I want to check whether they're still there, and if so send them a notification.)
I've implemented this by checking to see whether they've entered the region in my location update receiver and, if they have, scheduling like so:
Intent geofenceIntent = new Intent(context, GeofenceReceiver.class)
// ...intent contents not important...
PendingIntent pi = PendingIntent.getBroadcast(context, 0, geofenceIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Calendar c = Calendar.getInstance();
c.add(Calendar.SECOND, getGeofenceDelaySeconds());
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), pi);
Log.v("Scheduling notification check for "+c.getTime());
When the battery level is high (say, 50%), everything works like a charm. But when it's low (say, 10%), I get location updates, they schedule the alarm seemingly-successfully, but that alarm never actually triggers!
What gives? Does Android stop sending certain types of updates when it's trying to conserve power? How can I work around this (short of actually keeping my app active for the duration of the delay, which causes obvious issues with battery life)?
It turns out that this is related to the use of the real-time clock.
Although I could not find the documentation it quotes (it's not in AlarmManager), this StackOverflow answer suggests that AlarmManager.RTC_WAKEUP alarms do not trigger if the phone is in power-saving mode. AlarmManager.ELAPSED_REALTIME_WAKEUP alarms do not seem to suffer this problem, so I was able to fix the issue by switching to:
Intent geofenceIntent = new Intent(context, GeofenceReceiver.class)
// ...intent contents not important...
PendingIntent pi = PendingIntent.getBroadcast(context, 0, geofenceIntent, PendingIntent.FLAG_UPDATE_CURRENT);
long millis = SystemClock.elapsedRealtime() + 1000 * getGeofenceDelaySeconds();
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, millis, pi);
I'm just looking at how to cancel an alarm and I came across these two methods. Which one should be used in what situation and why? Are they both the same?
I am currently doing this:
Intent alarmIntent = new Intent(ChangeAlarmActivity.this, AlarmReceiver.class);
PendingIntent pendingAlarmIntent = PendingIntent.getBroadcast(ChangeAlarmActivity.this, (int)alarm.getID(),
alarmIntent, 0);
pendingAlarmIntent.cancel();
How is that different to this below?
Intent alarmIntent = new Intent(ChangeAlarmActivity.this, AlarmReceiver.class);
PendingIntent pendingAlarmIntent = PendingIntent.getBroadcast(ChangeAlarmActivity.this, (int)alarm.getID(),
alarmIntent, 0);
AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
alarmManager.cancel(pendingAlarmIntent);
Are they both the same?
No.
If you want to cancel an alarm, call cancel() on AlarmManager.
cancel() on PendingIntent probably will appear to have the same effect -- whatever your alarm events were supposed to trigger no longer happens. However, you are then assuming that AlarmManager will detect this and clean things up on its side, which is not guaranteed. Particularly for _WAKEUP alarms, this could result in the device being woken up for no good reason, wasting battery life.
Which one should be used in what situation and why?
I am sure that cancel() on PendingIntent has use cases. I cannot name any concrete ones, as I have never seen it used. Typically when you are using a PendingIntent, any "cancel" semantics are on the use of the PendingIntent (e.g., you cancel() the alarm via AlarmManager, you cancel() the notification via NotificationManager), not on the PendingIntent itself.
One place for cancel() on PendingIntent, therefore, would be some place where you pass off a PendingIntent and there is no "cancel" to revert that, or you explicitly wish to use cancel() as the revert mechanism. For example, if you are creating some sort of plugin mechanism, and the plugin sends a PendingIntent to the host app, the plugin might use cancel() to say "stop using that PendingIntent", which the host app would find out about the next time it tried to send() the PendingIntent and got the exception. Personally, I'm not a huge fan of this -- much along the AlarmManager lines, you don't know what resources might be used by the host app if it fails to properly handle this scenario. But, used properly, it could certainly work.
How is that different to this below?
The "below" one is what I would recommend that you use.
I'm setting up alarms using this code
//in onCreate()
mAlarmManager = (AlarmManager) getApplicationContext()
.getSystemService(ALARM_SERVICE);
//called for each timer I schedule
Intent intent = new Intent (Intents.MY_INTENT_ACTION);
PendingIntent pendIntent = PendingIntent.getBroadcast(
getApplicationContext(), alert.getID(),
intent, PendingIntent.FLAG_ONE_SHOT);
long delay = 1000 * alert.getDuration();
Calendar cal = Calendar.getInstance();
mAlarmManager.set(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis() + delay, pendIntent);
But the behavior I'm seeing doesn't match what I should see in the documtation1,
public void set(int type, long triggerAtTime, PendingIntent operation)
If there is already an alarm scheduled for the same IntentSender, it will first be canceled...If there is already an alarm for this Intent scheduled (with the equality of two intents being defined by filterEquals(Intent)), then it will be removed and replaced by this one...
which suggests that calling set(int type, long triggetAtTime, PendingIntent operation) for an already alarmed intent should replace the old alarm for that intent. I'm not seeing any alarms get dropped. Instead, every alarm i set fires, despite the fact that the intents that are fired by the pending intents should all match (by filterEquals(intent)), since all I've set on each intent is an identical action.
Am I doing something wrong, or is the API not behaving as documented?
Note: changing the PendingIntent instantiation to
PendingIntent pendIntent = PendingIntent.getBroadcast(
getApplicationContext(), CONSTANT_ID,
intent, PendingIntent.FLAG_ONE_SHOT);
Behaves as expected, dropping any already set alarm, and replacing it with a new alarm.
Maybe it is because you are giving each alarm a different ID (Does alert.getID() give different ID's or not?). By the documentation, it shouldn't matter but yet you should still try.
If it doesn't work too, hold a reference for your last set alarm, and when you need it to be canceled, cancel it yourself then set the next one.
Have you tried with PendingIntent flag : PendingIntent.FLAG_UPDATE_CURRENT intead of PendingIntent.FLAG_ONE_SHOT?
It appears the consensus is that the documentation for AlarmManager.set(), as well as other AlarmManager methods claiming that Intents (not just the wrapping PendingIntents) are compared to check whether a particular alarm is already set.
Do not rely on AlarmManager matching Intents, instead rely on the matching of PendingIntents, which appears to be working as advertised.
I've got a question about setting alarms in the AlarmManager. I found something in the docs I didn't understand (see below). I'd like to set 10 alarms which trigger the ringer mode alternately silent and normal, all with a different trigger time. Now the device goes asleep, and becomes active again after all 10 alarms are outdated. Does the AlarmManager then immediately broadcast an alarm? Would it be only the 10th (what about the ringer mode)?
Alarm intents are delivered with a
data extra of type int called
Intent.EXTRA_ALARM_COUNT that
indicates how many past alarm events
have been accumulated into this intent
broadcast. Recurring alarms that have
gone undelivered because the phone was
asleep may have a count greater than
one when delivered.
One thing that is most unknown (mainly because the Android documentation tells that its "not used at the moment") is that the PendingIntent will not be reused if the requestCode differs. So instead create the PI with an request code of 0:
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
You could implement a counter and do someting like:
PendingIntent pendingIntent = PendingIntent.getService(context, counter, intent, 0);
I know that this will work for SMS delivered/sent notifications PendingIntents, where you have the same problem: If the PendingIntent is reused and you have more than 1 outstanding notification, you will not know for which SMS it was.
But chances are good that this will also work for outstanding alarm PendingIntent.
Hope this helps.
From what I understand, when scheduling an alarm with an alarm manager, you have to provide a PendingIntent instance.
There are two Types of alarms ones that will wake up and do work even if the phone is asleep or locked and ones that will not.
Also, If you were to schedule 10 things at one time The AlarmManager will replace the existing Scheduled Pending intent with the new one, unless you were giving it different intent actions. When I use Alarms I have always used a sqlite database to queue up jobs that I wanted to execute on some schedule. From there I would schedule one alarm at a time, because they all executed the same Intent when the buzzer went ding.
The EXTRA_ALARM_COUNT extra would come into play if you had a reoccurring alarm scheduled and it went off multiple times when the users device was asleep. When the phone wakes up it will replay anything that it has queued up in the past. In this case your pending intent will fire off and have the value of how many times your Alarm was skipped because it was constructed with RTC or ELAPSED_REALTIME as the type when calling the set method.
Here is a sample of how I usually interact with the AlarmManger
protected void scheduleNext(Context context) {
AlarmManager alarmManager = getAlarmManager();
Intent intent = new Intent(MyIntent.ACTION_DO_WORK);
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
String where = Queue.SCHEDULED_DATE + "= (select min(" + Queue.SCHEDULED_DATE + ") from queue where " + Queue.COMPLETED_DATE + " is null)";
Cursor cursor = context.getContentResolver().query(Queue.CONTENT_URI, Queue.PROJECTION, where, null, null);
if (cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex(Queue._ID));
long when = cursor.getLong(cursor.getColumnIndex(Queue.SCHEDULED_DATE));
alarmManager.set(AlarmManager.RTC_WAKEUP, when, pendingIntent);
}
cursor.close();
}