Daily notifications at a specific time set by the user

7

I've created a method to send a notification to the user with the name sendNotification() using NotificationCompat.Builder and NotificationManager .

I need this notification to be launched every day at 07:30 AM , and this time can be adjusted by the user, with the time being set using the SharedPreference class.

It seems to me that the public class AlarmManager can do this, but I'm not sure if I have to create a service or if it would be the service itself.

According to this answer , you just have to set the exact time, such as at 07: 30AM . So in my application I put it this way:

calendar.set(Calendar.HOUR_OF_DAY, 7); 
calendar.set(Calendar.MINUTE, 30);
calendar.set(Calendar.SECOND, 0);

But I did a lot of testing, and it's not reporting properly at the specified time.

To not pollute a lot of code here, I put what I did in GitHubGist .

How could this notification be made daily at a specific time?

    
asked by anonymous 10.03.2017 / 18:16

1 answer

4

Using the code of BroadcastReceiver of the answer that refers and implementing what is referred to in the notes, it will be like this:

public class StartUpBootReceiver extends BroadcastReceiver {

    private static String HOUR = "hour";
    private static String MINUTE = "minute";


    public static void setAlarm(Context context, int hour, int minute){
        SharedPreferences preferences =  PreferenceManager.getDefaultSharedPreferences(context);
        preferences.edit()
                .putInt(HOUR, hour)
                .putInt(MINUTE, minute)
                .apply();
        setAlarm(context);
    }


    @Override
    public void onReceive(Context context, Intent intent) {

        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            setAlarm(context);
            Toast.makeText(context, "Alarm set", Toast.LENGTH_LONG).show();
        }

    }

    private static void setAlarm(Context context) {

        int hour = getHour(context);
        int minute = getMinute(context);

        if(hour == -1 || minute == -1){
            //nenhum horário definido
            return;
        }

        // Cria um Calendar para o horário estipulado
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);

        //Se já passou
        if(isDateBeforeNow(calendar)){
            //adiciona um dia
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }


        //PendingIntent para lançar o serviço
        Intent serviceIntent = new Intent(context, BootService.class);
        PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent, 0);

        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        //Cancela um possível alarme existente
        alarmManager.cancel(pendingIntent);

        //Alarme que se repete todos os dias a uma determinada hora
        alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP,
                calendar.getTimeInMillis(),
                AlarmManager.INTERVAL_DAY,
                pendingIntent);
    }

    private static int getHour(Context context){
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getInt(HOUR, -1);
    }
    private static int getMinute(Context context){
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getInt(MINUTE, -1);
    }

    private static boolean isDateBeforeNow(Calendar calendar){
        return calendar.getTimeInMillis() <= System.currentTimeMillis();
    }
}

Use the

StartUpBootReceiver.setAlarm(context, hour, minute);

to set / change the alarm time.

If the device is switched off, the alarm is re-registered when the device is switched on.

Declare BroadcastReceiver in AndroidManifest.xml

<receiver android:name="aSuaPackage.StartUpBootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>  

Add the permission

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

Implement the service to launch the notification:

public class BootService extends IntentService {

    private PowerManager.WakeLock wakeLock;

    public BootService() {
        super("name");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
        wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK |
                PowerManager.ACQUIRE_CAUSES_WAKEUP |
                PowerManager.ON_AFTER_RELEASE, "BootService");
        wakeLock.acquire();
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {

        //Lance a notificação aqui.
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    if(wakeLock.isHeld()){
        //Verificou-se que o iluminar do ecrã
        //não acontecia devido ao WakeLock ser
        //rapidamente libertado(apesar de PowerManager.ON_AFTER_RELEASE !?).
        try {
            //Atrasa a libertação do WakeLock
            //de forma a permitir a iluminação do ecrâ.
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            wakeLock.release();
        }
    }
}

Declare it in AndroidManifest.xml

<service android:name=".BootService"/>

and add permission to get Wake Lock

<uses-permission android:name="android.permission.WAKE_LOCK"/>

Note: The first release of an inexact repeating alarm , as highlighted in documentation will never be before the specified time, but may not occur for most of the time after time. If the replay interval is large and you set the alarm to a time thereafter, the first posting may only occur after the interval has elapsed.

An alternative is to use the setRepeating() method instead of setInexactRepeating() . However, from API 19 all "repeating alarms" are considered "inexact" .

The solution is to set an alarm using the set() method and then add the interval to the calendar and set another alarm using setInexactRepeating() .

A possible implementation for INTERVAL_DAY will look like this:

public class StartUpBootReceiver extends BroadcastReceiver {

    private static int REPEATING_ID = 1001;
    private static int ON_TIME_ID = 1002;
    private static String HOUR = "hour";
    private static String MINUTE = "minute";


    public static void setAlarm(Context context, int hour, int minute){
        SharedPreferences preferences =  PreferenceManager.getDefaultSharedPreferences(context);
        preferences.edit()
                .putInt(HOUR, hour)
                .putInt(MINUTE, minute)
                .apply();
        setAlarm(context);
    }


    @Override
    public void onReceive(Context context, Intent intent) {

        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            setAlarm(context);
            Toast.makeText(context, "Alarm set", Toast.LENGTH_LONG).show();
        }

    }

    private static void setAlarm(Context context) {

        int hour = getHour(context);
        int minute = getMinute(context);

        if(hour == -1 || minute == -1){
            //nenhum horário definido
            return;
        }
        //Cancela possiveis alarmes existentes
        cancelAlarm(context);

        Calendar calendar = getCalendar(hour, minute);

        //Se já passou
        if(isDateBeforeNow(calendar)){
            //adiciona um dia
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }else{
            //Alarme para o horário especificado
            setOneTimeAlarm(context, calendar);
            //adiciona um dia para repetir o alarme no dia seguinte
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }
        //Repete o alarme no dia seguinte
        setRepeatingAlarm(context, calendar);

    }

    private static void setRepeatingAlarm(Context context, Calendar calendar){

        PendingIntent pendingIntent = getPendingIntent(context, REPEATING_ID);

        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

        //Alarme que se repete todos os dias a uma determinada hora
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
                calendar.getTimeInMillis(),
                AlarmManager.INTERVAL_DAY,
                pendingIntent);
    }

    private static void setOneTimeAlarm(Context context, Calendar calendar){

        PendingIntent pendingIntent= getPendingIntent(context, ON_TIME_ID);

        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

        //Alarme para o horário especificado
        alarmManager.set(AlarmManager.RTC_WAKEUP,
                         calendar.getTimeInMillis(),
                         pendingIntent);
    }

    private static void cancelAlarm(Context context){

        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(getPendingIntent(context, ON_TIME_ID));
        alarmManager.cancel(getPendingIntent(context, REPEATING_ID));

    }

    private static PendingIntent getPendingIntent(Context context, int id){
        //PendingIntent para lançar o serviço
        Intent serviceIntent = new Intent(context, BootService.class);
        return PendingIntent.getService(context, id, serviceIntent, 0);
    }

    private static Calendar getCalendar(int hour, int minute){

        // Cria um Calendar para o horário especificado
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, 0);
        return calendar;
    }

    private static int getHour(Context context){
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getInt(HOUR, -1);
    }
    private static int getMinute(Context context){
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getInt(MINUTE, -1);
    }

    private static boolean isDateBeforeNow(Calendar calendar){
        return calendar.getTimeInMillis() <= System.currentTimeMillis();
    }
}
    
11.03.2017 / 02:18