From 2a39a7d044d67096d555f09c22dde183b2f9b264 Mon Sep 17 00:00:00 2001 From: Nico L Date: Fri, 10 Jul 2020 20:44:02 +0200 Subject: [PATCH] fix(android): restore local notifications after device reboot (#3027) --- .../capacitor/src/main/AndroidManifest.xml | 10 ++++ .../plugin/LocalNotifications.java | 2 +- .../notification/LocalNotification.java | 38 +++++++----- .../LocalNotificationManager.java | 13 ++-- .../LocalNotificationRestoreReceiver.java | 60 +++++++++++++++++++ .../notification/NotificationStorage.java | 46 +++++++++++++- 6 files changed, 145 insertions(+), 24 deletions(-) create mode 100644 android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationRestoreReceiver.java diff --git a/android/capacitor/src/main/AndroidManifest.xml b/android/capacitor/src/main/AndroidManifest.xml index aebb64cc3..cf1428824 100644 --- a/android/capacitor/src/main/AndroidManifest.xml +++ b/android/capacitor/src/main/AndroidManifest.xml @@ -14,5 +14,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java index 552071282..bfca9fb1d 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java @@ -74,7 +74,7 @@ public void schedule(PluginCall call) { } JSONArray ids = manager.schedule(call, localNotifications); if (ids != null) { - notificationStorage.appendNotificationIds(localNotifications); + notificationStorage.appendNotifications(localNotifications); JSObject result = new JSObject(); JSArray jsArray = new JSArray(); for (int i=0; i < ids.length(); i++) { diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java index 7f16e3794..b00847c20 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java @@ -186,31 +186,37 @@ public static List buildNotificationList(PluginCall call) { call.error("Invalid JSON object sent to NotificationPlugin", e); return null; } - LocalNotification activeLocalNotification = new LocalNotification(); - activeLocalNotification.setSource(notification.toString()); - activeLocalNotification.setId(notification.getInteger("id")); - activeLocalNotification.setBody(notification.getString("body")); - activeLocalNotification.setActionTypeId(notification.getString("actionTypeId")); - activeLocalNotification.setGroup(notification.getString("group")); - activeLocalNotification.setSound(notification.getString("sound")); - activeLocalNotification.setTitle(notification.getString("title")); - activeLocalNotification.setSmallIcon(notification.getString("smallIcon")); - activeLocalNotification.setIconColor(notification.getString("iconColor")); - activeLocalNotification.setAttachments(LocalNotificationAttachment.getAttachments(notification)); - activeLocalNotification.setGroupSummary(notification.getBoolean("groupSummary", false)); - activeLocalNotification.setChannelId(notification.getString("channelId")); + try { - activeLocalNotification.setSchedule(new LocalNotificationSchedule(notification)); + LocalNotification activeLocalNotification = buildNotificationFromJSObject(notification); + resultLocalNotifications.add(activeLocalNotification); } catch (ParseException e) { call.error("Invalid date format sent to Notification plugin", e); return null; } - activeLocalNotification.setExtra(notification.getJSObject("extra")); - resultLocalNotifications.add(activeLocalNotification); } return resultLocalNotifications; } + public static LocalNotification buildNotificationFromJSObject(JSObject jsonObject) throws ParseException { + LocalNotification localNotification = new LocalNotification(); + localNotification.setSource(jsonObject.toString()); + localNotification.setId(jsonObject.getInteger("id")); + localNotification.setBody(jsonObject.getString("body")); + localNotification.setActionTypeId(jsonObject.getString("actionTypeId")); + localNotification.setGroup(jsonObject.getString("group")); + localNotification.setSound(jsonObject.getString("sound")); + localNotification.setTitle(jsonObject.getString("title")); + localNotification.setSmallIcon(jsonObject.getString("smallIcon")); + localNotification.setIconColor(jsonObject.getString("iconColor")); + localNotification.setAttachments(LocalNotificationAttachment.getAttachments(jsonObject)); + localNotification.setGroupSummary(jsonObject.getBoolean("groupSummary", false)); + localNotification.setChannelId(jsonObject.getString("channelId")); + localNotification.setSchedule(new LocalNotificationSchedule(jsonObject)); + localNotification.setExtra(jsonObject.getJSObject("extra")); + + return localNotification; + } public static List getLocalNotificationPendingList(PluginCall call) { List notifications = null; diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java index d6cd583c5..c0675465e 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java @@ -137,13 +137,17 @@ public JSONArray schedule(PluginCall call, List localNotifica boolean notificationsEnabled = notificationManager.areNotificationsEnabled(); if (!notificationsEnabled) { - call.error("Notifications not enabled on this device"); + if(call != null){ + call.error("Notifications not enabled on this device"); + } return null; } for (LocalNotification localNotification : localNotifications) { Integer id = localNotification.getId(); if (localNotification.getId() == null) { - call.error("LocalNotification missing identifier"); + if(call != null) { + call.error("LocalNotification missing identifier"); + } return null; } dismissVisibleNotification(id); @@ -214,7 +218,9 @@ private void buildNotification(NotificationManagerCompat notificationManager, Lo try { mBuilder.setColor(Color.parseColor(iconColor)); } catch (IllegalArgumentException ex) { - call.error("Invalid color provided. Must be a hex string (ex: #ff0000"); + if(call != null) { + call.error("Invalid color provided. Must be a hex string (ex: #ff0000"); + } return; } } @@ -294,7 +300,6 @@ private Intent buildIntent(LocalNotification localNotification, String action) { * on a certain date "shape" (such as every first of the month) */ // TODO support different AlarmManager.RTC modes depending on priority - // TODO restore alarm on device shutdown (requires persistence) private void triggerScheduledNotification(Notification notification, LocalNotification request) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); LocalNotificationSchedule schedule = request.getSchedule(); diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationRestoreReceiver.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationRestoreReceiver.java new file mode 100644 index 000000000..68d1f1a6c --- /dev/null +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationRestoreReceiver.java @@ -0,0 +1,60 @@ +package com.getcapacitor.plugin.notification; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.UserManager; + +import com.getcapacitor.CapConfig; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import static android.os.Build.VERSION.SDK_INT; + +public class LocalNotificationRestoreReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if (SDK_INT >= 24) { + UserManager um = context.getSystemService(UserManager.class); + if (um == null || !um.isUserUnlocked()) return; + } + + NotificationStorage storage = new NotificationStorage(context); + List ids = storage.getSavedNotificationIds(); + + ArrayList notifications = new ArrayList<>(ids.size()); + ArrayList updatedNotifications = new ArrayList<>(); + for (String id : ids) { + LocalNotification notification = storage.getSavedNotification(id); + if(notification == null) { + continue; + } + + LocalNotificationSchedule schedule = notification.getSchedule(); + if(schedule != null) { + Date at = schedule.getAt(); + if(at != null && at.before(new Date())) { + // modify the scheduled date in order to show notifications that would have been delivered while device was off. + long newDateTime = new Date().getTime() + 15 * 1000; + schedule.setAt(new Date(newDateTime)); + notification.setSchedule(schedule); + updatedNotifications.add(notification); + } + } + + notifications.add(notification); + } + + if(updatedNotifications.size() > 0){ + storage.appendNotifications(updatedNotifications); + } + + CapConfig config = new CapConfig(context.getAssets(), null); + LocalNotificationManager localNotificationManager = new LocalNotificationManager(storage, null, context, config); + + localNotificationManager.schedule(null, notifications); + } +} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationStorage.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationStorage.java index f6e4ff8b8..7d5c77150 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationStorage.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationStorage.java @@ -3,7 +3,14 @@ import android.content.Context; import android.content.SharedPreferences; +import com.getcapacitor.JSObject; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.ParseException; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; @@ -31,13 +38,12 @@ public NotificationStorage(Context context) { /** * Persist the id of currently scheduled notification */ - public void appendNotificationIds(List localNotifications) { + public void appendNotifications(List localNotifications) { SharedPreferences storage = getStorage(NOTIFICATION_STORE_ID); SharedPreferences.Editor editor = storage.edit(); - long creationTime = new Date().getTime(); for (LocalNotification request : localNotifications) { String key = request.getId().toString(); - editor.putLong(key, creationTime); + editor.putString(key, request.getSource()); } editor.apply(); } @@ -51,6 +57,40 @@ public List getSavedNotificationIds() { return new ArrayList<>(); } + public JSObject getSavedNotificationAsJSObject(String key) { + SharedPreferences storage = getStorage(NOTIFICATION_STORE_ID); + String notificationString = storage.getString(key, null); + + if(notificationString == null){ + return null; + } + + JSObject jsNotification; + try { + jsNotification = new JSObject(notificationString); + } catch (JSONException ex) { + return null; + } + + return jsNotification; + } + + public LocalNotification getSavedNotification(String key) { + JSObject jsNotification = getSavedNotificationAsJSObject(key); + if(jsNotification == null) { + return null; + } + + LocalNotification notification; + try { + notification = LocalNotification.buildNotificationFromJSObject(jsNotification); + } catch (ParseException ex) { + return null; + } + + return notification; + } + /** * Remove the stored notifications */