diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java index d417da74d..dcb2a66c8 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java @@ -84,9 +84,11 @@ import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; /** FlutterLocalNotificationsPlugin */ @Keep @@ -99,7 +101,9 @@ public class FlutterLocalNotificationsPlugin private static final String DRAWABLE = "drawable"; private static final String DEFAULT_ICON = "defaultIcon"; private static final String SELECT_NOTIFICATION = "SELECT_NOTIFICATION"; - private static final String SCHEDULED_NOTIFICATIONS = "scheduled_notifications"; + private static final String SCHEDULED_NOTIFICATIONS_FILE = "scheduled_notifications"; + private static final String SCHEDULED_NOTIFICATIONS_STRING = "scheduled_notifications"; + private static final String SCHEDULED_NOTIFICATIONS_SET = "scheduled_notifications_set"; private static final String INITIALIZE_METHOD = "initialize"; private static final String GET_CALLBACK_HANDLE_METHOD = "getCallbackHandle"; private static final String ARE_NOTIFICATIONS_ENABLED_METHOD = "areNotificationsEnabled"; @@ -392,24 +396,63 @@ static Gson buildGson() { private static ArrayList loadScheduledNotifications(Context context) { ArrayList scheduledNotifications = new ArrayList<>(); SharedPreferences sharedPreferences = - context.getSharedPreferences(SCHEDULED_NOTIFICATIONS, Context.MODE_PRIVATE); - String json = sharedPreferences.getString(SCHEDULED_NOTIFICATIONS, null); - if (json != null) { + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); + Set jsonNotifications = getScheduledNotificationsJsonSet(sharedPreferences); + if (jsonNotifications != null) { Gson gson = buildGson(); - Type type = new TypeToken>() {}.getType(); - scheduledNotifications = gson.fromJson(json, type); + Type type = new TypeToken() {}.getType(); + for (String json : jsonNotifications) { + scheduledNotifications.add(gson.fromJson(json, type)); + } + } + return scheduledNotifications; + } + + /** + * Get scheduled notifications from shared preferences, converting from old format if present. + * + * Returns null if neither are present + * The returned Set may not be mutated! + */ + private static Set getScheduledNotificationsJsonSet(SharedPreferences sharedPreferences) { + Set jsonNotifications = sharedPreferences.getStringSet(SCHEDULED_NOTIFICATIONS_SET, null); + if (jsonNotifications != null) { + return jsonNotifications; } + String notificationsJson = sharedPreferences.getString(SCHEDULED_NOTIFICATIONS_STRING, null); + if (notificationsJson == null) { + return null; + } + + // Convert (once) from the old format to the new format and delete the old + Gson gson = buildGson(); + Type type = new TypeToken>() {}.getType(); + ArrayList notificationsList = gson.fromJson(notificationsJson, type); + int amount = notificationsList.size(); + String[] jsonNotificationsToSave = new String[amount]; + for (int i = 0; i < amount; i++) { + jsonNotificationsToSave[i] = gson.toJson(notificationsList.get(i)); + } + Set scheduledNotifications = Set.of(jsonNotificationsToSave); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove(SCHEDULED_NOTIFICATIONS_STRING); + editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, scheduledNotifications); + editor.apply(); return scheduledNotifications; } private static void saveScheduledNotifications( Context context, ArrayList scheduledNotifications) { Gson gson = buildGson(); - String json = gson.toJson(scheduledNotifications); + int amount = scheduledNotifications.size(); + String[] jsonNotificationsToSave = new String[amount]; + for (int i = 0; i < amount; i++) { + jsonNotificationsToSave[i] = gson.toJson(scheduledNotifications.get(i)); + } SharedPreferences sharedPreferences = - context.getSharedPreferences(SCHEDULED_NOTIFICATIONS, Context.MODE_PRIVATE); + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(SCHEDULED_NOTIFICATIONS, json); + editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, Set.of(jsonNotificationsToSave)); editor.apply(); } @@ -450,11 +493,8 @@ private static void scheduleNotification( AlarmManager alarmManager = getAlarmManager(context); if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { - AlarmManagerCompat.setExactAndAllowWhileIdle( - alarmManager, - AlarmManager.RTC_WAKEUP, - notificationDetails.millisecondsSinceEpoch, - pendingIntent); + AlarmManagerCompat.setAlarmClock( + alarmManager, notificationDetails.millisecondsSinceEpoch, pendingIntent, pendingIntent); } else { AlarmManagerCompat.setExact( alarmManager, @@ -492,8 +532,7 @@ private static void zonedScheduleNotification( .toInstant() .toEpochMilli(); if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { - AlarmManagerCompat.setExactAndAllowWhileIdle( - alarmManager, AlarmManager.RTC_WAKEUP, epochMilli, pendingIntent); + AlarmManagerCompat.setAlarmClock(alarmManager, epochMilli, pendingIntent, pendingIntent); } else { AlarmManagerCompat.setExact(alarmManager, AlarmManager.RTC_WAKEUP, epochMilli, pendingIntent); } @@ -515,8 +554,8 @@ static void scheduleNextRepeatingNotification( PendingIntent pendingIntent = getBroadcastPendingIntent(context, notificationDetails.id, notificationIntent); AlarmManager alarmManager = getAlarmManager(context); - AlarmManagerCompat.setExactAndAllowWhileIdle( - alarmManager, AlarmManager.RTC_WAKEUP, notificationTriggerTime, pendingIntent); + AlarmManagerCompat.setAlarmClock( + alarmManager, notificationTriggerTime, pendingIntent, pendingIntent); saveScheduledNotification(context, notificationDetails); } @@ -568,8 +607,8 @@ private static void repeatNotification( AlarmManager alarmManager = getAlarmManager(context); if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { - AlarmManagerCompat.setExactAndAllowWhileIdle( - alarmManager, AlarmManager.RTC_WAKEUP, notificationTriggerTime, pendingIntent); + AlarmManagerCompat.setAlarmClock( + alarmManager, notificationTriggerTime, pendingIntent, pendingIntent); } else { alarmManager.setInexactRepeating( AlarmManager.RTC_WAKEUP, notificationTriggerTime, repeatInterval, pendingIntent); @@ -610,18 +649,24 @@ private static long calculateRepeatIntervalMilliseconds(NotificationDetails noti return repeatInterval; } + // Note: This will now allow duplicate notifications on ID which would be overwritten before. private static void saveScheduledNotification( Context context, NotificationDetails notificationDetails) { - ArrayList scheduledNotifications = loadScheduledNotifications(context); - ArrayList scheduledNotificationsToSave = new ArrayList<>(); - for (NotificationDetails scheduledNotification : scheduledNotifications) { - if (scheduledNotification.id.equals(notificationDetails.id)) { - continue; - } - scheduledNotificationsToSave.add(scheduledNotification); + SharedPreferences sharedPreferences = + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); + Set jsonNotifications = getScheduledNotificationsJsonSet(sharedPreferences); + if (jsonNotifications == null) { + jsonNotifications = new HashSet(); } - scheduledNotificationsToSave.add(notificationDetails); - saveScheduledNotifications(context, scheduledNotificationsToSave); + + // Mutations on the Set from getStringSet() are not allowed! + Set jsonNotificationsToSave = new HashSet(jsonNotifications); + + Gson gson = buildGson(); + jsonNotificationsToSave.add(gson.toJson(notificationDetails)); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putStringSet(SCHEDULED_NOTIFICATIONS_SET, jsonNotificationsToSave); + editor.apply(); } private static int getDrawableResourceId(Context context, String name) { @@ -1619,7 +1664,11 @@ private void cancelAllNotifications(Result result) { alarmManager.cancel(pendingIntent); } - saveScheduledNotifications(applicationContext, new ArrayList<>()); + SharedPreferences sharedPreferences = + applicationContext.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove(SCHEDULED_NOTIFICATIONS_SET); + editor.apply(); result.success(null); }