Android Alarm Manager Set Repeating at Specific Timing
Asked Answered
J

1

1

I am having some problem with alarm manager in Android. So what I am trying to do is set the alarm to repeat to run the DB insertion every day around 12.01AM.

Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.set(Calendar.HOUR_OF_DAY, 0 );
    calendar.set(Calendar.MINUTE, 1);
        notificationCount = notificationCount + 1;
        AlarmManager mgr = (AlarmManager) context
                .getSystemService(Context.ALARM_SERVICE);
        Intent notificationIntent = new Intent(context,
                ReminderAlarm.class);

        notificationIntent.putExtra("NotifyCount", notificationCount);
        PendingIntent pi = PendingIntent.getBroadcast(context,
                notificationCount, notificationIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        mgr.setInexactRepeating(AlarmManager.RTC_WAKEUP,
                calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pi);

So basically I've came up with these code. However, the alarm manager execute again after the minute I set it.

Let's say I run the apps on 01/10/2014 5.48PM. I wanted this to run the DB insertion when onReceive every day after I set it around 12.01AM only. But somehow, the alarm manager execute at 01/10/2014 5.49PM which is one minute after I set it and it stopped working.

I wonder which part I did wrongly.

Thanks in advance.

EDIT

Recurring class For this class, it will trigger the alarm manager everyday and pass the variables along to reminder alarm class for DB insertion.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.recurring);
    context = this;
    buildListView();
    if(!alarmInitialized(this)) { 
        scheduleAlarms(this); 
    }
}

// And the few methods you suggested to schedule the alarm
public static void scheduleAlarms(Context context) {
    Calendar calendar = Calendar.getInstance();
    if (hasRunnedToday(context)) { // if the alarm has run this day
        calendar.add(Calendar.DATE, 1); // schedule it to run again starting
                                        // tomorrow
    }

    long firstRunTime = calendar.getTimeInMillis();
    AlarmManager mgr = (AlarmManager) context
            .getSystemService(Context.ALARM_SERVICE);
    Intent notificationIntent = new Intent(context, ReminderAlarm.class);
    PendingIntent pi = PendingIntent.getActivity(context, 0,
            notificationIntent, 0);
    mgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstRunTime,
            AlarmManager.INTERVAL_DAY, pi);

    ComponentName receiver = new ComponentName(context, BootReceiver.class);
    PackageManager pm = context.getPackageManager();

    pm.setComponentEnabledSetting(receiver,
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP);
}

BootReceiver class

public void onReceive(Context context, Intent i) {
    if (i.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
        Recurring.scheduleAlarms(context);
    }
}

ReminderAlarm class Basically for this class it just grab the variable passed from Recurring class and execute the DB insertion. I did inserted some Toast.makeText to test if it is retrieving but no luck by testing it.

public class ReminderAlarm extends BroadcastReceiver {
private NotificationManager mNotificationManager;
private Notification notification;

@Override
public void onReceive(Context context, Intent intent) {
    String recurID = null;
    String recurStartDate = null;
    String currentDate = null;
    String description = null;
    String type = null;
    String amount = null;
    String categoryName = null;
    String frequencyStr = null;
    String nextPaymentDate = null;
    SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");

    DatabaseAdapter mDbHelper = new DatabaseAdapter(context);
    mDbHelper.createDatabase();
    mDbHelper.open();
    RecurringController rc = new RecurringController(mDbHelper.open());
    ArrayList<RecurringModel> recur_list = rc.getAllRecurring();

    // THIS PART TO GET DATA FROM DATABASE
    for (int i = 0; i < recur_list.size(); i++) {
        recurID = recur_list.get(i).getRecurringID();
        recurStartDate = recur_list.get(i).getRecurringStartDate();
        currentDate = dateFormat.format(new Date());
        description = recur_list.get(i).getRecurringDesc();
        type = recur_list.get(i).getRecurringType();
        amount = Float.toString(recur_list.get(i).getRecurringAmount());
        categoryName = recur_list.get(i).getCategoryID();
        frequencyStr = recur_list.get(i).getFrequency();

        Toast.makeText(context,
                    description, Toast.LENGTH_LONG)
                    .show();
        Toast.makeText(context,
                    recurStartDate Toast.LENGTH_LONG)
                    .show();

        Calendar cal = Calendar.getInstance();
        try {
            cal.setTime(dateFormat.parse(recurStartDate));
            if (frequencyStr.equals("Daily")) {
                cal.add(Calendar.DAY_OF_MONTH, 1);
                nextPaymentDate = dateFormat.format(cal.getTimeInMillis());
                cal.add(Calendar.DAY_OF_MONTH, -1);
            } else if (frequencyStr.equals("Weekly")) {
                cal.add(Calendar.WEEK_OF_YEAR, 1);
                nextPaymentDate = dateFormat.format(cal.getTimeInMillis());
                cal.add(Calendar.WEEK_OF_YEAR, -1);
            } else if (frequencyStr.equals("Monthly")) {
                cal.add(Calendar.MONTH, 1);
                nextPaymentDate = dateFormat.format(cal.getTimeInMillis());
                cal.add(Calendar.MONTH, -1);
            } else if (frequencyStr.equals("Yearly")) {
                cal.add(Calendar.YEAR, 1);
                nextPaymentDate = dateFormat.format(cal.getTimeInMillis());
                cal.add(Calendar.YEAR, -1);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }

        // If dates match then execute the SQL statements
        if (currentDate.equals(nextPaymentDate)) {
            // mDbHelper.createDatabase();
            // mDbHelper.open();
            TransactionRecModel trm = new TransactionRecModel();
            CategoryController cc = new CategoryController(mDbHelper.open());

            trm.setDate(currentDate);
            trm.setTransDescription(description);
            trm.setType(type);
            trm.setAmount(Float.parseFloat(amount));

            // Get the categoryID based on categoryName
            String catID = cc.getCatIDByName(categoryName);
            trm.setCategory(catID);

            // Check if the recurring record exists before insert new
            // transaction record
            boolean recurExist = rc.checkRecurExist(recurStartDate,
                    description, catID);
            if (recurExist == true) {
                TransactionRecController trc = new TransactionRecController(
                        mDbHelper.open());
                // Check if the transaction record exists to prevent
                // duplication
                boolean moveNext = trc.checkTransExist(trm);
                if (moveNext == false) {

                    if (trc.addTransactionRec(trm)) {
                        // Update recurring start date after insertion of
                        // transaction
                        RecurringModel rm = new RecurringModel();
                        rm.setRecurringID(recurID);
                        rm.setRecurringStartDate(currentDate);

                        if (rc.updateRecurringDate(rm)) {
                            mNotificationManager = (NotificationManager) context
                                    .getSystemService(Context.NOTIFICATION_SERVICE);
                            PendingIntent contentIntent = PendingIntent
                                    .getActivity(
                                            context,
                                            Integer.parseInt(intent
                                                    .getExtras()
                                                    .get("NotifyCount")
                                                    .toString()),
                                            new Intent(), 0);
                            notification = new Notification(
                                    R.drawable.ic_launcher, "Notification",
                                    System.currentTimeMillis());
                            notification.setLatestEventInfo(context,
                                    description, nextPaymentDate,
                                    contentIntent);
                            mNotificationManager
                                    .notify(Integer.parseInt(intent
                                            .getExtras().get("NotifyCount")
                                            .toString()), notification);
                            mDbHelper.close();
                        }
                    }
                }
            }
            mDbHelper.close();
        }
    }
    mDbHelper.close();
    Recurring.updateAlarmLastRun(context);
}
}

I've added this part of codes in the part you suggested to schedule the alarm to call the BootReceiver class. Then from BootReceiver class, I will call back to the Recurring class and Reminder Alarm class:

ComponentName receiver = new ComponentName(context, BootReceiver.class);
    PackageManager pm = context.getPackageManager();

    pm.setComponentEnabledSetting(receiver,
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP);
Jeffers answered 1/10, 2014 at 9:50 Comment(13)
"...the alarm manager execute at 01/10/2014 5.49PM which is one minute after I set it and it stopped working." You are setting the time backwards by setting Calendar.HOUR_OF_DAY to 0. This immediately makes the alarm trigger and it won't trigger again until 00:01 the next day.Chloroprene
@Chloroprene So do you have any ideas on how to fix this? Because from my research, I thought by doing this able to trigger the alarm at specific time.Jeffers
@Chloroprene So where do I put these codes? Would you mind to post the whole parts as answer?Jeffers
@Chloroprene Should I wrap the setInExactRepeating in the if statement you provided previously? I am actually not quite clear about it :)Jeffers
Put the code I suggested after calendar.set(Calendar.MINUTE, 1);. See if it fixes your problem and if it does I'll post a full answer.Chloroprene
@Chloroprene Okay sure I'll get back to you in a few hours time as I've to wait until 12.01AM. Thanks a lot!Jeffers
@Chloroprene Hey there, your if statement does not work. Do you have any ideas?Jeffers
No, not really. I do the same thing (in my case a 24 hour repeating alarm to download files and update a database). I've looked over your code several times and can't see any significant difference from the code I use.Chloroprene
@Chloroprene It did trigger the alarm manager. But let's say I set a recurring task on 01/10/2014. It supposed to run the DB insertion on 02/10/2014 but somehow it only execute on 03/10/2014 which is delayed by one day. Do you have any ideas?Jeffers
Two questions. Where do you call this code? Why do you increment your notificationCount variable?Chloroprene
@Chloroprene I am calling this code when I retrieve data from database at the onCreate. As for the notificationCount, I not really sure as I followed the tutorial online.Jeffers
When you say onCreate there are various Android classes which have that method. Activity, Application, Service, SQLiteOpenHelper - which one?Chloroprene
@Chloroprene onCreate of Activity. Check my edited portion :)Jeffers
C
4

The problem is in calendar.getTimeInMillis() in

mgr.setInexactRepeating(AlarmManager.RTC_WAKEUP,
            calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pi);

The second argument to setInexactRepeating quoting the doc

triggerAtMillis time in milliseconds that the alarm should first go off, using the appropriate clock (depending on the alarm type). This is inexact: the alarm will not fire before this time, but there may be a delay of almost an entire alarm interval before the first invocation of the alarm.

Meaning it will run the first time aproximally one minute after you set it because of

calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 0 );
calendar.set(Calendar.MINUTE, 1);

If you wan't the first run of the alarm to be the next day do a calendar.add(Calendar. DATE, 1);`

As to the it stopped working, did you reboot de device ? AlarmCalendar alarms don't persist to device reboot, you can register a BroadcastReceiver to receive BOOT_COMPLETED event and register the alarm again check does Alarm Manager persist even after reboot?

Update: as you requested here is some help after reviewing your code

In your BOOT_COMPLETED Receiver class:

public void onReceive(Context context, Intent i) {
    if (i.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
        ReminderAlarm.scheduleAlarms(this);
    }
}

In your ReminderAlarm class

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.recurring);

if(!alarmInitialized(this) { 
    scheduleAlarms(this); 
}

}

public static void scheduleAlarms(Context context) {

    Calendar calendar = Calendar.getInstance();

    if(hasRunnedToday(context)) {       //if the alarm has run this day
        calendar.add(Calendar.DATE, 1); //schedule it to run again starting tomorrow
    }

    long firstRunTime = calendar.getTimeInMillis();

    AlarmManager mgr = (AlarmManager) context
            .getSystemService(Context.ALARM_SERVICE);
    Intent notificationIntent = new Intent(context, ReminderAlarm.class);
    PendingIntent pi = PendingIntent.getActivity(context, 0,
            notificationIntent, 0);

    mgr.setInexactRepeating(AlarmManager.RTC_WAKEUP,
            firstRunTime, AlarmManager.INTERVAL_DAY, pi);
}

public static boolean alarmInitialized(Context context) {
    SharedPreferences preferences = context.getSharedPreferences("alarm_prefs", MODE_PRIVATE);

    long alarmLastRun = preferences.getLong("AlarmLastRun", -1);


    return alarmLastRun != -1;

}

public static void updateAlarmLastRun(Context context) {
    SharedPreferences preferences = context.getSharedPreferences("alarm_prefs", MODE_PRIVATE);


    preferences.edit()
            .putLong("AlarmLastRun", new Date().getTime())
        .apply();
}

public static boolean hasRunnedToday(Context context) {
    SharedPreferences preferences = context.getSharedPreferences("alarm_prefs", MODE_PRIVATE);

    long alarmLastRun = preferences.getLong("AlarmLastRun", -1);

    if(alarmLastRun == -1) {
        return false;
    }

    //check by comparing day, month and year
    Date now = new Date();
    Date lastRun = new Date(alarmLastRun);


    return now.getTime() - lastRun.getTime() < TimeUnit.DAYS.toMillis(1);
}

Each time your Reminder class alarm runs you should call updateAlarmLastRun to update the last time the alarm has run, this is necessary because the alarm may be schedule to be run on a day and the user reboots the device before the alarm has run in that case we don't want to use calendar.add(Calendar.DATE, 1); since that would skip a day.

On your Manifest.xml

<receiver android:name=".BootReceiver" android:enabled="true" android:exported="false" android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
     <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
    </receiver>

Notes:

  1. You shouldn't do context = this if context is a class field since the object holds a reference to its field context and context field holds a reference to the object that would leak
  2. Your Receiver 'onReceive` doesn't has the extras you assumed to have like "notificationCount" onReceive by the system when your device finish boot.
  3. Once your alarm runs call updateAlarmLastRun

Hope any of this helps

Cartwright answered 9/10, 2014 at 11:26 Comment(22)
Yeah, I added the code so that the alarm will still persist upon reboot. But where do I should add the instance.add() ?Jeffers
So I should add that line in both of my classes? Can you help me check my edited portion?Jeffers
But if I do the scheduleAlarm in separate method, how should I parse the recurID, recurDate, recurType etc along? As in those variables that I pass along to ReminderAlarm class.Jeffers
What are you trying to track with those variables ?Cartwright
I have to pass those variables into ReminderAlarm class so that to execute DB insertion based on the dates. I not sure how should I pass those variable along as parameters.Jeffers
I've updated the question. Do you have any ideas? So basically I am triggering the alarm manager from Recurring.java. Then, I will pass along the data retrieved from database to ReminderAlarm.java.Jeffers
If i were you i would simply place the data in the database, or in SharedPreferences, because like i told you if the user reboots the device the alarm is lost, so is the data unless you know how to get the data on the BroadcastReceiver for BOOT_COMPLETE and if you can recover it in that case, then there is no point in passing the that in the PendingIntent in the first case.Cartwright
I've updated the question. Can you please help me check if I am doing in the correct way? Because from what I'd now, it does not execute the code inside Reminder Alarm class. For what I've changed is I will trigger the alarm manager in the way you suggested, but every time it goes into the ReminderAlarm class, then I retrieve the data from database and compareJeffers
Would you mind to pinpoint which part that I've done wrongly? Cause the alarm manager is not running at all and there is no error message.Jeffers
I had a typo in scheduleAlarms where it was PendingIntent.getService it must be PendingIntent.getActivityCartwright
What's the difference between them both? :0 Do you think I should use getBraodcast()? because from my previous codes, I am using thatJeffers
You really oughta read the documentation PendingIntent.getActivity returns a PendingIntentthat can be used to launch an Activity, getBroadCastreturn one that starts an BroadcastReceiver etc etc checkout developer.android.com/reference/android/app/…, int, android.content.Intent, int)Cartwright
But then when I set a recurring task for yesterday and repeated daily, the next payment day ought to be today. And by right, it should trigger the alarm manager and execute the DB insertion. Unfortunately, no luck. Do you have any ideas?Jeffers
with mgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstRunTime, AlarmManager.INTERVAL_DAY, pi); the alarm is set to run at firstRunTimemillis for the first time, if the time is in the past it is run the first time the AlarmManager has the chance, and then each time for the interval set, so if you set the alarm firstRunTime for the next day it will run the next day and then once each day it must work correctly as long as the arguments are correctedCartwright
Okay so can I assume that if I wanted to do a testing, I should set a daily repeatance recurring task today and wait for next day to trigger the alarm manager? Am I correct?Jeffers
Yes, you could also use smaller intervals for testing like 1 minute or soCartwright
But I've set it to run by one minute and it does not trigger the alarm manager. I've updated my question again. Could you please take a look?Jeffers
Are you that scheduleAlarms is called at least once?Cartwright
Hey just curious, do you need to put these codes to ensure the alarm persist even after rebooted? ComponentName receiver = new ComponentName(context, BootReceiver.class); PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting(receiver, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);Jeffers
You need to use a Broadcast receiver like the one I posted that receives the event on boot completed so that when the device boots it runs the broadcast receiver and in the receiver your alarms are runed, note that when the device boots you need to check when your alarm has last runed, imagina the case when the device is off for one or more daysyCartwright
Could you please help me check the edited portion again? Because codes from there does not work and it never throw me an error message so I not sure what went wrong. :(Jeffers
Sorry because this is getting lengthy. In your post, you said need not to have calendar.add(Calendar.DATE, 1); so that means I shouldnt have hasRunnedToday() and the if statement in scheduleAlarm()?Jeffers

© 2022 - 2024 — McMap. All rights reserved.