2

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!

TheAlmac2
  • 193
  • 2
  • 15
  • "setExact" does not work as the name implies on Android 6+ [API 21+]. It will be subject to doze restrictions ~ when the device is not charging and the screen is OFF. To raise a reliable user facing alarm, the "setAlarmClock" method of the AlarmManager should be used. – Elletlar May 10 '18 at 14:27
  • Also, the alarm normally fires right away when the initial time set is in the past. From the code, I think you're already aware that RTC_WAKEUP is based on System.currentTimeMillis(). This tool [EPOCH Converter](https://www.epochconverter.com/) is useful for translating the time from milliseconds to human time. – Elletlar May 10 '18 at 14:37
  • @Elletlar thanks for the reply, okay so should I try the setAlarmClock method? and also have I code the "repeat" correctly in the broadcast receiver? thanks again – TheAlmac2 May 10 '18 at 15:17
  • For Android 5 devices and below setExact must be used. For Android 6 and above, setAlarmClock should be used. So you will likely need to implement both unless the minimum API is set to 21. Did you check the repeat times in the EPOCH Converter against the clock time on the phone? – Elletlar May 10 '18 at 15:42
  • Hi @Elletlar. The first alarm works so I tested it there and it alarmed at 9.07 when my check was due at 9.12 so that is correct. Only thing is how can I repeat this now in the broadcast receiver? – TheAlmac2 May 11 '18 at 08:08
  • @Elletlar I have updated my code to my broadcast receiver. It seems to be whenever I have the code in to try and repeat it it is breaking the initial alarm and it isn't working correctly. Could you have a look and see what I am doing wrong – TheAlmac2 May 11 '18 at 09:39
  • Creating a unique code in each PendingIntent should prevent your alarms from replacing each other. Does that fix it? – Elletlar May 11 '18 at 10:12
  • @Elletlar how can I do that? – TheAlmac2 May 11 '18 at 10:26
  • apologies I just seen your update below @Elletlar. so say In my activity my alarm request code is 0 in my pendingintents and in broadcast receiver the requestcodes are 1's, this should do it? – TheAlmac2 May 11 '18 at 10:35
  • Sorry I've not had time to run your code, but it should help... – Elletlar May 11 '18 at 10:43
  • I think its worked!! @Elletlar thanks so much – TheAlmac2 May 11 '18 at 11:04
  • Np. Great! :) :) :) – Elletlar May 11 '18 at 11:26

1 Answers1

1

"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)
Elletlar
  • 3,136
  • 7
  • 32
  • 38
  • I will try that out! Is this repeatable? – TheAlmac2 May 10 '18 at 15:28
  • I had this working for some time then when I went to demo it , it did not work at all. (Standard for demos) I was just wondering if you would know why or have experienced this? – TheAlmac2 Jun 08 '18 at 09:27