Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To give notifications on Android on specific time in Android Oreo?

I am looking for a way to create a preference in Settings to send a notification at a specific time of the day (set by user in settings) in an Android app. I have looked at different threads like this, however this is not working in Android Oreo.

Can someone help me with this or point me to a tutorial?

like image 686
Tricky Bay Avatar asked Jul 14 '18 21:07

Tricky Bay


People also ask

Can you set notifications for certain times Android?

On Android, you will need to open Settings. Then choose Sound>Do Not Disturb>Schedules. On this screen, you have several options to set the days and times for Do Not Disturb, choose which apps to apply it to, and set exceptions.

How do I schedule notifications?

Tap Settings. Tap Notifications. Tap Scheduled digest. Tap to customize your desired delivery time.

What is Oreo notification?

Notification in Android 8.0(oreo): Implementing Notification Channels. Android 8.0 Oreo comes with many cool features like Picture-in-Picture (PiP) Mode, Notification Snoozing, Smart Text Selection and much more. One of them is Notification Dots.


2 Answers

After looking at different posts and some research on AlarmManager implementation, this is what worked for me.

The base for this is this post and Schedule repeating Alarms Android Documentation.

This is my current implementation:

I have a SwitchPreference and a TimePicker implementation is Settings

SwitchPreference to ask if user wants to enable Repeating Daily Notifications.

TimePicker to set the Notification time.

In MainActivity's OnCreate method or wherever you are reading the SharedPreferences do this:

PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
Boolean dailyNotify = sharedPref.getBoolean(SettingsActivity.KEY_PREF_DAILY_NOTIFICATION, true);
PackageManager pm = this.getPackageManager();
ComponentName receiver = new ComponentName(this, DeviceBootReceiver.class);
Intent alarmIntent = new Intent(this, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, 0);
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

// if user enabled daily notifications
if (dailyNotify) {
    //region Enable Daily Notifications
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.set(Calendar.HOUR_OF_DAY, sharedPref.getInt("dailyNotificationHour", 7));
    calendar.set(Calendar.MINUTE, sharedPref.getInt("dailyNotificationMin", 15));
    calendar.set(Calendar.SECOND, 1);
    // if notification time is before selected time, send notification the next day
    if (calendar.before(Calendar.getInstance())) {
        calendar.add(Calendar.DATE, 1);
    }
    if (manager != null) {
        manager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                AlarmManager.INTERVAL_DAY, pendingIntent);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
        }
    }
    //To enable Boot Receiver class
    pm.setComponentEnabledSetting(receiver,
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP);
    //endregion
} else { //Disable Daily Notifications
    if (PendingIntent.getBroadcast(this, 0, alarmIntent, 0) != null && manager != null) {
        manager.cancel(pendingIntent);
        //Toast.makeText(this,"Notifications were disabled",Toast.LENGTH_SHORT).show();
    }
    pm.setComponentEnabledSetting(receiver,
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP);
}

Next add AlarmReceiver class that implements BroadcastReceiver like this:

public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {

    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(Objects.requireNonNull(context));
    SharedPreferences.Editor sharedPrefEditor = prefs.edit();

    NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    Intent notificationIntent = new Intent(context, MainActivity.class);

    notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
            | Intent.FLAG_ACTIVITY_SINGLE_TOP);

    PendingIntent pendingI = PendingIntent.getActivity(context, 0,
            notificationIntent, 0);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("default",
                    "Daily Notification",
                    NotificationManager.IMPORTANCE_DEFAULT);
            channel.setDescription("Daily Notification");
            if (nm != null) {
                nm.createNotificationChannel(channel);
            }
        }
        NotificationCompat.Builder b = new NotificationCompat.Builder(context, "default");
        b.setAutoCancel(true)
                .setDefaults(NotificationCompat.DEFAULT_ALL)
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher_foreground)
                .setTicker("{Time to watch some cool stuff!}")
                .setContentTitle("My Cool App")
                .setContentText("Time to watch some cool stuff!")
                .setContentInfo("INFO")
                .setContentIntent(pendingI);

        if (nm != null) {
            nm.notify(1, b.build());
            Calendar nextNotifyTime = Calendar.getInstance();
            nextNotifyTime.add(Calendar.DATE, 1);
            sharedPrefEditor.putLong("nextNotifyTime", nextNotifyTime.getTimeInMillis());
            sharedPrefEditor.apply();
        }
    }
}

The system will turn off the AlarmManager if Device is powered off or reboots, so restart it again on BOOT COMPLETE add this class:

public class DeviceBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
    if (Objects.equals(intent.getAction(), "android.intent.action.BOOT_COMPLETED")) {
        // on device boot complete, reset the alarm
        Intent alarmIntent = new Intent(context, AlarmReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0);

        AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(Objects.requireNonNull(context));

        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        calendar.set(Calendar.HOUR_OF_DAY, sharedPref.getInt("dailyNotificationHour", 7));
        calendar.set(Calendar.MINUTE, sharedPref.getInt("dailyNotificationMin", 15));
        calendar.set(Calendar.SECOND, 1);

        Calendar newC = new GregorianCalendar();
        newC.setTimeInMillis(sharedPref.getLong("nextNotifyTime", Calendar.getInstance().getTimeInMillis()));

        if (calendar.after(newC)) {
            calendar.add(Calendar.HOUR, 1);
        }

        if (manager != null) {
            manager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                    AlarmManager.INTERVAL_DAY, pendingIntent);
        }
    }
}
}

And Finally do not forget to add these permissions to AndroidManidest:

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

and register your receivers in AndroidManifest

<application
    <!--YOUR APPLICATION STUFF-->

    <receiver android:name=".DeviceBootReceiver"
        android:enabled="false">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
    <receiver android:name=".AlarmReceiver" />

The Notification should be set by this at a specific time of the day specified by TimePicker and if user enabled the SwitchPreference.

like image 86
Tricky Bay Avatar answered Nov 14 '22 19:11

Tricky Bay


There are tons of examples of how to do this on StackOverflow, but unfortunately none of them work anymore because Google changed how the AlarmManager works.

  • Android 6: Doze
  • Android 7: Aggressive Doze [Disables CPU shortly after screen is turned OFF]
  • Android 8: Additional restrictions on background services

The only AlarmManager option that allows a developer to wakeup a device at a specific time is AlarmManager.setAlarmClock.

Using setAlarmClock

In addition to the alarm, you can set an intent that will enable you to launch your app when the alarm is clicked.

The device can also be awoken at a specific time using a high priority FCM, Firebase Cloud Message.

But in summary, there is no other option: 'setExact' and associated methods no longer work as the name advertises.

like image 36
Elletlar Avatar answered Nov 14 '22 19:11

Elletlar