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

后端 未结 2 1916
误落风尘
误落风尘 2020-12-31 11:55

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 Andr

相关标签:
2条回答
  • 2020-12-31 12:17

    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.

    0 讨论(0)
  • 2020-12-31 12:32

    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.

    0 讨论(0)
提交回复
热议问题