I have an application that uses alarms using AlarmManager. I set an alarm at a certain time and, when it triggers, it's set again 2 hours later. The second time the alarm is triggered it's not set again, so it's triggered just 2 times.
To know if the alarm triggered is the first one or the second I pass a boolean as a parameter to the BroadcastReceiver, like this:
Bundle b = new Bundle();
b.putBoolean(TODAlarm.KEY_IS_INFRACTION, true);
Intent intent = new Intent(me, TODAlarm.class);
intent.putExtras(b);
PendingIntent pendingIntent = PendingIntent.getBroadcast(me, TODAlarm.TYPE_DAILY_REST_NEEDED,
intent, PendingIntent.FLAG_ONE_SHOT);
alarmManager.set(AlarmManager.RTC_WAKEUP, time+2*HOURS, pendingIntent);
The first time I program the alarm I set KEY_IS_INFRACTION to false. When it's triggered I use the code from above and set it to true.
If I reinstall the application (using Eclipse->Run) when the alarm has been triggered once (I have an alarm programmed with KEY_IS_INFRACTION = true but it hasn't been triggered yet) the alarm programmed after the reinstall has KEY_IS_INFRACTION = false, but the intent received by the BroadcastReceiver has KEY_IS_INFRACTION = true (it seems like it receives the alarm programmed BEFORE reinstalling the application, even if I use alarmManager.cancel). This only happens when I reinstall the application, if I reboot the phone the intent received is ok. I always use alarmManager.cancel before alarmManager.set, but it's not working either.
The final application won't be reinstalled often, but if I publish an update this could lead to errors in the application behaviour. Anybody knows how can I fix it?
Here's the CORRECT sequence of actions (what the application should do):
1- Install aplication. Alarm is set to hour X with INFRACTION = false.
2- Hour X is reached and the alarm is triggered. The BroadcastReceiver receives an intent with INFRACTION = false and the alarm is set again with INFRACTION = true.
3- Reinstall application. Alarm is set to hour X with INFRACTION = false. Since hour X has already passed, the alarm is triggered. The BroadcastReceiver receives an intent with INFRACTION = false and the alarm is set again with INFRACTION = true.
Here's what it REALLY happens:
1- Install aplication. Alarm is set to hour X with INFRACTION = false.
2- Hour X is reached and the alarm is triggered. The BroadcastReceiver receives an intent with INFRACTION = false and the alarm is set again with INFRACTION = true.
3- Reinstall application. Alarm is set to hour X with INFRACTION = false. Since hour X has already passed, the alarm is triggered. The BroadcastReceiver receives an intent with INFRACTION = true and the alarm isn't set again.
[EDIT] Add some code. This is the function to set the alarm when the application is first started:
private void setNeededRestAlarmOrWarning(int type, long timeToStart){
Bundle b = new Bundle();
b.putInt(TODAlarm.KEY_ALARM_TYPE, type);
if(type == TODAlarm.TYPE_DAILY_REST_NEEDED){
b.putBoolean(TODAlarm.KEY_IS_INFRACTION, arr_bControls[CONTROL_PERMITIR_DORMIR_9_HORAS] ||
arr_iControls[CONTROL_LIMIT_TO_RECUPERATE] == 0 ||
arr_iControls[CONTROL_TIME_TO_RECUPERATE_COUNTER] >= Times.CTRL_PERMITTED_DESCANSOS_REDUCIDOS_POR_SEMANA);
Log.d(TAG, "INFRACTION IS "+b.getBoolean(TODAlarm.KEY_IS_INFRACTION));
}
Intent intent = new Intent(me, TODAlarm.class);
intent.putExtras(b);
PendingIntent pendingIntent = PendingIntent.getBroadcast(me, type,
intent, PendingIntent.FLAG_ONE_SHOT);
alarmManager.set(AlarmManager.RTC_WAKEUP, timeToStart, pendingIntent);
}
When the alarm is triggered I use the code posted above, before the EDIT.
[/EDIT]
Related
I am have been reading over this tutorial for creating repeating apps:
http://rdyonline.net/android-bytes-alarms/
I have followed the instructions and it is working beautifully. However I don't like using something if I don't understand how it works.
Alarm manager is using an version >= 19 so instead of alarm repeating (exact) it requires a one off alarm that is reset on exiting the intent.
Now as I said it is working, Every 15 minutes it is going off (in my version). I can see that they are bundling data with the intent, but I really have no understanding what is re-triggering the single shot alarm.
This is their code:
Repeating alarms
If you’re targeting any Android version before API 19 (KitKat), or,
you don’t need them to be exact then repeating alarms are nice and
easy. All you need in this case is to use the setRepeating call.
In some cases, it will be important that you set a repeating alarm
that is accurate, I’ll go in to a little more detail on how to handle
this.
The trick here is to make sure you schedule the next alarm once the
previous alarm goes off. You’ll have to check whether the alarm you
have set is intended to be repeated and also make sure the platform
you’re running on is above API 19
#Override
public void onReceive(android.content.Context context,
android.content.Intent intent) {
WrappedAlarmManager am = new WrappedAlarmManager(context);
Bundle extras = intent.getExtras();
if (am.isSingleAlarm(extras)) {
Toast.makeText(context, "Single alarm", Toast.LENGTH_SHORT).show();
} else if (am.isRepeatAlarm(extras)) {
Toast.makeText(context, "Repeat alarm", Toast.LENGTH_SHORT).show();
if (android.os.Build.VERSION.SDK_INT >= 19) {
am.scheduleRepeatingAlarm(context);
}
}
}
A quick check to see if it’s a repeating alarm and then the repeating
alarm is scheduled again. Below are the two pertinent methods to deal
with this logic:
public boolean isRepeatAlarm(Bundle extras) {
return extras.containsKey(KEY_REPEAT) && extras.getBoolean(KEY_REPEAT);
}
public void scheduleRepeatingAlarm(Context context) {
Intent intent = new Intent(context, NotificationReceiver.class);
Bundle extras = new Bundle();
extras.putBoolean(KEY_REPEAT, true);
intent.putExtras(extras);
PendingIntent pIntent = PendingIntent.getBroadcast(context,
REPEAT_ALARM_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Calendar futureDate = Calendar.getInstance();
futureDate.add(Calendar.SECOND, (int) (INTERVAL_SEVEN_SECONDS / 1000));
if (android.os.Build.VERSION.SDK_INT >= 19) {
setSingleExactAlarm(futureDate.getTime().getTime(), pIntent);
} else {
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, futureDate
.getTime().getTime(), INTERVAL_SEVEN_SECONDS, pIntent);
}
}
Thanks for your help
To schedule a repeating alarm in SDK versions >= 19, when alarm broadcast is received , the boolean value KEY_REPEAT is checked and if it is true then a single exact alarm is scheduled for INTERVAL_SEVEN_SECONDS / 1000 seconds later.
After INTERVAL_SEVEN_SECONDS / 1000 seconds later the broadcast is again received and next alram is set.
This receiving broadcast and scheduling next alarm cycle repeats continuously.
I am writing unit tests for my app and i want to test, if the alarm is properly cancelled, but i can't find the right solution to do that. I am familiar with the method of checking if the alarm is active by using the PendingIntent.getBroadcast() with FLAG_NO_CREATE flag.
I managed to successfully use the getBroadcast() function repeatedly in same test by first checking if the alarm is not set at the launch of the app and then later to check if the alarm is set, both times returning the expected boolean values.
public void testTimerButtonStartProcess(){
Intent intent = new Intent (appContext, OnAlarmReceiver.class);
intent.putExtra(Scheduler.PERIOD_TYPE, 1);
intent.putExtra(Scheduler.DELAY_COUNT, 0);
intent.setAction(Scheduler.CUSTOM_INTENT_ALARM_PERIOD_END);
boolean alarmUp = (PendingIntent.getBroadcast(appContext, 0, intent,PendingIntent.FLAG_NO_CREATE) != null);
assertFalse("the alarm manager wasnt running when app is lauched", alarmUp);
solo.clickOnView(timerLayout);
instr.waitForIdleSync();
solo.sleep(5000);
alarmUp = (PendingIntent.getBroadcast(appContext, 0, intent,PendingIntent.FLAG_NO_CREATE) != null);
assertTrue("the alarm is set", alarmUp);
}
But when i try to do that in reverse order (first checking if the alarm is set and then later - if it is no longer set), my test failed, because after the second check (when the alarm should be cancelled) the getBroadcast() returned true (i expected to get false).
public void testTimerButtonLongPress(){
solo.clickOnView(timerLayout);
instr.waitForIdleSync();
Intent intent = new Intent (appContext, OnAlarmReceiver.class);
intent.putExtra(Scheduler.PERIOD_TYPE, 1);
intent.putExtra(Scheduler.DELAY_COUNT, 0);
intent.setAction(Scheduler.CUSTOM_INTENT_ALARM_PERIOD_END);
boolean alarmUp = (PendingIntent.getBroadcast(appContext, 0, intent,PendingIntent.FLAG_NO_CREATE) != null);
assertTrue("the alarm manager is running", alarmUp);
solo.clickLongOnView(timerLayout, 1500);
instr.waitForIdleSync();
solo.sleep(3000);
alarmUp = (PendingIntent.getBroadcast(appContext, 0, intent,PendingIntent.FLAG_NO_CREATE) != null);
assertFalse("the alarm manager was not running anymore", alarmUp);
}
I also tried to use the getBroadcast only once after the app cancelled the alarm, but i still got returned true.
Meanwhile i am assured, that the cancellation of the alarm works as expected, because app stops alarming after being "turned off".
In my app, I use the AlarmManager class to set an alarm. To trigger the alarm after the mobile is rebooted I have used BroadcastReceiver. All works fine and my alarm is triggered at regular intervals. Now the problem arises in this case :
Suppose my current time is 2:30 pm and I set my alarm at 2:35 pm. After that, I switch off the mobile. After an hour when I switch on my mobile, no alarm is pop-up as the time on which the alarm is set. This is happening because the current time exceeds the time on which I set the alarm. To solve this issue what should I do. I have posted my code for setting alarm in the AlarmManager class. Please help me to solve this out
public class AlarmReceiver extends BroadcastReceiver {
#SuppressWarnings("static-access")
#Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
Intent myIntent = new Intent(context, MyAlarmService.class);
PendingIntent pendingIntent = PendingIntent.getService(context, i, myIntent, i);
AlarmManager alarmManager = (AlarmManager)context.getSystemService(context.ALARM_SERVICE);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.MILLISECOND, (int) Utilities.diff(NoteManager.getSingletonObject().getAlarmTime(i)));
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
}
}
}
public static long diff(Date date) {
long difference = 0;
try {
// set current time
Calendar c = Calendar.getInstance();
difference = date.getTime() - c.getTimeInMillis();
if (difference < 0) {
// if difference is -1 - means alarm time is of previous time then current
// then firstly change it to +positive and subtract form 86400000 to get exact new time to play alarm
// 86400000-Total no of milliseconds of 24hr Day
difference = difference * -1;
difference = 86400000 - difference;
}
}
catch (Exception e) {
e.printStackTrace();
}
return difference;
}
In The Manifest File
<receiver android:name=".AlarmReciever">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
better way is to store that alarm details in database and retrieve it on boot via broadcast receiver as you are saying you implemented one. once notified remove the details from the database. this way u can track all your alarms. even you can start a Service on startup and do this operation
The Alarm app in the Android does the same, if your phone is switched off and there is Alarm to ring up, It will make your phone switch On , ring the alarm and go to sleep again.
Here is the link of source of Alarm app Git_Alarm app you can download it and see how it is doing this.
and if you are doing something else in your alarm reciever then to ring Alarm up. you can basically set alarmreciever again in the phone Boot up, here is the one answer which may help you Alarm problem if phone is switched off
Edit :- one link was broken, replaced it
I'm trying to check if my alarm is active or not. The alarmIsSet method will return false before the alarm is set, true when the alarm is set. So far so good, however, after the alarm i canceled alarmIsSet will continue to return true until I reboot the device.
How do I fix this?
public class Alarm extends Activity {
private Intent intent = new Intent("PROPOSE_A_TOAST");
private void alarm (boolean activate) {
AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, 0);
if(activate == true) {
int type = AlarmManager.ELAPSED_REALTIME_WAKEUP;
long interval = 3000;
long triggerTime = SystemClock.elapsedRealtime();
am.setRepeating(type, triggerTime, interval, pi);
} else {
am.cancel(pi);
}
}
private boolean alarmIsSet() {
return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_NO_CREATE) != null;
}
}
You just have to add
pi.cancel();
after
am.cancel(pi);
After having some headaches with this stuff myself, I found out that if I somehow had created a pending intent while testing stuff, that it actually was not cleared between tests. Even killing the app didn't do it. The intent still stayed in the system and kept returning true when checking for it. I actually had to write some code to kill it before it tested right.
Easiest way is to check the values of the (date and) time in the alarm variable, if it is not the same value as when an alarm has not been set (for you to check once what that is) then it would indicate the alarm is active and at the time of the check in the program it is either a time that has passed and the alarm has sounded or it is a time that is yet to arrive and the alarm has not yet sounded or gone off. Note that the rules may permit only one alarm activation per device session before a reboot or power off or every 12 or 24 hours and that could be why the status is not cleared.
I changed AlarmController.java in ApiDemo a little bit, so I want the alarm not to go off when the phone is sleeping by using AlarmManager.RTC.
Intent intent = new Intent(AlarmController.this, RepeatingAlarm.class);
PendingIntent sender = PendingIntent.getBroadcast(AlarmController.this,
0, intent, 0);
// We want the alarm to go off 30 seconds from now.
long firstTime = SystemClock.elapsedRealtime();
firstTime += 15*1000;
// Schedule the alarm!
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC, //AlarmManager.ELAPSED_REALTIME_WAKEUP,
firstTime, 15*1000, sender);
The receiver code is like below:
public class RepeatingAlarm extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Log.d("DEBUG", "In RepeatingAlarm.onReceive, intent=" + intent);
Toast.makeText(context, R.string.repeating_received, Toast.LENGTH_SHORT).show();
}
}
I ran the modified app, but I still see many log messages as below after the phone wento sleep (the screen was black):
D/DEBUG ( 1390): In RepeatingAlarm.onReceive, intent=Intent { flg=0x4 cmp=com.example.android.apis/.app.RepeatingAlarm (has extras) }
This means the flag AlarmManager.RTC didn't work. Can someone tell me why?
Thanks.
Since you are using elapsedRealtime to get the alarm start time, I think you need to use the ELAPSED_REALTIME flag instead of the RTC flag.
My guess is that the alarm manager is thinking it's missed a ton of alarms because you are using the RTC flag which means the alarm manager is expecting you to send a time value in milliseconds since Jan 1st 1970, but instead you are sending elapsed milliseconds since the device booted, which is going to be a much much smaller number.
If you use the RTC flags you need to use System.currentTimeMillis() or get the time in milliseconds from a Java Date or Calendar object. If you use ELAPSED_REALTIME flags then you need to use SystemClock.elapsedRealtime().