Search code examples
javaandroidalarmmanagerrepeatingalarm

Using setExact() in broadcast receiver to repeat


Scenario: Production line workers perform checks at certain time intervals. On my app the user enters this time interval into a textbox. There is date and time pickers so that the user can select when the process started. I am trying to fire an alarm 5 minutes before the next check is due.

For example: Process starts at 2.30. User has entered in intervals of 20 minutes. Therefore next check is due at 2.50. The alarm should sound at 2.45. This process will then repeat so next check would be 3.10 and the alarm should sound at 3.05.

Here is the code I have:

 MainActivity.calculate();
        commencedTime = new Date();
        mTime = Calendar.getInstance();
        mTime.setTime(alarmTime);
        timeCommenced = trimSecsAndMillisecs(mTime).getTimeInMillis();

        currentTime = new Date();
        currentTime.getTime();

        alertDialogBuilder = new AlertDialog.Builder(MainActivity.this);
        //set alert
        alertDialogBuilder
                .setTitle("IP Check frequency: " + time.getText() + " minutes")
                .setMessage("Processing commenced at \n" + startTime.getText())
                .setCancelable(false)
                .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                        if (currentTime.after(alarmTime)) {
                            Toast.makeText(MainActivity.this, "Missed first alert", Toast.LENGTH_LONG).show();
                        }

                        i = Integer.parseInt(time.getText().toString());
                        long scTime2 = ((i * 60 * 1000)); //TIME ENTERED IN MILLISECONDS

                        intent1 = new Intent(MainActivity.this, MyBroadcastReceiver.class);
                        intent1.putExtra("timeEntered", scTime2);
                        intent1.putExtra("timeCommenced", timeCommenced);
                        sendBroadcast(intent1);

                        pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, intent1, PendingIntent.FLAG_UPDATE_CURRENT);

                        manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
                        manager.setExact(AlarmManager.RTC_WAKEUP, timeCommenced, pendingIntent);

                        Toast.makeText(MainActivity.this, "Alert Set", Toast.LENGTH_SHORT).show();
                        stopped.setVisibility(View.VISIBLE);
                        commenced1.setVisibility(View.GONE);

                        //Change editText to TextView
                        time.setVisibility(View.GONE);
                        timeText.setVisibility(View.VISIBLE);
                        timeText.setText(time.getText().toString());
                        processingText.setText(R.string.processing_commenced);

                    }
                })
                .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                });
        alertDialog = alertDialogBuilder.create();
        alertDialog.show();

    }
}

Broadcast Receiver:

@Override
public void onReceive(Context context, Intent intent) {
    // Vibrate the mobile phone
    vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
    long[] pattern = {0, 10000, 3, 10000};
    vibrator.vibrate(pattern, -1); // vibration for 20 seconds

    //create as instance of the media player
    mediaPlayer = MediaPlayer.create(context, R.raw.alarm_sound);
    mediaPlayer.start();

   createNotification(context, "AlmaIPC", "IP check is due", "Alert");

    AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
    Intent intent2 = new Intent(context, MainActivity.class);
    PendingIntent pi2 = PendingIntent.getActivity(context, 0, intent2, 0);

    Long timeEntered =intent.getLongExtra("timeEntered", 0);


    AlarmManager.AlarmClockInfo ac=
            new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + timeEntered,
                    pi2);

    manager.setAlarmClock(ac, pi);


}

the timecommenced variable that I have inside my setExact() parameters is debugging correctly as to which time it should be firing the alarm at.

Inside my broadcast receiver then I am trying to pass this value through and add on the interval that the user has entered which would keep the timing of the alarm in tact. e.g. 2.45 then 3.05.

For some reason why I click "Processing commenced" instead of the alarm sounding when timecommenced states, it is firing right away. Then after that the alarm timings are completely off and it has a mind of its own.

Can anyone see something that I am doing wrong or give me some advice?

Thanks!


Solution

  • "setExact" does not work as the name implies on Android 6+ [API 21+]. It will be subject to doze restrictions roughly when the device is not charging and the screen is OFF. [Note: The problem is more acute beginning with Android 7 since it has a much more aggressive form of Doze]

    To raise a reliable user facing alarm, the "setAlarmClock" method of the AlarmManager should be used.

    AlarmManager am =
      (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
    Intent intent1 = new Intent(context, myReceiver.class);
    PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
    Intent intent2 = new Intent(context, myActivity.class);
    PendingIntent pi2 = PendingIntent.getActivity(context, 0, i2, 0);
    
    AlarmManager.AlarmClockInfo ac=
      new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + DELAY,
        pi2);
    
    am.setAlarmClock(ac, pi);
    

    From the docs:

    setAlarmClock added in API level 21

    void setAlarmClock (AlarmManager.AlarmClockInfo info, 
                PendingIntent operation)
    

    Schedule an alarm that represents an alarm clock, which will be used to notify the user when it goes off. The expectation is that when this alarm triggers, the application will further wake up the device to tell the user about the alarm -- turning on the screen, playing a sound, vibrating, etc. As such, the system will typically also use the information supplied here to tell the user about this upcoming alarm if appropriate.

    Due to the nature of this kind of alarm, similar to setExactAndAllowWhileIdle(int, long, PendingIntent), these alarms will be allowed to trigger even if the system is in a low-power idle (a.k.a. doze) mode

    Alarm Manager Docs

    To set more than 1 alarm, give each one a unique 'requestCode' in the PendingIntent:

     static PendingIntent   getBroadcast(Context context, int requestCode, Intent intent, int flags)